(3.0.1) mypy compliance
This commit is contained in:
parent
18d2c504c7
commit
a7113babb7
7 changed files with 81 additions and 41 deletions
|
@ -1,6 +1,7 @@
|
|||
from pyflowery.exceptions import (
|
||||
ClientError,
|
||||
InternalServerError,
|
||||
ResponseError,
|
||||
RetryLimitExceeded,
|
||||
TooManyRequests,
|
||||
)
|
||||
|
@ -9,14 +10,15 @@ from pyflowery.pyflowery import FloweryAPI
|
|||
from pyflowery.version import VERSION
|
||||
|
||||
__all__ = [
|
||||
'FloweryAPI',
|
||||
'FloweryAPIConfig',
|
||||
'Language',
|
||||
'Result',
|
||||
'Voice',
|
||||
'VERSION',
|
||||
'ClientError',
|
||||
'InternalServerError',
|
||||
'RetryLimitExceeded',
|
||||
'TooManyRequests',
|
||||
"FloweryAPI",
|
||||
"FloweryAPIConfig",
|
||||
"Language",
|
||||
"Result",
|
||||
"Voice",
|
||||
"VERSION",
|
||||
"ResponseError",
|
||||
"ClientError",
|
||||
"InternalServerError",
|
||||
"RetryLimitExceeded",
|
||||
"TooManyRequests",
|
||||
]
|
||||
|
|
|
@ -1,19 +1,33 @@
|
|||
class ResponseError(Exception):
|
||||
"""Raised when an API response is empty or has an unexpected format"""
|
||||
|
||||
def __init__(self, message) -> None:
|
||||
self.message = "Invalid response from Flowery API: " + message
|
||||
|
||||
|
||||
class InternalServerError(Exception):
|
||||
"""Raised when the API returns a 5xx status code"""
|
||||
def __init__(self, message):
|
||||
|
||||
def __init__(self, message) -> None:
|
||||
self.message = message
|
||||
|
||||
|
||||
class ClientError(Exception):
|
||||
"""Raised when the API returns a 4xx status code"""
|
||||
def __init__(self, message):
|
||||
|
||||
def __init__(self, message) -> None:
|
||||
self.message = message
|
||||
|
||||
|
||||
class TooManyRequests(Exception):
|
||||
"""Raised when the API returns a 429 status code"""
|
||||
def __init__(self, message):
|
||||
|
||||
def __init__(self, message) -> None:
|
||||
self.message = message
|
||||
|
||||
|
||||
class RetryLimitExceeded(Exception):
|
||||
"""Raised when the retry limit is exceeded"""
|
||||
def __init__(self, message):
|
||||
|
||||
def __init__(self, message) -> None:
|
||||
self.message = message
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import asyncio
|
||||
from typing import AsyncGenerator, Tuple
|
||||
|
||||
from pyflowery.exceptions import ResponseError
|
||||
from pyflowery.models import FloweryAPIConfig, Language, Voice
|
||||
from pyflowery.rest_adapter import RestAdapter
|
||||
|
||||
|
@ -15,8 +16,8 @@ class FloweryAPI:
|
|||
|
||||
def __init__(self, config: FloweryAPIConfig) -> None:
|
||||
self.config = config
|
||||
self.adapter = RestAdapter(config)
|
||||
self._voices_cache: Tuple[Voice] = ()
|
||||
self.adapter = RestAdapter(config=config)
|
||||
self._voices_cache: Tuple[Voice, ...] = ()
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
|
@ -30,9 +31,12 @@ class FloweryAPI:
|
|||
async def _populate_voices_cache(self) -> None:
|
||||
"""Populate the voices cache. This method is called automatically when the FloweryAPI object is created, and should not be called directly."""
|
||||
self._voices_cache = tuple([voice async for voice in self.fetch_voices()]) # pylint: disable=consider-using-generator
|
||||
if self._voices_cache == ():
|
||||
raise ValueError("Failed to populate voices cache! Please report this issue at https://www.coastalcommits.com/cswimr/PyFlowery/issues.")
|
||||
else:
|
||||
self.config.logger.info("Voices cache populated!")
|
||||
|
||||
def get_voices(self, voice_id: str | None = None, name: str | None = None) -> Tuple[Voice] | None:
|
||||
def get_voices(self, voice_id: str | None = None, name: str | None = None) -> Tuple[Voice, ...] | None:
|
||||
"""Get a set of voices from the cache.
|
||||
|
||||
Args:
|
||||
|
@ -43,8 +47,8 @@ class FloweryAPI:
|
|||
Tuple[Voice] | None: A tuple of Voice objects if found, otherwise None
|
||||
"""
|
||||
if voice_id:
|
||||
voice = next((voice for voice in self._voices_cache if voice.id == voice_id))
|
||||
return (voice,) or None
|
||||
voice = next((voice for voice in self._voices_cache if voice.id == voice_id), None)
|
||||
return (voice,) if voice else None
|
||||
if name:
|
||||
voices = []
|
||||
for voice in self._voices_cache:
|
||||
|
@ -81,13 +85,16 @@ class FloweryAPI:
|
|||
TooManyRequests: Raised when the Flowery API returns a 429 status code
|
||||
ClientError: Raised when the Flowery API returns a 4xx status code
|
||||
InternalServerError: Raised when the Flowery API returns a 5xx status code
|
||||
ResponseError: Raised when the Flowery API returns an empty response or a response with an unexpected format
|
||||
RetryLimitExceeded: Raised when the retry limit defined in the `FloweryAPIConfig` class (default 3) is exceeded
|
||||
|
||||
Returns:
|
||||
AsyncGenerator[Voice, None]: A generator of Voices
|
||||
"""
|
||||
request = await self.adapter.get(endpoint="/tts/voices")
|
||||
for voice in request.data["voices"]:
|
||||
if request is not None:
|
||||
if isinstance(request.data, dict):
|
||||
for voice in request.data.get("voices", []):
|
||||
yield Voice(
|
||||
id=voice["id"],
|
||||
name=voice["name"],
|
||||
|
@ -95,6 +102,10 @@ class FloweryAPI:
|
|||
source=voice["source"],
|
||||
language=Language(**voice["language"]),
|
||||
)
|
||||
else:
|
||||
raise ResponseError(f"Invalid response from Flowery API: {request.data!r}")
|
||||
else:
|
||||
raise ResponseError("Invalid response from Flowery API: Empty Response!}")
|
||||
|
||||
async def fetch_tts(
|
||||
self, text: str, voice: Voice | str | None = None, translate: bool = False, silence: int = 0, audio_format: str = "mp3", speed: float = 1.0
|
||||
|
@ -114,6 +125,7 @@ class FloweryAPI:
|
|||
TooManyRequests: Raised when the Flowery API returns a 429 status code
|
||||
ClientError: Raised when the Flowery API returns a 4xx status code
|
||||
InternalServerError: Raised when the Flowery API returns a 5xx status code
|
||||
ResponseError: Raised when the Flowery API returns an empty response or a response with an unexpected format
|
||||
RetryLimitExceeded: Raised when the retry limit defined in the `FloweryAPIConfig` class (default 3) is exceeded
|
||||
|
||||
Returns:
|
||||
|
@ -132,5 +144,13 @@ class FloweryAPI:
|
|||
}
|
||||
if voice:
|
||||
params["voice"] = voice.id if isinstance(voice, Voice) else voice
|
||||
|
||||
request = await self.adapter.get(endpoint="/tts", params=params, timeout=180)
|
||||
|
||||
if request is not None:
|
||||
if isinstance(request.data, bytes):
|
||||
return request.data
|
||||
else:
|
||||
raise ResponseError(f"Invalid response from Flowery API: {request.data!r}")
|
||||
else:
|
||||
raise ResponseError("Invalid response from Flowery API: Empty Response!}")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""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
|
||||
|
||||
|
@ -27,7 +28,7 @@ class RestAdapter:
|
|||
self._url = "https://api.flowery.pw/v1"
|
||||
self.config = config
|
||||
|
||||
async def _do(self, http_method: str, endpoint: str, params: dict = None, timeout: float = 60) -> Result | None:
|
||||
async def _do(self, http_method: str, endpoint: str, params: dict | None = None, timeout: float = 60) -> Result | None:
|
||||
"""Internal method to make a request to the Flowery API. You shouldn't use this directly.
|
||||
|
||||
Args:
|
||||
|
@ -55,26 +56,26 @@ class RestAdapter:
|
|||
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:
|
||||
async with session.request(method=http_method, url=full_url, params=sanitized_params, headers=headers, timeout=timeout) as response: # type: ignore
|
||||
try:
|
||||
data = await response.json()
|
||||
except (JSONDecodeError, aiohttp.ContentTypeError):
|
||||
data = await response.read()
|
||||
|
||||
result = Result(
|
||||
success=response.status,
|
||||
success=response.status < 300,
|
||||
status_code=response.status,
|
||||
message=response.reason,
|
||||
message=response.reason or "Unknown error",
|
||||
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}")
|
||||
raise TooManyRequests(f"{result.message} - {result.data!r}")
|
||||
if 400 <= result.status_code < 500:
|
||||
raise ClientError(f"{result.status_code} - {result.message} - {result.data}")
|
||||
raise ClientError(f"{result.status_code} - {result.message} - {result.data!r}")
|
||||
if 500 <= result.status_code < 600:
|
||||
raise InternalServerError(f"{result.status_code} - {result.message} - {result.data}")
|
||||
raise InternalServerError(f"{result.status_code} - {result.message} - {result.data!r}")
|
||||
except (TooManyRequests, InternalServerError) as e:
|
||||
if retry_counter < self.config.retry_limit:
|
||||
interval = self.config.interval * retry_counter
|
||||
|
@ -84,8 +85,9 @@ class RestAdapter:
|
|||
continue
|
||||
raise RetryLimitExceeded(message=f"Request failed more than {self.config.retry_limit} times, not retrying") from e
|
||||
return result
|
||||
return None
|
||||
|
||||
async def get(self, endpoint: str, params: dict = None, timeout: float = 60) -> Result | None:
|
||||
async def get(self, endpoint: str, params: dict | None = None, timeout: float = 60) -> Result | None:
|
||||
"""Make a GET request to the Flowery API. You should almost never have to use this directly.
|
||||
|
||||
If you need to use this method because an endpoint is missing, please open an issue on the [CoastalCommits repository](https://www.coastalcommits.com/cswimr/PyFlowery/issues).
|
||||
|
|
|
@ -1 +1 @@
|
|||
VERSION = "3.0.0"
|
||||
VERSION = "3.0.1"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "pyflowery"
|
||||
version = "3.0.0"
|
||||
version = "3.0.1"
|
||||
description = "A Python API wrapper for the Flowery API"
|
||||
readme = "README.md"
|
||||
requires-python = "<4.0,>=3.11"
|
||||
|
|
|
@ -22,8 +22,10 @@ STORMTROOPER = "191c5adc-a092-5eea-b4ff-ce01f66153ae" # TikTok
|
|||
|
||||
async def test_fetch_tts() -> None:
|
||||
"""Test the fetch_tts method"""
|
||||
voice = api.get_voices(voice_id=ALEXANDER)[0]
|
||||
tts = await api.fetch_tts(text="Sphinx of black quartz, judge my vow. The quick brown fox jumps over a lazy dog.", voice=voice)
|
||||
voice = api.get_voices(voice_id=ALEXANDER)
|
||||
if voice is None:
|
||||
raise ValueError("Voice not found")
|
||||
tts = await api.fetch_tts(text="Sphinx of black quartz, judge my vow. The quick brown fox jumps over a lazy dog.", voice=voice[0])
|
||||
try:
|
||||
with open(file="test.mp3", mode="wb") as f:
|
||||
f.write(tts)
|
||||
|
|
Loading…
Reference in a new issue