(3.0.1) mypy compliance
Some checks failed
Actions / Build (push) Successful in 18s
Actions / Lint with Ruff & Pylint (push) Failing after 15s
Actions / Build Documentation (push) Successful in 24s

This commit is contained in:
Seaswimmer 2024-11-15 09:51:11 -05:00
parent 18d2c504c7
commit a7113babb7
Signed by: cswimr
GPG key ID: A9C162E867C851FA
7 changed files with 81 additions and 41 deletions

View file

@ -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",
]

View file

@ -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

View file

@ -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
self.config.logger.info("Voices cache populated!")
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,20 +85,27 @@ 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"]:
yield Voice(
id=voice["id"],
name=voice["name"],
gender=voice["gender"],
source=voice["source"],
language=Language(**voice["language"]),
)
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"],
gender=voice["gender"],
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)
return request.data
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!}")

View file

@ -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).

View file

@ -1 +1 @@
VERSION = "3.0.0"
VERSION = "3.0.1"

View file

@ -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"

View file

@ -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)