Adds basic retries to rest API requests for Python asyncio (#17014)

* Adds basic retries to API requests

 * This mimics the basic retries performed by urllib3 in the sync version

* update samples

* Only use retry client if number of retries is specified in the config

* Reorganize

* Remove class attribute

* close retry_client

---------

Co-authored-by: William Cheng <wing328hk@gmail.com>
This commit is contained in:
Pat Buxton 2023-11-21 02:42:34 +00:00 committed by GitHub
parent 489d369ee7
commit 9970c06f8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 56 additions and 2 deletions

View File

@ -8,11 +8,14 @@ import re
import ssl import ssl
import aiohttp import aiohttp
import aiohttp_retry
from {{packageName}}.exceptions import ApiException, ApiValueError from {{packageName}}.exceptions import ApiException, ApiValueError
RESTResponseType = aiohttp.ClientResponse RESTResponseType = aiohttp.ClientResponse
ALLOW_RETRY_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE'})
class RESTResponse(io.IOBase): class RESTResponse(io.IOBase):
def __init__(self, resp) -> None: def __init__(self, resp) -> None:
@ -68,8 +71,24 @@ class RESTClientObject:
trust_env=True trust_env=True
) )
retries = configuration.retries
if retries is not None:
self.retry_client = aiohttp_retry.RetryClient(
client_session=self.pool_manager,
retry_options=aiohttp_retry.ExponentialRetry(
attempts=retries,
factor=0.0,
start_timeout=0.0,
max_timeout=120.0
)
)
else:
self.retry_client = None
async def close(self): async def close(self):
await self.pool_manager.close() await self.pool_manager.close()
if self.retry_client is not None:
await self.retry_client.close()
async def request( async def request(
self, self,
@ -168,7 +187,12 @@ class RESTClientObject:
declared content type.""" declared content type."""
raise ApiException(status=0, reason=msg) raise ApiException(status=0, reason=msg)
r = await self.pool_manager.request(**args) if self.retry_client is not None and method in ALLOW_RETRY_METHODS:
pool_manager = self.retry_client
else:
pool_manager = self.pool_manager
r = await pool_manager.request(**args)
return RESTResponse(r) return RESTResponse(r)

View File

@ -16,6 +16,7 @@ urllib3 = ">= 1.25.3"
python-dateutil = ">=2.8.2" python-dateutil = ">=2.8.2"
{{#asyncio}} {{#asyncio}}
aiohttp = ">= 3.8.4" aiohttp = ">= 3.8.4"
aiohttp-retry = ">= 2.8.3"
{{/asyncio}} {{/asyncio}}
{{#tornado}} {{#tornado}}
tornado = ">=4.2,<5" tornado = ">=4.2,<5"

View File

@ -5,6 +5,7 @@ pydantic >= 2
typing-extensions >= 4.7.1 typing-extensions >= 4.7.1
{{#asyncio}} {{#asyncio}}
aiohttp >= 3.0.0 aiohttp >= 3.0.0
aiohttp-retry >= 2.8.3
{{/asyncio}} {{/asyncio}}
{{#hasHttpSignatureMethods}} {{#hasHttpSignatureMethods}}
pycryptodome >= 3.9.0 pycryptodome >= 3.9.0

View File

@ -21,6 +21,7 @@ REQUIRES = [
"python-dateutil", "python-dateutil",
{{#asyncio}} {{#asyncio}}
"aiohttp >= 3.0.0", "aiohttp >= 3.0.0",
"aiohttp-retry >= 2.8.3",
{{/asyncio}} {{/asyncio}}
{{#tornado}} {{#tornado}}
"tornado>=4.2,<5", "tornado>=4.2,<5",

View File

@ -18,11 +18,14 @@ import re
import ssl import ssl
import aiohttp import aiohttp
import aiohttp_retry
from petstore_api.exceptions import ApiException, ApiValueError from petstore_api.exceptions import ApiException, ApiValueError
RESTResponseType = aiohttp.ClientResponse RESTResponseType = aiohttp.ClientResponse
ALLOW_RETRY_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE'})
class RESTResponse(io.IOBase): class RESTResponse(io.IOBase):
def __init__(self, resp) -> None: def __init__(self, resp) -> None:
@ -78,8 +81,24 @@ class RESTClientObject:
trust_env=True trust_env=True
) )
retries = configuration.retries
if retries is not None:
self.retry_client = aiohttp_retry.RetryClient(
client_session=self.pool_manager,
retry_options=aiohttp_retry.ExponentialRetry(
attempts=retries,
factor=0.0,
start_timeout=0.0,
max_timeout=120.0
)
)
else:
self.retry_client = None
async def close(self): async def close(self):
await self.pool_manager.close() await self.pool_manager.close()
if self.retry_client is not None:
await self.retry_client.close()
async def request( async def request(
self, self,
@ -178,7 +197,12 @@ class RESTClientObject:
declared content type.""" declared content type."""
raise ApiException(status=0, reason=msg) raise ApiException(status=0, reason=msg)
r = await self.pool_manager.request(**args) if self.retry_client is not None and method in ALLOW_RETRY_METHODS:
pool_manager = self.retry_client
else:
pool_manager = self.pool_manager
r = await pool_manager.request(**args)
return RESTResponse(r) return RESTResponse(r)

View File

@ -15,6 +15,7 @@ python = "^3.7"
urllib3 = ">= 1.25.3" urllib3 = ">= 1.25.3"
python-dateutil = ">=2.8.2" python-dateutil = ">=2.8.2"
aiohttp = ">= 3.8.4" aiohttp = ">= 3.8.4"
aiohttp-retry = ">= 2.8.3"
pem = ">= 19.3.0" pem = ">= 19.3.0"
pycryptodome = ">= 3.9.0" pycryptodome = ">= 3.9.0"
pydantic = ">=2" pydantic = ">=2"

View File

@ -4,4 +4,5 @@ urllib3 >= 1.25.3, < 2.1.0
pydantic >= 2 pydantic >= 2
typing-extensions >= 4.7.1 typing-extensions >= 4.7.1
aiohttp >= 3.0.0 aiohttp >= 3.0.0
aiohttp-retry >= 2.8.3
pycryptodome >= 3.9.0 pycryptodome >= 3.9.0

View File

@ -27,6 +27,7 @@ REQUIRES = [
"urllib3 >= 1.25.3, < 2.1.0", "urllib3 >= 1.25.3, < 2.1.0",
"python-dateutil", "python-dateutil",
"aiohttp >= 3.0.0", "aiohttp >= 3.0.0",
"aiohttp-retry >= 2.8.3",
"pem>=19.3.0", "pem>=19.3.0",
"pycryptodome>=3.9.0", "pycryptodome>=3.9.0",
"pydantic >= 2", "pydantic >= 2",