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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ It can:
- call your own remote LanguageTool server,
- be used from Python code and from a CLI.

Default local download target: `latest` snapshot (currently `6.9-SNAPSHOT`).
Default local download target: LanguageTool `6.8`.

## Documentation

Expand Down Expand Up @@ -90,9 +90,9 @@ with language_tool_python.LanguageTool(
```

Accepted formats:
- `latest` (default): latest snapshot configured by this package (`6.9-SNAPSHOT` at the moment)
- `latest`: latest snapshot available from the snapshot server
- `YYYYMMDD`: snapshot by date (example: `20260201`)
- `X.Y`: release version (example: `6.7`, `4.0`)
- `X.Y`: release version (default: `6.8`. Examples: `6.7`, `4.0`)

Notes:
- Only relevant when using a local server (no `remote_server`).
Expand Down Expand Up @@ -341,8 +341,8 @@ Example:

```bash
export LTP_PATH=/path/to/cache
export LTP_JAR_DIR_PATH=/path/to/LanguageTool-6.9-SNAPSHOT
export LTP_DOWNLOAD_SHA256_6_9_SNAPSHOT=<sha256>
export LTP_JAR_DIR_PATH=/path/to/LanguageTool-6.8
export LTP_DOWNLOAD_SHA256_6_8=<sha256>
# export LTP_BYPASS_VERIFIED_DOWNLOADS=true
```

Expand Down
36 changes: 17 additions & 19 deletions language_tool_python/download_lt.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
)
FILENAME_RELEASE = "LanguageTool-{version}.zip"

LTP_DOWNLOAD_VERSION = "latest"
LT_SNAPSHOT_CURRENT_VERSION = "6.9-SNAPSHOT"
LTP_DOWNLOAD_VERSION = "6.8"
LT_SNAPSHOT_LATEST_VERSION = "latest"
LTP_DOWNLOAD_SHA256_ENV_VAR = "LTP_DOWNLOAD_SHA256"
LTP_BYPASS_VERIFIED_DOWNLOADS_ENV_VAR = "LTP_BYPASS_VERIFIED_DOWNLOADS"
LTP_MAX_DOWNLOAD_BYTES_ENV_VAR = "LTP_MAX_DOWNLOAD_BYTES"
Expand Down Expand Up @@ -399,13 +399,16 @@ def from_version_name(
This factory method determines the appropriate subclass (ReleaseLocalLanguageTool
or SnapshotLocalLanguageTool) based on the version name format.

:param version_name: The version name (e.g., '6.0', '20240101', or 'latest').
:param version_name: The version name (e.g., '6.8', '20240101', or 'latest').
:type version_name: str
:return: An instance of the appropriate LocalLanguageTool subclass.
:rtype: LocalLanguageTool
:raises ValueError: If the version name format is not recognized.
"""
if re.match(r"^\d{8}$", version_name) or version_name == LTP_DOWNLOAD_VERSION:
if (
re.match(r"^\d{8}$", version_name)
or version_name == LT_SNAPSHOT_LATEST_VERSION
):
return SnapshotLocalLanguageTool(version_name)
if re.match(r"^\d+\.\d+$", version_name):
return ReleaseLocalLanguageTool(version_name)
Expand All @@ -430,11 +433,7 @@ def from_path(cls, path: Path) -> "LocalLanguageTool":
err = f"Could not determine LanguageTool version from path: {path}"
raise ValueError(err)
version_name = match.group(1)
return cls.from_version_name(
version_name
if version_name != LT_SNAPSHOT_CURRENT_VERSION
else LTP_DOWNLOAD_VERSION
)
return cls.from_version_name(version_name)

@abstractmethod
def download(self) -> None:
Expand Down Expand Up @@ -856,6 +855,11 @@ def __init__(self, version_name: str) -> None:
Initialize a SnapshotLocalLanguageTool instance.
"""
self._version_name = version_name
self._install_version_name = (
datetime.now().strftime("%Y%m%d")
if version_name == LT_SNAPSHOT_LATEST_VERSION
else version_name
)

def download(self) -> None:
"""
Expand Down Expand Up @@ -915,15 +919,13 @@ def version_name(self) -> str:
"""
Get the snapshot version name.

Returns the current snapshot version string if 'latest' was specified,
otherwise returns the specified date string.
Returns the current date if 'latest' was specified, otherwise returns the
specified date string.

:return: The snapshot version string.
:rtype: str
"""
if self._version_name == LTP_DOWNLOAD_VERSION:
return LT_SNAPSHOT_CURRENT_VERSION
return self._version_name
return self._install_version_name

@property
def version_into(self) -> datetime:
Expand All @@ -936,11 +938,7 @@ def version_into(self) -> datetime:
:return: A datetime object representing the snapshot date.
:rtype: datetime
"""
if self._version_name == LTP_DOWNLOAD_VERSION:
date_str = datetime.now().strftime("%Y%m%d")
else:
date_str = self._version_name
return datetime.strptime(date_str, "%Y%m%d")
return datetime.strptime(self.version_name, "%Y%m%d")

@property
def download_url(self) -> str:
Expand Down
61 changes: 61 additions & 0 deletions tests/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,29 @@ def test_http_get_rejects_invalid_content_length(
LocalLanguageTool.from_version_name()._get_remote_zip(io.BytesIO())


def test_latest_snapshot_uses_latest_download_url_and_current_date(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""
Test that latest remains a snapshot alias installed under the current date.
"""
monkeypatch.setattr(
download_lt,
"BASE_URL_SNAPSHOT",
"https://example.test/snapshots/",
)

with patch("language_tool_python.download_lt.datetime") as datetime_mock:
datetime_mock.now.return_value.strftime.return_value = "20240514"
local_language_tool = LocalLanguageTool.from_version_name("latest")

assert local_language_tool.version_name == "20240514"
assert (
local_language_tool.download_url
== "https://example.test/snapshots/LanguageTool-latest-snapshot.zip"
)


@pytest.mark.parametrize("release_version", ["6.7", "6.8"]) # type: ignore[untyped-decorator]
def test_release_download_url_uses_new_release_base_from_6_7(
release_version: str,
Expand Down Expand Up @@ -497,6 +520,44 @@ def test_snapshot_download_renames_archive_root_to_requested_date(
get_mock.assert_not_called()


def test_latest_snapshot_download_renames_archive_root_to_current_date(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""
Test that latest snapshots are installed under the current date name.
"""
current_snapshot_date = "20240514"
payload = make_zip_payload(
{"LanguageTool-6.9-SNAPSHOT/languagetool-server.jar": b"jar"}
)
with patch("language_tool_python.download_lt.datetime") as datetime_mock:
datetime_mock.now.return_value.strftime.return_value = current_snapshot_date
local_language_tool = LocalLanguageTool.from_version_name("latest")
monkeypatch.setattr(download_lt, "confirm_java_compatibility", lambda _: None)

with (
workspace_temp_dir() as temp_dir,
patch(
"language_tool_python.download_lt.requests.get",
return_value=MockDownloadResponse(payload),
),
):
monkeypatch.setattr(
download_lt, "get_language_tool_download_path", lambda: temp_dir
)
local_language_tool.download()

expected_dir = temp_dir / f"LanguageTool-{current_snapshot_date}"
assert (expected_dir / "languagetool-server.jar").read_bytes() == b"jar"
assert not (temp_dir / "LanguageTool-6.9-SNAPSHOT").exists()
assert local_language_tool.get_directory_path() == expected_dir

with patch("language_tool_python.download_lt.requests.get") as get_mock:
local_language_tool.download()

get_mock.assert_not_called()


def test_install_oldest_supported_version() -> None:
"""
Test that downloading the oldest supported LanguageTool version works correctly.
Expand Down
5 changes: 2 additions & 3 deletions tests/test_server_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,11 @@ def test_session_only_new_spellings() -> None:
import hashlib

import language_tool_python
from language_tool_python.download_lt import LT_SNAPSHOT_CURRENT_VERSION
from language_tool_python.download_lt import LTP_DOWNLOAD_VERSION
from language_tool_python.utils import get_language_tool_download_path

library_path = (
get_language_tool_download_path()
/ f"LanguageTool-{LT_SNAPSHOT_CURRENT_VERSION}"
get_language_tool_download_path() / f"LanguageTool-{LTP_DOWNLOAD_VERSION}"
)
spelling_file_path = (
library_path
Expand Down