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
23 changes: 3 additions & 20 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,9 @@ jobs:
distribution: 'temurin'
java-version: '26'

- name: Install dependencies
run: |
uv sync --group tests --frozen

- name: Verify installed packages
run: |
uv pip list

- name: Import language_tool_python
run: |
printf "import language_tool_python\n" | uv run python

- name: Test with pytest
run: |
uv run pytest
make test

lint:
timeout-minutes: 10
Expand All @@ -71,8 +59,7 @@ jobs:
# Keep in sync ruff version with .pre-commit-config.yaml
- name: Run Ruff Linter
run: |
uvx ruff@0.15.12 check .
uvx ruff@0.15.12 format --check .
make ruff-check

type_check:
timeout-minutes: 10
Expand All @@ -84,12 +71,8 @@ jobs:

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # 8.1.0

- name: Install dependencies
run: |
uv sync --group types --frozen

# Keep in sync mypy version with .pre-commit-config.yaml
- name: Run Mypy Type Checker
run: |
uvx mypy@2.0.0
make mypy-check
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
repos:
# Ruff for linting and formatting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12 # Keep in sync with GitHub Actions workflow
rev: v0.15.12 # Keep in sync ruff version with pyproject.toml
hooks:
# Run the linter
- id: ruff
Expand All @@ -11,7 +11,7 @@ repos:

# mypy for type checking
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v2.0.0 # Keep in sync with GitHub Actions workflow
rev: v2.0.0 # Keep in sync mypy version with pyproject.toml
hooks:
- id: mypy
args: [--config-file=pyproject.toml]
Expand Down
17 changes: 7 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ If you want to contribute, you first need to fork the repo (and preferably creat

To start developing, you can install all the necessary packages in your python environment with this command (optional dependencies will be installed):
```shell
uv sync --group tests --group docs --group types
make install
```

When pushing commits, please use the project naming conventions, which are available in [this guide](https://www.conventionalcommits.org/en/v1.0.0/).
Expand All @@ -30,17 +30,14 @@ The documentation style used in the project is **ReStructuredText**. Please, if

Before creating your pull request, when you have made all your commits, you need to run this:
```shell
# Run linters (maybe you will have to fix some issues)
uvx ruff@0.15.12 check language_tool_python tests
# Format your code
make format

# Format code
uvx ruff@0.15.12 format language_tool_python tests
# Run linters, check code formatting and types (maybe you will have to fix some issues)
make check

# Check types
uvx mypy@2.0.0

# Tests
pytest
# Run tests (if you have added or modified some, make sure they are passing)
make test
```

Please do not manually bump the version number in [pyproject.toml](./pyproject.toml), this will be handled by the maintainers during release.
Expand Down
43 changes: 34 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,23 +1,48 @@
.PHONY: default check test doc publish
.PHONY: default install format fix ruff-check mypy-check check test doc publish

UV := $(shell command -v uv 2>/dev/null || true)
ifeq ($(UV),)
$(warning uv not found. Install uv (curl -LsSf https://astral.sh/uv/install.sh | sh) to use Makefile targets)
endif

default:
@echo "Usage: make [check|test|doc|publish]"
@echo "Usage: make [install|format|fix|ruff-check|mypy-check|check|test|doc|publish]"
@exit 1

install:
uv sync --all-groups --locked

format:
uv run --group quality --locked ruff format language_tool_python tests

fix:
uv run --group quality --locked ruff check --fix language_tool_python tests

ruff-check:
uv run --group quality --locked ruff check language_tool_python tests
uv run --group quality --locked ruff format --check language_tool_python tests

mypy-check:
@if uv run --locked python -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)'; then \
uv run --group tests --group types --group quality --locked mypy; \
else \
echo "Skipping mypy: Python 3.10 or newer is required."; \
fi

check:
uvx ruff@0.15.12 check language_tool_python tests
uvx ruff@0.15.12 format --check language_tool_python tests
uvx mypy@2.0.0
make ruff-check
make mypy-check

test:
pytest
uv run --group tests --locked pytest
uvx --with defusedxml genbadge coverage --input-file coverage.xml --silent

doc:
source ./.venv/bin/activate && uv run sphinx-apidoc -o docs/source/references language_tool_python
source ./.venv/bin/activate && cd ./docs && make html
uv run --group docs --locked sphinx-apidoc -o docs/source/references language_tool_python
uv run --group docs --locked sphinx-build -M html docs/source docs/build

publish:
rm -rf dist/ language_tool_python.egg-info/
rm -rf dist/
uv build
uvx twine check dist/*
uv publish
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,15 +388,16 @@ Main exceptions in `language_tool_python.exceptions`:

```bash
# Install dev dependencies
uv sync --group tests --group docs --group types
make install

# Format code
make format

# Lint / format / types
uvx ruff@0.15.12 check .
uvx ruff@0.15.12 format .
uvx mypy@2.0.0
make check

# Tests
pytest
make test
```

## License
Expand Down
69 changes: 67 additions & 2 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,112 @@
# uv export --format requirements-txt --no-hashes --group docs -o docs/requirements.txt
-e .
accessible-pygments==0.0.5
# via furo
alabaster==0.7.16 ; python_full_version < '3.10'
# via sphinx
alabaster==1.0.0 ; python_full_version >= '3.10'
# via sphinx
babel==2.18.0
# via sphinx
beautifulsoup4==4.14.3
# via furo
certifi==2026.4.22
# via requests
charset-normalizer==3.4.7
# via requests
colorama==0.4.6 ; sys_platform == 'win32'
# via
# sphinx
# tqdm
docutils==0.21.2 ; python_full_version < '3.11'
# via sphinx
docutils==0.22.4 ; python_full_version >= '3.11'
# via sphinx
furo==2025.12.19
idna==3.13
idna==3.15
# via requests
imagesize==1.5.0 ; python_full_version < '3.10'
# via sphinx
imagesize==2.0.0 ; python_full_version >= '3.10'
# via sphinx
importlib-metadata==8.7.1 ; python_full_version < '3.10'
# via sphinx
jinja2==3.1.6
# via sphinx
markupsafe==3.0.3
# via jinja2
packaging==26.2
# via
# language-tool-python
# sphinx
psutil==7.2.2
# via language-tool-python
pygments==2.20.0
# via
# accessible-pygments
# furo
# sphinx
requests==2.32.5 ; python_full_version < '3.10'
requests==2.33.1 ; python_full_version >= '3.10'
# via
# language-tool-python
# sphinx
requests==2.34.2 ; python_full_version >= '3.10'
# via
# language-tool-python
# sphinx
roman-numerals==4.1.0 ; python_full_version >= '3.11'
# via sphinx
snowballstemmer==3.0.1
# via sphinx
soupsieve==2.8.3
# via beautifulsoup4
sphinx==7.4.7 ; python_full_version < '3.10'
# via
# furo
# sphinx-basic-ng
# sphinx-design
sphinx==8.1.3 ; python_full_version == '3.10.*'
# via
# furo
# sphinx-basic-ng
# sphinx-design
sphinx==9.0.4 ; python_full_version == '3.11.*'
# via
# furo
# sphinx-basic-ng
# sphinx-design
sphinx==9.1.0 ; python_full_version >= '3.12'
# via
# furo
# sphinx-basic-ng
# sphinx-design
sphinx-basic-ng==1.0.0b2
# via furo
sphinx-design==0.6.1 ; python_full_version < '3.11'
sphinx-design==0.7.0 ; python_full_version >= '3.11'
sphinxcontrib-applehelp==2.0.0
# via sphinx
sphinxcontrib-devhelp==2.0.0
# via sphinx
sphinxcontrib-htmlhelp==2.1.0
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==2.0.0
# via sphinx
sphinxcontrib-serializinghtml==2.0.0
# via sphinx
toml==0.10.2
# via language-tool-python
tomli==2.4.1 ; python_full_version < '3.11'
# via sphinx
tqdm==4.67.3
# via language-tool-python
typing-extensions==4.15.0
# via beautifulsoup4
urllib3==2.6.3 ; python_full_version < '3.10'
# via requests
urllib3==2.7.0 ; python_full_version >= '3.10'
# via requests
zipp==3.23.1 ; python_full_version < '3.10'
# via importlib-metadata
4 changes: 2 additions & 2 deletions language_tool_python/_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
"""

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

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

def deprecated(
def deprecated( # type: ignore [no-redef, unused-ignore]
message: str,
/,
*,
Expand Down
22 changes: 13 additions & 9 deletions language_tool_python/download_lt.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ def confirm_java_compatibility(
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore
) # type: ignore[untyped-decorator, unused-ignore]
def get_common_prefix(z: zipfile.ZipFile) -> Optional[str]:
"""
Determine the common prefix of all file names in a zip archive.
Expand All @@ -265,7 +265,7 @@ def get_common_prefix(z: zipfile.ZipFile) -> Optional[str]:
@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore
) # type: ignore[untyped-decorator, unused-ignore]
def http_get(
url: str,
out_file: IO[bytes],
Expand All @@ -287,14 +287,14 @@ def http_get(
# Fallback to default behavior if the extracted version is not supported
local_lt = LocalLanguageTool.from_version_name(LTP_DOWNLOAD_VERSION)

with local_lt._get_remote_zip(out_file, proxies=proxies): # type: ignore
with local_lt._get_remote_zip(out_file, proxies=proxies):
pass


@deprecated(
"This function is no longer used internally and will be removed in 4.0.",
stacklevel=2,
) # type: ignore
) # 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 @@ -323,7 +323,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
) # type: ignore[untyped-decorator, unused-ignore]
def download_zip(url: str, directory: Path) -> None:
"""
Downloads a ZIP file from the given URL and extracts it to the specified directory.
Expand All @@ -339,18 +339,18 @@ def download_zip(url: str, directory: Path) -> None:
logger.info("Downloading from %s to %s", url, directory)
# Download file using a context manager.
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as downloaded_file:
http_get(url, downloaded_file) # type: ignore
http_get(url, downloaded_file)
temp_name = downloaded_file.name
# Extract zip file to path.
unzip_file(temp_name, directory) # type: ignore
unzip_file(temp_name, directory)
# Remove the temporary file.
Path(temp_name).unlink(missing_ok=True)


@deprecated(
"This function is no longer used internally and will be removed in 4.0.\nUse instead language_tool_python.download_lt.LocalLanguageTool.download.",
stacklevel=2,
) # type: ignore
) # type: ignore[untyped-decorator, unused-ignore]
def download_lt(language_tool_version: str = LTP_DOWNLOAD_VERSION) -> None:
"""
Downloads and extracts the specified version of LanguageTool.
Expand Down Expand Up @@ -731,7 +731,11 @@ def __lt__(self, other: object) -> bool:
# At this point, both objects are the same type, so version_into will be the same type
self_version = self.version_into
other_version = other.version_into
return self_version < other_version # type: ignore
if isinstance(self_version, Version) and isinstance(other_version, Version):
return self_version < other_version
if isinstance(self_version, datetime) and isinstance(other_version, datetime):
return self_version < other_version
return NotImplemented


class ReleaseLocalLanguageTool(LocalLanguageTool):
Expand Down
Loading