(2.1.0) fix running inside of async loops and made user_agent mandatory
this release finishes the usage.md page on the documentation, makes specifying a `user_agent` mandatory when instantiating a `FloweryAPIConfig` class, and fixes a bug that would prevent the `FloweryAPI` class from being instantiated inside of an async event loop
This commit is contained in:
parent
f5bef8acc8
commit
798b551138
6 changed files with 121 additions and 20 deletions
|
@ -0,0 +1,96 @@
|
||||||
|
# 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)
|
||||||
|
```
|
|
@ -52,6 +52,8 @@ 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
|
||||||
|
|
|
@ -55,13 +55,13 @@ class FloweryAPIConfig:
|
||||||
"""Configuration for the Flowery API
|
"""Configuration for the Flowery API
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
user_agent (str | None): User-Agent string to use for the HTTP requests
|
user_agent (str): User-Agent string to use for the HTTP requests. Required as of 2.1.0.
|
||||||
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
|
allow_truncation (bool): Whether to allow truncation of text that is too long, defaults to `True`
|
||||||
retry_limit (int): Number of times to retry a request before giving up
|
retry_limit (int): Number of times to retry a request before giving up, defaults to `3`
|
||||||
interval (int): Seconds to wait between each retried request, multiplied by how many attempted requests have been made
|
interval (int): Seconds to wait between each retried request, multiplied by how many attempted requests have been made, defaults to `5`
|
||||||
"""
|
"""
|
||||||
user_agent: str | None = None
|
user_agent: str
|
||||||
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,6 +69,4 @@ 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})"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import AsyncGenerator, List, Tuple
|
from typing import AsyncGenerator, 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,15 +12,23 @@ 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 = FloweryAPIConfig()):
|
def __init__(self, config: FloweryAPIConfig):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.adapter = RestAdapter(config)
|
self.adapter = RestAdapter(config)
|
||||||
self._voices_cache: List[Voice] = []
|
self._voices_cache: Tuple[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 = [voice async for voice in self.fetch_voices()]
|
self._voices_cache = tuple([voice async for voice in self.fetch_voices()]) # pylint: disable=consider-using-generator
|
||||||
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:
|
||||||
|
@ -30,11 +38,8 @@ 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: 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.
|
Tuple[Voice] | None: A tuple of Voice objects if found, otherwise 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))
|
||||||
|
@ -45,7 +50,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
|
||||||
raise ValueError('You must provide either a voice_id or a name!')
|
return self._voices_cache or None
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
VERSION = "2.0.1"
|
VERSION = "2.1.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyflowery"
|
name = "pyflowery"
|
||||||
version = "2.0.1"
|
version = "2.1.0"
|
||||||
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"
|
||||||
|
|
Loading…
Reference in a new issue