Skip to content

Commit 152f83a

Browse files
committed
[python,aiohttp] Don't create persistent aiohttp.ClientSession in __init__
aiohttp's `ClientSession` & `TCPConnector` used to obtain an event loop in __init__ (via `asyncio.get_event_loop`). However, as of aio-libs/aiohttp#8512 both classes now obtain the running event loop and won't potentially create one. This makes it impossible to create `ClientSession` and `TCPConnector` objects outside of coroutines, as `get_running_loop` must be called from a coroutine. Thus we defer the creation of a `ClientSession` into the actual request and cache it for later usage. Thereby we pay only a very small price on the first request, but subsequent requests will not be any more expensive.
1 parent b218e23 commit 152f83a

File tree

3 files changed

+62
-76
lines changed

3 files changed

+62
-76
lines changed

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

+31-34
Original file line numberDiff line numberDiff line change
@@ -44,51 +44,31 @@ class RESTClientObject:
4444
def __init__(self, configuration) -> None:
4545

4646
# maxsize is number of requests to host that are allowed in parallel
47-
maxsize = configuration.connection_pool_maxsize
47+
self.maxsize = configuration.connection_pool_maxsize
4848

49-
ssl_context = ssl.create_default_context(
49+
self.ssl_context = ssl.create_default_context(
5050
cafile=configuration.ssl_ca_cert
5151
)
5252
if configuration.cert_file:
53-
ssl_context.load_cert_chain(
53+
self.ssl_context.load_cert_chain(
5454
configuration.cert_file, keyfile=configuration.key_file
5555
)
5656

5757
if not configuration.verify_ssl:
58-
ssl_context.check_hostname = False
59-
ssl_context.verify_mode = ssl.CERT_NONE
60-
61-
connector = aiohttp.TCPConnector(
62-
limit=maxsize,
63-
ssl=ssl_context
64-
)
58+
self.ssl_context.check_hostname = False
59+
self.ssl_context.verify_mode = ssl.CERT_NONE
6560

6661
self.proxy = configuration.proxy
6762
self.proxy_headers = configuration.proxy_headers
6863

69-
# https pool manager
70-
self.pool_manager = aiohttp.ClientSession(
71-
connector=connector,
72-
trust_env=True
73-
)
64+
self.retries = configuration.retries
7465

75-
retries = configuration.retries
76-
self.retry_client: Optional[aiohttp_retry.RetryClient]
77-
if retries is not None:
78-
self.retry_client = aiohttp_retry.RetryClient(
79-
client_session=self.pool_manager,
80-
retry_options=aiohttp_retry.ExponentialRetry(
81-
attempts=retries,
82-
factor=2.0,
83-
start_timeout=0.1,
84-
max_timeout=120.0
85-
)
86-
)
87-
else:
88-
self.retry_client = None
66+
self.pool_manager: Optional[aiohttp.ClientSession] = None
67+
self.retry_client: Optional[aiohttp_retry.RetryClient] = None
8968

90-
async def close(self):
91-
await self.pool_manager.close()
69+
async def close(self) -> None:
70+
if self.pool_manager:
71+
await self.pool_manager.close()
9272
if self.retry_client is not None:
9373
await self.retry_client.close()
9474

@@ -195,10 +175,27 @@ class RESTClientObject:
195175
raise ApiException(status=0, reason=msg)
196176

197177
pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient]
198-
if self.retry_client is not None and method in ALLOW_RETRY_METHODS:
178+
179+
# https pool manager
180+
if self.pool_manager is None:
181+
self.pool_manager = aiohttp.ClientSession(
182+
connector=aiohttp.TCPConnector(limit=self.maxsize, ssl=self.ssl_context),
183+
trust_env=True,
184+
)
185+
pool_manager = self.pool_manager
186+
187+
if self.retries is not None and method in ALLOW_RETRY_METHODS:
188+
if self.retry_client is None:
189+
self.retry_client = aiohttp_retry.RetryClient(
190+
client_session=self.pool_manager,
191+
retry_options=aiohttp_retry.ExponentialRetry(
192+
attempts=self.retries,
193+
factor=2.0,
194+
start_timeout=0.1,
195+
max_timeout=120.0
196+
)
197+
)
199198
pool_manager = self.retry_client
200-
else:
201-
pool_manager = self.pool_manager
202199

203200
r = await pool_manager.request(**args)
204201

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

+31-34
Original file line numberDiff line numberDiff line change
@@ -54,51 +54,31 @@ class RESTClientObject:
5454
def __init__(self, configuration) -> None:
5555

5656
# maxsize is number of requests to host that are allowed in parallel
57-
maxsize = configuration.connection_pool_maxsize
57+
self.maxsize = configuration.connection_pool_maxsize
5858

59-
ssl_context = ssl.create_default_context(
59+
self.ssl_context = ssl.create_default_context(
6060
cafile=configuration.ssl_ca_cert
6161
)
6262
if configuration.cert_file:
63-
ssl_context.load_cert_chain(
63+
self.ssl_context.load_cert_chain(
6464
configuration.cert_file, keyfile=configuration.key_file
6565
)
6666

6767
if not configuration.verify_ssl:
68-
ssl_context.check_hostname = False
69-
ssl_context.verify_mode = ssl.CERT_NONE
70-
71-
connector = aiohttp.TCPConnector(
72-
limit=maxsize,
73-
ssl=ssl_context
74-
)
68+
self.ssl_context.check_hostname = False
69+
self.ssl_context.verify_mode = ssl.CERT_NONE
7570

7671
self.proxy = configuration.proxy
7772
self.proxy_headers = configuration.proxy_headers
7873

79-
# https pool manager
80-
self.pool_manager = aiohttp.ClientSession(
81-
connector=connector,
82-
trust_env=True
83-
)
74+
self.retries = configuration.retries
8475

85-
retries = configuration.retries
86-
self.retry_client: Optional[aiohttp_retry.RetryClient]
87-
if retries is not None:
88-
self.retry_client = aiohttp_retry.RetryClient(
89-
client_session=self.pool_manager,
90-
retry_options=aiohttp_retry.ExponentialRetry(
91-
attempts=retries,
92-
factor=2.0,
93-
start_timeout=0.1,
94-
max_timeout=120.0
95-
)
96-
)
97-
else:
98-
self.retry_client = None
76+
self.pool_manager: Optional[aiohttp.ClientSession] = None
77+
self.retry_client: Optional[aiohttp_retry.RetryClient] = None
9978

100-
async def close(self):
101-
await self.pool_manager.close()
79+
async def close(self) -> None:
80+
if self.pool_manager:
81+
await self.pool_manager.close()
10282
if self.retry_client is not None:
10383
await self.retry_client.close()
10484

@@ -205,10 +185,27 @@ async def request(
205185
raise ApiException(status=0, reason=msg)
206186

207187
pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient]
208-
if self.retry_client is not None and method in ALLOW_RETRY_METHODS:
188+
189+
# https pool manager
190+
if self.pool_manager is None:
191+
self.pool_manager = aiohttp.ClientSession(
192+
connector=aiohttp.TCPConnector(limit=self.maxsize, ssl=self.ssl_context),
193+
trust_env=True,
194+
)
195+
pool_manager = self.pool_manager
196+
197+
if self.retries is not None and method in ALLOW_RETRY_METHODS:
198+
if self.retry_client is None:
199+
self.retry_client = aiohttp_retry.RetryClient(
200+
client_session=self.pool_manager,
201+
retry_options=aiohttp_retry.ExponentialRetry(
202+
attempts=self.retries,
203+
factor=2.0,
204+
start_timeout=0.1,
205+
max_timeout=120.0
206+
)
207+
)
209208
pool_manager = self.retry_client
210-
else:
211-
pool_manager = self.pool_manager
212209

213210
r = await pool_manager.request(**args)
214211

samples/openapi3/client/petstore/python-aiohttp/tests/test_api_client.py

-8
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,6 @@
1010
HOST = 'http://localhost/v2'
1111

1212
class TestApiClient(unittest.IsolatedAsyncioTestCase):
13-
async def test_context_manager_closes_client(self):
14-
async with petstore_api.ApiClient() as client:
15-
# pool_manager
16-
self.assertFalse(client.rest_client.pool_manager.closed)
17-
rest_pool_ref = client.rest_client.pool_manager
18-
19-
self.assertTrue(rest_pool_ref.closed)
20-
2113
async def test_ignore_operation_servers(self):
2214
config = petstore_api.Configuration(host=HOST)
2315
async with petstore_api.ApiClient(config) as client:

0 commit comments

Comments
 (0)