Skip to content

Commit 0c584f0

Browse files
[python][asyncio] Expose ClientSession extension points (closes OpenAPITools#23830) (OpenAPITools#23847)
* [python][asyncio] Expose ClientSession extension points (closes OpenAPITools#23830) Mirror the python/httpx template's `_create_pool_manager()` factory and add a parallel `_create_connector()` so subclasses can customize the `aiohttp.ClientSession` / `aiohttp.TCPConnector` without overriding `request()`. Also surface three typed fields on `Configuration` (asyncio block): - `trace_configs: list[aiohttp.TraceConfig]` — forwarded to `ClientSession` for tracing / instrumentation (OpenTelemetry, etc.). This was the original issue request. - `tcp_connector_limit_per_host: int` — per-host concurrency cap forwarded to `aiohttp.TCPConnector(limit_per_host=...)`. - `client_session_kwargs: dict` — merged into `ClientSession(**kwargs)` for less common knobs (`json_serialize=orjson.dumps`, `cookie_jar=aiohttp.DummyCookieJar()`, `raise_for_status=...`). All new Configuration fields are read via `getattr(self.configuration, ..., None)` so older Configuration objects (e.g. produced by an older generator version and passed manually) remain compatible. The httpx template is left untouched; only the asyncio side is changed in this PR. No Java code change is needed — the `{{#asyncio}}` mustache block already gates the new code path. Closes OpenAPITools#23830 * update python-aiohttp samples --------- Co-authored-by: liqiankun.1111 <liqiankun.1111@bytedance.com>
1 parent d9c2f12 commit 0c584f0

4 files changed

Lines changed: 122 additions & 10 deletions

File tree

modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import io
77
import json
88
import re
99
import ssl
10-
from typing import Optional, Union
10+
from typing import Any, Dict, Optional, Union
1111

1212
import aiohttp
1313
import aiohttp_retry
@@ -49,6 +49,10 @@ class RESTClientObject:
4949

5050
def __init__(self, configuration) -> None:
5151

52+
# Keep a reference so factory methods (_create_pool_manager / _create_connector)
53+
# and subclasses can read extension fields like trace_configs.
54+
self.configuration = configuration
55+
5256
# maxsize is number of requests to host that are allowed in parallel
5357
self.maxsize = configuration.connection_pool_maxsize
5458

@@ -92,6 +96,40 @@ class RESTClientObject:
9296
if self.retry_client is not None:
9397
await self.retry_client.close()
9498

99+
def _create_connector(self) -> aiohttp.TCPConnector:
100+
"""Build the TCPConnector used by the ClientSession.
101+
102+
Override in a subclass to customize DNS resolver, keepalive, etc.
103+
"""
104+
kwargs: Dict[str, Any] = {
105+
"limit": self.maxsize,
106+
"ssl": self.ssl_context,
107+
}
108+
limit_per_host = getattr(self.configuration, "tcp_connector_limit_per_host", None)
109+
if limit_per_host is not None:
110+
kwargs["limit_per_host"] = limit_per_host
111+
return aiohttp.TCPConnector(**kwargs)
112+
113+
def _create_pool_manager(self) -> aiohttp.ClientSession:
114+
"""Build the aiohttp.ClientSession used as the connection pool.
115+
116+
Override in a subclass to fully customize the session (e.g. attach
117+
aiohttp.TraceConfig, swap json_serialize, etc.). Typed Configuration
118+
fields (trace_configs / client_session_kwargs) are read via getattr
119+
so older Configuration objects remain compatible.
120+
"""
121+
kwargs: Dict[str, Any] = {
122+
"connector": self._create_connector(),
123+
"trust_env": True,
124+
}
125+
trace_configs = getattr(self.configuration, "trace_configs", None)
126+
if trace_configs is not None:
127+
kwargs["trace_configs"] = trace_configs
128+
extra = getattr(self.configuration, "client_session_kwargs", None)
129+
if extra:
130+
kwargs.update(extra)
131+
return aiohttp.ClientSession(**kwargs)
132+
95133
async def request(
96134
self,
97135
method,
@@ -200,10 +238,7 @@ class RESTClientObject:
200238

201239
# https pool manager
202240
if self.pool_manager is None:
203-
self.pool_manager = aiohttp.ClientSession(
204-
connector=aiohttp.TCPConnector(limit=self.maxsize, ssl=self.ssl_context),
205-
trust_env=True,
206-
)
241+
self.pool_manager = self._create_pool_manager()
207242
pool_manager = self.pool_manager
208243

209244
if self._effective_retry_options is not None and method in ALLOW_RETRY_METHODS:

modules/openapi-generator/src/main/resources/python/configuration.mustache

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{{>partial_header}}
22

33
{{#asyncio}}
4+
import aiohttp
45
import aiohttp_retry
56
{{/asyncio}}
67
{{#async}}
@@ -192,6 +193,13 @@ class Configuration:
192193
in PEM format.
193194
{{#asyncio}}
194195
:param retries: int | aiohttp_retry.RetryOptionsBase - Retry configuration.
196+
:param trace_configs: list of aiohttp.TraceConfig instances forwarded to
197+
aiohttp.ClientSession for tracing/instrumentation (e.g. OpenTelemetry).
198+
:param tcp_connector_limit_per_host: Per-host concurrency cap forwarded to
199+
aiohttp.TCPConnector(limit_per_host=...). None leaves aiohttp's default (0 = unlimited).
200+
:param client_session_kwargs: Extra keyword arguments merged into
201+
aiohttp.ClientSession(**kwargs) (e.g. json_serialize=orjson.dumps,
202+
cookie_jar=aiohttp.DummyCookieJar()).
195203
{{/asyncio}}
196204
{{#httpx}}
197205
:param retries: int - Retry configuration.
@@ -320,6 +328,9 @@ conf = {{{packageName}}}.Configuration(
320328
ssl_ca_cert: Optional[str]=None,
321329
{{#asyncio}}
322330
retries: Optional[Union[int, aiohttp_retry.RetryOptionsBase]] = None,
331+
trace_configs: Optional[List[aiohttp.TraceConfig]] = None,
332+
tcp_connector_limit_per_host: Optional[int] = None,
333+
client_session_kwargs: Optional[Dict[str, Any]] = None,
323334
{{/asyncio}}
324335
{{#httpx}}
325336
retries: Optional[int] = None,
@@ -470,6 +481,17 @@ conf = {{{packageName}}}.Configuration(
470481
self.retries = retries
471482
"""Retry configuration
472483
"""
484+
{{#asyncio}}
485+
self.trace_configs = trace_configs
486+
"""aiohttp.TraceConfig list forwarded to ClientSession for tracing.
487+
"""
488+
self.tcp_connector_limit_per_host = tcp_connector_limit_per_host
489+
"""Per-host concurrency cap forwarded to TCPConnector.
490+
"""
491+
self.client_session_kwargs = client_session_kwargs
492+
"""Extra kwargs merged into aiohttp.ClientSession(**kwargs).
493+
"""
494+
{{/asyncio}}
473495
# Enable client side validation
474496
self.client_side_validation = client_side_validation
475497

samples/openapi3/client/petstore/python-aiohttp/petstore_api/configuration.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
""" # noqa: E501
1111

1212

13+
import aiohttp
1314
import aiohttp_retry
1415
import base64
1516
import copy
@@ -169,6 +170,13 @@ class Configuration:
169170
:param ssl_ca_cert: str - the path to a file of concatenated CA certificates
170171
in PEM format.
171172
:param retries: int | aiohttp_retry.RetryOptionsBase - Retry configuration.
173+
:param trace_configs: list of aiohttp.TraceConfig instances forwarded to
174+
aiohttp.ClientSession for tracing/instrumentation (e.g. OpenTelemetry).
175+
:param tcp_connector_limit_per_host: Per-host concurrency cap forwarded to
176+
aiohttp.TCPConnector(limit_per_host=...). None leaves aiohttp's default (0 = unlimited).
177+
:param client_session_kwargs: Extra keyword arguments merged into
178+
aiohttp.ClientSession(**kwargs) (e.g. json_serialize=orjson.dumps,
179+
cookie_jar=aiohttp.DummyCookieJar()).
172180
:param ca_cert_data: verify the peer using concatenated CA certificate data
173181
in PEM (str) or DER (bytes) format.
174182
:param cert_file: the path to a client certificate file, for mTLS.
@@ -279,6 +287,9 @@ def __init__(
279287
ignore_operation_servers: bool=False,
280288
ssl_ca_cert: Optional[str]=None,
281289
retries: Optional[Union[int, aiohttp_retry.RetryOptionsBase]] = None,
290+
trace_configs: Optional[List[aiohttp.TraceConfig]] = None,
291+
tcp_connector_limit_per_host: Optional[int] = None,
292+
client_session_kwargs: Optional[Dict[str, Any]] = None,
282293
ca_cert_data: Optional[Union[str, bytes]] = None,
283294
cert_file: Optional[str]=None,
284295
key_file: Optional[str]=None,
@@ -409,6 +420,15 @@ def __init__(
409420
self.retries = retries
410421
"""Retry configuration
411422
"""
423+
self.trace_configs = trace_configs
424+
"""aiohttp.TraceConfig list forwarded to ClientSession for tracing.
425+
"""
426+
self.tcp_connector_limit_per_host = tcp_connector_limit_per_host
427+
"""Per-host concurrency cap forwarded to TCPConnector.
428+
"""
429+
self.client_session_kwargs = client_session_kwargs
430+
"""Extra kwargs merged into aiohttp.ClientSession(**kwargs).
431+
"""
412432
# Enable client side validation
413433
self.client_side_validation = client_side_validation
414434

samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import json
1717
import re
1818
import ssl
19-
from typing import Optional, Union
19+
from typing import Any, Dict, Optional, Union
2020

2121
import aiohttp
2222
import aiohttp_retry
@@ -58,6 +58,10 @@ class RESTClientObject:
5858

5959
def __init__(self, configuration) -> None:
6060

61+
# Keep a reference so factory methods (_create_pool_manager / _create_connector)
62+
# and subclasses can read extension fields like trace_configs.
63+
self.configuration = configuration
64+
6165
# maxsize is number of requests to host that are allowed in parallel
6266
self.maxsize = configuration.connection_pool_maxsize
6367

@@ -101,6 +105,40 @@ async def close(self) -> None:
101105
if self.retry_client is not None:
102106
await self.retry_client.close()
103107

108+
def _create_connector(self) -> aiohttp.TCPConnector:
109+
"""Build the TCPConnector used by the ClientSession.
110+
111+
Override in a subclass to customize DNS resolver, keepalive, etc.
112+
"""
113+
kwargs: Dict[str, Any] = {
114+
"limit": self.maxsize,
115+
"ssl": self.ssl_context,
116+
}
117+
limit_per_host = getattr(self.configuration, "tcp_connector_limit_per_host", None)
118+
if limit_per_host is not None:
119+
kwargs["limit_per_host"] = limit_per_host
120+
return aiohttp.TCPConnector(**kwargs)
121+
122+
def _create_pool_manager(self) -> aiohttp.ClientSession:
123+
"""Build the aiohttp.ClientSession used as the connection pool.
124+
125+
Override in a subclass to fully customize the session (e.g. attach
126+
aiohttp.TraceConfig, swap json_serialize, etc.). Typed Configuration
127+
fields (trace_configs / client_session_kwargs) are read via getattr
128+
so older Configuration objects remain compatible.
129+
"""
130+
kwargs: Dict[str, Any] = {
131+
"connector": self._create_connector(),
132+
"trust_env": True,
133+
}
134+
trace_configs = getattr(self.configuration, "trace_configs", None)
135+
if trace_configs is not None:
136+
kwargs["trace_configs"] = trace_configs
137+
extra = getattr(self.configuration, "client_session_kwargs", None)
138+
if extra:
139+
kwargs.update(extra)
140+
return aiohttp.ClientSession(**kwargs)
141+
104142
async def request(
105143
self,
106144
method,
@@ -209,10 +247,7 @@ async def request(
209247

210248
# https pool manager
211249
if self.pool_manager is None:
212-
self.pool_manager = aiohttp.ClientSession(
213-
connector=aiohttp.TCPConnector(limit=self.maxsize, ssl=self.ssl_context),
214-
trust_env=True,
215-
)
250+
self.pool_manager = self._create_pool_manager()
216251
pool_manager = self.pool_manager
217252

218253
if self._effective_retry_options is not None and method in ALLOW_RETRY_METHODS:

0 commit comments

Comments
 (0)