(1.0.3) retry handlers and more
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:
parent
d585072d3e
commit
33b63fecc4
8 changed files with 89 additions and 25 deletions
|
@ -1,3 +1,9 @@
|
|||
from pyflowery.exceptions import (
|
||||
ClientError,
|
||||
InternalServerError,
|
||||
RetryLimitExceeded,
|
||||
TooManyRequests,
|
||||
)
|
||||
from pyflowery.models import FloweryAPIConfig, Language, Result, Voice
|
||||
from pyflowery.pyflowery import FloweryAPI
|
||||
from pyflowery.version import VERSION
|
||||
|
@ -5,8 +11,12 @@ from pyflowery.version import VERSION
|
|||
__all__ = [
|
||||
'FloweryAPI',
|
||||
'FloweryAPIConfig',
|
||||
'Language',
|
||||
'Result',
|
||||
'Voice',
|
||||
'Language',
|
||||
'VERSION',
|
||||
'ClientError',
|
||||
'InternalServerError',
|
||||
'RetryLimitExceeded',
|
||||
'TooManyRequests',
|
||||
]
|
||||
|
|
19
pyflowery/exceptions.py
Normal file
19
pyflowery/exceptions.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
class InternalServerError(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super(InternalServerError, self).__init__(message)
|
||||
|
||||
class ClientError(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super(ClientError, self).__init__(message)
|
||||
|
||||
class TooManyRequests(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super(TooManyRequests, self).__init__(message)
|
||||
|
||||
class RetryLimitExceeded(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super(RetryLimitExceeded, self).__init__(message)
|
|
@ -1,5 +1,6 @@
|
|||
from dataclasses import dataclass, field
|
||||
from logging import Logger, getLogger
|
||||
from sys import version as pyversion
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from pyflowery.version import VERSION
|
||||
|
@ -54,9 +55,20 @@ class FloweryAPIConfig:
|
|||
"""Configuration for the Flowery API
|
||||
|
||||
Attributes:
|
||||
user_agent (str): User-Agent string to use for the HTTP requests
|
||||
user_agent (str | None): User-Agent string to use for the HTTP requests
|
||||
logger (Logger): Logger to use for logging messages
|
||||
allow_truncation (bool): Whether to allow truncation of text that is too long
|
||||
retry_limit (int): Number of times to retry a request before giving up
|
||||
interval (int): Seconds to wait between each retried request, multiplied by how many attempted requests have been made
|
||||
"""
|
||||
user_agent: str = f"PyFlowery/{VERSION}"
|
||||
user_agent: str | None = None
|
||||
logger: Logger = getLogger('pyflowery')
|
||||
allow_truncation: bool = False
|
||||
retry_limit: int = 3
|
||||
interval: int = 5
|
||||
|
||||
def prepended_user_agent(self) -> str:
|
||||
"""Return the user_agent with the PyFlowery module version prepended"""
|
||||
if not self.user_agent:
|
||||
return f"PyFlowery/{VERSION} (Python {pyversion})"
|
||||
return f"PyFlowery/{VERSION} {self.user_agent} (Python {pyversion})"
|
||||
|
|
|
@ -72,6 +72,4 @@ class FloweryAPI:
|
|||
if voice:
|
||||
params['voice'] = voice.id if isinstance(voice, Voice) else voice
|
||||
request = await self.adapter.get('/tts', params, timeout=180)
|
||||
if request.status_code in range(400, 600):
|
||||
raise ValueError(request.data['message'])
|
||||
return request.data
|
||||
|
|
|
@ -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,12 +40,14 @@ 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:
|
||||
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()
|
||||
|
@ -47,12 +55,29 @@ class RestAdapter:
|
|||
data = await response.read()
|
||||
|
||||
result = Result(
|
||||
success=response.status in range(200, 299),
|
||||
success=response.status,
|
||||
status_code=response.status,
|
||||
message=response.reason,
|
||||
data=data,
|
||||
)
|
||||
self._logger.debug("Received response: %s %s", response.status, response.reason)
|
||||
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:
|
||||
|
|
|
@ -1 +1 @@
|
|||
VERSION = "1.0.2"
|
||||
VERSION = "1.0.3"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "pyflowery"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
description = "A Python API wrapper for the Flowery API"
|
||||
authors = ["cswimr <seaswimmerthefsh@gmail.com>"]
|
||||
license = "GPL 3.0-only"
|
||||
|
|
|
@ -14,7 +14,7 @@ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(messag
|
|||
handler.setFormatter(formatter)
|
||||
root.addHandler(handler)
|
||||
|
||||
api = FloweryAPI(FloweryAPIConfig())
|
||||
api = FloweryAPI(FloweryAPIConfig(user_agent="PyFloweryTests"))
|
||||
|
||||
ALEXANDER = "fa3ea565-121f-5efd-b4e9-59895c77df23" # TikTok
|
||||
JACOB = "38f45366-68e8-5d39-b1ef-3fd4eeb61cdb" # Microsoft Azure
|
||||
|
|
Loading…
Reference in a new issue