diff --git a/CHANGELOG.md b/CHANGELOG.md index b0c3a24..4260ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/src/language_tool_python/server.py b/src/language_tool_python/server.py index 317c5ed..e02b9b3 100644 --- a/src/language_tool_python/server.py +++ b/src/language_tool_python/server.py @@ -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.""" @@ -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." @@ -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: @@ -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. @@ -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]: @@ -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: @@ -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