Skip to content

Commit 0016c95

Browse files
authored
fix(python): missing transporter close (#3741)
1 parent 3082f05 commit 0016c95

File tree

8 files changed

+49
-40
lines changed

8 files changed

+49
-40
lines changed

clients/algoliasearch-client-python/algoliasearch/http/api_response.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010

1111
T = TypeVar("T")
1212

13+
PRIMITIVE_TYPES = (float, bool, bytes, str, int)
14+
1315

1416
class ApiResponse(Generic[T]):
1517
"""
1618
API response object
1719
"""
1820

19-
PRIMITIVE_TYPES = (float, bool, bytes, str, int)
20-
2121
def __init__(
2222
self,
2323
verb: Verb,
@@ -29,8 +29,8 @@ def __init__(
2929
is_timed_out_error: bool = False,
3030
path: str = "",
3131
query_parameters: Optional[Dict[str, Any]] = None,
32-
raw_data: str = None,
33-
status_code: int = None,
32+
raw_data: Optional[str] = None,
33+
status_code: Optional[int] = None,
3434
timeouts: Optional[Dict[str, int]] = None,
3535
url: str = "",
3636
) -> None:
@@ -51,34 +51,37 @@ def __init__(
5151
def to_json(self) -> str:
5252
return str(self.__dict__)
5353

54-
def deserialize(self, klass: any = None, data: any = None) -> T:
54+
@staticmethod
55+
def deserialize(klass: Any = None, data: Any = None) -> Any:
5556
"""Deserializes dict, list, str into an object.
5657
5758
:param data: dict, list or str.
5859
:param klass: class literal, or string of class name.
5960
6061
:return: object.
6162
"""
62-
if data is None:
63-
data = self.raw_data
6463
if data is None:
6564
return None
6665

6766
if hasattr(klass, "__origin__") and klass.__origin__ is list:
6867
sub_kls = klass.__args__[0]
6968
arr = json.loads(data)
70-
return [self.deserialize(sub_kls, sub_data) for sub_data in arr]
69+
return [ApiResponse.deserialize(sub_kls, sub_data) for sub_data in arr]
7170

7271
if isinstance(klass, str):
7372
if klass.startswith("List["):
74-
sub_kls = match(r"List\[(.*)]", klass).group(1)
75-
return [self.deserialize(sub_kls, sub_data) for sub_data in data]
73+
sub_kls = match(r"List\[(.*)]", klass)
74+
if sub_kls is not None:
75+
sub_kls = sub_kls.group(1)
76+
return [ApiResponse.deserialize(sub_kls, sub_data) for sub_data in data]
7677

7778
if klass.startswith("Dict["):
78-
sub_kls = match(r"Dict\[([^,]*), (.*)]", klass).group(2)
79-
return {k: self.deserialize(sub_kls, v) for k, v in data.items()}
79+
sub_kls = match(r"Dict\[([^,]*), (.*)]", klass)
80+
if sub_kls is not None:
81+
sub_kls = sub_kls.group(2)
82+
return {k: ApiResponse.deserialize(sub_kls, v) for k, v in data.items()}
8083

81-
if klass in self.PRIMITIVE_TYPES:
84+
if klass in PRIMITIVE_TYPES:
8285
try:
8386
return klass(data)
8487
except UnicodeEncodeError:

clients/algoliasearch-client-python/algoliasearch/http/helpers.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,37 @@
22

33
import asyncio
44
import time
5-
from typing import Callable, TypeVar
5+
from typing import Awaitable, Callable, Optional, TypeVar
66

77
T = TypeVar("T")
88

99

1010
class Timeout:
11-
def __call__(self) -> int:
12-
return 0
11+
def __call__(self, retry_count: int = 0) -> int:
12+
return retry_count
1313

1414
def __init__(self) -> None:
1515
pass
1616

1717

1818
class RetryTimeout(Timeout):
19-
def __call__(self, retry_count: int) -> int:
19+
def __call__(self, retry_count: int = 0) -> int:
2020
return int(min(retry_count * 0.2, 5))
2121

2222

2323
async def create_iterable(
24-
func: Callable[[T], T],
24+
func: Callable[[Optional[T]], Awaitable[T]],
2525
validate: Callable[[T], bool],
2626
aggregator: Callable[[T], None],
2727
timeout: Timeout = Timeout(),
28-
error_validate: Callable[[T], bool] = None,
29-
error_message: Callable[[T], str] = None,
28+
error_validate: Optional[Callable[[T], bool]] = None,
29+
error_message: Optional[Callable[[T], str]] = None,
3030
) -> T:
3131
"""
3232
Helper: Iterates until the given `func` until `timeout` or `validate`.
3333
"""
3434

35-
async def retry(prev: T = None) -> T:
35+
async def retry(prev: Optional[T] = None) -> T:
3636
resp = await func(prev)
3737

3838
if aggregator:
@@ -53,18 +53,18 @@ async def retry(prev: T = None) -> T:
5353

5454

5555
def create_iterable_sync(
56-
func: Callable[[T], T],
56+
func: Callable[[Optional[T]], T],
5757
validate: Callable[[T], bool],
5858
aggregator: Callable[[T], None],
5959
timeout: Timeout = Timeout(),
60-
error_validate: Callable[[T], bool] = None,
61-
error_message: Callable[[T], str] = None,
60+
error_validate: Optional[Callable[[T], bool]] = None,
61+
error_message: Optional[Callable[[T], str]] = None,
6262
) -> T:
6363
"""
6464
Helper: Iterates until the given `func` until `timeout` or `validate`.
6565
"""
6666

67-
def retry(prev: T = None) -> T:
67+
def retry(prev: Optional[T] = None) -> T:
6868
resp = func(prev)
6969

7070
if aggregator:

clients/algoliasearch-client-python/algoliasearch/http/request_options.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def to_dict(self) -> Dict[str, Any]:
4444
def to_json(self) -> str:
4545
return str(self.__dict__)
4646

47-
def from_dict(self, data: Optional[Dict[str, Dict[str, Any]]]) -> Self:
47+
def from_dict(self, data: Dict[str, Dict[str, Any]]) -> Self:
4848
return RequestOptions(
4949
config=self._config,
5050
headers=data.get("headers", {}),
@@ -57,8 +57,8 @@ def merge(
5757
self,
5858
query_parameters: List[Tuple[str, str]] = [],
5959
headers: Dict[str, Optional[str]] = {},
60-
timeouts: Dict[str, int] = {},
61-
data: Optional[Union[dict, list]] = None,
60+
_: Dict[str, int] = {},
61+
data: Optional[str] = None,
6262
user_request_options: Optional[Union[Self, Dict[str, Any]]] = None,
6363
) -> Self:
6464
"""
@@ -85,9 +85,6 @@ def merge(
8585
_user_request_options = user_request_options
8686

8787
for key, value in _user_request_options.items():
88-
if key == "data" and isinstance(value, dict):
89-
request_options.data = value
90-
continue
9188
request_options[key].update(value)
9289

9390
return self.from_dict(request_options)

clients/algoliasearch-client-python/algoliasearch/http/serializer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from json import dumps
2-
from typing import Any, Dict
2+
from typing import Any, Dict, Optional
33
from urllib.parse import urlencode
44

55
PRIMITIVE_TYPES = (float, bool, bytes, str, int)
@@ -27,7 +27,7 @@ def encoded(self) -> str:
2727
dict(sorted(self.query_parameters.items(), key=lambda val: val[0]))
2828
).replace("+", "%20")
2929

30-
def __init__(self, query_parameters: Dict[str, Any]) -> None:
30+
def __init__(self, query_parameters: Optional[Dict[str, Any]]) -> None:
3131
self.query_parameters = {}
3232
if query_parameters is None:
3333
return
@@ -39,7 +39,7 @@ def __init__(self, query_parameters: Dict[str, Any]) -> None:
3939
self.query_parameters[key] = self.parse(value)
4040

4141

42-
def bodySerializer(obj: Any) -> dict:
42+
def bodySerializer(obj: Any) -> Any:
4343
"""Builds a JSON POST object.
4444
4545
If obj is None, return None.

clients/algoliasearch-client-python/algoliasearch/http/transporter.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ def __init__(self, config: BaseConfig) -> None:
2525
self._retry_strategy = RetryStrategy()
2626
self._hosts = []
2727

28+
async def close(self) -> None:
29+
if self._session is not None:
30+
_session = self._session
31+
self._session = None
32+
33+
await _session.close()
34+
2835
async def request(
2936
self,
3037
verb: Verb,

templates/python/api.mustache

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ class {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}:
5252
transporter = Transporter{{#isSyncClient}}Sync{{/isSyncClient}}(config)
5353
self._transporter = transporter
5454

55-
def create_with_config(config: {{#lambda.pascalcase}}{{client}}Config{{/lambda.pascalcase}}, transporter: Optional[Transporter{{#isSyncClient}}Sync{{/isSyncClient}}] = None) -> Self:
55+
@classmethod
56+
def create_with_config(cls, config: {{#lambda.pascalcase}}{{client}}Config{{/lambda.pascalcase}}, transporter: Optional[Transporter{{#isSyncClient}}Sync{{/isSyncClient}}] = None) -> {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}:
5657
"""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`.
5758

5859
Args:
@@ -72,7 +73,7 @@ class {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}:
7273
return {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}(app_id=config.app_id, api_key=config.api_key, {{#hasRegionalHost}}region=config.region, {{/hasRegionalHost}}transporter=transporter, config=config)
7374

7475
{{^isSyncClient}}
75-
async def __aenter__(self) -> None:
76+
async def __aenter__(self) -> Self:
7677
return self
7778

7879
async def __aexit__(self, exc_type, exc_value, traceback) -> None:
@@ -202,7 +203,8 @@ class {{classname}}{{#isSyncClient}}Sync{{/isSyncClient}}:
202203
:return: Returns the deserialized response in a '{{{returnType}}}' result object.
203204
{{/returnType}}
204205
"""
205-
return ({{^isSyncClient}}await {{/isSyncClient}}self.{{operationId}}_with_http_info({{#allParams}}{{paramName}},{{/allParams}}request_options)).deserialize({{{returnType}}})
206+
resp = {{^isSyncClient}}await {{/isSyncClient}}self.{{operationId}}_with_http_info({{#allParams}}{{paramName}},{{/allParams}}request_options)
207+
return resp.deserialize({{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}None{{/returnType}}, resp.raw_data)
206208

207209
{{/operation}}
208210
{{/operations}}

templates/python/pyproject.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ classifiers = [
1414
]
1515

1616
[tool.poetry.dependencies]
17-
python = "^3.8.1"
17+
python = ">= 3.8.1"
1818
urllib3 = ">= 1.25.3"
1919
aiohttp = ">= 3.9.2"
2020
requests = ">=2.32.3"

templates/python/search_helpers.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
aggregator=_aggregator,
2323
validate=lambda _resp: _resp.status == "published",
2424
timeout=lambda: timeout(self._retry_count),
25-
error_validate=lambda x: self._retry_count >= max_retries,
25+
error_validate=lambda _: self._retry_count >= max_retries,
2626
error_message=lambda: f"The maximum number of retries exceeded. (${self._retry_count}/${max_retries})",
2727
)
2828

@@ -49,7 +49,7 @@
4949
aggregator=_aggregator,
5050
validate=lambda _resp: _resp.status == "published",
5151
timeout=lambda: timeout(self._retry_count),
52-
error_validate=lambda x: self._retry_count >= max_retries,
52+
error_validate=lambda _: self._retry_count >= max_retries,
5353
error_message=lambda: f"The maximum number of retries exceeded. (${self._retry_count}/${max_retries})",
5454
)
5555

0 commit comments

Comments
 (0)