Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 9 additions & 37 deletions language_tool_python/_deprecated.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,16 @@
"""Provide a deprecated decorator for marking functions or classes as deprecated.

It first attempts to import the deprecated decorator from the warnings module, available
in Python 3.13 and later. If the import fails (indicating an earlier Python version), it
defines a custom deprecated decorator. The decorator from warnings issues a
DeprecationWarning when the decorated object is used during runtime, and triggers static
linters to flag the usage as deprecated. The custom decorator also issues a
DeprecationWarning when the decorated object is used, but does not trigger static
linters.
If the Python version is 3.13 or higher, it uses the
built-in ``warnings.deprecated`` decorator.
If the Python version is lower than 3.13, it falls back to using the
``typing_extensions.deprecated`` decorator.
"""
Comment thread
mdevolde marked this conversation as resolved.

from __future__ import annotations

try:
from warnings import deprecated # type: ignore [attr-defined, unused-ignore]
except ImportError:
import functools
from collections.abc import Callable
from typing import TypeVar, cast
from warnings import warn

F = TypeVar("F", bound=Callable[..., object])

def deprecated( # type: ignore [no-redef, unused-ignore]
message: str,
/,
*,
category: type[Warning] | None = DeprecationWarning,
stacklevel: int = 1,
) -> Callable[[F], F]:
"""Indicate that a function is deprecated."""

def decorator(func: F) -> F:
@functools.wraps(func)
def wrapper(*args: object, **kwargs: object) -> object:
warn(message, category=category, stacklevel=stacklevel)
return func(*args, **kwargs)

return cast("F", wrapper)

return decorator
import sys

if sys.version_info >= (3, 13):
from warnings import deprecated
else:
from typing_extensions import deprecated

__all__ = ["deprecated"]
10 changes: 5 additions & 5 deletions language_tool_python/download_lt.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ def confirm_java_compatibility(
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def get_common_prefix(z: zipfile.ZipFile) -> str | None:
"""Determine the common prefix of all file names in a zip archive.

Expand All @@ -297,7 +297,7 @@ def get_common_prefix(z: zipfile.ZipFile) -> str | None:
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def http_get(
url: str,
out_file: IO[bytes],
Expand Down Expand Up @@ -329,7 +329,7 @@ def http_get(
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def unzip_file(temp_file_name: str, directory_to_extract_to: Path) -> None:
"""Unzips a zip file to a specified directory.

Expand Down Expand Up @@ -358,7 +358,7 @@ def unzip_file(temp_file_name: str, directory_to_extract_to: Path) -> None:
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def download_zip(url: str, directory: Path) -> None:
"""Download a ZIP file from the given URL and extract it to the specified directory.

Expand Down Expand Up @@ -389,7 +389,7 @@ def download_zip(url: str, directory: Path) -> None:
"Use instead language_tool_python.download_lt.LocalLanguageTool.download."
),
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def download_lt(language_tool_version: str = LTP_DOWNLOAD_VERSION) -> None:
"""Download and extract the specified version of LanguageTool.

Expand Down
2 changes: 1 addition & 1 deletion language_tool_python/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def get_match_ordered_dict() -> OrderedDictType[str, type]:
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def auto_type(obj: SupportsInt | SupportsFloat | object) -> int | float | object:
"""Attempt to automatically convert the input object to an integer or float.

Expand Down
14 changes: 7 additions & 7 deletions language_tool_python/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def get_language_tool_download_path() -> Path:
"Replace its usage by an inline alternative."
),
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def find_existing_language_tool_downloads(download_folder: Path) -> list[Path]:
"""Find existing LanguageTool downloads in the specified folder.

Expand All @@ -230,7 +230,7 @@ def find_existing_language_tool_downloads(download_folder: Path) -> list[Path]:
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def _extract_version(path: Path) -> version.Version:
"""Extract the version number from a LanguageTool directory path.

Expand Down Expand Up @@ -265,7 +265,7 @@ def _extract_version(path: Path) -> version.Version:
"get_latest_installed_version."
),
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def get_language_tool_directory() -> Path:
"""Get the directory path of the LanguageTool installation.

Expand Down Expand Up @@ -307,7 +307,7 @@ def get_language_tool_directory() -> Path:
"Use instead language_tool_python.download_lt.LocalLanguageTool.get_server_cmd."
),
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def get_server_cmd(
port: int | None = None,
config: LanguageToolConfig | None = None,
Expand Down Expand Up @@ -350,7 +350,7 @@ def get_server_cmd(
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
def get_jar_info() -> tuple[Path, Path]:
"""Retrieve the path to the Java executable and the LanguageTool JAR file.

Expand Down Expand Up @@ -458,7 +458,7 @@ def __bool__(self) -> bool:
@deprecated(
"This protocol is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
@runtime_checkable
class SupportsInt(Protocol):
"""Protocol for types that can be converted to an integer value."""
Expand All @@ -471,7 +471,7 @@ def __int__(self) -> int:
@deprecated(
"This protocol is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore[untyped-decorator, unused-ignore]
)
@runtime_checkable
class SupportsFloat(Protocol):
"""Protocol for types that can be converted to a float value."""
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ dependencies = [
"tqdm",
"packaging",
"psutil",
"toml"
"toml",
"typing_extensions; python_version < '3.13'", # only needed for py < 3.13 because warnings.deprecated added in 3.13 is used in the codebase
]

[project.urls]
Expand Down
10 changes: 5 additions & 5 deletions tests/test_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
def test_deprecated_emits_warning() -> None:
"""Test that the deprecated decorator emits a DeprecationWarning."""

@deprecated("This function is deprecated") # type: ignore[untyped-decorator, unused-ignore]
@deprecated("This function is deprecated")
def old_function() -> str:
return "result"

Expand All @@ -31,7 +31,7 @@ def old_function() -> str:
def test_deprecated_with_custom_category() -> None:
"""Test that the deprecated decorator can use a custom warning category."""

@deprecated("This is a user warning", category=UserWarning) # type: ignore[untyped-decorator, unused-ignore]
@deprecated("This is a user warning", category=UserWarning)
def old_function() -> int:
return 42

Expand All @@ -48,7 +48,7 @@ def old_function() -> int:
def test_deprecated_preserves_function_signature() -> None:
"""Test that the deprecated decorator preserves function metadata."""

@deprecated("Old function") # type: ignore[untyped-decorator, unused-ignore]
@deprecated("Old function")
def my_function(x: int, y: int) -> int:
"""Add two numbers."""
return x + y
Expand All @@ -63,7 +63,7 @@ def my_function(x: int, y: int) -> int:
def test_deprecated_with_multiple_calls() -> None:
"""Test that warning is emitted on each call."""

@deprecated("Deprecated function") # type: ignore[untyped-decorator, unused-ignore]
@deprecated("Deprecated function")
def func() -> str:
return "value"

Expand All @@ -80,7 +80,7 @@ def func() -> str:
def test_deprecated_with_args_and_kwargs() -> None:
"""Test that deprecated decorator works with functions that have args and kwargs."""

@deprecated("This function is obsolete") # type: ignore[untyped-decorator, unused-ignore]
@deprecated("This function is obsolete")
def complex_function(
a: int,
b: int,
Expand Down
2 changes: 2 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading