(1.0.3) retry handlers and more
Some checks failed
Actions / lint (push) Failing after 16s
Actions / build (push) Successful in 19s

added automatic retry handlers for 429 and 5xx error codes, as well as custom exceptions for ratelimiting, client errors, and server errors
also added a more advanced user agent string constructor. previously, setting FloweryAPIConfig.user_agent would override the default user_agent. so, if you set user_agent to `foobar`, the user agent string would be `foobar` in requests. now, the user agent string sent by requests would be the following on my development machine:
`'User-Agent': 'PyFlowery/1.0.3 PyFloweryTests (Python 3.12.6 (main, Sep  8 2024, 13:18:56) [GCC 14.2.1 20240805])'`
This commit is contained in:
cswimr 2024-09-17 23:24:14 -04:00
parent d585072d3e
commit 33b63fecc4
Signed by: cswimr
GPG key ID: 3813315477F26F82
8 changed files with 89 additions and 25 deletions

View file

@ -1,8 +1,15 @@
"""This module contains the RestAdapter class, which is used to make requests to the Flowery API."""""
from asyncio import sleep as asleep
from json import JSONDecodeError
import aiohttp
from pyflowery.exceptions import (
ClientError,
InternalServerError,
RetryLimitExceeded,
TooManyRequests,
)
from pyflowery.models import FloweryAPIConfig, Result
@ -17,8 +24,7 @@ class RestAdapter:
"""
def __init__(self, config = FloweryAPIConfig):
self._url = "https://api.flowery.pw/v1"
self._user_agent = config.user_agent
self._logger = config.logger
self.config = config
async def _do(self, http_method: str, endpoint: str, params: dict = None, timeout: float = 60):
"""Internal method to make a request to the Flowery API. You shouldn't use this directly.
@ -34,26 +40,45 @@ class RestAdapter:
"""
full_url = self._url + endpoint
headers = {
'User-Agent': self._user_agent,
'User-Agent': self.config.prepended_user_agent(),
}
sanitized_params = {k: str(v) if isinstance(v, bool) else v for k, v in params.items()} if params else None
self._logger.debug("Making %s request to %s with params %s", http_method, full_url, sanitized_params)
retry_counter = 0
async with aiohttp.ClientSession() as session:
async with session.request(method=http_method, url=full_url, params=sanitized_params, headers=headers, timeout=timeout) as response:
try:
data = await response.json()
except (JSONDecodeError, aiohttp.ContentTypeError):
data = await response.read()
while retry_counter < self.config.retry_limit:
self.config.logger.debug("Making %s request to %s with headers %s and params %s", http_method, full_url, headers, sanitized_params)
async with session.request(method=http_method, url=full_url, params=sanitized_params, headers=headers, timeout=timeout) as response:
try:
data = await response.json()
except (JSONDecodeError, aiohttp.ContentTypeError):
data = await response.read()
result = Result(
success=response.status in range(200, 299),
status_code=response.status,
message=response.reason,
data=data,
)
self._logger.debug("Received response: %s %s", response.status, response.reason)
return result
result = Result(
success=response.status,
status_code=response.status,
message=response.reason,
data=data,
)
self.config.logger.debug("Received response: %s %s", response.status, response.reason)
try:
if result.status_code == 429:
raise TooManyRequests(f"{result.message} - {result.data}")
elif 400 <= result.status_code < 500:
raise ClientError(f"{result.status_code} - {result.message} - {result.data}")
elif 500 <= result.status_code < 600:
raise InternalServerError(f"{result.status_code} - {result.message} - {result.data}")
else:
pass
except (TooManyRequests, InternalServerError) as e:
if retry_counter < self.config.retry_limit:
interval = self.config.interval * retry_counter
self.config.logger.error("%s - retrying in %s seconds", e, interval, exc_info=True)
retry_counter += 1
await asleep(interval)
continue
raise RetryLimitExceeded(message=f"Request failed more than {self.config.retry_limit} times, not retrying") from e
return result
async def get(self, endpoint: str, params: dict = None, timeout: float = 60) -> Result:
"""Make a GET request to the Flowery API. You should almost never have to use this directly.