diff --git a/bibtexparser/entrypoint.py b/bibtexparser/entrypoint.py index eac69a6..e654c60 100644 --- a/bibtexparser/entrypoint.py +++ b/bibtexparser/entrypoint.py @@ -75,6 +75,52 @@ def _build_unparse_stack( return list(prepend_middleware) + list(unparse_stack) +def _handle_deprecated_write_params( + unparse_stack: Optional[Iterable[Middleware]], + prepend_middleware: Optional[Iterable[Middleware]], + kwargs: dict, + function_name: str, +) -> tuple[Optional[Iterable[Middleware]], Optional[Iterable[Middleware]]]: + """Handle deprecated parameter names for write functions. + + :param unparse_stack: Current unparse_stack value + :param prepend_middleware: Current prepend_middleware value + :param kwargs: Dictionary of keyword arguments to check for deprecated params + :param function_name: Name of the calling function (for error messages) + :return: Tuple of (unparse_stack, prepend_middleware) with deprecated values migrated + """ + if "parse_stack" in kwargs: + warnings.warn( + "Parameter 'parse_stack' is deprecated. Use 'unparse_stack' instead.", + DeprecationWarning, + stacklevel=3, + ) + if unparse_stack is not None: + raise ValueError( + "Cannot provide both 'parse_stack' (deprecated) and 'unparse_stack'. " + "Use 'unparse_stack' instead." + ) + unparse_stack = kwargs.pop("parse_stack") + + if "append_middleware" in kwargs: + warnings.warn( + "Parameter 'append_middleware' is deprecated. Use 'prepend_middleware' instead.", + DeprecationWarning, + stacklevel=3, + ) + if prepend_middleware is not None: + raise ValueError( + "Cannot provide both 'append_middleware' (deprecated) and 'prepend_middleware'. " + "Use 'prepend_middleware' instead." + ) + prepend_middleware = kwargs.pop("append_middleware") + + if kwargs: + raise TypeError(f"{function_name}() got unexpected keyword arguments: {', '.join(kwargs)}") + + return unparse_stack, prepend_middleware + + def parse_string( bibtex_str: str, parse_stack: Optional[Iterable[Middleware]] = None, @@ -147,6 +193,7 @@ def write_file( prepend_middleware: Optional[Iterable[Middleware]] = None, bibtex_format: Optional[BibtexFormat] = None, encoding: str = "UTF-8", + **kwargs, ) -> None: """Write a BibTeX database to a file. @@ -157,7 +204,16 @@ def write_file( :param prepend_middleware: List of middleware to prepend to the default stack. Only applicable if `unparse_stack` is None. :param bibtex_format: Customized BibTeX format to use (optional). - :param encoding: Encoding of the .bib file. Default encoding is ``"UTF-8"``.""" + :param encoding: Encoding of the .bib file. Default encoding is ``"UTF-8"``. + + .. deprecated:: (next version) + Parameters 'parse_stack' and 'append_middleware' are deprecated, will be deleted soon. + Use 'unparse_stack' and 'prepend_middleware' instead. + """ + unparse_stack, prepend_middleware = _handle_deprecated_write_params( + unparse_stack, prepend_middleware, kwargs, "write_file" + ) + bibtex_str = write_string( library=library, unparse_stack=unparse_stack, @@ -176,6 +232,7 @@ def write_string( unparse_stack: Optional[Iterable[Middleware]] = None, prepend_middleware: Optional[Iterable[Middleware]] = None, bibtex_format: Optional["BibtexFormat"] = None, + **kwargs, ) -> str: """Serialize a BibTeX database to a string. @@ -185,7 +242,15 @@ def write_string( :param prepend_middleware: List of middleware to prepend to the default stack. Only applicable if `unparse_stack` is None. :param bibtex_format: Customized BibTeX format to use (optional). + + .. deprecated:: (next version) + Parameters 'parse_stack' and 'append_middleware' are deprecated. + Use 'unparse_stack' and 'prepend_middleware' instead. """ + unparse_stack, prepend_middleware = _handle_deprecated_write_params( + unparse_stack, prepend_middleware, kwargs, "write_string" + ) + middleware: Middleware for middleware in _build_unparse_stack(unparse_stack, prepend_middleware): library = middleware.transform(library=library) diff --git a/tests/test_entrypoint.py b/tests/test_entrypoint.py index d2bb129..43ace6c 100644 --- a/tests/test_entrypoint.py +++ b/tests/test_entrypoint.py @@ -2,9 +2,13 @@ import os import tempfile +import warnings + +import pytest from bibtexparser import parse_file from bibtexparser import write_file +from bibtexparser import write_string from bibtexparser.library import Library from bibtexparser.model import Entry from bibtexparser.model import Field @@ -90,3 +94,150 @@ def test_write_file_roundtrip_gbk(): assert library2.entries[0]["journal"] == original_journal finally: os.unlink(temp_path) + + +# Deprecation warning tests for write_file and write_string +def test_write_file_deprecated_parse_stack_parameter(): + """Test that using deprecated 'parse_stack' parameter issues a warning.""" + library = Library([]) + + with tempfile.NamedTemporaryFile(mode="w", suffix=".bib", delete=False) as f: + temp_path = f.name + + try: + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + write_file(temp_path, library, parse_stack=[]) + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "parse_stack" in str(w[0].message) + assert "unparse_stack" in str(w[0].message) + finally: + os.unlink(temp_path) + + +def test_write_file_deprecated_append_middleware_parameter(): + """Test that using deprecated 'append_middleware' parameter issues a warning.""" + library = Library([]) + + with tempfile.NamedTemporaryFile(mode="w", suffix=".bib", delete=False) as f: + temp_path = f.name + + try: + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + write_file(temp_path, library, append_middleware=[]) + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "append_middleware" in str(w[0].message) + assert "prepend_middleware" in str(w[0].message) + finally: + os.unlink(temp_path) + + +def test_write_file_both_parse_stack_and_unparse_stack_raises_error(): + """Test that providing both parse_stack and unparse_stack raises ValueError.""" + library = Library([]) + + with tempfile.NamedTemporaryFile(mode="w", suffix=".bib", delete=False) as f: + temp_path = f.name + + try: + with pytest.raises(ValueError) as excinfo: + write_file(temp_path, library, parse_stack=[], unparse_stack=[]) + assert "parse_stack" in str(excinfo.value) + assert "unparse_stack" in str(excinfo.value) + assert "Use 'unparse_stack' instead" in str(excinfo.value) + finally: + os.unlink(temp_path) + + +def test_write_file_both_append_and_prepend_middleware_raises_error(): + """Test that providing both append_middleware and prepend_middleware raises ValueError.""" + library = Library([]) + + with tempfile.NamedTemporaryFile(mode="w", suffix=".bib", delete=False) as f: + temp_path = f.name + + try: + with pytest.raises(ValueError) as excinfo: + write_file(temp_path, library, append_middleware=[], prepend_middleware=[]) + assert "append_middleware" in str(excinfo.value) + assert "prepend_middleware" in str(excinfo.value) + assert "Use 'prepend_middleware' instead" in str(excinfo.value) + finally: + os.unlink(temp_path) + + +def test_write_file_unexpected_keyword_argument_raises_error(): + """Test that unexpected keyword arguments raise TypeError.""" + library = Library([]) + + with tempfile.NamedTemporaryFile(mode="w", suffix=".bib", delete=False) as f: + temp_path = f.name + + try: + with pytest.raises(TypeError) as excinfo: + write_file(temp_path, library, unknown_param="value") + assert "unexpected keyword arguments" in str(excinfo.value) + assert "unknown_param" in str(excinfo.value) + finally: + os.unlink(temp_path) + + +def test_write_string_deprecated_parse_stack_parameter(): + """Test that using deprecated 'parse_stack' parameter issues a warning.""" + library = Library([]) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + write_string(library, parse_stack=[]) + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "parse_stack" in str(w[0].message) + assert "unparse_stack" in str(w[0].message) + + +def test_write_string_deprecated_append_middleware_parameter(): + """Test that using deprecated 'append_middleware' parameter issues a warning.""" + library = Library([]) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + write_string(library, append_middleware=[]) + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "append_middleware" in str(w[0].message) + assert "prepend_middleware" in str(w[0].message) + + +def test_write_string_both_parse_stack_and_unparse_stack_raises_error(): + """Test that providing both parse_stack and unparse_stack raises ValueError.""" + library = Library([]) + + with pytest.raises(ValueError) as excinfo: + write_string(library, parse_stack=[], unparse_stack=[]) + assert "parse_stack" in str(excinfo.value) + assert "unparse_stack" in str(excinfo.value) + assert "Use 'unparse_stack' instead" in str(excinfo.value) + + +def test_write_string_both_append_and_prepend_middleware_raises_error(): + """Test that providing both append_middleware and prepend_middleware raises ValueError.""" + library = Library([]) + + with pytest.raises(ValueError) as excinfo: + write_string(library, append_middleware=[], prepend_middleware=[]) + assert "append_middleware" in str(excinfo.value) + assert "prepend_middleware" in str(excinfo.value) + assert "Use 'prepend_middleware' instead" in str(excinfo.value) + + +def test_write_string_unexpected_keyword_argument_raises_error(): + """Test that unexpected keyword arguments raise TypeError.""" + library = Library([]) + + with pytest.raises(TypeError) as excinfo: + write_string(library, unknown_param="value") + assert "unexpected keyword arguments" in str(excinfo.value) + assert "unknown_param" in str(excinfo.value)