Compare commits

..

No commits in common. "798b551138cc5b6503d36614d12dc499992b0e3f" and "25e61b9a79f54eff0ac104064cf176c5c35ca351" have entirely different histories.

6 changed files with 25 additions and 126 deletions

View file

@ -1,96 +0,0 @@
# Usage
This page contains a few examples of how to use the `pyflowery` package. This page does not cover installation, for that see the [installation](installation.md) page.
## Creating an API client
To create an API client, you need to first import the `pyflowery.pyflowery.FloweryAPI` class. Then, you can create an instance of the class by passing in a `pyflowery.models.FloweryAPIConfig` class.
```python
from pyflowery import FloweryAPI, FloweryAPIConfig
config = FloweryAPIConfig(user_agent="PyFlowery Documentation Example/example@gmail.com")
api = FloweryAPI(config)
```
Okay, now we have a `FloweryAPI` class. Let's move on to the next example.
## Retrieving a voice
So, whenever a `FloweryAPI` class is instantiated, it will automatically fetch a list of voices from the Flowery API, and cache it in the class. You can access this cache by calling the `get_voices` method with either a voice's ID or the name of a voice. If you want to get a list of all voices, you can call the `get_voices` method without any arguments.
```python
# Set up the API client
from pyflowery import FloweryAPI, FloweryAPIConfig
config = FloweryAPIConfig(user_agent="PyFlowery Documentation Example/example@gmail.com")
api = FloweryAPI(config) # This will fetch all of the voices from the API and cache them automatically, you don't need to do that manually
voices = api.get_voices(name="Alexander")
print(voices) # (Voice(id='fa3ea565-121f-5efd-b4e9-59895c77df23', name='Alexander', gender='Male', source='TikTok', language=Language(name='English (United States)', code='en-US')),)
print(voices[0].id) # 'fa3ea565-121f-5efd-b4e9-59895c77df23'
```
## Updating the API client's voice cache
In most use cases, it is not necessary to manually update the voice cache. But, for applications that run for an extended period of time, it may be necessary to manually update the voice cache. To do this, you can call the `_populate_voices_cache()` async method.
```python
import asyncio # This is required to run asynchronous code outside of async functions
from pyflowery import FloweryAPI, FloweryAPIConfig
config = FloweryAPIConfig(user_agent="PyFlowery Documentation Example/example@gmail.com")
api = FloweryAPI(config) # This will fetch all of the voices from the API and cache them automatically, you don't need to do that manually
asyncio.run(api._populate_voices_cache()) # This will update the voice cache. This is what `FloweryAPI` calls automatically when it is instantiated
```
## Retrieving a list of voices from the API directly
If necessary, you can call the `fetch_voices()` or `fetch_voice()` methods. These methods will fetch the voices from the API directly, skipping the cache. This isn't recommended, though, as it puts more strain on the Flowery API.
=== "`fetch_voices()`"
`fetch_voices()` returns an `AsyncContextManager`, so you need to iterate through it when you call it.
```python
import asyncio
from pyflowery import FloweryAPI, FloweryAPIConfig
config = FloweryAPIConfig(user_agent="PyFlowery Documentation Example/example@gmail.com")
api = FloweryAPI(config)
async def fetch_voices():
voices_list = []
async for voice in api.fetch_voices():
voices_list.append(voice)
return voices_list
voices = asyncio.run(fetch_voices())
```
=== "`fetch_voice()`"
```python
import asyncio
from pyflowery import FloweryAPI, FloweryAPIConfig
config = FloweryAPIConfig(user_agent="PyFlowery Documentation Example/example@gmail.com")
api = FloweryAPI(config)
voice_id = "38f45366-68e8-5d39-b1ef-3fd4eeb61cdb"
voice = asyncio.run(api.fetch_voice(voice_id))
print(voice) # Voice(id='38f45366-68e8-5d39-b1ef-3fd4eeb61cdb', name='Jacob', gender='Male', source='Microsoft Azure', language=Language(name='English (United States)', code='en-US'))
```
## Converting text to audio
Finally, let's convert some text into audio. To do this, you can call the `fetch_tts()` method. This will return the bytes of the audio file.
```python
import asyncio
from pyflowery import FloweryAPI, FloweryAPIConfig
config = FloweryAPIConfig(user_agent="PyFlowery Documentation Example/example@gmail.com")
api = FloweryAPI(config)
voice = api.get_voices(name="Alexander")[0]
tts = asyncio.run(api.fetch_tts("Hello, world!", voice))
with open("hello_world.mp3", "wb") as f:
f.write(tts)
```

View file

@ -52,8 +52,6 @@ markdown_extensions:
- pymdownx.inlinehilite - pymdownx.inlinehilite
- pymdownx.snippets - pymdownx.snippets
- pymdownx.superfences - pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.highlight: - pymdownx.highlight:
anchor_linenums: true anchor_linenums: true
line_spans: __span line_spans: __span

View file

@ -42,12 +42,12 @@ class Result:
success (bool): Boolean of whether the request was successful success (bool): Boolean of whether the request was successful
status_code (int): Standard HTTP Status code status_code (int): Standard HTTP Status code
message (str = ''): Human readable result message (str = ''): Human readable result
data (Union[List[Dict], Dict, bytes]): Python List of Dictionaries (or maybe just a single Dictionary on error), can also be a ByteString data (Union[List[Dict], Dict]): Python List of Dictionaries (or maybe just a single Dictionary on error)
""" """
success: bool success: bool
status_code: int status_code: int
message: str = '' message: str = ''
data: Union[List[Dict], Dict, bytes] = field(default_factory=dict) data: Union[List[Dict], Dict] = field(default_factory=dict)
@dataclass @dataclass
@ -55,13 +55,13 @@ class FloweryAPIConfig:
"""Configuration for the Flowery API """Configuration for the Flowery API
Attributes: Attributes:
user_agent (str): User-Agent string to use for the HTTP requests. Required as of 2.1.0. user_agent (str | None): User-Agent string to use for the HTTP requests
logger (Logger): Logger to use for logging messages logger (Logger): Logger to use for logging messages
allow_truncation (bool): Whether to allow truncation of text that is too long, defaults to `True` 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, defaults to `3` 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, defaults to `5` interval (int): Seconds to wait between each retried request, multiplied by how many attempted requests have been made
""" """
user_agent: str user_agent: str | None = None
logger: Logger = getLogger('pyflowery') logger: Logger = getLogger('pyflowery')
allow_truncation: bool = False allow_truncation: bool = False
retry_limit: int = 3 retry_limit: int = 3
@ -69,4 +69,6 @@ class FloweryAPIConfig:
def prepended_user_agent(self) -> str: def prepended_user_agent(self) -> str:
"""Return the user_agent with the PyFlowery module version prepended""" """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})" return f"PyFlowery/{VERSION} {self.user_agent} (Python {pyversion})"

View file

@ -1,5 +1,5 @@
import asyncio import asyncio
from typing import AsyncGenerator, Tuple from typing import AsyncGenerator, List, Tuple
from pyflowery.models import FloweryAPIConfig, Language, Voice from pyflowery.models import FloweryAPIConfig, Language, Voice
from pyflowery.rest_adapter import RestAdapter from pyflowery.rest_adapter import RestAdapter
@ -12,23 +12,15 @@ class FloweryAPI:
config (FloweryAPIConfig): Configuration object for the API config (FloweryAPIConfig): Configuration object for the API
adapter (RestAdapter): Adapter for making HTTP requests adapter (RestAdapter): Adapter for making HTTP requests
""" """
def __init__(self, config: FloweryAPIConfig): def __init__(self, config: FloweryAPIConfig = FloweryAPIConfig()):
self.config = config self.config = config
self.adapter = RestAdapter(config) self.adapter = RestAdapter(config)
self._voices_cache: Tuple[Voice] = () self._voices_cache: List[Voice] = []
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
self.config.logger.info("Async event loop is already running. Adding `_populate_voices_cache()` to the event loop.")
asyncio.create_task(self._populate_voices_cache())
else:
asyncio.run(self._populate_voices_cache()) asyncio.run(self._populate_voices_cache())
async def _populate_voices_cache(self): async def _populate_voices_cache(self):
"""Populate the voices cache. This method is called automatically when the FloweryAPI object is created, and should not be called directly.""" """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._voices_cache = [voice async for voice in self.fetch_voices()]
self.config.logger.info('Voices cache populated!') 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:
@ -38,8 +30,11 @@ class FloweryAPI:
voice_id (str): The ID of the voice voice_id (str): The ID of the voice
name (str): The name of the voice name (str): The name of the voice
Raises:
ValueError: Raised when neither voice_id nor name is provided
Returns: Returns:
Tuple[Voice] | None: A tuple of Voice objects if found, otherwise None Tuple[Voice] | None: The voice or a list of voices, depending on the arguments provided and if there are multiple Voices with the same name. If no voice is found, returns None.
""" """
if voice_id: if voice_id:
voice = next((voice for voice in self._voices_cache if voice.id == voice_id)) voice = next((voice for voice in self._voices_cache if voice.id == voice_id))
@ -50,7 +45,7 @@ class FloweryAPI:
if voice.name == name: if voice.name == name:
voices.append(voice) voices.append(voice)
return tuple(voices) or None return tuple(voices) or None
return self._voices_cache or None raise ValueError('You must provide either a voice_id or a name!')
async def fetch_voice(self, voice_id: str) -> Voice: async def fetch_voice(self, voice_id: str) -> Voice:
"""Fetch a voice from the Flowery API. This method bypasses the cache and directly queries the Flowery API. You should usually use `get_voice()` instead. """Fetch a voice from the Flowery API. This method bypasses the cache and directly queries the Flowery API. You should usually use `get_voice()` instead.
@ -95,7 +90,7 @@ class FloweryAPI:
language=Language(**voice['language']), language=Language(**voice['language']),
) )
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) -> bytes: 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):
"""Fetch a TTS audio file from the Flowery API """Fetch a TTS audio file from the Flowery API
Args: Args:
@ -114,11 +109,11 @@ class FloweryAPI:
RetryLimitExceeded: Raised when the retry limit defined in the `FloweryAPIConfig` class (default 3) is exceeded RetryLimitExceeded: Raised when the retry limit defined in the `FloweryAPIConfig` class (default 3) is exceeded
Returns: Returns:
bytes: The audio file in bytes bytes: The audio file
""" """
if len(text) > 2048: if len(text) > 2048:
if not self.config.allow_truncation: if not self.config.allow_truncation:
raise ValueError('Text must be less than or equal to 2048 characters') raise ValueError('Text must be less than 2048 characters')
self.config.logger.warning('Text is too long, will be truncated to 2048 characters by the API') self.config.logger.warning('Text is too long, will be truncated to 2048 characters by the API')
params = { params = {
'text': text, 'text': text,

View file

@ -1 +1 @@
VERSION = "2.1.0" VERSION = "2.0.1"

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyflowery" name = "pyflowery"
version = "2.1.0" version = "2.0.1"
description = "A Python API wrapper for the Flowery API" description = "A Python API wrapper for the Flowery API"
authors = ["cswimr <seaswimmerthefsh@gmail.com>"] authors = ["cswimr <seaswimmerthefsh@gmail.com>"]
readme = "README.md" readme = "README.md"