From 9c3c20156526f3ff616d85645438212d68abff2b Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 15 Sep 2024 20:29:02 +0200 Subject: [PATCH 1/5] fix(python): missing transporter close --- .../algoliasearch/http/api_response.py | 29 ++++++++++--------- .../algoliasearch/http/helpers.py | 24 +++++++-------- .../algoliasearch/http/request_options.py | 9 ++---- .../algoliasearch/http/serializer.py | 6 ++-- .../algoliasearch/http/transporter.py | 7 +++++ 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/clients/algoliasearch-client-python/algoliasearch/http/api_response.py b/clients/algoliasearch-client-python/algoliasearch/http/api_response.py index 213ebb3c811..8c5e0748ec2 100644 --- a/clients/algoliasearch-client-python/algoliasearch/http/api_response.py +++ b/clients/algoliasearch-client-python/algoliasearch/http/api_response.py @@ -10,14 +10,14 @@ T = TypeVar("T") +PRIMITIVE_TYPES = (float, bool, bytes, str, int) + class ApiResponse(Generic[T]): """ API response object """ - PRIMITIVE_TYPES = (float, bool, bytes, str, int) - def __init__( self, verb: Verb, @@ -29,8 +29,8 @@ def __init__( is_timed_out_error: bool = False, path: str = "", query_parameters: Optional[Dict[str, Any]] = None, - raw_data: str = None, - status_code: int = None, + raw_data: Optional[str] = None, + status_code: Optional[int] = None, timeouts: Optional[Dict[str, int]] = None, url: str = "", ) -> None: @@ -51,7 +51,8 @@ def __init__( def to_json(self) -> str: return str(self.__dict__) - def deserialize(self, klass: any = None, data: any = None) -> T: + @staticmethod + def deserialize(klass: Any = None, data: Any = None) -> Any: """Deserializes dict, list, str into an object. :param data: dict, list or str. @@ -59,26 +60,28 @@ def deserialize(self, klass: any = None, data: any = None) -> T: :return: object. """ - if data is None: - data = self.raw_data if data is None: return None if hasattr(klass, "__origin__") and klass.__origin__ is list: sub_kls = klass.__args__[0] arr = json.loads(data) - return [self.deserialize(sub_kls, sub_data) for sub_data in arr] + return [ApiResponse.deserialize(sub_kls, sub_data) for sub_data in arr] if isinstance(klass, str): if klass.startswith("List["): - sub_kls = match(r"List\[(.*)]", klass).group(1) - return [self.deserialize(sub_kls, sub_data) for sub_data in data] + sub_kls = match(r"List\[(.*)]", klass) + if sub_kls is not None: + sub_kls = sub_kls.group(1) + return [ApiResponse.deserialize(sub_kls, sub_data) for sub_data in data] if klass.startswith("Dict["): - sub_kls = match(r"Dict\[([^,]*), (.*)]", klass).group(2) - return {k: self.deserialize(sub_kls, v) for k, v in data.items()} + sub_kls = match(r"Dict\[([^,]*), (.*)]", klass) + if sub_kls is not None: + sub_kls = sub_kls.group(2) + return {k: ApiResponse.deserialize(sub_kls, v) for k, v in data.items()} - if klass in self.PRIMITIVE_TYPES: + if klass in PRIMITIVE_TYPES: try: return klass(data) except UnicodeEncodeError: diff --git a/clients/algoliasearch-client-python/algoliasearch/http/helpers.py b/clients/algoliasearch-client-python/algoliasearch/http/helpers.py index 48459cfa013..196694d1cc3 100644 --- a/clients/algoliasearch-client-python/algoliasearch/http/helpers.py +++ b/clients/algoliasearch-client-python/algoliasearch/http/helpers.py @@ -2,37 +2,37 @@ import asyncio import time -from typing import Callable, TypeVar +from typing import Awaitable, Callable, Optional, TypeVar T = TypeVar("T") class Timeout: - def __call__(self) -> int: - return 0 + def __call__(self, retry_count: int = 0) -> int: + return retry_count def __init__(self) -> None: pass class RetryTimeout(Timeout): - def __call__(self, retry_count: int) -> int: + def __call__(self, retry_count: int = 0) -> int: return int(min(retry_count * 0.2, 5)) async def create_iterable( - func: Callable[[T], T], + func: Callable[[Optional[T]], Awaitable[T]], validate: Callable[[T], bool], aggregator: Callable[[T], None], timeout: Timeout = Timeout(), - error_validate: Callable[[T], bool] = None, - error_message: Callable[[T], str] = None, + error_validate: Optional[Callable[[T], bool]] = None, + error_message: Optional[Callable[[T], str]] = None, ) -> T: """ Helper: Iterates until the given `func` until `timeout` or `validate`. """ - async def retry(prev: T = None) -> T: + async def retry(prev: Optional[T] = None) -> T: resp = await func(prev) if aggregator: @@ -53,18 +53,18 @@ async def retry(prev: T = None) -> T: def create_iterable_sync( - func: Callable[[T], T], + func: Callable[[Optional[T]], T], validate: Callable[[T], bool], aggregator: Callable[[T], None], timeout: Timeout = Timeout(), - error_validate: Callable[[T], bool] = None, - error_message: Callable[[T], str] = None, + error_validate: Optional[Callable[[T], bool]] = None, + error_message: Optional[Callable[[T], str]] = None, ) -> T: """ Helper: Iterates until the given `func` until `timeout` or `validate`. """ - def retry(prev: T = None) -> T: + def retry(prev: Optional[T] = None) -> T: resp = func(prev) if aggregator: diff --git a/clients/algoliasearch-client-python/algoliasearch/http/request_options.py b/clients/algoliasearch-client-python/algoliasearch/http/request_options.py index cd88c25bd3a..41104a09291 100644 --- a/clients/algoliasearch-client-python/algoliasearch/http/request_options.py +++ b/clients/algoliasearch-client-python/algoliasearch/http/request_options.py @@ -44,7 +44,7 @@ def to_dict(self) -> Dict[str, Any]: def to_json(self) -> str: return str(self.__dict__) - def from_dict(self, data: Optional[Dict[str, Dict[str, Any]]]) -> Self: + def from_dict(self, data: Dict[str, Dict[str, Any]]) -> Self: return RequestOptions( config=self._config, headers=data.get("headers", {}), @@ -57,8 +57,8 @@ def merge( self, query_parameters: List[Tuple[str, str]] = [], headers: Dict[str, Optional[str]] = {}, - timeouts: Dict[str, int] = {}, - data: Optional[Union[dict, list]] = None, + _: Dict[str, int] = {}, + data: Optional[str] = None, user_request_options: Optional[Union[Self, Dict[str, Any]]] = None, ) -> Self: """ @@ -85,9 +85,6 @@ def merge( _user_request_options = user_request_options for key, value in _user_request_options.items(): - if key == "data" and isinstance(value, dict): - request_options.data = value - continue request_options[key].update(value) return self.from_dict(request_options) diff --git a/clients/algoliasearch-client-python/algoliasearch/http/serializer.py b/clients/algoliasearch-client-python/algoliasearch/http/serializer.py index 9b57f254bc6..52bba424c5a 100644 --- a/clients/algoliasearch-client-python/algoliasearch/http/serializer.py +++ b/clients/algoliasearch-client-python/algoliasearch/http/serializer.py @@ -1,5 +1,5 @@ from json import dumps -from typing import Any, Dict +from typing import Any, Dict, Optional from urllib.parse import urlencode PRIMITIVE_TYPES = (float, bool, bytes, str, int) @@ -27,7 +27,7 @@ def encoded(self) -> str: dict(sorted(self.query_parameters.items(), key=lambda val: val[0])) ).replace("+", "%20") - def __init__(self, query_parameters: Dict[str, Any]) -> None: + def __init__(self, query_parameters: Optional[Dict[str, Any]]) -> None: self.query_parameters = {} if query_parameters is None: return @@ -39,7 +39,7 @@ def __init__(self, query_parameters: Dict[str, Any]) -> None: self.query_parameters[key] = self.parse(value) -def bodySerializer(obj: Any) -> dict: +def bodySerializer(obj: Any) -> Any: """Builds a JSON POST object. If obj is None, return None. diff --git a/clients/algoliasearch-client-python/algoliasearch/http/transporter.py b/clients/algoliasearch-client-python/algoliasearch/http/transporter.py index 6383325133d..abbc26097e6 100644 --- a/clients/algoliasearch-client-python/algoliasearch/http/transporter.py +++ b/clients/algoliasearch-client-python/algoliasearch/http/transporter.py @@ -25,6 +25,13 @@ def __init__(self, config: BaseConfig) -> None: self._retry_strategy = RetryStrategy() self._hosts = [] + async def close(self) -> None: + if self._session is not None: + _session = self._session + self._session = None + + _session.close() + async def request( self, verb: Verb, From 10cc7ec1a5b5ce695e19bf2c4b4a6cffdc6e3cf7 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 15 Sep 2024 20:50:12 +0200 Subject: [PATCH 2/5] fix: api templates --- templates/python/api.mustache | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/templates/python/api.mustache b/templates/python/api.mustache index 59a18637019..b3c73a47893 100644 --- a/templates/python/api.mustache +++ b/templates/python/api.mustache @@ -52,7 +52,8 @@ class {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}: transporter = Transporter{{#isSyncClient}}Sync{{/isSyncClient}}(config) self._transporter = transporter - def create_with_config(config: {{#lambda.pascalcase}}{{client}}Config{{/lambda.pascalcase}}, transporter: Optional[Transporter{{#isSyncClient}}Sync{{/isSyncClient}}] = None) -> Self: + @classmethod + def create_with_config(cls, config: {{#lambda.pascalcase}}{{client}}Config{{/lambda.pascalcase}}, transporter: Optional[Transporter{{#isSyncClient}}Sync{{/isSyncClient}}] = None) -> {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}: """Allows creating a client with a customized `{{#lambda.pascalcase}}{{client}}Config{{/lambda.pascalcase}}` and `Transporter{{#isSyncClient}}Sync{{/isSyncClient}}`. If `transporter` is not provided, the default one will be initialized from the given `config`. Args: @@ -72,7 +73,7 @@ class {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}: return {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}(app_id=config.app_id, api_key=config.api_key, {{#hasRegionalHost}}region=config.region, {{/hasRegionalHost}}transporter=transporter, config=config) {{^isSyncClient}} - async def __aenter__(self) -> None: + async def __aenter__(self) -> Self: return self async def __aexit__(self, exc_type, exc_value, traceback) -> None: @@ -202,7 +203,8 @@ class {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}: :return: Returns the deserialized response in a '{{{returnType}}}' result object. {{/returnType}} """ - return ({{^isSyncClient}}await {{/isSyncClient}}self.{{operationId}}_with_http_info({{#allParams}}{{paramName}},{{/allParams}}request_options)).deserialize({{{returnType}}}) + resp = {{^isSyncClient}}await {{/isSyncClient}}self.{{operationId}}_with_http_info({{#allParams}}{{paramName}},{{/allParams}}request_options) + return resp.deserialize({{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}None{{/returnType}}, resp.raw_data) {{/operation}} {{/operations}} From be0458d757e0b23295e968d6ef1e3a3bca20d136 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 15 Sep 2024 20:51:23 +0200 Subject: [PATCH 3/5] fix: python min version --- templates/python/pyproject.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/python/pyproject.mustache b/templates/python/pyproject.mustache index f99ce737009..59b1068b2e4 100644 --- a/templates/python/pyproject.mustache +++ b/templates/python/pyproject.mustache @@ -14,7 +14,7 @@ classifiers = [ ] [tool.poetry.dependencies] -python = "^3.8.1" +python = ">= 3.8.1" urllib3 = ">= 1.25.3" aiohttp = ">= 3.9.2" requests = ">=2.32.3" From aa24eabce97419b21f0a872cd9f115483ad1cb43 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 15 Sep 2024 20:51:48 +0200 Subject: [PATCH 4/5] fix: search helpers --- templates/python/search_helpers.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/python/search_helpers.mustache b/templates/python/search_helpers.mustache index a21c8ae43b9..1582c7e3a14 100644 --- a/templates/python/search_helpers.mustache +++ b/templates/python/search_helpers.mustache @@ -22,7 +22,7 @@ aggregator=_aggregator, validate=lambda _resp: _resp.status == "published", timeout=lambda: timeout(self._retry_count), - error_validate=lambda x: self._retry_count >= max_retries, + error_validate=lambda _: self._retry_count >= max_retries, error_message=lambda: f"The maximum number of retries exceeded. (${self._retry_count}/${max_retries})", ) @@ -49,7 +49,7 @@ aggregator=_aggregator, validate=lambda _resp: _resp.status == "published", timeout=lambda: timeout(self._retry_count), - error_validate=lambda x: self._retry_count >= max_retries, + error_validate=lambda _: self._retry_count >= max_retries, error_message=lambda: f"The maximum number of retries exceeded. (${self._retry_count}/${max_retries})", ) From 8e70f57cfe7d8a60a68d7edad86303b1c8110126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Vannicatte?= Date: Mon, 16 Sep 2024 00:47:54 +0200 Subject: [PATCH 5/5] Update clients/algoliasearch-client-python/algoliasearch/http/transporter.py --- .../algoliasearch/http/transporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/algoliasearch-client-python/algoliasearch/http/transporter.py b/clients/algoliasearch-client-python/algoliasearch/http/transporter.py index abbc26097e6..a52e31276cb 100644 --- a/clients/algoliasearch-client-python/algoliasearch/http/transporter.py +++ b/clients/algoliasearch-client-python/algoliasearch/http/transporter.py @@ -30,7 +30,7 @@ async def close(self) -> None: _session = self._session self._session = None - _session.close() + await _session.close() async def request( self,