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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
- Added `language_tool_python.config_file.ConfigValue` public type alias representing all accepted value types for `LanguageToolConfig`.

### Changed
- HTTP requests to the LanguageTool server now reuse a persistent `requests.Session`, reducing TCP connection overhead for repeated calls to `.check()`.
- **Breaking:** Dropped Python 3.9 support, now supports Python 3.10-3.15.
- **Breaking:** Moved internal utilities to a private `_internals` subpackage:
- `language_tool_python.safe_zip` -> `language_tool_python._internals.safe_zip`
Expand Down
26 changes: 18 additions & 8 deletions src/language_tool_python/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ class LanguageTool:
_proxies: dict[str, str] | None
"""A dictionary of proxies for network requests (used in requests to the server)."""

_session: requests.Session
"""The HTTP session used for all requests, enabling connection reuse across
calls."""

_premium_username: str | None
"""The premium API username for the LanguageTool API."""

Expand Down Expand Up @@ -255,6 +259,9 @@ def __init__( # noqa: PLR0913 # Too many arguments, but they are all necessary
self._port = self._available_ports.pop()
self._server = None
self._proxies = proxies
self._session = requests.Session()
if proxies:
self._session.proxies.update(proxies)

if remote_server and config is not None:
err = "Cannot use both remote_server and config parameters."
Expand Down Expand Up @@ -363,10 +370,12 @@ def close(self) -> None:
"""Close the server and perform necessary cleanup operations.

This method performs the following actions:
1. Checks if the server is alive, not remote and terminates it if necessary.
2. If new spellings are not set to persist and there are new spellings,
1. Closes the HTTP session to release connection pool resources cleanly.
2. Checks if the server is alive, not remote and terminates it if necessary.
3. If new spellings are not set to persist and there are new spellings,
it unregisters the spellings and clears the list of new spellings.
"""
self._session.close()
if self._remote is not True and self._server_is_alive():
self._terminate_server()
if not self._new_spellings_persist and self._new_spellings:
Expand Down Expand Up @@ -439,7 +448,7 @@ def proxies(self, proxies: dict[str, str] | None) -> None:
"""Set the proxies for server requests.

Proxies can only be used with remote servers. Local LanguageTool servers do not
support proxy configuration.
support proxy configuration. The underlying HTTP session is updated accordingly.

:param proxies: A dictionary of proxies (e.g., {'http': 'http://proxy:port'}),
or None to unset.
Expand All @@ -453,6 +462,9 @@ def proxies(self, proxies: dict[str, str] | None) -> None:
)
raise ValueError(err)
self._proxies = proxies
self._session.proxies.clear()
if proxies:
self._session.proxies.update(proxies)

@property
def disabled_rules(self) -> set[str]:
Expand Down Expand Up @@ -1030,18 +1042,16 @@ def _query_server(
for n in range(num_tries):
try:
if method == "post":
response_context = requests.post(
response_context = self._session.post(
url,
data=params,
timeout=self._TIMEOUT,
proxies=self._proxies,
)
else:
response_context = requests.get(
response_context = self._session.get(
url,
params=params,
timeout=self._TIMEOUT,
proxies=self._proxies,
)
with response_context as response:
try:
Expand Down Expand Up @@ -1182,7 +1192,7 @@ def _wait_for_server_ready(self, timeout: int = 15) -> None:

# Attempt to connect
with contextlib.suppress(requests.RequestException):
r = requests.get(url, timeout=2)
r = self._session.get(url, timeout=2)
if r.ok:
return

Expand Down
Loading