diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md deleted file mode 100644 index c60eab72..00000000 --- a/DEVELOPMENT.md +++ /dev/null @@ -1,94 +0,0 @@ -# Getstream Python SDK - -## Project structure: - -1. `./getstream` - Core Stream SDK -2. `./protocol` - Protocol definitions for Stream SDKs -3. `./plugins` - Plugins for Core Stream SDK -4. `./examples` - Examples for Core Stream SDK -5. `./tests` - Tests for Core Stream SDK -6. `.github/workflows` - GitHub Actions workflows - - -## Package structure: - -1. `getstream` - Core Stream SDK in `./getstream` - - Video RTC SDK in `./getstream/video/rtc` -2. `getstream.plugins` - Plugins for Core Stream SDK in `./plugins` - - -``` -NOTE -getstream acts as a namespace package which means all imports will be relative to the getstream package. Since getstream is a namespace package, it is required to have only one __init__.py file in the root of the package. The plugins cannot have their own __init__.py files in their respective getstream folders. When the core SDK is built and installed, you only get the core SDK getstream folder in the source distribution. When plugins are built and installed, they are merged into the core SDK namespace at getstream/plugins. -``` - -## Workspace - -We use uv to manage the workspace. The project root is the root of the workspace. The following packages are added to the workspace: -- `getstream` -- `getstream.plugins` - -Workspace is configured in `pyproject.toml` and `uv.lock`. -In the workspace root pyproject.toml, -```toml -[tool.uv.workspace] -members = [ - "getstream", - "plugins/*", -] -``` - -In workspace members, -```toml -[tool.uv.sources] -getstream = { workspace = true } -``` -You can also specify different sources in the sources table including local paths, git repositories, package indexes etc. Refer to the [uv documentation](https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-sources) for more details. - -To sync the workspace, run the following command: - -```bash -uv sync -``` - -To sync the workspace with all packages and extras, run the following command: -```bash -uv sync --all-packages --all-extras --dev -``` - -This pull the dependencies from the workspace members. - -To sync dependencies from upstream, run the following command: - -```bash -uv sync --no-sources --all-packages --all-extras --dev -``` -Use `--no-sources` in commands to avoid pulling dependencies from the workspace members. - - -Add dependencies to the workspace: - -```bash -uv add numpy -``` -This adds the dependency to the workspace. If you want to add a dependency to a specific package, you can do it like this: - -```bash -uv add numpy --package getstream -``` - -`examples/*` are excluded from the workspace so as to be able to run the examples without installing the workspace. - -## Build - -To build the workspace, run the following command from the respective package (or use the `--package` flag to build a specific package): - -```bash -uv build -``` - -To disable workspace build, run the following command: - -```bash -uv build --no-sources -``` diff --git a/examples/audio_moderation/main.py b/examples/audio_moderation/main.py index bd6681ad..e4ca72f0 100644 --- a/examples/audio_moderation/main.py +++ b/examples/audio_moderation/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Real-time Call Transcription with Deepgram STT +"""Example: Real-time Call Transcription with Deepgram STT This example demonstrates how to: 1. Join a Stream video call @@ -18,46 +17,46 @@ import argparse import asyncio import logging -import warnings import os import time import uuid +import warnings import webbrowser from urllib.parse import urlencode from dotenv import load_dotenv -from getstream.models import UserRequest +from getstream.models import CheckResponse, ModerationPayload, UserRequest +from getstream.plugins.deepgram.stt import DeepgramSTT from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc.track_util import PcmData -from getstream.models import CheckResponse, ModerationPayload -from getstream.plugins.deepgram.stt import DeepgramSTT logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") # Suppress dataclasses_json missing value RuntimeWarnings warnings.filterwarnings( - "ignore", category=RuntimeWarning, module="dataclasses_json.core" + "ignore", + category=RuntimeWarning, + module="dataclasses_json.core", ) def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -66,6 +65,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -90,7 +90,6 @@ def moderate(client: Stream, text: str, user_name: str) -> CheckResponse: thread with ``asyncio.to_thread`` from async code without blocking the event loop. """ - return client.moderation.check( config_key="custom:python-ai-test", # your moderation config key entity_creator_id=user_name, @@ -127,7 +126,7 @@ async def main(client: Stream): print("\nπŸ€– Starting moderation bot...") print("The bot will join the call and moderate all audio it receives.") print( - "Join the call in your browser and speak to see moderation results appear here!" + "Join the call in your browser and speak to see moderation results appear here!", ) print("\nPress Ctrl+C to stop the moderation bot.\n") @@ -153,21 +152,26 @@ async def on_transcript(event): user = event.user_metadata["user"] user_info = user.name if hasattr(user, "name") else str(user) print(f"[{timestamp}] {user_info}: {event.text}") - if hasattr(event, 'confidence') and event.confidence: + if hasattr(event, "confidence") and event.confidence: print(f" └─ confidence: {event.confidence:.2%}") - if hasattr(event, 'processing_time_ms') and event.processing_time_ms: + if hasattr(event, "processing_time_ms") and event.processing_time_ms: print(f" └─ processing time: {event.processing_time_ms:.1f}ms") # Moderation check (executed in a background thread to avoid blocking) - moderation = await asyncio.to_thread(moderate, client, event.text, user_info) + moderation = await asyncio.to_thread( + moderate, + client, + event.text, + user_info, + ) print( - f" └─ moderation recommended action: {moderation.recommended_action} for transcript: {event.text}" + f" └─ moderation recommended action: {moderation.recommended_action} for transcript: {event.text}", ) @stt.on("error") async def on_stt_error(event): print(f"\n❌ STT Error: {event.error_message}") - if hasattr(event, 'context') and event.context: + if hasattr(event, "context") and event.context: print(f" └─ context: {event.context}") # Keep the connection alive and wait for audio @@ -189,7 +193,7 @@ async def on_stt_error(event): def parse_args(): parser = argparse.ArgumentParser( - description="Stream Real-time Audio Moderation Example" + description="Stream Real-time Audio Moderation Example", ) parser.add_argument("--setup", action="store_true", help="Setup moderation config") return parser.parse_args() diff --git a/examples/audio_moderation/view_flagged.py b/examples/audio_moderation/view_flagged.py index 1b49f320..b8515d2f 100644 --- a/examples/audio_moderation/view_flagged.py +++ b/examples/audio_moderation/view_flagged.py @@ -1,5 +1,6 @@ -from getstream import Stream from dotenv import load_dotenv + +from getstream import Stream from getstream.models import QueryReviewQueueResponse, ReviewQueueItemResponse load_dotenv() diff --git a/examples/event_system/event_system_example.py b/examples/event_system/event_system_example.py index 45a25f8b..c761462c 100644 --- a/examples/event_system/event_system_example.py +++ b/examples/event_system/event_system_example.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example demonstrating the GetStream AI Plugins Event System. +"""Example demonstrating the GetStream AI Plugins Event System. This example shows how to: 1. Use structured events with different plugin types @@ -22,12 +21,12 @@ STT, TTS, VAD, - EventType, - STTTranscriptEvent, - VADAudioEvent, # Event utilities EventFilter, EventRegistry, + EventType, + STTTranscriptEvent, + VADAudioEvent, get_global_registry, ) from getstream.plugins.common.event_metrics import ( @@ -35,8 +34,8 @@ calculate_tts_metrics, ) from getstream.plugins.common.event_serialization import ( - serialize_events, deserialize_event, + serialize_events, ) # Set up logging @@ -76,7 +75,7 @@ async def _process_audio_impl(self, pcm_data, user_metadata=None): async def close(self): logger.info( - f"Closing STT plugin after {self.transcription_count} transcriptions" + f"Closing STT plugin after {self.transcription_count} transcriptions", ) await super().close() @@ -165,7 +164,8 @@ def analyze_stt_performance(registry: EventRegistry): def analyze_tts_performance(registry: EventRegistry): """Analyze TTS performance from events.""" tts_filter = EventFilter( - event_types=[EventType.TTS_SYNTHESIS_COMPLETE], time_window_ms=60000 + event_types=[EventType.TTS_SYNTHESIS_COMPLETE], + time_window_ms=60000, ) tts_events = registry.get_events(tts_filter) @@ -195,7 +195,7 @@ def demonstrate_event_filtering(registry: EventRegistry): # Filter by event type error_filter = EventFilter( - event_types=[EventType.STT_ERROR, EventType.TTS_ERROR, EventType.VAD_ERROR] + event_types=[EventType.STT_ERROR, EventType.TTS_ERROR, EventType.VAD_ERROR], ) error_events = registry.get_events(error_filter) @@ -203,7 +203,8 @@ def demonstrate_event_filtering(registry: EventRegistry): # Filter by confidence threshold high_confidence_filter = EventFilter( - event_types=[EventType.STT_TRANSCRIPT], min_confidence=0.9 + event_types=[EventType.STT_TRANSCRIPT], + min_confidence=0.9, ) high_confidence_events = registry.get_events(high_confidence_filter) @@ -243,7 +244,7 @@ def demonstrate_event_serialization(registry: EventRegistry): print(f"Saved events to {filename}") # Load and deserialize - with open(filename, "r") as f: + with open(filename) as f: loaded_json = f.read() events_data = json.loads(loaded_json) @@ -254,7 +255,7 @@ def demonstrate_event_serialization(registry: EventRegistry): # Verify restoration for original, restored in zip(recent_events, restored_events): print( - f"Original: {original.event_type.value} - Restored: {restored.event_type.value}" + f"Original: {original.event_type.value} - Restored: {restored.event_type.value}", ) @@ -275,7 +276,7 @@ async def on_stt_transcript(event: STTTranscriptEvent): @tts_plugin.on("synthesis_complete") async def on_tts_complete(event): print( - f"πŸ”Š TTS Complete: {event.total_audio_bytes} bytes in {event.synthesis_time_ms:.1f}ms" + f"πŸ”Š TTS Complete: {event.total_audio_bytes} bytes in {event.synthesis_time_ms:.1f}ms", ) @vad_plugin.on("speech_start") @@ -300,9 +301,10 @@ async def write(self, data): # Simulate STT processing print("--- STT Processing ---") - from getstream.video.rtc.track_util import PcmData import numpy as np + from getstream.video.rtc.track_util import PcmData + for i in range(3): # Create mock audio data mock_audio = np.random.randint(-32768, 32767, 1600, dtype=np.int16) diff --git a/examples/fal_stt_translate/main.py b/examples/fal_stt_translate/main.py index 35cf4a71..f771ac47 100644 --- a/examples/fal_stt_translate/main.py +++ b/examples/fal_stt_translate/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Real-time Call Transcription with Deepgram STT +"""Example: Real-time Call Transcription with Deepgram STT This example demonstrates how to: 1. Join a Stream video call @@ -26,31 +25,30 @@ from dotenv import load_dotenv from getstream.models import UserRequest +from getstream.plugins.fal.stt import FalWizperSTT +from getstream.plugins.silero.vad import SileroVAD from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc.track_util import PcmData -from getstream.plugins.fal.stt import FalWizperSTT -from getstream.plugins.silero.vad import SileroVAD logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -59,6 +57,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -112,7 +111,7 @@ async def main(): print("\nπŸ€– Starting transcription bot...") print( - "The bot will join the call and transcribe all audio it receives, optionally translating it to French." + "The bot will join the call and transcribe all audio it receives, optionally translating it to French.", ) print("Join the call in your browser and speak to see transcriptions appear here!") print("\nPress Ctrl+C to stop the transcription bot.\n") @@ -132,7 +131,7 @@ async def _on_pcm(pcm: PcmData, user): @vad.on("audio") # type: ignore[arg-type] async def on_speech_detected(pcm: PcmData, user): print( - f"{time.time()} Speech detected from user: {user} duration {pcm.duration}" + f"{time.time()} Speech detected from user: {user} duration {pcm.duration}", ) # Process audio through FAL.ai STT with user metadata user_metadata = {"user": user} if user else None @@ -146,15 +145,15 @@ async def on_transcript(event): user = event.user_metadata["user"] user_info = user.name if hasattr(user, "name") else str(user) print(f"[{timestamp}] {user_info}: {event.text}") - if hasattr(event, 'confidence') and event.confidence: + if hasattr(event, "confidence") and event.confidence: print(f" └─ confidence: {event.confidence:.2%}") - if hasattr(event, 'processing_time_ms') and event.processing_time_ms: + if hasattr(event, "processing_time_ms") and event.processing_time_ms: print(f" └─ processing time: {event.processing_time_ms:.1f}ms") @stt.on("error") async def on_stt_error(event): print(f"\n❌ STT Error: {event.error_message}") - if hasattr(event, 'context') and event.context: + if hasattr(event, "context") and event.context: print(f" └─ context: {event.context}") # Keep the connection alive and wait for audio diff --git a/examples/gemini_live/main.py b/examples/gemini_live/main.py index e39f8d6f..99de935f 100644 --- a/examples/gemini_live/main.py +++ b/examples/gemini_live/main.py @@ -1,9 +1,9 @@ import asyncio import logging import os -from uuid import uuid4 import webbrowser from urllib.parse import urlencode +from uuid import uuid4 from dotenv import load_dotenv @@ -13,7 +13,6 @@ from getstream.video import rtc from getstream.video.rtc.track_util import PcmData - logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", @@ -25,21 +24,20 @@ def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -48,6 +46,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -67,7 +66,6 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: async def main(): """Run a demo call with a Gemini Live Speech-to-Speech agent attached.""" - load_dotenv() client = Stream.from_env() diff --git a/examples/llm_audio_conversation/main.py b/examples/llm_audio_conversation/main.py index fd56e60c..1ee02327 100644 --- a/examples/llm_audio_conversation/main.py +++ b/examples/llm_audio_conversation/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: AI Voice Conversation Bot with Stream, Deepgram STT, ElevenLabs TTS, and OpenAI +"""Example: AI Voice Conversation Bot with Stream, Deepgram STT, ElevenLabs TTS, and OpenAI This example demonstrates how to: 1. Create a Stream video call and add an AI bot @@ -21,9 +20,9 @@ import asyncio import os import time -from uuid import uuid4 import webbrowser from urllib.parse import urlencode +from uuid import uuid4 from dotenv import load_dotenv from openai import OpenAI @@ -39,21 +38,20 @@ def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -62,6 +60,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -144,7 +143,7 @@ async def on_audio(pcm: PcmData, user): @vad.on("audio") async def on_speech_detected(pcm: PcmData, user): print( - f"{time.time()} Speech detected from user: {user} duration {pcm.duration}" + f"{time.time()} Speech detected from user: {user} duration {pcm.duration}", ) # Process audio through STT with user metadata user_metadata = {"user": user} if user else None @@ -158,7 +157,7 @@ async def on_transcript(event): user_info = str(user) print( f"{time.time()} got text from user {user_info}, with metadata {event.to_dict()}" - f"will send the transcript to the LLM: {event.text}" + f"will send the transcript to the LLM: {event.text}", ) response = openai_client.responses.create( diff --git a/examples/mcp/agent.py b/examples/mcp/agent.py index b618b08f..8cc78fb2 100644 --- a/examples/mcp/agent.py +++ b/examples/mcp/agent.py @@ -1,9 +1,10 @@ -import logging -import openai -import fastmcp import json +import logging import re +import fastmcp +import openai + logging.basicConfig(level=logging.INFO) @@ -62,7 +63,7 @@ async def chat_with_tools(prompt: str, client: fastmcp.Client) -> str: f"The previous tool call arguments were not valid JSON (error: {e}). " f'Please retry using a valid JSON object inside the brackets, e.g. {tool_name}["param":"value"].' ), - } + }, ) continue @@ -70,14 +71,14 @@ async def chat_with_tools(prompt: str, client: fastmcp.Client) -> str: try: result = await client.call_tool(tool_name, args) history.append( - {"role": "assistant", "content": f"(result) {result.data}"} + {"role": "assistant", "content": f"(result) {result.data}"}, ) except Exception as e: history.append( { "role": "assistant", "content": f"(error) Tool '{tool_name}' failed: {e}", - } + }, ) tool_calls += 1 @@ -86,18 +87,17 @@ async def chat_with_tools(prompt: str, client: fastmcp.Client) -> str: { "role": "user", "content": "You may make at most one more tool call. If not strictly necessary, answer the user directly now.", - } + }, ) if tool_calls >= 3: history.append( { "role": "user", "content": "Do not call more tools. Answer the user's question directly now using the information you have.", - } + }, ) final = call_llm(history) return final continue - else: - return reply + return reply diff --git a/examples/mcp/main.py b/examples/mcp/main.py index 423dae25..9cf96164 100644 --- a/examples/mcp/main.py +++ b/examples/mcp/main.py @@ -1,42 +1,41 @@ +import asyncio import logging import os import uuid import webbrowser from urllib.parse import urlencode +import fastmcp +from agent import chat_with_tools from dotenv import load_dotenv from getstream.models import UserRequest +from getstream.plugins.deepgram.stt import DeepgramSTT +from getstream.plugins.elevenlabs.tts import ElevenLabsTTS from getstream.stream import Stream from getstream.video import rtc from getstream.video.call import Call -from getstream.plugins.deepgram.stt import DeepgramSTT -from getstream.plugins.elevenlabs.tts import ElevenLabsTTS from getstream.video.rtc import audio_track from getstream.video.rtc.track_util import PcmData -from agent import chat_with_tools -import asyncio -import fastmcp logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -45,6 +44,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -64,8 +64,8 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: async def run_bot(call: Call, bot_user_id: str): """Join the call as a bot, convert speechβ†’text, call MCP tools - when the transcript is final, then speak the answer back.""" - + when the transcript is final, then speak the answer back. + """ stt = DeepgramSTT() tts = ElevenLabsTTS() track = audio_track.AudioStreamTrack(framerate=16000) @@ -103,7 +103,7 @@ async def on_transcript(event): @stt.on("error") async def on_stt_error(event): logging.error("STT error: %s", event.error_message) - if hasattr(event, 'context') and event.context: + if hasattr(event, "context") and event.context: logging.error("Context: %s", event.context) logging.info("🎧 Bot is listening… (Ctrl-C to stop)") diff --git a/examples/mcp/transport.py b/examples/mcp/transport.py index a4585778..179b28b8 100644 --- a/examples/mcp/transport.py +++ b/examples/mcp/transport.py @@ -1,5 +1,5 @@ -from fastmcp import FastMCP import httpx +from fastmcp import FastMCP mcp = FastMCP("Stream MCP Server") diff --git a/examples/openai_realtime_speech_to_speech/main.py b/examples/openai_realtime_speech_to_speech/main.py index 5f1b65ef..96705711 100644 --- a/examples/openai_realtime_speech_to_speech/main.py +++ b/examples/openai_realtime_speech_to_speech/main.py @@ -1,9 +1,9 @@ import asyncio import logging import os -from uuid import uuid4 import webbrowser from urllib.parse import urlencode +from uuid import uuid4 from dotenv import load_dotenv @@ -11,7 +11,6 @@ from getstream.models import CallRequest, StartClosedCaptionsResponse, UserRequest from getstream.plugins.openai.sts import OpenAIRealtime - logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", @@ -23,21 +22,20 @@ def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -46,6 +44,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -65,7 +64,6 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: async def main(): """Run a demo call with an OpenAI Speech-to-Speech agent attached.""" - load_dotenv() client = Stream.from_env() @@ -116,7 +114,7 @@ async def main(): "properties": {}, "required": [], }, - } + }, ] await sts_bot.update_session( @@ -133,7 +131,7 @@ async def main(): logging.info("🎧 Listening for responses... (Press Ctrl+C to stop)") logging.info( - "πŸ’‘ Try speaking in the browser – ask it something like 'start closed captions' to trigger the function call." + "πŸ’‘ Try speaking in the browser – ask it something like 'start closed captions' to trigger the function call.", ) async def start_closed_captions() -> StartClosedCaptionsResponse: @@ -156,7 +154,8 @@ async def start_closed_captions() -> StartClosedCaptionsResponse: result = await start_closed_captions() await sts_bot.send_function_call_output( - tool_call_id, result.to_json() + tool_call_id, + result.to_json(), ) await sts_bot.request_assistant_response() logging.info("πŸ›  Replied to tool call with result: %s", result) diff --git a/examples/record_audio/main.py b/examples/record_audio/main.py index 0f9e267b..931a0517 100644 --- a/examples/record_audio/main.py +++ b/examples/record_audio/main.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 +import argparse import asyncio -import os import logging -import argparse +import os from uuid import uuid4 -from dotenv import load_dotenv + from aiortc.contrib.media import MediaPlayer +from dotenv import load_dotenv from examples.utils import create_user from getstream.stream import Stream @@ -182,7 +183,8 @@ async def main(): ): # Add all audio tracks for connection, player in zip( - [bot1_connection, bot2_connection, bot3_connection], players + [bot1_connection, bot2_connection, bot3_connection], + players, ): if player.audio: await connection.add_tracks(audio=player.audio) @@ -225,10 +227,10 @@ async def main(): print("Before running this example:") print(" 1. Update the AUDIO_FILES list with paths to your actual audio files") print( - " 2. Make sure you have a .env file with STREAM_API_KEY and STREAM_API_SECRET" + " 2. Make sure you have a .env file with STREAM_API_KEY and STREAM_API_SECRET", ) print( - " 3. Audio files should be in a format supported by aiortc (WAV, MP3, MP4, etc.)" + " 3. Audio files should be in a format supported by aiortc (WAV, MP3, MP4, etc.)", ) print(" 4. Run with --type composite or --type track to specify recording type") print() diff --git a/examples/stt_assemblyai_transcription/README.md b/examples/stt_assemblyai_transcription/README.md new file mode 100644 index 00000000..512b3173 --- /dev/null +++ b/examples/stt_assemblyai_transcription/README.md @@ -0,0 +1,89 @@ +# Stream + AssemblyAI STT Example + +This example demonstrates how to build a real-time transcription bot that joins a Stream video call and transcribes speech using AssemblyAI's Speech-to-Text API. + +## What it does + +- πŸ€– Creates a transcription bot that joins a Stream video call +- 🌐 Opens a browser interface for users to join the call +- πŸŽ™οΈ Transcribes speech in real-time using AssemblyAI STT +- πŸ“ Displays transcriptions with timestamps and confidence scores in the terminal + +## Prerequisites + +1. **Stream Account**: Get your API credentials from [Stream Dashboard](https://dashboard.getstream.io) +2. **AssemblyAI Account**: Get your API key from [AssemblyAI Console](https://www.assemblyai.com/) +3. **Python 3.10+**: Required for running the example + +## Installation + +You can use your preferred package manager, but we recommend [`uv`](https://docs.astral.sh/uv/). + +1. **Navigate to this directory:** + ```bash + cd examples/stt_assemblyai_transcription + ``` + +2. **Install dependencies:** + ```bash + uv sync + ``` + +3. **Set up environment variables:** + Rename `env.example` to `.env` and fill in your actual credentials. + +## Usage + +Run the example: +```bash +uv run main.py +``` + +## Configuration Options + +You can customize the AssemblyAI STT settings in the `main.py` file: + +```python +stt = AssemblyAISTT( + sample_rate=48000, # Audio sample rate + language="en", # Language code + interim_results=True, # Enable interim results + enable_partials=True, # Enable partial transcripts + enable_automatic_punctuation=True, # Auto-punctuation + enable_utterance_end_detection=True, # Utterance detection +) +``` + +## Features + +- **Real-time transcription** with low latency +- **Partial transcripts** for immediate feedback +- **Automatic punctuation** for better readability +- **Utterance end detection** for natural speech segmentation +- **Multi-language support** (change the `language` parameter) +- **Confidence scoring** for transcription quality + +## How it works + +1. **Call Setup**: Creates a Stream video call with unique IDs +2. **Bot Joins**: A transcription bot joins the call as a participant +3. **Audio Processing**: Captures audio from all participants +4. **Real-time Transcription**: Sends audio to AssemblyAI for processing +5. **Results Display**: Shows transcripts in the terminal with timestamps + +## Troubleshooting + +- **No audio detected**: Ensure your microphone is working and permissions are granted +- **API errors**: Check your AssemblyAI API key and account status +- **Connection issues**: Verify your internet connection and Stream credentials + +## AssemblyAI Features + +AssemblyAI provides high-quality transcription with: +- **Nova-2 model** for best accuracy +- **Real-time streaming** for low latency +- **Automatic language detection** support +- **Speaker diarization** capabilities +- **Custom vocabulary** support + +For more information, visit [AssemblyAI Documentation](https://www.assemblyai.com/docs). diff --git a/examples/stt_assemblyai_transcription/__init__.py b/examples/stt_assemblyai_transcription/__init__.py new file mode 100644 index 00000000..67ccfb5e --- /dev/null +++ b/examples/stt_assemblyai_transcription/__init__.py @@ -0,0 +1 @@ +# AssemblyAI STT Transcription Example diff --git a/examples/stt_assemblyai_transcription/env.example b/examples/stt_assemblyai_transcription/env.example new file mode 100644 index 00000000..28c20e3c --- /dev/null +++ b/examples/stt_assemblyai_transcription/env.example @@ -0,0 +1,7 @@ +# Stream API credentials +STREAM_API_KEY=your_stream_api_key_here +STREAM_API_SECRET=your_stream_api_secret_here +EXAMPLE_BASE_URL=https://pronto.getstream.io + +# AssemblyAI API credentials +ASSEMBLYAI_API_KEY=your_assemblyai_api_key_here diff --git a/examples/stt_assemblyai_transcription/main.py b/examples/stt_assemblyai_transcription/main.py new file mode 100644 index 00000000..7e28361a --- /dev/null +++ b/examples/stt_assemblyai_transcription/main.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +"""Example: Real-time Call Transcription with AssemblyAI STT + +This example demonstrates how to: +1. Join a Stream video call +2. Transcribe audio in real-time using AssemblyAI +3. Open a browser link for users to join the call + +Usage: + python main.py + +Requirements: + - Create a .env file with your Stream and AssemblyAI credentials (see env.example) + - Install dependencies: pip install -e . +""" + +import asyncio +import logging +import os +import time +import uuid +import webbrowser +from urllib.parse import urlencode + +from dotenv import load_dotenv + +from getstream.models import UserRequest +from getstream.plugins.assemblyai.stt import AssemblyAISTT +from getstream.stream import Stream +from getstream.video import rtc +from getstream.video.rtc.track_util import PcmData + +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") + + +def create_user(client: Stream, id: str, name: str) -> None: + """Create a user with a unique Stream ID. + + Args: + client: Stream client instance + id: Unique user ID + name: Display name for the user + + """ + user_request = UserRequest(id=id, name=name) + client.upsert_users(user_request) + + +def open_browser(api_key: str, token: str, call_id: str) -> str: + """Helper function to open browser with Stream call link. + + Args: + api_key: Stream API key + token: JWT token for the user + call_id: ID of the call + + Returns: + The URL that was opened + + """ + base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" + params = {"api_key": api_key, "token": token, "skip_lobby": "true"} + + url = f"{base_url}{call_id}?{urlencode(params)}" + print(f"Opening browser to: {url}") + + try: + webbrowser.open(url) + print("Browser opened successfully!") + except Exception as e: + print(f"Failed to open browser: {e}") + print(f"Please manually open this URL: {url}") + + return url + + +async def main(): + """Main example function.""" + print("πŸŽ™οΈ Stream + AssemblyAI Real-time Transcription Example") + print("=" * 58) + + # Load environment variables + load_dotenv() + + # Initialize Stream client from ENV + client = Stream.from_env() + + # Create a unique call ID for this session + call_id = str(uuid.uuid4()) + print(f"πŸ“ž Call ID: {call_id}") + + user_id = f"user-{uuid.uuid4()}" + create_user(client, user_id, "My User") + logging.info("πŸ‘€ Created user: %s", user_id) + + user_token = client.create_token(user_id, expiration=3600) + logging.info("πŸ”‘ Created token for user: %s", user_id) + + bot_user_id = f"transcription-bot-{uuid.uuid4()}" + create_user(client, bot_user_id, "Transcription Bot") + logging.info("πŸ€– Created bot user: %s", bot_user_id) + + # Create the call + call = client.video.call("default", call_id) + call.get_or_create(data={"created_by_id": bot_user_id}) + print(f"πŸ“ž Call created: {call_id}") + + # Open browser for users to join with the user token + open_browser(client.api_key, user_token, call_id) + + print("\nπŸ€– Starting transcription bot...") + print("The bot will join the call and transcribe all audio it receives.") + print("Join the call in your browser and speak to see transcriptions appear here!") + print("\nPress Ctrl+C to stop the transcription bot.\n") + + # Initialize AssemblyAI STT (api_key comes from .env) + stt = AssemblyAISTT( + sample_rate=48000, + language="en", + interim_results=True, + enable_partials=True, + enable_automatic_punctuation=True, + enable_utterance_end_detection=True, + ) + + try: + async with await rtc.join(call, bot_user_id) as connection: + print(f"βœ… Bot joined call: {call_id}") + + # Set up transcription handlers + @connection.on("audio") + async def on_audio(pcm: PcmData, user): + # Process audio through AssemblyAI STT with user metadata + user_metadata = {"user": user} if user else None + await stt.process_audio(pcm, user_metadata) + + @stt.on("transcript") + async def on_transcript(event): + timestamp = time.strftime("%H:%M:%S") + user_info = "unknown" + if event.user_metadata and "user" in event.user_metadata: + user = event.user_metadata["user"] + user_info = user.name if hasattr(user, "name") else str(user) + print(f"[{timestamp}] {user_info}: {event.text}") + if hasattr(event, "confidence") and event.confidence: + print(f" └─ confidence: {event.confidence:.2%}") + if hasattr(event, "processing_time_ms") and event.processing_time_ms: + print(f" └─ processing time: {event.processing_time_ms:.1f}ms") + + @stt.on("partial_transcript") + async def on_partial_transcript(event): + if event.text.strip(): # Only show non-empty partial transcripts + user_info = "unknown" + if event.user_metadata and "user" in event.user_metadata: + user = event.user_metadata["user"] + user_info = user.name if hasattr(user, "name") else str(user) + print( + f" {user_info} (partial): {event.text}", + end="\r", + ) # Overwrite line + + @stt.on("error") + async def on_stt_error(event): + print(f"\n❌ STT Error: {event.error_message}") + if hasattr(event, "context") and event.context: + print(f" └─ context: {event.context}") + + # Keep the connection alive and wait for audio + print("🎧 Listening for audio... (Press Ctrl+C to stop)") + await connection.wait() + + except asyncio.CancelledError: + print("\n⏹️ Stopping transcription bot...") + except Exception as e: + print(f"❌ Error: {e}") + import traceback + + traceback.print_exc() + finally: + await stt.close() + client.delete_users([user_id, bot_user_id]) + print("🧹 Cleanup completed") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/stt_assemblyai_transcription/pyproject.toml b/examples/stt_assemblyai_transcription/pyproject.toml new file mode 100644 index 00000000..729eaaeb --- /dev/null +++ b/examples/stt_assemblyai_transcription/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "getstream-stt-assemblyai-transcription-example" +version = "0.1.0" +description = "Example project showing how to transcribe a call using STT with AssemblyAI" +readme = "README.md" +requires-python = ">=3.10" +license = {text = "MIT"} + +dependencies = [ + "getstream[webrtc]>=2.3.0a0", + "getstream-plugins-assemblyai>=0.1.0", + "python-dotenv>=1.1.1", +] diff --git a/examples/stt_assemblyai_transcription/uv.lock b/examples/stt_assemblyai_transcription/uv.lock new file mode 100644 index 00000000..342b1fd9 --- /dev/null +++ b/examples/stt_assemblyai_transcription/uv.lock @@ -0,0 +1,407 @@ +version = 1 +revision = 2 +requires-python = ">=3.10" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "assemblyai" +version = "0.43.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/a6/51324e434a250f44f5d6d041e5acb8a98451604dbe8aaa9e5f91bf1dc902/assemblyai-0.43.1.tar.gz", hash = "sha256:c39e8ddb4434af316ade3259e51f86be8134c24be821d8cd84502850ea551951", size = 53970, upload-time = "2025-08-12T21:22:14.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c4/3df745c07cc66be2d1134629b7007e3c8f3b9931caa5a62e023dcc7984cd/assemblyai-0.43.1-py3-none-any.whl", hash = "sha256:4e6e11eb07130c5c87aea84cc63d80933a6588e9b3aeee80026c2419756a3513", size = 50053, upload-time = "2025-08-12T21:22:13.255Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "dataclasses-json" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "getstream" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dataclasses-json" }, + { name = "httpx" }, + { name = "marshmallow" }, + { name = "pyjwt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/72/7df6e6a80283562386f728faddba9ca16c36a737536f9e49bf202c30ad9b/getstream-2.3.1.tar.gz", hash = "sha256:8af5834abbb2eb20a8e6b6978c48abd87240d9c008d9ce37907762cb2aa47c8a", size = 132757, upload-time = "2025-08-15T13:04:24.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/52/a23ffce8f5c36bfb6c7aec77837d77f2585eabe27118507161bf0be17307/getstream-2.3.1-py3-none-any.whl", hash = "sha256:077f6d9c2e8e34ec598755c2eda2399b12e7ece93bcde919317933b6a6a986e6", size = 80504, upload-time = "2025-08-15T13:04:23.134Z" }, +] + +[[package]] +name = "getstream-stt-assemblyai-transcription-example" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "assemblyai" }, + { name = "getstream" }, + { name = "python-dotenv" }, +] + +[package.metadata] +requires-dist = [ + { name = "assemblyai", specifier = ">=0.43.1" }, + { name = "getstream", extras = ["webrtc"], specifier = ">=2.3.0a0" }, + { name = "python-dotenv", specifier = ">=1.1.1" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "marshmallow" +version = "3.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825, upload-time = "2025-02-03T15:32:25.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, + { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, + { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, + { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, + { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] diff --git a/examples/stt_deepgram_transcription/main.py b/examples/stt_deepgram_transcription/main.py index 7ff0bf5b..ca00d1f0 100644 --- a/examples/stt_deepgram_transcription/main.py +++ b/examples/stt_deepgram_transcription/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Real-time Call Transcription with Deepgram STT +"""Example: Real-time Call Transcription with Deepgram STT This example demonstrates how to: 1. Join a Stream video call @@ -26,30 +25,29 @@ from dotenv import load_dotenv from getstream.models import CallRequest, UserRequest +from getstream.plugins.deepgram.stt import DeepgramSTT from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc.track_util import PcmData -from getstream.plugins.deepgram.stt import DeepgramSTT logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -58,6 +56,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -136,9 +135,9 @@ async def on_transcript(event): user = event.user_metadata["user"] user_info = user.name if hasattr(user, "name") else str(user) print(f"[{timestamp}] {user_info}: {event.text}") - if hasattr(event, 'confidence') and event.confidence: + if hasattr(event, "confidence") and event.confidence: print(f" └─ confidence: {event.confidence:.2%}") - if hasattr(event, 'processing_time_ms') and event.processing_time_ms: + if hasattr(event, "processing_time_ms") and event.processing_time_ms: print(f" └─ processing time: {event.processing_time_ms:.1f}ms") @stt.on("partial_transcript") @@ -149,13 +148,14 @@ async def on_partial_transcript(event): user = event.user_metadata["user"] user_info = user.name if hasattr(user, "name") else str(user) print( - f" {user_info} (partial): {event.text}", end="\r" + f" {user_info} (partial): {event.text}", + end="\r", ) # Overwrite line @stt.on("error") async def on_stt_error(event): print(f"\n❌ STT Error: {event.error_message}") - if hasattr(event, 'context') and event.context: + if hasattr(event, "context") and event.context: print(f" └─ context: {event.context}") # Keep the connection alive and wait for audio diff --git a/examples/stt_moonshine_transcription/main.py b/examples/stt_moonshine_transcription/main.py index f5b8aa2b..766284ab 100644 --- a/examples/stt_moonshine_transcription/main.py +++ b/examples/stt_moonshine_transcription/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Real-time Call Transcription with Moonshine STT +"""Example: Real-time Call Transcription with Moonshine STT Uses the local Moonshine model via the Moonshine plugin in this repo. @@ -27,32 +26,30 @@ from dotenv import load_dotenv from getstream.models import UserRequest +from getstream.plugins.moonshine.stt import MoonshineSTT +from getstream.plugins.silero.vad import SileroVAD from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc.track_util import PcmData -from getstream.plugins.moonshine.stt import MoonshineSTT -from getstream.plugins.silero.vad import SileroVAD - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -61,6 +58,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -126,7 +124,7 @@ async def _on_audio(pcm: PcmData, user): @vad.on("audio") async def _on_speech_detected(pcm: PcmData, user): print( - f"🎀 Speech detected from user: {user.name}, duration: {pcm.duration:.2f}s" + f"🎀 Speech detected from user: {user.name}, duration: {pcm.duration:.2f}s", ) # Process audio through STT with user metadata user_metadata = {"user": user} if user else None @@ -140,15 +138,15 @@ async def _on_transcript(event): user = event.user_metadata["user"] user_info = str(user) print(f"[{ts}] {user_info}: {event.text}") - if hasattr(event, 'confidence') and event.confidence: + if hasattr(event, "confidence") and event.confidence: print(f" └─ confidence: {event.confidence:.2%}") - if hasattr(event, 'processing_time_ms') and event.processing_time_ms: + if hasattr(event, "processing_time_ms") and event.processing_time_ms: print(f" └─ processing time: {event.processing_time_ms:.1f}ms") @stt.on("error") async def _on_error(event): print(f"\n❌ STT Error: {event.error_message}") - if hasattr(event, 'context') and event.context: + if hasattr(event, "context") and event.context: print(f" └─ context: {event.context}") print("🎧 Listening for audio… (Press Ctrl+C to stop)") diff --git a/examples/tts_cartesia/main.py b/examples/tts_cartesia/main.py index 94f95f1a..94660f2e 100644 --- a/examples/tts_cartesia/main.py +++ b/examples/tts_cartesia/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Text-to-Speech bot with Cartesia +"""Example: Text-to-Speech bot with Cartesia This minimal example shows how to: 1. Spin up a Stream video call @@ -21,38 +20,36 @@ import asyncio import logging import os -from uuid import uuid4 import webbrowser from urllib.parse import urlencode +from uuid import uuid4 from dotenv import load_dotenv from getstream.models import UserRequest +from getstream.plugins.cartesia.tts import CartesiaTTS from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc import audio_track -from getstream.plugins.cartesia.tts import CartesiaTTS - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -61,6 +58,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -80,7 +78,6 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: async def main() -> None: """Create a video call and let a TTS bot greet participants.""" - load_dotenv() client = Stream.from_env() @@ -159,7 +156,7 @@ async def on_track_published(event): if existing_participants: logging.info( - f"πŸ‘‹ Found {len(existing_participants)} existing participants" + f"πŸ‘‹ Found {len(existing_participants)} existing participants", ) await send_greeting_once() diff --git a/examples/tts_elevenlabs/main.py b/examples/tts_elevenlabs/main.py index 87467c12..d344a7fc 100644 --- a/examples/tts_elevenlabs/main.py +++ b/examples/tts_elevenlabs/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Text-to-Speech bot with ElevenLabs +"""Example: Text-to-Speech bot with ElevenLabs This minimal example shows how to: 1. Spin up a Stream video call @@ -21,38 +20,36 @@ import asyncio import logging import os -from uuid import uuid4 import webbrowser from urllib.parse import urlencode +from uuid import uuid4 from dotenv import load_dotenv from getstream.models import UserRequest +from getstream.plugins.elevenlabs.tts import ElevenLabsTTS from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc import audio_track -from getstream.plugins.elevenlabs.tts import ElevenLabsTTS - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -61,6 +58,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -80,7 +78,6 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: async def main() -> None: """Create a video call and let a TTS bot greet participants.""" - load_dotenv() client = Stream.from_env() @@ -159,7 +156,7 @@ async def on_track_published(event): if existing_participants: logging.info( - f"πŸ‘‹ Found {len(existing_participants)} existing participants" + f"πŸ‘‹ Found {len(existing_participants)} existing participants", ) await send_greeting_once() diff --git a/examples/tts_kokoro/main.py b/examples/tts_kokoro/main.py index 5820be3f..b5bd04ca 100644 --- a/examples/tts_kokoro/main.py +++ b/examples/tts_kokoro/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Text-to-Speech bot with Kokoro +"""Example: Text-to-Speech bot with Kokoro This minimal example shows how to: 1. Spin up a Stream video call @@ -22,21 +21,21 @@ from __future__ import annotations import asyncio +import importlib import logging import os -from uuid import uuid4 -import importlib import sys import webbrowser from urllib.parse import urlencode +from uuid import uuid4 from dotenv import load_dotenv from getstream.models import UserRequest +from getstream.plugins.kokoro.tts import KokoroTTS from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc import audio_track -from getstream.plugins.kokoro.tts import KokoroTTS logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") @@ -60,21 +59,20 @@ def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -83,6 +81,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -102,7 +101,6 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: async def main() -> None: """Create a video call and let a Kokoro TTS bot greet participants.""" - load_dotenv() client: Stream = Stream.from_env() @@ -187,7 +185,7 @@ async def on_track_published(event): if existing_participants: logging.info( - f"πŸ‘‹ Found {len(existing_participants)} existing participants" + f"πŸ‘‹ Found {len(existing_participants)} existing participants", ) await send_greeting_once() diff --git a/examples/vad_silero/main.py b/examples/vad_silero/main.py index 62dbf77e..67ee43c6 100644 --- a/examples/vad_silero/main.py +++ b/examples/vad_silero/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Voice-Activity-Detection bot (Silero VAD) +"""Example: Voice-Activity-Detection bot (Silero VAD) The script joins a Stream video call with a bot that detects when anyone speaks, using the Silero VAD plugin. @@ -19,18 +18,18 @@ import logging import os import time -from typing import Any -from uuid import uuid4 import webbrowser +from typing import Any from urllib.parse import urlencode +from uuid import uuid4 from dotenv import load_dotenv from getstream.models import UserRequest +from getstream.plugins.silero.vad import SileroVAD from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc.track_util import PcmData -from getstream.plugins.silero.vad import SileroVAD # Logging setup – INFO level so we see joins / leaves, etc. logging.basicConfig( @@ -40,21 +39,20 @@ def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -63,6 +61,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -82,7 +81,6 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: async def main() -> None: """Create a call and start the Silero VAD bot.""" - # Load env from examples/.env load_dotenv() @@ -130,7 +128,7 @@ async def _on_turn(pcm: PcmData, user): "timestamp": ts, "duration": duration, "user": user, - } + }, ) # Optional: in-progress indicator diff --git a/examples/video_moderation/main.py b/examples/video_moderation/main.py index a5f2dcdc..aa1abf2a 100644 --- a/examples/video_moderation/main.py +++ b/examples/video_moderation/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example: Real-time Call Transcription with Deepgram STT +"""Example: Real-time Call Transcription with Deepgram STT This example demonstrates how to: 1. Join a Stream video call @@ -18,46 +17,46 @@ import argparse import asyncio import logging -import warnings import os import time import uuid +import warnings import webbrowser from urllib.parse import urlencode from dotenv import load_dotenv -from getstream.models import UserRequest +from getstream.models import CheckResponse, ModerationPayload, UserRequest +from getstream.plugins.deepgram.stt import DeepgramSTT from getstream.stream import Stream from getstream.video import rtc from getstream.video.rtc.track_util import PcmData -from getstream.models import CheckResponse, ModerationPayload -from getstream.plugins.deepgram.stt import DeepgramSTT logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") # Suppress dataclasses_json missing value RuntimeWarnings warnings.filterwarnings( - "ignore", category=RuntimeWarning, module="dataclasses_json.core" + "ignore", + category=RuntimeWarning, + module="dataclasses_json.core", ) def create_user(client: Stream, id: str, name: str) -> None: - """ - Create a user with a unique Stream ID. + """Create a user with a unique Stream ID. Args: client: Stream client instance id: Unique user ID name: Display name for the user + """ user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. Args: api_key: Stream API key @@ -66,6 +65,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} @@ -90,7 +90,6 @@ def moderate(client: Stream, text: str, user_name: str) -> CheckResponse: thread with ``asyncio.to_thread`` from async code without blocking the event loop. """ - return client.moderation.check( config_key="custom:python-ai-test", # your moderation config key entity_creator_id=user_name, @@ -127,7 +126,7 @@ async def main(client: Stream): print("\nπŸ€– Starting moderation bot...") print("The bot will join the call and moderate all audio it receives.") print( - "Join the call in your browser and speak to see moderation results appear here!" + "Join the call in your browser and speak to see moderation results appear here!", ) print("\nPress Ctrl+C to stop the moderation bot.\n") @@ -155,21 +154,26 @@ async def on_transcript(event): user = event.user_metadata["user"] user_info = user.name if hasattr(user, "name") else str(user) print(f"[{timestamp}] {user_info}: {event.text}") - if hasattr(event, 'confidence') and event.confidence: + if hasattr(event, "confidence") and event.confidence: print(f" └─ confidence: {event.confidence:.2%}") - if hasattr(event, 'processing_time_ms') and event.processing_time_ms: + if hasattr(event, "processing_time_ms") and event.processing_time_ms: print(f" └─ processing time: {event.processing_time_ms:.1f}ms") # Moderation check (executed in a background thread to avoid blocking) - moderation = await asyncio.to_thread(moderate, client, event.text, user_info) + moderation = await asyncio.to_thread( + moderate, + client, + event.text, + user_info, + ) print( - f" └─ moderation recommended action: {moderation.recommended_action} for transcript: {event.text}" + f" └─ moderation recommended action: {moderation.recommended_action} for transcript: {event.text}", ) @stt.on("error") async def on_stt_error(event): print(f"\n❌ STT Error: {event.error_message}") - if hasattr(event, 'context') and event.context: + if hasattr(event, "context") and event.context: print(f" └─ context: {event.context}") # Keep the connection alive and wait for audio @@ -191,7 +195,7 @@ async def on_stt_error(event): def parse_args(): parser = argparse.ArgumentParser( - description="Stream Real-time Audio Moderation Example" + description="Stream Real-time Audio Moderation Example", ) parser.add_argument("--setup", action="store_true", help="Setup moderation config") return parser.parse_args() diff --git a/examples/video_moderation/view_flagged.py b/examples/video_moderation/view_flagged.py index 1b49f320..b8515d2f 100644 --- a/examples/video_moderation/view_flagged.py +++ b/examples/video_moderation/view_flagged.py @@ -1,5 +1,6 @@ -from getstream import Stream from dotenv import load_dotenv + +from getstream import Stream from getstream.models import QueryReviewQueueResponse, ReviewQueueItemResponse load_dotenv() diff --git a/getstream/audio/__init__.py b/getstream/audio/__init__.py index 89cfb557..2f5dcf96 100644 --- a/getstream/audio/__init__.py +++ b/getstream/audio/__init__.py @@ -1,10 +1,10 @@ """Audio utilities for the getstream package.""" from .pcm_utils import ( - pcm_to_numpy_array, + log_audio_processing_info, numpy_array_to_bytes, + pcm_to_numpy_array, validate_sample_rate_compatibility, - log_audio_processing_info, ) from .utils import resample_audio diff --git a/getstream/audio/pcm_utils.py b/getstream/audio/pcm_utils.py index 161f8cdd..712db31c 100644 --- a/getstream/audio/pcm_utils.py +++ b/getstream/audio/pcm_utils.py @@ -1,5 +1,4 @@ -""" -Common PCM audio processing utilities for plugins. +"""Common PCM audio processing utilities for plugins. This module provides shared utilities for audio format conversion and validation to eliminate code duplication between STT, TTS, and VAD plugins. @@ -15,8 +14,7 @@ def pcm_to_numpy_array(pcm_data: PcmData) -> np.ndarray: - """ - Convert PcmData samples to numpy array. + """Convert PcmData samples to numpy array. Handles both bytes and numpy array inputs, ensuring consistent int16 output. @@ -28,29 +26,29 @@ def pcm_to_numpy_array(pcm_data: PcmData) -> np.ndarray: Raises: ValueError: If the input format is not supported + """ if isinstance(pcm_data.samples, bytes): # Convert bytes to numpy array return np.frombuffer(pcm_data.samples, dtype=np.int16) - elif isinstance(pcm_data.samples, np.ndarray): + if isinstance(pcm_data.samples, np.ndarray): # Ensure it's int16 format return pcm_data.samples.astype(np.int16) - else: - raise ValueError( - f"Unsupported samples type: {type(pcm_data.samples)}. " - "Expected bytes or numpy.ndarray" - ) + raise ValueError( + f"Unsupported samples type: {type(pcm_data.samples)}. " + "Expected bytes or numpy.ndarray", + ) def numpy_array_to_bytes(audio_array: np.ndarray) -> bytes: - """ - Convert numpy audio array to bytes. + """Convert numpy audio array to bytes. Args: audio_array: numpy array of audio samples Returns: bytes representation of the audio data + """ # Ensure int16 format before converting to bytes if audio_array.dtype != np.int16: @@ -60,10 +58,11 @@ def numpy_array_to_bytes(audio_array: np.ndarray) -> bytes: def validate_sample_rate_compatibility( - input_rate: int, target_rate: int, plugin_name: str + input_rate: int, + target_rate: int, + plugin_name: str, ) -> None: - """ - Validate sample rate compatibility and log appropriate messages. + """Validate sample rate compatibility and log appropriate messages. Args: input_rate: Input sample rate in Hz @@ -72,6 +71,7 @@ def validate_sample_rate_compatibility( Raises: ValueError: If sample rates are invalid (zero or negative) + """ if input_rate <= 0: raise ValueError(f"Invalid input sample rate: {input_rate}Hz") @@ -83,27 +83,29 @@ def validate_sample_rate_compatibility( if input_rate == 48000 and target_rate == 16000: # This is the expected WebRTC -> plugin conversion logger.debug( - f"Converting WebRTC audio (48kHz) to {plugin_name} format (16kHz)" + f"Converting WebRTC audio (48kHz) to {plugin_name} format (16kHz)", ) else: logger.warning( f"Unexpected sample rate conversion in {plugin_name}: " - f"{input_rate}Hz -> {target_rate}Hz" + f"{input_rate}Hz -> {target_rate}Hz", ) else: logger.debug("No resampling needed - sample rates match") def log_audio_processing_info( - pcm_data: PcmData, target_rate: int, plugin_name: str + pcm_data: PcmData, + target_rate: int, + plugin_name: str, ) -> None: - """ - Log detailed audio processing information for debugging. + """Log detailed audio processing information for debugging. Args: pcm_data: The PCM audio data being processed target_rate: Target sample rate for the plugin plugin_name: Name of the plugin for logging context + """ # Calculate duration safely duration_ms = 0.0 diff --git a/getstream/audio/utils.py b/getstream/audio/utils.py index a4fd9953..cecb247e 100644 --- a/getstream/audio/utils.py +++ b/getstream/audio/utils.py @@ -1,11 +1,12 @@ -""" -Shared helpers for audio manipulation used by VAD and STT plugins. +"""Shared helpers for audio manipulation used by VAD and STT plugins. Currently contains: * resample_audio() – high-quality multi-backend resampler. """ from __future__ import annotations + import logging + import numpy as np # Optional back-ends @@ -17,8 +18,8 @@ _has_scipy = False try: - import torchaudio import torch + import torchaudio _has_torchaudio = True except ImportError: @@ -28,8 +29,7 @@ def resample_audio(frame: np.ndarray, from_sr: int, to_sr: int) -> np.ndarray: - """ - High-quality resampling used across the codebase. + """High-quality resampling used across the codebase. – Prefers scipy.signal.resample_poly (best SNR & speed). – Falls back to torchaudio.transforms.Resample if SciPy missing. @@ -52,7 +52,8 @@ def resample_audio(frame: np.ndarray, from_sr: int, to_sr: int) -> np.ndarray: try: tensor = torch.tensor(frame, dtype=torch.float32) resampler = torchaudio.transforms.Resample( - orig_freq=from_sr, new_freq=to_sr + orig_freq=from_sr, + new_freq=to_sr, ) return resampler(tensor).numpy() except Exception as e: diff --git a/getstream/base.py b/getstream/base.py index 295fc14e..651b9f5e 100644 --- a/getstream/base.py +++ b/getstream/base.py @@ -1,14 +1,15 @@ import json +from abc import ABC from typing import Any, Dict, Optional, Type, get_origin +from urllib.parse import quote + +import httpx +from getstream.config import BaseConfig +from getstream.generic import T from getstream.models import APIError from getstream.rate_limit import extract_rate_limit from getstream.stream_response import StreamResponse -from getstream.generic import T -import httpx -from getstream.config import BaseConfig -from urllib.parse import quote -from abc import ABC def build_path(path: str, path_params: dict) -> str: @@ -47,7 +48,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.close() def _parse_response( - self, response: httpx.Response, data_type: Type[T] + self, + response: httpx.Response, + data_type: Type[T], ) -> StreamResponse[T]: if response.status_code >= 399: raise StreamAPIException( @@ -81,7 +84,10 @@ def patch( **kwargs, ) -> StreamResponse[T]: response = self.client.patch( - build_path(path, path_params), params=query_params, *args, **kwargs + build_path(path, path_params), + params=query_params, + *args, + **kwargs, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -95,7 +101,10 @@ def get( **kwargs, ) -> StreamResponse[T]: response = self.client.get( - build_path(path, path_params), params=query_params, *args, **kwargs + build_path(path, path_params), + params=query_params, + *args, + **kwargs, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -109,7 +118,10 @@ def post( **kwargs, ) -> StreamResponse[T]: response = self.client.post( - build_path(path, path_params), params=query_params, *args, **kwargs + build_path(path, path_params), + params=query_params, + *args, + **kwargs, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -124,7 +136,10 @@ def put( **kwargs, ) -> StreamResponse[T]: response = self.client.put( - build_path(path, path_params), params=query_params, *args, **kwargs + build_path(path, path_params), + params=query_params, + *args, + **kwargs, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -138,20 +153,20 @@ def delete( **kwargs, ) -> StreamResponse[T]: response = self.client.delete( - build_path(path, path_params), params=query_params, *args, **kwargs + build_path(path, path_params), + params=query_params, + *args, + **kwargs, ) return self._parse_response(response, data_type or Dict[str, Any]) def close(self): - """ - Close HTTPX client. - """ + """Close HTTPX client.""" self.client.close() class StreamAPIException(Exception): - """ - A custom exception for handling errors from a Stream API response. + """A custom exception for handling errors from a Stream API response. This exception is raised when an API call encounters an issue, providing detailed information from the HTTP response. It attempts to parse the response @@ -172,6 +187,7 @@ class StreamAPIException(Exception): Raises: ValueError: If the response content cannot be parsed into JSON, indicating that the server's response was not in the expected format. + """ def __init__(self, response: httpx.Response) -> None: @@ -189,5 +205,4 @@ def __init__(self, response: httpx.Response) -> None: def __str__(self) -> str: if self.api_error: return f'Stream error code {self.api_error.code}: {self.api_error.message}"' - else: - return f"Stream error HTTP code: {self.status_code}" + return f"Stream error HTTP code: {self.status_code}" diff --git a/getstream/chat/client.py b/getstream/chat/client.py index a901fde8..7ac2fe17 100644 --- a/getstream/chat/client.py +++ b/getstream/chat/client.py @@ -4,6 +4,9 @@ class ChatClient(ChatRestClient): def __init__(self, api_key: str, base_url, token, timeout, stream): super().__init__( - api_key=api_key, base_url=base_url, token=token, timeout=timeout + api_key=api_key, + base_url=base_url, + token=token, + timeout=timeout, ) self.stream = stream diff --git a/getstream/chat/rest_client.py b/getstream/chat/rest_client.py index db83b21a..85a72ba8 100644 --- a/getstream/chat/rest_client.py +++ b/getstream/chat/rest_client.py @@ -2,13 +2,12 @@ from getstream.base import BaseClient from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_body_dict, build_query_param class ChatRestClient(BaseClient): def __init__(self, api_key: str, base_url: str, timeout: float, token: str): - """ - Initializes ChatClient with BaseClient instance + """Initializes ChatClient with BaseClient instance :param api_key: A string representing the client's API key :param base_url: A string representing the base uniform resource locator :param timeout: A number representing the time limit for a request @@ -40,7 +39,9 @@ def query_campaigns( ) return self.post( - "/api/v2/chat/campaigns/query", QueryCampaignsResponse, json=json + "/api/v2/chat/campaigns/query", + QueryCampaignsResponse, + json=json, ) def get_campaign( @@ -123,12 +124,16 @@ def query_channels( return self.post("/api/v2/chat/channels", QueryChannelsResponse, json=json) def delete_channels( - self, cids: List[str], hard_delete: Optional[bool] = None + self, + cids: List[str], + hard_delete: Optional[bool] = None, ) -> StreamResponse[DeleteChannelsResponse]: json = build_body_dict(cids=cids, hard_delete=hard_delete) return self.post( - "/api/v2/chat/channels/delete", DeleteChannelsResponse, json=json + "/api/v2/chat/channels/delete", + DeleteChannelsResponse, + json=json, ) def mark_channels_read( @@ -138,7 +143,9 @@ def mark_channels_read( user: Optional[UserRequest] = None, ) -> StreamResponse[MarkReadResponse]: json = build_body_dict( - user_id=user_id, read_by_channel=read_by_channel, user=user + user_id=user_id, + read_by_channel=read_by_channel, + user=user, ) return self.post("/api/v2/chat/channels/read", MarkReadResponse, json=json) @@ -175,7 +182,10 @@ def get_or_create_distinct_channel( ) def delete_channel( - self, type: str, id: str, hard_delete: Optional[bool] = None + self, + type: str, + id: str, + hard_delete: Optional[bool] = None, ) -> StreamResponse[DeleteChannelResponse]: query_params = build_query_param(hard_delete=hard_delete) path_params = { @@ -302,7 +312,10 @@ def get_draft( ) def send_event( - self, type: str, id: str, event: EventRequest + self, + type: str, + id: str, + event: EventRequest, ) -> StreamResponse[EventResponse]: path_params = { "type": type, @@ -318,7 +331,10 @@ def send_event( ) def delete_file( - self, type: str, id: str, url: Optional[str] = None + self, + type: str, + id: str, + url: Optional[str] = None, ) -> StreamResponse[Response]: query_params = build_query_param(url=url) path_params = { @@ -375,7 +391,10 @@ def hide_channel( ) def delete_image( - self, type: str, id: str, url: Optional[str] = None + self, + type: str, + id: str, + url: Optional[str] = None, ) -> StreamResponse[Response]: query_params = build_query_param(url=url) path_params = { @@ -468,7 +487,10 @@ def send_message( ) def get_many_messages( - self, type: str, id: str, ids: List[str] + self, + type: str, + id: str, + ids: List[str], ) -> StreamResponse[GetManyMessagesResponse]: query_params = build_query_param(ids=ids) path_params = { @@ -530,7 +552,10 @@ def mark_read( "id": id, } json = build_body_dict( - message_id=message_id, thread_id=thread_id, user_id=user_id, user=user + message_id=message_id, + thread_id=thread_id, + user_id=user_id, + user=user, ) return self.post( @@ -607,7 +632,10 @@ def mark_unread( "id": id, } json = build_body_dict( - message_id=message_id, thread_id=thread_id, user_id=user_id, user=user + message_id=message_id, + thread_id=thread_id, + user_id=user_id, + user=user, ) return self.post( @@ -685,7 +713,9 @@ def create_channel_type( ) return self.post( - "/api/v2/chat/channeltypes", CreateChannelTypeResponse, json=json + "/api/v2/chat/channeltypes", + CreateChannelTypeResponse, + json=json, ) def delete_channel_type(self, name: str) -> StreamResponse[Response]: @@ -694,7 +724,9 @@ def delete_channel_type(self, name: str) -> StreamResponse[Response]: } return self.delete( - "/api/v2/chat/channeltypes/{name}", Response, path_params=path_params + "/api/v2/chat/channeltypes/{name}", + Response, + path_params=path_params, ) def get_channel_type(self, name: str) -> StreamResponse[GetChannelTypeResponse]: @@ -818,7 +850,9 @@ def get_command(self, name: str) -> StreamResponse[GetCommandResponse]: } return self.get( - "/api/v2/chat/commands/{name}", GetCommandResponse, path_params=path_params + "/api/v2/chat/commands/{name}", + GetCommandResponse, + path_params=path_params, ) def update_command( @@ -881,16 +915,21 @@ def export_channels( ) return self.post( - "/api/v2/chat/export_channels", ExportChannelsResponse, json=json + "/api/v2/chat/export_channels", + ExportChannelsResponse, + json=json, ) def query_members( - self, payload: Optional[QueryMembersPayload] = None + self, + payload: Optional[QueryMembersPayload] = None, ) -> StreamResponse[MembersResponse]: query_params = build_query_param(payload=payload) return self.get( - "/api/v2/chat/members", MembersResponse, query_params=query_params + "/api/v2/chat/members", + MembersResponse, + query_params=query_params, ) def query_message_history( @@ -902,15 +941,24 @@ def query_message_history( sort: Optional[List[SortParamRequest]] = None, ) -> StreamResponse[QueryMessageHistoryResponse]: json = build_body_dict( - filter=filter, limit=limit, next=next, prev=prev, sort=sort + filter=filter, + limit=limit, + next=next, + prev=prev, + sort=sort, ) return self.post( - "/api/v2/chat/messages/history", QueryMessageHistoryResponse, json=json + "/api/v2/chat/messages/history", + QueryMessageHistoryResponse, + json=json, ) def delete_message( - self, id: str, hard: Optional[bool] = None, deleted_by: Optional[str] = None + self, + id: str, + hard: Optional[bool] = None, + deleted_by: Optional[str] = None, ) -> StreamResponse[DeleteMessageResponse]: query_params = build_query_param(hard=hard, deleted_by=deleted_by) path_params = { @@ -925,7 +973,9 @@ def delete_message( ) def get_message( - self, id: str, show_deleted_message: Optional[bool] = None + self, + id: str, + show_deleted_message: Optional[bool] = None, ) -> StreamResponse[GetMessageResponse]: query_params = build_query_param(show_deleted_message=show_deleted_message) path_params = { @@ -950,7 +1000,9 @@ def update_message( "id": id, } json = build_body_dict( - message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push + message=message, + skip_enrich_url=skip_enrich_url, + skip_push=skip_push, ) return self.post( @@ -1033,7 +1085,9 @@ def send_reaction( "id": id, } json = build_body_dict( - reaction=reaction, enforce_unique=enforce_unique, skip_push=skip_push + reaction=reaction, + enforce_unique=enforce_unique, + skip_push=skip_push, ) return self.post( @@ -1044,7 +1098,10 @@ def send_reaction( ) def delete_reaction( - self, id: str, type: str, user_id: Optional[str] = None + self, + id: str, + type: str, + user_id: Optional[str] = None, ) -> StreamResponse[DeleteReactionResponse]: query_params = build_query_param(user_id=user_id) path_params = { @@ -1060,7 +1117,10 @@ def delete_reaction( ) def get_reactions( - self, id: str, limit: Optional[int] = None, offset: Optional[int] = None + self, + id: str, + limit: Optional[int] = None, + offset: Optional[int] = None, ) -> StreamResponse[GetReactionsResponse]: query_params = build_query_param(limit=limit, offset=offset) path_params = { @@ -1106,7 +1166,9 @@ def query_reactions( ) def translate_message( - self, id: str, language: str + self, + id: str, + language: str, ) -> StreamResponse[MessageResponse]: path_params = { "id": id, @@ -1131,7 +1193,9 @@ def undelete_message( "id": id, } json = build_body_dict( - message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push + message=message, + skip_enrich_url=skip_enrich_url, + skip_push=skip_push, ) return self.post( @@ -1163,7 +1227,11 @@ def cast_poll_vote( ) def remove_poll_vote( - self, message_id: str, poll_id: str, vote_id: str, user_id: Optional[str] = None + self, + message_id: str, + poll_id: str, + vote_id: str, + user_id: Optional[str] = None, ) -> StreamResponse[PollVoteResponse]: query_params = build_query_param(user_id=user_id) path_params = { @@ -1180,7 +1248,9 @@ def remove_poll_vote( ) def delete_reminder( - self, message_id: str, user_id: Optional[str] = None + self, + message_id: str, + user_id: Optional[str] = None, ) -> StreamResponse[DeleteReminderResponse]: query_params = build_query_param(user_id=user_id) path_params = { @@ -1276,7 +1346,8 @@ def get_replies( ) def query_message_flags( - self, payload: Optional[QueryMessageFlagsPayload] = None + self, + payload: Optional[QueryMessageFlagsPayload] = None, ) -> StreamResponse[QueryMessageFlagsResponse]: query_params = build_query_param(payload=payload) @@ -1294,11 +1365,16 @@ def mute_channel( user: Optional[UserRequest] = None, ) -> StreamResponse[MuteChannelResponse]: json = build_body_dict( - expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user + expiration=expiration, + user_id=user_id, + channel_cids=channel_cids, + user=user, ) return self.post( - "/api/v2/chat/moderation/mute/channel", MuteChannelResponse, json=json + "/api/v2/chat/moderation/mute/channel", + MuteChannelResponse, + json=json, ) def unmute_channel( @@ -1309,11 +1385,16 @@ def unmute_channel( user: Optional[UserRequest] = None, ) -> StreamResponse[UnmuteResponse]: json = build_body_dict( - expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user + expiration=expiration, + user_id=user_id, + channel_cids=channel_cids, + user=user, ) return self.post( - "/api/v2/chat/moderation/unmute/channel", UnmuteResponse, json=json + "/api/v2/chat/moderation/unmute/channel", + UnmuteResponse, + json=json, ) def create_poll( @@ -1395,7 +1476,11 @@ def query_polls( ) -> StreamResponse[QueryPollsResponse]: query_params = build_query_param(user_id=user_id) json = build_body_dict( - limit=limit, next=next, prev=prev, sort=sort, filter=filter + limit=limit, + next=next, + prev=prev, + sort=sort, + filter=filter, ) return self.post( @@ -1406,7 +1491,9 @@ def query_polls( ) def delete_poll( - self, poll_id: str, user_id: Optional[str] = None + self, + poll_id: str, + user_id: Optional[str] = None, ) -> StreamResponse[Response]: query_params = build_query_param(user_id=user_id) path_params = { @@ -1421,7 +1508,9 @@ def delete_poll( ) def get_poll( - self, poll_id: str, user_id: Optional[str] = None + self, + poll_id: str, + user_id: Optional[str] = None, ) -> StreamResponse[PollResponse]: query_params = build_query_param(user_id=user_id) path_params = { @@ -1468,7 +1557,11 @@ def create_poll_option( "poll_id": poll_id, } json = build_body_dict( - text=text, position=position, user_id=user_id, custom=custom, user=user + text=text, + position=position, + user_id=user_id, + custom=custom, + user=user, ) return self.post( @@ -1491,7 +1584,11 @@ def update_poll_option( "poll_id": poll_id, } json = build_body_dict( - id=id, text=text, user_id=user_id, custom=custom, user=user + id=id, + text=text, + user_id=user_id, + custom=custom, + user=user, ) return self.put( @@ -1502,7 +1599,10 @@ def update_poll_option( ) def delete_poll_option( - self, poll_id: str, option_id: str, user_id: Optional[str] = None + self, + poll_id: str, + option_id: str, + user_id: Optional[str] = None, ) -> StreamResponse[Response]: query_params = build_query_param(user_id=user_id) path_params = { @@ -1518,7 +1618,10 @@ def delete_poll_option( ) def get_poll_option( - self, poll_id: str, option_id: str, user_id: Optional[str] = None + self, + poll_id: str, + option_id: str, + user_id: Optional[str] = None, ) -> StreamResponse[PollOptionResponse]: query_params = build_query_param(user_id=user_id) path_params = { @@ -1548,7 +1651,11 @@ def query_poll_votes( "poll_id": poll_id, } json = build_body_dict( - limit=limit, next=next, prev=prev, sort=sort, filter=filter + limit=limit, + next=next, + prev=prev, + sort=sort, + filter=filter, ) return self.post( @@ -1560,19 +1667,25 @@ def query_poll_votes( ) def update_push_notification_preferences( - self, preferences: List[PushPreferenceInput] + self, + preferences: List[PushPreferenceInput], ) -> StreamResponse[UpsertPushPreferencesResponse]: json = build_body_dict(preferences=preferences) return self.post( - "/api/v2/chat/push_preferences", UpsertPushPreferencesResponse, json=json + "/api/v2/chat/push_preferences", + UpsertPushPreferencesResponse, + json=json, ) def get_push_templates( - self, push_provider_type: str, push_provider_name: Optional[str] = None + self, + push_provider_type: str, + push_provider_name: Optional[str] = None, ) -> StreamResponse[GetPushTemplatesResponse]: query_params = build_query_param( - push_provider_type=push_provider_type, push_provider_name=push_provider_name + push_provider_type=push_provider_type, + push_provider_name=push_provider_name, ) return self.get( @@ -1598,11 +1711,14 @@ def upsert_push_template( ) return self.post( - "/api/v2/chat/push_templates", UpsertPushTemplateResponse, json=json + "/api/v2/chat/push_templates", + UpsertPushTemplateResponse, + json=json, ) def query_banned_users( - self, payload: Optional[QueryBannedUsersPayload] = None + self, + payload: Optional[QueryBannedUsersPayload] = None, ) -> StreamResponse[QueryBannedUsersResponse]: query_params = build_query_param(payload=payload) @@ -1633,16 +1749,21 @@ def query_reminders( ) return self.post( - "/api/v2/chat/reminders/query", QueryRemindersResponse, json=json + "/api/v2/chat/reminders/query", + QueryRemindersResponse, + json=json, ) def search( - self, payload: Optional[SearchPayload] = None + self, + payload: Optional[SearchPayload] = None, ) -> StreamResponse[SearchResponse]: query_params = build_query_param(payload=payload) return self.get( - "/api/v2/chat/search", SearchResponse, query_params=query_params + "/api/v2/chat/search", + SearchResponse, + query_params=query_params, ) def query_segments( @@ -1654,11 +1775,17 @@ def query_segments( sort: Optional[List[SortParamRequest]] = None, ) -> StreamResponse[QuerySegmentsResponse]: json = build_body_dict( - filter=filter, limit=limit, next=next, prev=prev, sort=sort + filter=filter, + limit=limit, + next=next, + prev=prev, + sort=sort, ) return self.post( - "/api/v2/chat/segments/query", QuerySegmentsResponse, json=json + "/api/v2/chat/segments/query", + QuerySegmentsResponse, + json=json, ) def delete_segment(self, id: str) -> StreamResponse[Response]: @@ -1667,7 +1794,9 @@ def delete_segment(self, id: str) -> StreamResponse[Response]: } return self.delete( - "/api/v2/chat/segments/{id}", Response, path_params=path_params + "/api/v2/chat/segments/{id}", + Response, + path_params=path_params, ) def get_segment(self, id: str) -> StreamResponse[GetSegmentResponse]: @@ -1676,11 +1805,15 @@ def get_segment(self, id: str) -> StreamResponse[GetSegmentResponse]: } return self.get( - "/api/v2/chat/segments/{id}", GetSegmentResponse, path_params=path_params + "/api/v2/chat/segments/{id}", + GetSegmentResponse, + path_params=path_params, ) def delete_segment_targets( - self, id: str, target_ids: List[str] + self, + id: str, + target_ids: List[str], ) -> StreamResponse[Response]: path_params = { "id": id, @@ -1695,7 +1828,9 @@ def delete_segment_targets( ) def segment_target_exists( - self, id: str, target_id: str + self, + id: str, + target_id: str, ) -> StreamResponse[Response]: path_params = { "id": id, @@ -1721,7 +1856,11 @@ def query_segment_targets( "id": id, } json = build_body_dict( - limit=limit, next=next, prev=prev, sort=sort, filter=filter + limit=limit, + next=next, + prev=prev, + sort=sort, + filter=filter, ) return self.post( @@ -1806,16 +1945,21 @@ def unread_counts(self) -> StreamResponse[WrappedUnreadCountsResponse]: return self.get("/api/v2/chat/unread", WrappedUnreadCountsResponse) def unread_counts_batch( - self, user_ids: List[str] + self, + user_ids: List[str], ) -> StreamResponse[UnreadCountsBatchResponse]: json = build_body_dict(user_ids=user_ids) return self.post( - "/api/v2/chat/unread_batch", UnreadCountsBatchResponse, json=json + "/api/v2/chat/unread_batch", + UnreadCountsBatchResponse, + json=json, ) def send_user_custom_event( - self, user_id: str, event: UserCustomEventRequest + self, + user_id: str, + event: UserCustomEventRequest, ) -> StreamResponse[Response]: path_params = { "user_id": user_id, diff --git a/getstream/common/client.py b/getstream/common/client.py index 28755645..5a72afb9 100644 --- a/getstream/common/client.py +++ b/getstream/common/client.py @@ -4,5 +4,8 @@ class CommonClient(CommonRestClient): def __init__(self, api_key: str, base_url, token, timeout): super().__init__( - api_key=api_key, base_url=base_url, token=token, timeout=timeout + api_key=api_key, + base_url=base_url, + token=token, + timeout=timeout, ) diff --git a/getstream/common/rest_client.py b/getstream/common/rest_client.py index 6ad2134b..ae759784 100644 --- a/getstream/common/rest_client.py +++ b/getstream/common/rest_client.py @@ -2,13 +2,12 @@ from getstream.base import BaseClient from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_body_dict, build_query_param class CommonRestClient(BaseClient): def __init__(self, api_key: str, base_url: str, timeout: float, token: str): - """ - Initializes CommonClient with BaseClient instance + """Initializes CommonClient with BaseClient instance :param api_key: A string representing the client's API key :param base_url: A string representing the base uniform resource locator :param timeout: A number representing the time limit for a request @@ -125,12 +124,15 @@ def update_app( return self.patch("/api/v2/app", Response, json=json) def list_block_lists( - self, team: Optional[str] = None + self, + team: Optional[str] = None, ) -> StreamResponse[ListBlockListResponse]: query_params = build_query_param(team=team) return self.get( - "/api/v2/blocklists", ListBlockListResponse, query_params=query_params + "/api/v2/blocklists", + ListBlockListResponse, + query_params=query_params, ) def create_block_list( @@ -145,7 +147,9 @@ def create_block_list( return self.post("/api/v2/blocklists", CreateBlockListResponse, json=json) def delete_block_list( - self, name: str, team: Optional[str] = None + self, + name: str, + team: Optional[str] = None, ) -> StreamResponse[Response]: query_params = build_query_param(team=team) path_params = { @@ -160,7 +164,9 @@ def delete_block_list( ) def get_block_list( - self, name: str, team: Optional[str] = None + self, + name: str, + team: Optional[str] = None, ) -> StreamResponse[GetBlockListResponse]: query_params = build_query_param(team=team) path_params = { @@ -175,7 +181,10 @@ def get_block_list( ) def update_block_list( - self, name: str, team: Optional[str] = None, words: Optional[List[str]] = None + self, + name: str, + team: Optional[str] = None, + words: Optional[List[str]] = None, ) -> StreamResponse[UpdateBlockListResponse]: path_params = { "name": name, @@ -224,7 +233,9 @@ def check_sns( sns_topic_arn: Optional[str] = None, ) -> StreamResponse[CheckSNSResponse]: json = build_body_dict( - sns_key=sns_key, sns_secret=sns_secret, sns_topic_arn=sns_topic_arn + sns_key=sns_key, + sns_secret=sns_secret, + sns_topic_arn=sns_topic_arn, ) return self.post("/api/v2/check_sns", CheckSNSResponse, json=json) @@ -240,19 +251,24 @@ def check_sqs( return self.post("/api/v2/check_sqs", CheckSQSResponse, json=json) def delete_device( - self, id: str, user_id: Optional[str] = None + self, + id: str, + user_id: Optional[str] = None, ) -> StreamResponse[Response]: query_params = build_query_param(id=id, user_id=user_id) return self.delete("/api/v2/devices", Response, query_params=query_params) def list_devices( - self, user_id: Optional[str] = None + self, + user_id: Optional[str] = None, ) -> StreamResponse[ListDevicesResponse]: query_params = build_query_param(user_id=user_id) return self.get( - "/api/v2/devices", ListDevicesResponse, query_params=query_params + "/api/v2/devices", + ListDevicesResponse, + query_params=query_params, ) def create_device( @@ -304,11 +320,14 @@ def create_external_storage( ) return self.post( - "/api/v2/external_storage", CreateExternalStorageResponse, json=json + "/api/v2/external_storage", + CreateExternalStorageResponse, + json=json, ) def delete_external_storage( - self, name: str + self, + name: str, ) -> StreamResponse[DeleteExternalStorageResponse]: path_params = { "name": name, @@ -350,7 +369,8 @@ def update_external_storage( ) def check_external_storage( - self, name: str + self, + name: str, ) -> StreamResponse[CheckExternalStorageResponse]: path_params = { "name": name, @@ -368,7 +388,8 @@ def create_guest(self, user: UserRequest) -> StreamResponse[CreateGuestResponse] return self.post("/api/v2/guest", CreateGuestResponse, json=json) def create_import_url( - self, filename: Optional[str] = None + self, + filename: Optional[str] = None, ) -> StreamResponse[CreateImportURLResponse]: json = build_body_dict(filename=filename) @@ -378,7 +399,9 @@ def list_imports(self) -> StreamResponse[ListImportsResponse]: return self.get("/api/v2/imports", ListImportsResponse) def create_import( - self, mode: str, path: str + self, + mode: str, + path: str, ) -> StreamResponse[CreateImportResponse]: json = build_body_dict(mode=mode, path=path) @@ -390,7 +413,9 @@ def get_import(self, id: str) -> StreamResponse[GetImportResponse]: } return self.get( - "/api/v2/imports/{id}", GetImportResponse, path_params=path_params + "/api/v2/imports/{id}", + GetImportResponse, + path_params=path_params, ) def get_og(self, url: str) -> StreamResponse[GetOGResponse]: @@ -416,12 +441,15 @@ def list_push_providers(self) -> StreamResponse[ListPushProvidersResponse]: return self.get("/api/v2/push_providers", ListPushProvidersResponse) def upsert_push_provider( - self, push_provider: Optional[PushProvider] = None + self, + push_provider: Optional[PushProvider] = None, ) -> StreamResponse[UpsertPushProviderResponse]: json = build_body_dict(push_provider=push_provider) return self.post( - "/api/v2/push_providers", UpsertPushProviderResponse, json=json + "/api/v2/push_providers", + UpsertPushProviderResponse, + json=json, ) def delete_push_provider(self, type: str, name: str) -> StreamResponse[Response]: @@ -431,7 +459,9 @@ def delete_push_provider(self, type: str, name: str) -> StreamResponse[Response] } return self.delete( - "/api/v2/push_providers/{type}/{name}", Response, path_params=path_params + "/api/v2/push_providers/{type}/{name}", + Response, + path_params=path_params, ) def get_rate_limits( @@ -451,7 +481,9 @@ def get_rate_limits( ) return self.get( - "/api/v2/rate_limits", GetRateLimitsResponse, query_params=query_params + "/api/v2/rate_limits", + GetRateLimitsResponse, + query_params=query_params, ) def list_roles(self) -> StreamResponse[ListRolesResponse]: @@ -482,7 +514,9 @@ def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: return self.delete("/api/v2/uploads/file", Response, query_params=query_params) def upload_file( - self, file: Optional[str] = None, user: Optional[OnlyUserID] = None + self, + file: Optional[str] = None, + user: Optional[OnlyUserID] = None, ) -> StreamResponse[FileUploadResponse]: json = build_body_dict(file=file, user=user) @@ -504,33 +538,39 @@ def upload_image( return self.post("/api/v2/uploads/image", ImageUploadResponse, json=json) def query_users( - self, payload: Optional[QueryUsersPayload] = None + self, + payload: Optional[QueryUsersPayload] = None, ) -> StreamResponse[QueryUsersResponse]: query_params = build_query_param(payload=payload) return self.get("/api/v2/users", QueryUsersResponse, query_params=query_params) def update_users_partial( - self, users: List[UpdateUserPartialRequest] + self, + users: List[UpdateUserPartialRequest], ) -> StreamResponse[UpdateUsersResponse]: json = build_body_dict(users=users) return self.patch("/api/v2/users", UpdateUsersResponse, json=json) def update_users( - self, users: Dict[str, UserRequest] + self, + users: Dict[str, UserRequest], ) -> StreamResponse[UpdateUsersResponse]: json = build_body_dict(users=users) return self.post("/api/v2/users", UpdateUsersResponse, json=json) def get_blocked_users( - self, user_id: Optional[str] = None + self, + user_id: Optional[str] = None, ) -> StreamResponse[GetBlockedUsersResponse]: query_params = build_query_param(user_id=user_id) return self.get( - "/api/v2/users/block", GetBlockedUsersResponse, query_params=query_params + "/api/v2/users/block", + GetBlockedUsersResponse, + query_params=query_params, ) def block_users( @@ -540,7 +580,9 @@ def block_users( user: Optional[UserRequest] = None, ) -> StreamResponse[BlockUsersResponse]: json = build_body_dict( - blocked_user_id=blocked_user_id, user_id=user_id, user=user + blocked_user_id=blocked_user_id, + user_id=user_id, + user=user, ) return self.post("/api/v2/users/block", BlockUsersResponse, json=json) @@ -584,7 +626,8 @@ def delete_users( return self.post("/api/v2/users/delete", DeleteUsersResponse, json=json) def get_user_live_locations( - self, user_id: Optional[str] = None + self, + user_id: Optional[str] = None, ) -> StreamResponse[SharedLocationsResponse]: query_params = build_query_param(user_id=user_id) @@ -647,7 +690,9 @@ def unblock_users( user: Optional[UserRequest] = None, ) -> StreamResponse[UnblockUsersResponse]: json = build_body_dict( - blocked_user_id=blocked_user_id, user_id=user_id, user=user + blocked_user_id=blocked_user_id, + user_id=user_id, + user=user, ) return self.post("/api/v2/users/unblock", UnblockUsersResponse, json=json) @@ -662,7 +707,8 @@ def deactivate_user( "user_id": user_id, } json = build_body_dict( - created_by_id=created_by_id, mark_messages_deleted=mark_messages_deleted + created_by_id=created_by_id, + mark_messages_deleted=mark_messages_deleted, ) return self.post( @@ -694,7 +740,9 @@ def reactivate_user( "user_id": user_id, } json = build_body_dict( - created_by_id=created_by_id, name=name, restore_messages=restore_messages + created_by_id=created_by_id, + name=name, + restore_messages=restore_messages, ) return self.post( diff --git a/getstream/config.py b/getstream/config.py index a5554e33..ce118992 100644 --- a/getstream/config.py +++ b/getstream/config.py @@ -32,5 +32,4 @@ def _get_user_agent(self): def _get_auth_type(self): if self.anonymous: return "anonymous" - else: - return "jwt" + return "jwt" diff --git a/getstream/generic.py b/getstream/generic.py index 4812d26e..b186d5ad 100644 --- a/getstream/generic.py +++ b/getstream/generic.py @@ -1,4 +1,3 @@ from typing import TypeVar - T = TypeVar("T") diff --git a/getstream/models/__init__.py b/getstream/models/__init__.py index 6ddd06f1..cf9dd663 100644 --- a/getstream/models/__init__.py +++ b/getstream/models/__init__.py @@ -1,12 +1,14 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from dataclasses import dataclass from dataclasses import field as dc_field +from datetime import datetime +from typing import Dict, Final, List, NewType, Optional + from dataclasses_json import DataClassJsonMixin from dataclasses_json import config as dc_config -from datetime import datetime from marshmallow import fields -from typing import List, Dict, Optional, Final, NewType -from getstream.utils import encode_datetime, datetime_from_unix_ns + +from getstream.utils import datetime_from_unix_ns, encode_datetime @dataclass @@ -15,7 +17,8 @@ class AIImageConfig(DataClassJsonMixin): ocr_rules: "List[OCRRule]" = dc_field(metadata=dc_config(field_name="ocr_rules")) rules: "List[AWSRekognitionRule]" = dc_field(metadata=dc_config(field_name="rules")) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) @@ -25,10 +28,11 @@ class AITextConfig(DataClassJsonMixin): profile: str = dc_field(metadata=dc_config(field_name="profile")) rules: "List[BodyguardRule]" = dc_field(metadata=dc_config(field_name="rules")) severity_rules: "List[BodyguardSeverityRule]" = dc_field( - metadata=dc_config(field_name="severity_rules") + metadata=dc_config(field_name="severity_rules"), ) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) @@ -37,7 +41,8 @@ class AIVideoConfig(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) rules: "List[AWSRekognitionRule]" = dc_field(metadata=dc_config(field_name="rules")) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) @@ -50,42 +55,53 @@ class APIError(DataClassJsonMixin): status_code: int = dc_field(metadata=dc_config(field_name="StatusCode")) details: "List[int]" = dc_field(metadata=dc_config(field_name="details")) unrecoverable: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="unrecoverable") + default=None, + metadata=dc_config(field_name="unrecoverable"), ) exception_fields: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="exception_fields") + default=None, + metadata=dc_config(field_name="exception_fields"), ) @dataclass class APNConfig(DataClassJsonMixin): auth_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="auth_key") + default=None, + metadata=dc_config(field_name="auth_key"), ) auth_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="auth_type") + default=None, + metadata=dc_config(field_name="auth_type"), ) bundle_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="bundle_id") + default=None, + metadata=dc_config(field_name="bundle_id"), ) development: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="development") + default=None, + metadata=dc_config(field_name="development"), ) disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="Disabled") + default=None, + metadata=dc_config(field_name="Disabled"), ) host: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="host")) key_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="key_id") + default=None, + metadata=dc_config(field_name="key_id"), ) notification_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="notification_template") + default=None, + metadata=dc_config(field_name="notification_template"), ) p12_cert: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="p12_cert") + default=None, + metadata=dc_config(field_name="p12_cert"), ) team_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="team_id") + default=None, + metadata=dc_config(field_name="team_id"), ) @@ -94,26 +110,33 @@ class APNConfigFields(DataClassJsonMixin): development: bool = dc_field(metadata=dc_config(field_name="development")) enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) auth_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="auth_key") + default=None, + metadata=dc_config(field_name="auth_key"), ) auth_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="auth_type") + default=None, + metadata=dc_config(field_name="auth_type"), ) bundle_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="bundle_id") + default=None, + metadata=dc_config(field_name="bundle_id"), ) host: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="host")) key_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="key_id") + default=None, + metadata=dc_config(field_name="key_id"), ) notification_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="notification_template") + default=None, + metadata=dc_config(field_name="notification_template"), ) p12_cert: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="p12_cert") + default=None, + metadata=dc_config(field_name="p12_cert"), ) team_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="team_id") + default=None, + metadata=dc_config(field_name="team_id"), ) @@ -122,16 +145,20 @@ class APNS(DataClassJsonMixin): body: str = dc_field(metadata=dc_config(field_name="body")) title: str = dc_field(metadata=dc_config(field_name="title")) content_available: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="content-available") + default=None, + metadata=dc_config(field_name="content-available"), ) mutable_content: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="mutable-content") + default=None, + metadata=dc_config(field_name="mutable-content"), ) sound: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sound") + default=None, + metadata=dc_config(field_name="sound"), ) data: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) @@ -148,10 +175,12 @@ class Action(DataClassJsonMixin): text: str = dc_field(metadata=dc_config(field_name="text")) type: str = dc_field(metadata=dc_config(field_name="type")) style: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="style") + default=None, + metadata=dc_config(field_name="style"), ) value: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="value") + default=None, + metadata=dc_config(field_name="value"), ) @@ -163,25 +192,28 @@ class ActionLog(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) reason: str = dc_field(metadata=dc_config(field_name="reason")) reporter_type: str = dc_field(metadata=dc_config(field_name="reporter_type")) review_queue_item_id: str = dc_field( - metadata=dc_config(field_name="review_queue_item_id") + metadata=dc_config(field_name="review_queue_item_id"), ) target_user_id: str = dc_field(metadata=dc_config(field_name="target_user_id")) type: str = dc_field(metadata=dc_config(field_name="type")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) review_queue_item: "Optional[ReviewQueueItem]" = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item") + default=None, + metadata=dc_config(field_name="review_queue_item"), ) target_user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="target_user") + default=None, + metadata=dc_config(field_name="target_user"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -193,7 +225,7 @@ class ActionLogResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) reason: str = dc_field(metadata=dc_config(field_name="reason")) @@ -202,13 +234,16 @@ class ActionLogResponse(DataClassJsonMixin): user_id: str = dc_field(metadata=dc_config(field_name="user_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) review_queue_item: "Optional[ReviewQueueItemResponse]" = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item") + default=None, + metadata=dc_config(field_name="review_queue_item"), ) target_user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="target_user") + default=None, + metadata=dc_config(field_name="target_user"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -231,7 +266,7 @@ class AnyEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(default="*", metadata=dc_config(field_name="type")) @@ -239,52 +274,52 @@ class AnyEvent(DataClassJsonMixin): @dataclass class AppResponseFields(DataClassJsonMixin): async_url_enrich_enabled: bool = dc_field( - metadata=dc_config(field_name="async_url_enrich_enabled") + metadata=dc_config(field_name="async_url_enrich_enabled"), ) auto_translation_enabled: bool = dc_field( - metadata=dc_config(field_name="auto_translation_enabled") + metadata=dc_config(field_name="auto_translation_enabled"), ) campaign_enabled: bool = dc_field(metadata=dc_config(field_name="campaign_enabled")) cdn_expiration_seconds: int = dc_field( - metadata=dc_config(field_name="cdn_expiration_seconds") + metadata=dc_config(field_name="cdn_expiration_seconds"), ) custom_action_handler_url: str = dc_field( - metadata=dc_config(field_name="custom_action_handler_url") + metadata=dc_config(field_name="custom_action_handler_url"), ) disable_auth_checks: bool = dc_field( - metadata=dc_config(field_name="disable_auth_checks") + metadata=dc_config(field_name="disable_auth_checks"), ) disable_permissions_checks: bool = dc_field( - metadata=dc_config(field_name="disable_permissions_checks") + metadata=dc_config(field_name="disable_permissions_checks"), ) enforce_unique_usernames: str = dc_field( - metadata=dc_config(field_name="enforce_unique_usernames") + metadata=dc_config(field_name="enforce_unique_usernames"), ) guest_user_creation_disabled: bool = dc_field( - metadata=dc_config(field_name="guest_user_creation_disabled") + metadata=dc_config(field_name="guest_user_creation_disabled"), ) image_moderation_enabled: bool = dc_field( - metadata=dc_config(field_name="image_moderation_enabled") + metadata=dc_config(field_name="image_moderation_enabled"), ) moderation_enabled: bool = dc_field( - metadata=dc_config(field_name="moderation_enabled") + metadata=dc_config(field_name="moderation_enabled"), ) moderation_multitenant_blocklist_enabled: bool = dc_field( - metadata=dc_config(field_name="moderation_multitenant_blocklist_enabled") + metadata=dc_config(field_name="moderation_multitenant_blocklist_enabled"), ) moderation_webhook_url: str = dc_field( - metadata=dc_config(field_name="moderation_webhook_url") + metadata=dc_config(field_name="moderation_webhook_url"), ) multi_tenant_enabled: bool = dc_field( - metadata=dc_config(field_name="multi_tenant_enabled") + metadata=dc_config(field_name="multi_tenant_enabled"), ) name: str = dc_field(metadata=dc_config(field_name="name")) organization: str = dc_field(metadata=dc_config(field_name="organization")) permission_version: str = dc_field( - metadata=dc_config(field_name="permission_version") + metadata=dc_config(field_name="permission_version"), ) reminders_interval: int = dc_field( - metadata=dc_config(field_name="reminders_interval") + metadata=dc_config(field_name="reminders_interval"), ) sns_key: str = dc_field(metadata=dc_config(field_name="sns_key")) sns_secret: str = dc_field(metadata=dc_config(field_name="sns_secret")) @@ -294,40 +329,41 @@ class AppResponseFields(DataClassJsonMixin): sqs_url: str = dc_field(metadata=dc_config(field_name="sqs_url")) suspended: bool = dc_field(metadata=dc_config(field_name="suspended")) suspended_explanation: str = dc_field( - metadata=dc_config(field_name="suspended_explanation") + metadata=dc_config(field_name="suspended_explanation"), ) use_hook_v2: bool = dc_field(metadata=dc_config(field_name="use_hook_v2")) webhook_url: str = dc_field(metadata=dc_config(field_name="webhook_url")) event_hooks: "List[EventHook]" = dc_field( - metadata=dc_config(field_name="event_hooks") + metadata=dc_config(field_name="event_hooks"), ) user_search_disallowed_roles: List[str] = dc_field( - metadata=dc_config(field_name="user_search_disallowed_roles") + metadata=dc_config(field_name="user_search_disallowed_roles"), ) webhook_events: List[str] = dc_field( - metadata=dc_config(field_name="webhook_events") + metadata=dc_config(field_name="webhook_events"), ) call_types: "Dict[str, Optional[CallType]]" = dc_field( - metadata=dc_config(field_name="call_types") + metadata=dc_config(field_name="call_types"), ) channel_configs: "Dict[str, ChannelConfig]" = dc_field( - metadata=dc_config(field_name="channel_configs") + metadata=dc_config(field_name="channel_configs"), ) file_upload_config: "FileUploadConfig" = dc_field( - metadata=dc_config(field_name="file_upload_config") + metadata=dc_config(field_name="file_upload_config"), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) image_upload_config: "FileUploadConfig" = dc_field( - metadata=dc_config(field_name="image_upload_config") + metadata=dc_config(field_name="image_upload_config"), ) policies: "Dict[str, List[Policy]]" = dc_field( - metadata=dc_config(field_name="policies") + metadata=dc_config(field_name="policies"), ) push_notifications: "PushNotificationFields" = dc_field( - metadata=dc_config(field_name="push_notifications") + metadata=dc_config(field_name="push_notifications"), ) before_message_send_hook_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="before_message_send_hook_url") + default=None, + metadata=dc_config(field_name="before_message_send_hook_url"), ) revoke_tokens_issued_before: Optional[datetime] = dc_field( default=None, @@ -339,16 +375,20 @@ class AppResponseFields(DataClassJsonMixin): ), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) geofences: "Optional[List[GeofenceResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="geofences") + default=None, + metadata=dc_config(field_name="geofences"), ) image_moderation_labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="image_moderation_labels") + default=None, + metadata=dc_config(field_name="image_moderation_labels"), ) datadog_info: "Optional[DataDogInfo]" = dc_field( - default=None, metadata=dc_config(field_name="datadog_info") + default=None, + metadata=dc_config(field_name="datadog_info"), ) moderation_dashboard_preferences: "Optional[ModerationDashboardPreferences]" = ( dc_field( @@ -366,7 +406,7 @@ class AsyncBulkImageModerationEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) finished_at: datetime = dc_field( metadata=dc_config( @@ -374,7 +414,7 @@ class AsyncBulkImageModerationEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) started_at: datetime = dc_field( metadata=dc_config( @@ -382,7 +422,7 @@ class AsyncBulkImageModerationEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) task_id: str = dc_field(metadata=dc_config(field_name="task_id")) url: str = dc_field(metadata=dc_config(field_name="url")) @@ -410,7 +450,7 @@ class AsyncExportChannelsEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) finished_at: datetime = dc_field( metadata=dc_config( @@ -418,7 +458,7 @@ class AsyncExportChannelsEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) started_at: datetime = dc_field( metadata=dc_config( @@ -426,13 +466,14 @@ class AsyncExportChannelsEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) task_id: str = dc_field(metadata=dc_config(field_name="task_id")) url: str = dc_field(metadata=dc_config(field_name="url")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="export.channels.success", metadata=dc_config(field_name="type") + default="export.channels.success", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -453,7 +494,7 @@ class AsyncExportErrorEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) error: str = dc_field(metadata=dc_config(field_name="error")) finished_at: datetime = dc_field( @@ -462,7 +503,7 @@ class AsyncExportErrorEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) started_at: datetime = dc_field( metadata=dc_config( @@ -470,12 +511,13 @@ class AsyncExportErrorEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) task_id: str = dc_field(metadata=dc_config(field_name="task_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="export.channels.error", metadata=dc_config(field_name="type") + default="export.channels.error", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -496,7 +538,7 @@ class AsyncExportModerationLogsEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) finished_at: datetime = dc_field( metadata=dc_config( @@ -504,7 +546,7 @@ class AsyncExportModerationLogsEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) started_at: datetime = dc_field( metadata=dc_config( @@ -512,13 +554,14 @@ class AsyncExportModerationLogsEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) task_id: str = dc_field(metadata=dc_config(field_name="task_id")) url: str = dc_field(metadata=dc_config(field_name="url")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="export.moderation_logs.success", metadata=dc_config(field_name="type") + default="export.moderation_logs.success", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -539,7 +582,7 @@ class AsyncExportUsersEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) finished_at: datetime = dc_field( metadata=dc_config( @@ -547,7 +590,7 @@ class AsyncExportUsersEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) started_at: datetime = dc_field( metadata=dc_config( @@ -555,13 +598,14 @@ class AsyncExportUsersEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) task_id: str = dc_field(metadata=dc_config(field_name="task_id")) url: str = dc_field(metadata=dc_config(field_name="url")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="export.users.success", metadata=dc_config(field_name="type") + default="export.users.success", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -578,17 +622,20 @@ class AsyncExportUsersEvent(DataClassJsonMixin): class AsyncModerationCallbackConfig(DataClassJsonMixin): mode: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mode")) server_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="server_url") + default=None, + metadata=dc_config(field_name="server_url"), ) @dataclass class AsyncModerationConfiguration(DataClassJsonMixin): timeout_ms: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="timeout_ms") + default=None, + metadata=dc_config(field_name="timeout_ms"), ) callback: "Optional[AsyncModerationCallbackConfig]" = dc_field( - default=None, metadata=dc_config(field_name="callback") + default=None, + metadata=dc_config(field_name="callback"), ) @@ -596,82 +643,102 @@ class AsyncModerationConfiguration(DataClassJsonMixin): class Attachment(DataClassJsonMixin): custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) asset_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="asset_url") + default=None, + metadata=dc_config(field_name="asset_url"), ) author_icon: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="author_icon") + default=None, + metadata=dc_config(field_name="author_icon"), ) author_link: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="author_link") + default=None, + metadata=dc_config(field_name="author_link"), ) author_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="author_name") + default=None, + metadata=dc_config(field_name="author_name"), ) color: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="color") + default=None, + metadata=dc_config(field_name="color"), ) fallback: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="fallback") + default=None, + metadata=dc_config(field_name="fallback"), ) footer: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="footer") + default=None, + metadata=dc_config(field_name="footer"), ) footer_icon: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="footer_icon") + default=None, + metadata=dc_config(field_name="footer_icon"), ) image_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image_url") + default=None, + metadata=dc_config(field_name="image_url"), ) og_scrape_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="og_scrape_url") + default=None, + metadata=dc_config(field_name="og_scrape_url"), ) original_height: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="original_height") + default=None, + metadata=dc_config(field_name="original_height"), ) original_width: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="original_width") + default=None, + metadata=dc_config(field_name="original_width"), ) pretext: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="pretext") + default=None, + metadata=dc_config(field_name="pretext"), ) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) thumb_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thumb_url") + default=None, + metadata=dc_config(field_name="thumb_url"), ) title: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="title") + default=None, + metadata=dc_config(field_name="title"), ) title_link: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="title_link") + default=None, + metadata=dc_config(field_name="title_link"), ) type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) actions: "Optional[List[Action]]" = dc_field( - default=None, metadata=dc_config(field_name="actions") + default=None, + metadata=dc_config(field_name="actions"), ) fields: "Optional[List[Field]]" = dc_field( - default=None, metadata=dc_config(field_name="fields") + default=None, + metadata=dc_config(field_name="fields"), ) giphy: "Optional[Images]" = dc_field( - default=None, metadata=dc_config(field_name="giphy") + default=None, + metadata=dc_config(field_name="giphy"), ) @dataclass class AudioSettings(DataClassJsonMixin): access_request_enabled: bool = dc_field( - metadata=dc_config(field_name="access_request_enabled") + metadata=dc_config(field_name="access_request_enabled"), ) default_device: str = dc_field(metadata=dc_config(field_name="default_device")) mic_default_on: bool = dc_field(metadata=dc_config(field_name="mic_default_on")) opus_dtx_enabled: bool = dc_field(metadata=dc_config(field_name="opus_dtx_enabled")) redundant_coding_enabled: bool = dc_field( - metadata=dc_config(field_name="redundant_coding_enabled") + metadata=dc_config(field_name="redundant_coding_enabled"), ) speaker_default_on: bool = dc_field( - metadata=dc_config(field_name="speaker_default_on") + metadata=dc_config(field_name="speaker_default_on"), ) noise_cancellation: "Optional[NoiseCancellationSettings]" = dc_field( - default=None, metadata=dc_config(field_name="noise_cancellation") + default=None, + metadata=dc_config(field_name="noise_cancellation"), ) @@ -679,60 +746,72 @@ class AudioSettings(DataClassJsonMixin): class AudioSettingsRequest(DataClassJsonMixin): default_device: str = dc_field(metadata=dc_config(field_name="default_device")) access_request_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="access_request_enabled") + default=None, + metadata=dc_config(field_name="access_request_enabled"), ) mic_default_on: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mic_default_on") + default=None, + metadata=dc_config(field_name="mic_default_on"), ) opus_dtx_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="opus_dtx_enabled") + default=None, + metadata=dc_config(field_name="opus_dtx_enabled"), ) redundant_coding_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="redundant_coding_enabled") + default=None, + metadata=dc_config(field_name="redundant_coding_enabled"), ) speaker_default_on: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="speaker_default_on") + default=None, + metadata=dc_config(field_name="speaker_default_on"), ) noise_cancellation: "Optional[NoiseCancellationSettings]" = dc_field( - default=None, metadata=dc_config(field_name="noise_cancellation") + default=None, + metadata=dc_config(field_name="noise_cancellation"), ) @dataclass class AudioSettingsResponse(DataClassJsonMixin): access_request_enabled: bool = dc_field( - metadata=dc_config(field_name="access_request_enabled") + metadata=dc_config(field_name="access_request_enabled"), ) default_device: str = dc_field(metadata=dc_config(field_name="default_device")) mic_default_on: bool = dc_field(metadata=dc_config(field_name="mic_default_on")) opus_dtx_enabled: bool = dc_field(metadata=dc_config(field_name="opus_dtx_enabled")) redundant_coding_enabled: bool = dc_field( - metadata=dc_config(field_name="redundant_coding_enabled") + metadata=dc_config(field_name="redundant_coding_enabled"), ) speaker_default_on: bool = dc_field( - metadata=dc_config(field_name="speaker_default_on") + metadata=dc_config(field_name="speaker_default_on"), ) noise_cancellation: "Optional[NoiseCancellationSettings]" = dc_field( - default=None, metadata=dc_config(field_name="noise_cancellation") + default=None, + metadata=dc_config(field_name="noise_cancellation"), ) @dataclass class AutomodDetails(DataClassJsonMixin): action: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="action") + default=None, + metadata=dc_config(field_name="action"), ) original_message_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="original_message_type") + default=None, + metadata=dc_config(field_name="original_message_type"), ) image_labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="image_labels") + default=None, + metadata=dc_config(field_name="image_labels"), ) message_details: "Optional[FlagMessageDetails]" = dc_field( - default=None, metadata=dc_config(field_name="message_details") + default=None, + metadata=dc_config(field_name="message_details"), ) result: "Optional[MessageModerationResult]" = dc_field( - default=None, metadata=dc_config(field_name="result") + default=None, + metadata=dc_config(field_name="result"), ) @@ -741,7 +820,8 @@ class AutomodPlatformCircumventionConfig(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) rules: "List[AutomodRule]" = dc_field(metadata=dc_config(field_name="rules")) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) @@ -756,10 +836,11 @@ class AutomodRule(DataClassJsonMixin): class AutomodSemanticFiltersConfig(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) rules: "List[AutomodSemanticFiltersRule]" = dc_field( - metadata=dc_config(field_name="rules") + metadata=dc_config(field_name="rules"), ) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) @@ -775,7 +856,8 @@ class AutomodToxicityConfig(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) rules: "List[AutomodRule]" = dc_field(metadata=dc_config(field_name="rules")) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) @@ -784,7 +866,7 @@ class AzureRequest(DataClassJsonMixin): abs_account_name: str = dc_field(metadata=dc_config(field_name="abs_account_name")) abs_client_id: str = dc_field(metadata=dc_config(field_name="abs_client_id")) abs_client_secret: str = dc_field( - metadata=dc_config(field_name="abs_client_secret") + metadata=dc_config(field_name="abs_client_secret"), ) abs_tenant_id: str = dc_field(metadata=dc_config(field_name="abs_tenant_id")) @@ -793,17 +875,20 @@ class AzureRequest(DataClassJsonMixin): class BackstageSettings(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) join_ahead_time_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="join_ahead_time_seconds") + default=None, + metadata=dc_config(field_name="join_ahead_time_seconds"), ) @dataclass class BackstageSettingsRequest(DataClassJsonMixin): enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) join_ahead_time_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="join_ahead_time_seconds") + default=None, + metadata=dc_config(field_name="join_ahead_time_seconds"), ) @@ -811,7 +896,8 @@ class BackstageSettingsRequest(DataClassJsonMixin): class BackstageSettingsResponse(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) join_ahead_time_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="join_ahead_time_seconds") + default=None, + metadata=dc_config(field_name="join_ahead_time_seconds"), ) @@ -823,7 +909,7 @@ class Ban(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) shadow: bool = dc_field(metadata=dc_config(field_name="shadow")) expires: Optional[datetime] = dc_field( @@ -836,38 +922,48 @@ class Ban(DataClassJsonMixin): ), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) channel: "Optional[Channel]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) created_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) target: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="target") + default=None, + metadata=dc_config(field_name="target"), ) @dataclass class BanActionRequest(DataClassJsonMixin): channel_ban_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="channel_ban_only") + default=None, + metadata=dc_config(field_name="channel_ban_only"), ) delete_messages: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="delete_messages") + default=None, + metadata=dc_config(field_name="delete_messages"), ) ip_ban: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="ip_ban") + default=None, + metadata=dc_config(field_name="ip_ban"), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) shadow: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="shadow") + default=None, + metadata=dc_config(field_name="shadow"), ) timeout: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="timeout") + default=None, + metadata=dc_config(field_name="timeout"), ) @@ -875,28 +971,36 @@ class BanActionRequest(DataClassJsonMixin): class BanRequest(DataClassJsonMixin): target_user_id: str = dc_field(metadata=dc_config(field_name="target_user_id")) banned_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="banned_by_id") + default=None, + metadata=dc_config(field_name="banned_by_id"), ) channel_cid: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_cid") + default=None, + metadata=dc_config(field_name="channel_cid"), ) delete_messages: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="delete_messages") + default=None, + metadata=dc_config(field_name="delete_messages"), ) ip_ban: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="ip_ban") + default=None, + metadata=dc_config(field_name="ip_ban"), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) shadow: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="shadow") + default=None, + metadata=dc_config(field_name="shadow"), ) timeout: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="timeout") + default=None, + metadata=dc_config(field_name="timeout"), ) banned_by: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="banned_by") + default=None, + metadata=dc_config(field_name="banned_by"), ) @@ -908,7 +1012,7 @@ class BanResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) expires: Optional[datetime] = dc_field( default=None, @@ -920,19 +1024,24 @@ class BanResponse(DataClassJsonMixin): ), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) shadow: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="shadow") + default=None, + metadata=dc_config(field_name="shadow"), ) banned_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="banned_by") + default=None, + metadata=dc_config(field_name="banned_by"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -941,7 +1050,8 @@ class BlockListConfig(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) rules: "List[BlockListRule]" = dc_field(metadata=dc_config(field_name="rules")) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) @@ -999,17 +1109,19 @@ class BlockUserResponse(DataClassJsonMixin): class BlockUsersRequest(DataClassJsonMixin): blocked_user_id: str = dc_field(metadata=dc_config(field_name="blocked_user_id")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class BlockUsersResponse(DataClassJsonMixin): blocked_by_user_id: str = dc_field( - metadata=dc_config(field_name="blocked_by_user_id") + metadata=dc_config(field_name="blocked_by_user_id"), ) blocked_user_id: str = dc_field(metadata=dc_config(field_name="blocked_user_id")) created_at: datetime = dc_field( @@ -1018,7 +1130,7 @@ class BlockUsersResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) duration: str = dc_field(metadata=dc_config(field_name="duration")) @@ -1032,14 +1144,16 @@ class BlockedUserEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field( - default="call.blocked_user", metadata=dc_config(field_name="type") + default="call.blocked_user", + metadata=dc_config(field_name="type"), ) blocked_by_user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="blocked_by_user") + default=None, + metadata=dc_config(field_name="blocked_by_user"), ) @@ -1052,11 +1166,11 @@ class BlockedUserResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) blocked_user: "UserResponse" = dc_field( - metadata=dc_config(field_name="blocked_user") + metadata=dc_config(field_name="blocked_user"), ) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) @@ -1066,7 +1180,7 @@ class BodyguardRule(DataClassJsonMixin): action: str = dc_field(metadata=dc_config(field_name="action")) label: str = dc_field(metadata=dc_config(field_name="label")) severity_rules: "List[BodyguardSeverityRule]" = dc_field( - metadata=dc_config(field_name="severity_rules") + metadata=dc_config(field_name="severity_rules"), ) @@ -1086,23 +1200,28 @@ class Bound(DataClassJsonMixin): class BroadcastSettings(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) hls: "Optional[HLSSettings]" = dc_field( - default=None, metadata=dc_config(field_name="hls") + default=None, + metadata=dc_config(field_name="hls"), ) rtmp: "Optional[RTMPSettings]" = dc_field( - default=None, metadata=dc_config(field_name="rtmp") + default=None, + metadata=dc_config(field_name="rtmp"), ) @dataclass class BroadcastSettingsRequest(DataClassJsonMixin): enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) hls: "Optional[HLSSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="hls") + default=None, + metadata=dc_config(field_name="hls"), ) rtmp: "Optional[RTMPSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="rtmp") + default=None, + metadata=dc_config(field_name="rtmp"), ) @@ -1117,7 +1236,8 @@ class BroadcastSettingsResponse(DataClassJsonMixin): class BrowserDataResponse(DataClassJsonMixin): name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) version: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="version") + default=None, + metadata=dc_config(field_name="version"), ) @@ -1144,11 +1264,11 @@ class Call(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_user_id: str = dc_field(metadata=dc_config(field_name="CreatedByUserID")) current_session_id: str = dc_field( - metadata=dc_config(field_name="CurrentSessionID") + metadata=dc_config(field_name="CurrentSessionID"), ) id: str = dc_field(metadata=dc_config(field_name="ID")) last_session_id: str = dc_field(metadata=dc_config(field_name="LastSessionID")) @@ -1161,13 +1281,13 @@ class Call(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) blocked_user_i_ds: List[str] = dc_field( - metadata=dc_config(field_name="BlockedUserIDs") + metadata=dc_config(field_name="BlockedUserIDs"), ) blocked_users: "List[User]" = dc_field( - metadata=dc_config(field_name="BlockedUsers") + metadata=dc_config(field_name="BlockedUsers"), ) egresses: "List[CallEgress]" = dc_field(metadata=dc_config(field_name="Egresses")) members: "List[CallMember]" = dc_field(metadata=dc_config(field_name="Members")) @@ -1200,7 +1320,8 @@ class Call(DataClassJsonMixin): ), ) join_ahead_time_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="JoinAheadTimeSeconds") + default=None, + metadata=dc_config(field_name="JoinAheadTimeSeconds"), ) last_heartbeat_at: Optional[datetime] = dc_field( default=None, @@ -1212,7 +1333,8 @@ class Call(DataClassJsonMixin): ), ) member_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="MemberCount") + default=None, + metadata=dc_config(field_name="MemberCount"), ) starts_at: Optional[datetime] = dc_field( default=None, @@ -1224,22 +1346,28 @@ class Call(DataClassJsonMixin): ), ) call_type: "Optional[CallType]" = dc_field( - default=None, metadata=dc_config(field_name="CallType") + default=None, + metadata=dc_config(field_name="CallType"), ) created_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="CreatedBy") + default=None, + metadata=dc_config(field_name="CreatedBy"), ) member_lookup: "Optional[MemberLookup]" = dc_field( - default=None, metadata=dc_config(field_name="MemberLookup") + default=None, + metadata=dc_config(field_name="MemberLookup"), ) session: "Optional[CallSession]" = dc_field( - default=None, metadata=dc_config(field_name="Session") + default=None, + metadata=dc_config(field_name="Session"), ) settings: "Optional[CallSettings]" = dc_field( - default=None, metadata=dc_config(field_name="Settings") + default=None, + metadata=dc_config(field_name="Settings"), ) settings_overrides: "Optional[CallSettings]" = dc_field( - default=None, metadata=dc_config(field_name="SettingsOverrides") + default=None, + metadata=dc_config(field_name="SettingsOverrides"), ) @@ -1252,7 +1380,7 @@ class CallAcceptedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) @@ -1267,7 +1395,7 @@ class CallClosedCaption(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) speaker_id: str = dc_field(metadata=dc_config(field_name="speaker_id")) start_time: datetime = dc_field( @@ -1276,7 +1404,7 @@ class CallClosedCaption(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) text: str = dc_field(metadata=dc_config(field_name="text")) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) @@ -1291,10 +1419,11 @@ class CallClosedCaptionsFailedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="call.closed_captions_failed", metadata=dc_config(field_name="type") + default="call.closed_captions_failed", + metadata=dc_config(field_name="type"), ) @@ -1307,10 +1436,11 @@ class CallClosedCaptionsStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="call.closed_captions_started", metadata=dc_config(field_name="type") + default="call.closed_captions_started", + metadata=dc_config(field_name="type"), ) @@ -1323,10 +1453,11 @@ class CallClosedCaptionsStoppedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="call.closed_captions_stopped", metadata=dc_config(field_name="type") + default="call.closed_captions_stopped", + metadata=dc_config(field_name="type"), ) @@ -1339,7 +1470,7 @@ class CallCreatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) @@ -1355,7 +1486,7 @@ class CallDeletedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field(default="call.deleted", metadata=dc_config(field_name="type")) @@ -1364,14 +1495,14 @@ class CallDeletedEvent(DataClassJsonMixin): @dataclass class CallDurationReport(DataClassJsonMixin): histogram: "List[ReportByHistogramBucket]" = dc_field( - metadata=dc_config(field_name="histogram") + metadata=dc_config(field_name="histogram"), ) @dataclass class CallDurationReportResponse(DataClassJsonMixin): daily: "List[DailyAggregateCallDurationReportResponse]" = dc_field( - metadata=dc_config(field_name="daily") + metadata=dc_config(field_name="daily"), ) @@ -1389,7 +1520,7 @@ class CallEgress(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) state: str = dc_field(metadata=dc_config(field_name="state")) updated_at: datetime = dc_field( @@ -1398,7 +1529,7 @@ class CallEgress(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) stopped_at: Optional[datetime] = dc_field( default=None, @@ -1410,7 +1541,8 @@ class CallEgress(DataClassJsonMixin): ), ) config: "Optional[EgressTaskConfig]" = dc_field( - default=None, metadata=dc_config(field_name="config") + default=None, + metadata=dc_config(field_name="config"), ) @@ -1423,15 +1555,17 @@ class CallEndedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field(default="call.ended", metadata=dc_config(field_name="type")) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -1444,12 +1578,13 @@ class CallFrameRecordingFailedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.frame_recording_failed", metadata=dc_config(field_name="type") + default="call.frame_recording_failed", + metadata=dc_config(field_name="type"), ) @@ -1462,7 +1597,7 @@ class CallFrameRecordingFrameReadyEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_at: datetime = dc_field( metadata=dc_config( @@ -1470,7 +1605,7 @@ class CallFrameRecordingFrameReadyEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) @@ -1478,7 +1613,8 @@ class CallFrameRecordingFrameReadyEvent(DataClassJsonMixin): url: str = dc_field(metadata=dc_config(field_name="url")) users: "Dict[str, UserResponse]" = dc_field(metadata=dc_config(field_name="users")) type: str = dc_field( - default="call.frame_recording_ready", metadata=dc_config(field_name="type") + default="call.frame_recording_ready", + metadata=dc_config(field_name="type"), ) @@ -1491,12 +1627,13 @@ class CallFrameRecordingStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.frame_recording_started", metadata=dc_config(field_name="type") + default="call.frame_recording_started", + metadata=dc_config(field_name="type"), ) @@ -1509,12 +1646,13 @@ class CallFrameRecordingStoppedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.frame_recording_stopped", metadata=dc_config(field_name="type") + default="call.frame_recording_stopped", + metadata=dc_config(field_name="type"), ) @@ -1527,10 +1665,11 @@ class CallHLSBroadcastingFailedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="call.hls_broadcasting_failed", metadata=dc_config(field_name="type") + default="call.hls_broadcasting_failed", + metadata=dc_config(field_name="type"), ) @@ -1543,12 +1682,13 @@ class CallHLSBroadcastingStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) hls_playlist_url: str = dc_field(metadata=dc_config(field_name="hls_playlist_url")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.hls_broadcasting_started", metadata=dc_config(field_name="type") + default="call.hls_broadcasting_started", + metadata=dc_config(field_name="type"), ) @@ -1561,10 +1701,11 @@ class CallHLSBroadcastingStoppedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="call.hls_broadcasting_stopped", metadata=dc_config(field_name="type") + default="call.hls_broadcasting_stopped", + metadata=dc_config(field_name="type"), ) @@ -1582,11 +1723,12 @@ class CallLiveStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.live_started", metadata=dc_config(field_name="type") + default="call.live_started", + metadata=dc_config(field_name="type"), ) @@ -1598,7 +1740,7 @@ class CallMember(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) role: str = dc_field(metadata=dc_config(field_name="role")) updated_at: datetime = dc_field( @@ -1607,7 +1749,7 @@ class CallMember(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) @@ -1621,7 +1763,8 @@ class CallMember(DataClassJsonMixin): ), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -1634,12 +1777,13 @@ class CallMemberAddedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.member_added", metadata=dc_config(field_name="type") + default="call.member_added", + metadata=dc_config(field_name="type"), ) @@ -1652,12 +1796,13 @@ class CallMemberRemovedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) members: List[str] = dc_field(metadata=dc_config(field_name="members")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.member_removed", metadata=dc_config(field_name="type") + default="call.member_removed", + metadata=dc_config(field_name="type"), ) @@ -1670,12 +1815,13 @@ class CallMemberUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.member_updated", metadata=dc_config(field_name="type") + default="call.member_updated", + metadata=dc_config(field_name="type"), ) @@ -1688,15 +1834,16 @@ class CallMemberUpdatedPermissionEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) capabilities_by_role: "Dict[str, List[str]]" = dc_field( - metadata=dc_config(field_name="capabilities_by_role") + metadata=dc_config(field_name="capabilities_by_role"), ) type: str = dc_field( - default="call.member_updated_permission", metadata=dc_config(field_name="type") + default="call.member_updated_permission", + metadata=dc_config(field_name="type"), ) @@ -1709,7 +1856,7 @@ class CallMissedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) notify_user: bool = dc_field(metadata=dc_config(field_name="notify_user")) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) @@ -1728,12 +1875,13 @@ class CallModerationBlurEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="call.moderation_blur", metadata=dc_config(field_name="type") + default="call.moderation_blur", + metadata=dc_config(field_name="type"), ) @@ -1746,13 +1894,14 @@ class CallModerationWarningEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message: str = dc_field(metadata=dc_config(field_name="message")) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="call.moderation_warning", metadata=dc_config(field_name="type") + default="call.moderation_warning", + metadata=dc_config(field_name="type"), ) @@ -1765,14 +1914,15 @@ class CallNotificationEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field( - default="call.notification", metadata=dc_config(field_name="type") + default="call.notification", + metadata=dc_config(field_name="type"), ) @@ -1786,7 +1936,7 @@ class CallParticipant(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) online: bool = dc_field(metadata=dc_config(field_name="online")) role: str = dc_field(metadata=dc_config(field_name="role")) @@ -1831,10 +1981,12 @@ class CallParticipant(DataClassJsonMixin): ), ) invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") + default=None, + metadata=dc_config(field_name="invisible"), ) language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + default=None, + metadata=dc_config(field_name="language"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -1873,24 +2025,26 @@ class CallParticipant(DataClassJsonMixin): ), ) teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") + default=None, + metadata=dc_config(field_name="teams"), ) privacy_settings: "Optional[PrivacySettings]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) @dataclass class CallParticipantCountReport(DataClassJsonMixin): histogram: "List[ReportByHistogramBucket]" = dc_field( - metadata=dc_config(field_name="histogram") + metadata=dc_config(field_name="histogram"), ) @dataclass class CallParticipantCountReportResponse(DataClassJsonMixin): daily: "List[DailyAggregateCallParticipantCountReportResponse]" = dc_field( - metadata=dc_config(field_name="daily") + metadata=dc_config(field_name="daily"), ) @@ -1902,7 +2056,7 @@ class CallParticipantResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) role: str = dc_field(metadata=dc_config(field_name="role")) user_session_id: str = dc_field(metadata=dc_config(field_name="user_session_id")) @@ -1918,11 +2072,12 @@ class CallReactionEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) reaction: "ReactionResponse" = dc_field(metadata=dc_config(field_name="reaction")) type: str = dc_field( - default="call.reaction_new", metadata=dc_config(field_name="type") + default="call.reaction_new", + metadata=dc_config(field_name="type"), ) @@ -1934,7 +2089,7 @@ class CallRecording(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) filename: str = dc_field(metadata=dc_config(field_name="filename")) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) @@ -1944,7 +2099,7 @@ class CallRecording(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) url: str = dc_field(metadata=dc_config(field_name="url")) @@ -1958,11 +2113,12 @@ class CallRecordingFailedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) type: str = dc_field( - default="call.recording_failed", metadata=dc_config(field_name="type") + default="call.recording_failed", + metadata=dc_config(field_name="type"), ) @@ -1975,14 +2131,15 @@ class CallRecordingReadyEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) call_recording: "CallRecording" = dc_field( - metadata=dc_config(field_name="call_recording") + metadata=dc_config(field_name="call_recording"), ) type: str = dc_field( - default="call.recording_ready", metadata=dc_config(field_name="type") + default="call.recording_ready", + metadata=dc_config(field_name="type"), ) @@ -1995,11 +2152,12 @@ class CallRecordingStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) type: str = dc_field( - default="call.recording_started", metadata=dc_config(field_name="type") + default="call.recording_started", + metadata=dc_config(field_name="type"), ) @@ -2012,11 +2170,12 @@ class CallRecordingStoppedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) type: str = dc_field( - default="call.recording_stopped", metadata=dc_config(field_name="type") + default="call.recording_stopped", + metadata=dc_config(field_name="type"), ) @@ -2029,13 +2188,14 @@ class CallRejectedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field(default="call.rejected", metadata=dc_config(field_name="type")) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) @@ -2065,10 +2225,12 @@ class CallReportResponse(DataClassJsonMixin): @dataclass class CallRequest(DataClassJsonMixin): channel_cid: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_cid") + default=None, + metadata=dc_config(field_name="channel_cid"), ) created_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="created_by_id") + default=None, + metadata=dc_config(field_name="created_by_id"), ) starts_at: Optional[datetime] = dc_field( default=None, @@ -2081,19 +2243,24 @@ class CallRequest(DataClassJsonMixin): ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) video: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="video") + default=None, + metadata=dc_config(field_name="video"), ) members: "Optional[List[MemberRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="members") + default=None, + metadata=dc_config(field_name="members"), ) created_by: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) settings_override: "Optional[CallSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="settings_override") + default=None, + metadata=dc_config(field_name="settings_override"), ) @@ -2108,10 +2275,10 @@ class CallResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) current_session_id: str = dc_field( - metadata=dc_config(field_name="current_session_id") + metadata=dc_config(field_name="current_session_id"), ) id: str = dc_field(metadata=dc_config(field_name="id")) recording: bool = dc_field(metadata=dc_config(field_name="recording")) @@ -2123,20 +2290,21 @@ class CallResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) blocked_user_ids: List[str] = dc_field( - metadata=dc_config(field_name="blocked_user_ids") + metadata=dc_config(field_name="blocked_user_ids"), ) created_by: "UserResponse" = dc_field(metadata=dc_config(field_name="created_by")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) egress: "EgressResponse" = dc_field(metadata=dc_config(field_name="egress")) ingress: "CallIngressResponse" = dc_field(metadata=dc_config(field_name="ingress")) settings: "CallSettingsResponse" = dc_field( - metadata=dc_config(field_name="settings") + metadata=dc_config(field_name="settings"), ) channel_cid: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_cid") + default=None, + metadata=dc_config(field_name="channel_cid"), ) ended_at: Optional[datetime] = dc_field( default=None, @@ -2148,7 +2316,8 @@ class CallResponse(DataClassJsonMixin): ), ) join_ahead_time_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="join_ahead_time_seconds") + default=None, + metadata=dc_config(field_name="join_ahead_time_seconds"), ) starts_at: Optional[datetime] = dc_field( default=None, @@ -2161,10 +2330,12 @@ class CallResponse(DataClassJsonMixin): ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) session: "Optional[CallSessionResponse]" = dc_field( - default=None, metadata=dc_config(field_name="session") + default=None, + metadata=dc_config(field_name="session"), ) thumbnails: "Optional[ThumbnailResponse]" = dc_field( - default=None, metadata=dc_config(field_name="thumbnails") + default=None, + metadata=dc_config(field_name="thumbnails"), ) @@ -2177,7 +2348,7 @@ class CallRingEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) video: bool = dc_field(metadata=dc_config(field_name="video")) @@ -2196,11 +2367,12 @@ class CallRtmpBroadcastFailedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) name: str = dc_field(metadata=dc_config(field_name="name")) type: str = dc_field( - default="call.rtmp_broadcast_failed", metadata=dc_config(field_name="type") + default="call.rtmp_broadcast_failed", + metadata=dc_config(field_name="type"), ) @@ -2213,11 +2385,12 @@ class CallRtmpBroadcastStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) name: str = dc_field(metadata=dc_config(field_name="name")) type: str = dc_field( - default="call.rtmp_broadcast_started", metadata=dc_config(field_name="type") + default="call.rtmp_broadcast_started", + metadata=dc_config(field_name="type"), ) @@ -2230,18 +2403,19 @@ class CallRtmpBroadcastStoppedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) name: str = dc_field(metadata=dc_config(field_name="name")) type: str = dc_field( - default="call.rtmp_broadcast_stopped", metadata=dc_config(field_name="type") + default="call.rtmp_broadcast_stopped", + metadata=dc_config(field_name="type"), ) @dataclass class CallSession(DataClassJsonMixin): anonymous_participant_count: int = dc_field( - metadata=dc_config(field_name="AnonymousParticipantCount") + metadata=dc_config(field_name="AnonymousParticipantCount"), ) app_pk: int = dc_field(metadata=dc_config(field_name="AppPK")) call_id: str = dc_field(metadata=dc_config(field_name="CallID")) @@ -2252,14 +2426,14 @@ class CallSession(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) session_id: str = dc_field(metadata=dc_config(field_name="SessionID")) active_sf_us: "List[SFUIDLastSeen]" = dc_field( - metadata=dc_config(field_name="ActiveSFUs") + metadata=dc_config(field_name="ActiveSFUs"), ) participants: "List[CallParticipant]" = dc_field( - metadata=dc_config(field_name="Participants") + metadata=dc_config(field_name="Participants"), ) sfui_ds: List[str] = dc_field(metadata=dc_config(field_name="SFUIDs")) accepted_by: "Dict[str, datetime]" = dc_field( @@ -2268,7 +2442,7 @@ class CallSession(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) missed_by: "Dict[str, datetime]" = dc_field( metadata=dc_config( @@ -2276,10 +2450,10 @@ class CallSession(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) participants_count_by_role: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="ParticipantsCountByRole") + metadata=dc_config(field_name="ParticipantsCountByRole"), ) rejected_by: "Dict[str, datetime]" = dc_field( metadata=dc_config( @@ -2287,10 +2461,10 @@ class CallSession(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_permission_overrides: "Dict[str, Dict[str, bool]]" = dc_field( - metadata=dc_config(field_name="UserPermissionOverrides") + metadata=dc_config(field_name="UserPermissionOverrides"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -2366,12 +2540,13 @@ class CallSessionEndedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.session_ended", metadata=dc_config(field_name="type") + default="call.session_ended", + metadata=dc_config(field_name="type"), ) @@ -2384,14 +2559,15 @@ class CallSessionParticipantJoinedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) participant: "CallParticipantResponse" = dc_field( - metadata=dc_config(field_name="participant") + metadata=dc_config(field_name="participant"), ) type: str = dc_field( - default="call.session_participant_joined", metadata=dc_config(field_name="type") + default="call.session_participant_joined", + metadata=dc_config(field_name="type"), ) @@ -2404,26 +2580,27 @@ class CallSessionParticipantLeftEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) duration_seconds: int = dc_field(metadata=dc_config(field_name="duration_seconds")) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) participant: "CallParticipantResponse" = dc_field( - metadata=dc_config(field_name="participant") + metadata=dc_config(field_name="participant"), ) type: str = dc_field( - default="call.session_participant_left", metadata=dc_config(field_name="type") + default="call.session_participant_left", + metadata=dc_config(field_name="type"), ) @dataclass class CallSessionResponse(DataClassJsonMixin): anonymous_participant_count: int = dc_field( - metadata=dc_config(field_name="anonymous_participant_count") + metadata=dc_config(field_name="anonymous_participant_count"), ) id: str = dc_field(metadata=dc_config(field_name="id")) participants: "List[CallParticipantResponse]" = dc_field( - metadata=dc_config(field_name="participants") + metadata=dc_config(field_name="participants"), ) accepted_by: "Dict[str, datetime]" = dc_field( metadata=dc_config( @@ -2431,7 +2608,7 @@ class CallSessionResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) missed_by: "Dict[str, datetime]" = dc_field( metadata=dc_config( @@ -2439,10 +2616,10 @@ class CallSessionResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) participants_count_by_role: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="participants_count_by_role") + metadata=dc_config(field_name="participants_count_by_role"), ) rejected_by: "Dict[str, datetime]" = dc_field( metadata=dc_config( @@ -2450,7 +2627,7 @@ class CallSessionResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) ended_at: Optional[datetime] = dc_field( default=None, @@ -2508,98 +2685,125 @@ class CallSessionStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) type: str = dc_field( - default="call.session_started", metadata=dc_config(field_name="type") + default="call.session_started", + metadata=dc_config(field_name="type"), ) @dataclass class CallSettings(DataClassJsonMixin): audio: "Optional[AudioSettings]" = dc_field( - default=None, metadata=dc_config(field_name="audio") + default=None, + metadata=dc_config(field_name="audio"), ) backstage: "Optional[BackstageSettings]" = dc_field( - default=None, metadata=dc_config(field_name="backstage") + default=None, + metadata=dc_config(field_name="backstage"), ) broadcasting: "Optional[BroadcastSettings]" = dc_field( - default=None, metadata=dc_config(field_name="broadcasting") + default=None, + metadata=dc_config(field_name="broadcasting"), ) frame_recording: "Optional[FrameRecordSettings]" = dc_field( - default=None, metadata=dc_config(field_name="frame_recording") + default=None, + metadata=dc_config(field_name="frame_recording"), ) geofencing: "Optional[GeofenceSettings]" = dc_field( - default=None, metadata=dc_config(field_name="geofencing") + default=None, + metadata=dc_config(field_name="geofencing"), ) limits: "Optional[LimitsSettings]" = dc_field( - default=None, metadata=dc_config(field_name="limits") + default=None, + metadata=dc_config(field_name="limits"), ) recording: "Optional[RecordSettings]" = dc_field( - default=None, metadata=dc_config(field_name="recording") + default=None, + metadata=dc_config(field_name="recording"), ) ring: "Optional[RingSettings]" = dc_field( - default=None, metadata=dc_config(field_name="ring") + default=None, + metadata=dc_config(field_name="ring"), ) screensharing: "Optional[ScreensharingSettings]" = dc_field( - default=None, metadata=dc_config(field_name="screensharing") + default=None, + metadata=dc_config(field_name="screensharing"), ) session: "Optional[SessionSettings]" = dc_field( - default=None, metadata=dc_config(field_name="session") + default=None, + metadata=dc_config(field_name="session"), ) thumbnails: "Optional[ThumbnailsSettings]" = dc_field( - default=None, metadata=dc_config(field_name="thumbnails") + default=None, + metadata=dc_config(field_name="thumbnails"), ) transcription: "Optional[TranscriptionSettings]" = dc_field( - default=None, metadata=dc_config(field_name="transcription") + default=None, + metadata=dc_config(field_name="transcription"), ) video: "Optional[VideoSettings]" = dc_field( - default=None, metadata=dc_config(field_name="video") + default=None, + metadata=dc_config(field_name="video"), ) @dataclass class CallSettingsRequest(DataClassJsonMixin): audio: "Optional[AudioSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="audio") + default=None, + metadata=dc_config(field_name="audio"), ) backstage: "Optional[BackstageSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="backstage") + default=None, + metadata=dc_config(field_name="backstage"), ) broadcasting: "Optional[BroadcastSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="broadcasting") + default=None, + metadata=dc_config(field_name="broadcasting"), ) frame_recording: "Optional[FrameRecordingSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="frame_recording") + default=None, + metadata=dc_config(field_name="frame_recording"), ) geofencing: "Optional[GeofenceSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="geofencing") + default=None, + metadata=dc_config(field_name="geofencing"), ) limits: "Optional[LimitsSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="limits") + default=None, + metadata=dc_config(field_name="limits"), ) recording: "Optional[RecordSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="recording") + default=None, + metadata=dc_config(field_name="recording"), ) ring: "Optional[RingSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="ring") + default=None, + metadata=dc_config(field_name="ring"), ) screensharing: "Optional[ScreensharingSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="screensharing") + default=None, + metadata=dc_config(field_name="screensharing"), ) session: "Optional[SessionSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="session") + default=None, + metadata=dc_config(field_name="session"), ) thumbnails: "Optional[ThumbnailsSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="thumbnails") + default=None, + metadata=dc_config(field_name="thumbnails"), ) transcription: "Optional[TranscriptionSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="transcription") + default=None, + metadata=dc_config(field_name="transcription"), ) video: "Optional[VideoSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="video") + default=None, + metadata=dc_config(field_name="video"), ) @@ -2607,33 +2811,33 @@ class CallSettingsRequest(DataClassJsonMixin): class CallSettingsResponse(DataClassJsonMixin): audio: "AudioSettingsResponse" = dc_field(metadata=dc_config(field_name="audio")) backstage: "BackstageSettingsResponse" = dc_field( - metadata=dc_config(field_name="backstage") + metadata=dc_config(field_name="backstage"), ) broadcasting: "BroadcastSettingsResponse" = dc_field( - metadata=dc_config(field_name="broadcasting") + metadata=dc_config(field_name="broadcasting"), ) frame_recording: "FrameRecordingSettingsResponse" = dc_field( - metadata=dc_config(field_name="frame_recording") + metadata=dc_config(field_name="frame_recording"), ) geofencing: "GeofenceSettingsResponse" = dc_field( - metadata=dc_config(field_name="geofencing") + metadata=dc_config(field_name="geofencing"), ) limits: "LimitsSettingsResponse" = dc_field(metadata=dc_config(field_name="limits")) recording: "RecordSettingsResponse" = dc_field( - metadata=dc_config(field_name="recording") + metadata=dc_config(field_name="recording"), ) ring: "RingSettingsResponse" = dc_field(metadata=dc_config(field_name="ring")) screensharing: "ScreensharingSettingsResponse" = dc_field( - metadata=dc_config(field_name="screensharing") + metadata=dc_config(field_name="screensharing"), ) session: "SessionSettingsResponse" = dc_field( - metadata=dc_config(field_name="session") + metadata=dc_config(field_name="session"), ) thumbnails: "ThumbnailsSettingsResponse" = dc_field( - metadata=dc_config(field_name="thumbnails") + metadata=dc_config(field_name="thumbnails"), ) transcription: "TranscriptionSettingsResponse" = dc_field( - metadata=dc_config(field_name="transcription") + metadata=dc_config(field_name="transcription"), ) video: "VideoSettingsResponse" = dc_field(metadata=dc_config(field_name="video")) @@ -2642,7 +2846,7 @@ class CallSettingsResponse(DataClassJsonMixin): class CallStateResponseFields(DataClassJsonMixin): members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) own_capabilities: "List[OwnCapability]" = dc_field( - metadata=dc_config(field_name="own_capabilities") + metadata=dc_config(field_name="own_capabilities"), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) @@ -2656,11 +2860,12 @@ class CallStatsReportReadyEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) type: str = dc_field( - default="call.stats_report_ready", metadata=dc_config(field_name="type") + default="call.stats_report_ready", + metadata=dc_config(field_name="type"), ) @@ -2668,7 +2873,7 @@ class CallStatsReportReadyEvent(DataClassJsonMixin): class CallStatsReportSummaryResponse(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) call_duration_seconds: int = dc_field( - metadata=dc_config(field_name="call_duration_seconds") + metadata=dc_config(field_name="call_duration_seconds"), ) call_session_id: str = dc_field(metadata=dc_config(field_name="call_session_id")) call_status: str = dc_field(metadata=dc_config(field_name="call_status")) @@ -2678,7 +2883,7 @@ class CallStatsReportSummaryResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_at: Optional[datetime] = dc_field( default=None, @@ -2690,10 +2895,12 @@ class CallStatsReportSummaryResponse(DataClassJsonMixin): ), ) min_user_rating: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="min_user_rating") + default=None, + metadata=dc_config(field_name="min_user_rating"), ) quality_score: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="quality_score") + default=None, + metadata=dc_config(field_name="quality_score"), ) @@ -2705,7 +2912,7 @@ class CallTranscription(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) filename: str = dc_field(metadata=dc_config(field_name="filename")) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) @@ -2715,7 +2922,7 @@ class CallTranscription(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) url: str = dc_field(metadata=dc_config(field_name="url")) @@ -2729,14 +2936,16 @@ class CallTranscriptionFailedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) type: str = dc_field( - default="call.transcription_failed", metadata=dc_config(field_name="type") + default="call.transcription_failed", + metadata=dc_config(field_name="type"), ) error: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="error") + default=None, + metadata=dc_config(field_name="error"), ) @@ -2749,14 +2958,15 @@ class CallTranscriptionReadyEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) call_transcription: "CallTranscription" = dc_field( - metadata=dc_config(field_name="call_transcription") + metadata=dc_config(field_name="call_transcription"), ) type: str = dc_field( - default="call.transcription_ready", metadata=dc_config(field_name="type") + default="call.transcription_ready", + metadata=dc_config(field_name="type"), ) @@ -2769,11 +2979,12 @@ class CallTranscriptionStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) type: str = dc_field( - default="call.transcription_started", metadata=dc_config(field_name="type") + default="call.transcription_started", + metadata=dc_config(field_name="type"), ) @@ -2786,11 +2997,12 @@ class CallTranscriptionStoppedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) type: str = dc_field( - default="call.transcription_stopped", metadata=dc_config(field_name="type") + default="call.transcription_stopped", + metadata=dc_config(field_name="type"), ) @@ -2803,7 +3015,7 @@ class CallType(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) external_storage: str = dc_field(metadata=dc_config(field_name="ExternalStorage")) name: str = dc_field(metadata=dc_config(field_name="Name")) @@ -2814,13 +3026,15 @@ class CallType(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) notification_settings: "Optional[NotificationSettings]" = dc_field( - default=None, metadata=dc_config(field_name="NotificationSettings") + default=None, + metadata=dc_config(field_name="NotificationSettings"), ) settings: "Optional[CallSettings]" = dc_field( - default=None, metadata=dc_config(field_name="Settings") + default=None, + metadata=dc_config(field_name="Settings"), ) @@ -2832,7 +3046,7 @@ class CallTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) name: str = dc_field(metadata=dc_config(field_name="name")) updated_at: datetime = dc_field( @@ -2841,17 +3055,18 @@ class CallTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) notification_settings: "NotificationSettings" = dc_field( - metadata=dc_config(field_name="notification_settings") + metadata=dc_config(field_name="notification_settings"), ) settings: "CallSettingsResponse" = dc_field( - metadata=dc_config(field_name="settings") + metadata=dc_config(field_name="settings"), ) external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) @@ -2864,11 +3079,11 @@ class CallUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) capabilities_by_role: "Dict[str, List[str]]" = dc_field( - metadata=dc_config(field_name="capabilities_by_role") + metadata=dc_config(field_name="capabilities_by_role"), ) type: str = dc_field(default="call.updated", metadata=dc_config(field_name="type")) @@ -2882,23 +3097,27 @@ class CallUserFeedbackSubmittedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) rating: int = dc_field(metadata=dc_config(field_name="rating")) session_id: str = dc_field(metadata=dc_config(field_name="session_id")) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field( - default="call.user_feedback_submitted", metadata=dc_config(field_name="type") + default="call.user_feedback_submitted", + metadata=dc_config(field_name="type"), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) sdk: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="sdk")) sdk_version: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sdk_version") + default=None, + metadata=dc_config(field_name="sdk_version"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -2911,14 +3130,15 @@ class CallUserMutedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) from_user_id: str = dc_field(metadata=dc_config(field_name="from_user_id")) muted_user_ids: List[str] = dc_field( - metadata=dc_config(field_name="muted_user_ids") + metadata=dc_config(field_name="muted_user_ids"), ) type: str = dc_field( - default="call.user_muted", metadata=dc_config(field_name="type") + default="call.user_muted", + metadata=dc_config(field_name="type"), ) @@ -2930,7 +3150,7 @@ class CallsPerDayReport(DataClassJsonMixin): @dataclass class CallsPerDayReportResponse(DataClassJsonMixin): daily: "List[DailyAggregateCallsPerDayReportResponse]" = dc_field( - metadata=dc_config(field_name="daily") + metadata=dc_config(field_name="daily"), ) @@ -2941,7 +3161,8 @@ class CampaignChannelTemplate(DataClassJsonMixin): id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) members: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="members") + default=None, + metadata=dc_config(field_name="members"), ) @@ -2953,11 +3174,12 @@ class CampaignCompletedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="campaign.completed", metadata=dc_config(field_name="type") + default="campaign.completed", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -2969,7 +3191,8 @@ class CampaignCompletedEvent(DataClassJsonMixin): ), ) campaign: "Optional[CampaignResponse]" = dc_field( - default=None, metadata=dc_config(field_name="campaign") + default=None, + metadata=dc_config(field_name="campaign"), ) @@ -2978,7 +3201,7 @@ class CampaignMessageTemplate(DataClassJsonMixin): poll_id: str = dc_field(metadata=dc_config(field_name="poll_id")) text: str = dc_field(metadata=dc_config(field_name="text")) attachments: "List[Attachment]" = dc_field( - metadata=dc_config(field_name="attachments") + metadata=dc_config(field_name="attachments"), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) @@ -2992,7 +3215,7 @@ class CampaignResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) description: str = dc_field(metadata=dc_config(field_name="description")) id: str = dc_field(metadata=dc_config(field_name="id")) @@ -3009,7 +3232,7 @@ class CampaignResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) segment_ids: List[str] = dc_field(metadata=dc_config(field_name="segment_ids")) segments: "List[Segment]" = dc_field(metadata=dc_config(field_name="segments")) @@ -3035,13 +3258,16 @@ class CampaignResponse(DataClassJsonMixin): ), ) channel_template: "Optional[CampaignChannelTemplate]" = dc_field( - default=None, metadata=dc_config(field_name="channel_template") + default=None, + metadata=dc_config(field_name="channel_template"), ) message_template: "Optional[CampaignMessageTemplate]" = dc_field( - default=None, metadata=dc_config(field_name="message_template") + default=None, + metadata=dc_config(field_name="message_template"), ) sender: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="sender") + default=None, + metadata=dc_config(field_name="sender"), ) @@ -3053,11 +3279,12 @@ class CampaignStartedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="campaign.started", metadata=dc_config(field_name="type") + default="campaign.started", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -3069,7 +3296,8 @@ class CampaignStartedEvent(DataClassJsonMixin): ), ) campaign: "Optional[CampaignResponse]" = dc_field( - default=None, metadata=dc_config(field_name="campaign") + default=None, + metadata=dc_config(field_name="campaign"), ) @@ -3077,7 +3305,7 @@ class CampaignStartedEvent(DataClassJsonMixin): class CampaignStatsResponse(DataClassJsonMixin): progress: float = dc_field(metadata=dc_config(field_name="progress")) stats_channels_created: int = dc_field( - metadata=dc_config(field_name="stats_channels_created") + metadata=dc_config(field_name="stats_channels_created"), ) stats_completed_at: datetime = dc_field( metadata=dc_config( @@ -3085,10 +3313,10 @@ class CampaignStatsResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) stats_messages_sent: int = dc_field( - metadata=dc_config(field_name="stats_messages_sent") + metadata=dc_config(field_name="stats_messages_sent"), ) stats_started_at: datetime = dc_field( metadata=dc_config( @@ -3096,7 +3324,7 @@ class CampaignStatsResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) stats_users_read: int = dc_field(metadata=dc_config(field_name="stats_users_read")) stats_users_sent: int = dc_field(metadata=dc_config(field_name="stats_users_sent")) @@ -3105,20 +3333,23 @@ class CampaignStatsResponse(DataClassJsonMixin): @dataclass class CastPollVoteRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) vote: "Optional[VoteData]" = dc_field( - default=None, metadata=dc_config(field_name="vote") + default=None, + metadata=dc_config(field_name="vote"), ) @dataclass class Channel(DataClassJsonMixin): auto_translation_language: str = dc_field( - metadata=dc_config(field_name="auto_translation_language") + metadata=dc_config(field_name="auto_translation_language"), ) cid: str = dc_field(metadata=dc_config(field_name="cid")) created_at: datetime = dc_field( @@ -3127,7 +3358,7 @@ class Channel(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) disabled: bool = dc_field(metadata=dc_config(field_name="disabled")) frozen: bool = dc_field(metadata=dc_config(field_name="frozen")) @@ -3139,14 +3370,16 @@ class Channel(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) auto_translation_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="auto_translation_enabled") + default=None, + metadata=dc_config(field_name="auto_translation_enabled"), ) cooldown: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="cooldown") + default=None, + metadata=dc_config(field_name="cooldown"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -3158,7 +3391,8 @@ class Channel(DataClassJsonMixin): ), ) last_campaigns: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="last_campaigns") + default=None, + metadata=dc_config(field_name="last_campaigns"), ) last_message_at: Optional[datetime] = dc_field( default=None, @@ -3170,29 +3404,37 @@ class Channel(DataClassJsonMixin): ), ) member_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="member_count") + default=None, + metadata=dc_config(field_name="member_count"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) active_live_locations: "Optional[List[SharedLocation]]" = dc_field( - default=None, metadata=dc_config(field_name="active_live_locations") + default=None, + metadata=dc_config(field_name="active_live_locations"), ) invites: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="invites") + default=None, + metadata=dc_config(field_name="invites"), ) members: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="members") + default=None, + metadata=dc_config(field_name="members"), ) config: "Optional[ChannelConfig]" = dc_field( - default=None, metadata=dc_config(field_name="config") + default=None, + metadata=dc_config(field_name="config"), ) config_overrides: "Optional[ConfigOverrides]" = dc_field( - default=None, metadata=dc_config(field_name="config_overrides") + default=None, + metadata=dc_config(field_name="config_overrides"), ) created_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) truncated_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="truncated_by") + default=None, + metadata=dc_config(field_name="truncated_by"), ) @@ -3207,20 +3449,20 @@ class ChannelConfig(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom_events: bool = dc_field(metadata=dc_config(field_name="custom_events")) mark_messages_pending: bool = dc_field( - metadata=dc_config(field_name="mark_messages_pending") + metadata=dc_config(field_name="mark_messages_pending"), ) max_message_length: int = dc_field( - metadata=dc_config(field_name="max_message_length") + metadata=dc_config(field_name="max_message_length"), ) mutes: bool = dc_field(metadata=dc_config(field_name="mutes")) name: str = dc_field(metadata=dc_config(field_name="name")) polls: bool = dc_field(metadata=dc_config(field_name="polls")) push_notifications: bool = dc_field( - metadata=dc_config(field_name="push_notifications") + metadata=dc_config(field_name="push_notifications"), ) quotes: bool = dc_field(metadata=dc_config(field_name="quotes")) reactions: bool = dc_field(metadata=dc_config(field_name="reactions")) @@ -3230,7 +3472,7 @@ class ChannelConfig(DataClassJsonMixin): search: bool = dc_field(metadata=dc_config(field_name="search")) shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( - metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") + metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), ) typing_events: bool = dc_field(metadata=dc_config(field_name="typing_events")) updated_at: datetime = dc_field( @@ -3239,34 +3481,41 @@ class ChannelConfig(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) uploads: bool = dc_field(metadata=dc_config(field_name="uploads")) url_enrichment: bool = dc_field(metadata=dc_config(field_name="url_enrichment")) user_message_reminders: bool = dc_field( - metadata=dc_config(field_name="user_message_reminders") + metadata=dc_config(field_name="user_message_reminders"), ) commands: List[str] = dc_field(metadata=dc_config(field_name="commands")) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) partition_size: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_size") + default=None, + metadata=dc_config(field_name="partition_size"), ) partition_ttl: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_ttl") + default=None, + metadata=dc_config(field_name="partition_ttl"), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) blocklists: "Optional[List[BlockListOptions]]" = dc_field( - default=None, metadata=dc_config(field_name="blocklists") + default=None, + metadata=dc_config(field_name="blocklists"), ) automod_thresholds: "Optional[Thresholds]" = dc_field( - default=None, metadata=dc_config(field_name="automod_thresholds") + default=None, + metadata=dc_config(field_name="automod_thresholds"), ) @@ -3281,20 +3530,20 @@ class ChannelConfigWithInfo(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom_events: bool = dc_field(metadata=dc_config(field_name="custom_events")) mark_messages_pending: bool = dc_field( - metadata=dc_config(field_name="mark_messages_pending") + metadata=dc_config(field_name="mark_messages_pending"), ) max_message_length: int = dc_field( - metadata=dc_config(field_name="max_message_length") + metadata=dc_config(field_name="max_message_length"), ) mutes: bool = dc_field(metadata=dc_config(field_name="mutes")) name: str = dc_field(metadata=dc_config(field_name="name")) polls: bool = dc_field(metadata=dc_config(field_name="polls")) push_notifications: bool = dc_field( - metadata=dc_config(field_name="push_notifications") + metadata=dc_config(field_name="push_notifications"), ) quotes: bool = dc_field(metadata=dc_config(field_name="quotes")) reactions: bool = dc_field(metadata=dc_config(field_name="reactions")) @@ -3304,7 +3553,7 @@ class ChannelConfigWithInfo(DataClassJsonMixin): search: bool = dc_field(metadata=dc_config(field_name="search")) shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( - metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") + metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), ) typing_events: bool = dc_field(metadata=dc_config(field_name="typing_events")) updated_at: datetime = dc_field( @@ -3313,37 +3562,45 @@ class ChannelConfigWithInfo(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) uploads: bool = dc_field(metadata=dc_config(field_name="uploads")) url_enrichment: bool = dc_field(metadata=dc_config(field_name="url_enrichment")) user_message_reminders: bool = dc_field( - metadata=dc_config(field_name="user_message_reminders") + metadata=dc_config(field_name="user_message_reminders"), ) commands: "List[Command]" = dc_field(metadata=dc_config(field_name="commands")) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) partition_size: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_size") + default=None, + metadata=dc_config(field_name="partition_size"), ) partition_ttl: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="partition_ttl") + default=None, + metadata=dc_config(field_name="partition_ttl"), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) blocklists: "Optional[List[BlockListOptions]]" = dc_field( - default=None, metadata=dc_config(field_name="blocklists") + default=None, + metadata=dc_config(field_name="blocklists"), ) automod_thresholds: "Optional[Thresholds]" = dc_field( - default=None, metadata=dc_config(field_name="automod_thresholds") + default=None, + metadata=dc_config(field_name="automod_thresholds"), ) grants: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="grants") + default=None, + metadata=dc_config(field_name="grants"), ) @@ -3355,10 +3612,11 @@ class ChannelCreatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.created", metadata=dc_config(field_name="type") + default="channel.created", + metadata=dc_config(field_name="type"), ) @@ -3366,7 +3624,7 @@ class ChannelCreatedEvent(DataClassJsonMixin): class ChannelDeletedEvent(DataClassJsonMixin): channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") + metadata=dc_config(field_name="channel_member_count"), ) channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) cid: str = dc_field(metadata=dc_config(field_name="cid")) @@ -3376,14 +3634,16 @@ class ChannelDeletedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.deleted", metadata=dc_config(field_name="type") + default="channel.deleted", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) @@ -3423,35 +3683,43 @@ class ChannelFrozenEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.frozen", metadata=dc_config(field_name="type") + default="channel.frozen", + metadata=dc_config(field_name="type"), ) @dataclass class ChannelGetOrCreateRequest(DataClassJsonMixin): hide_for_creator: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hide_for_creator") + default=None, + metadata=dc_config(field_name="hide_for_creator"), ) state: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="state") + default=None, + metadata=dc_config(field_name="state"), ) thread_unread_counts: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="thread_unread_counts") + default=None, + metadata=dc_config(field_name="thread_unread_counts"), ) data: "Optional[ChannelInput]" = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) members: "Optional[PaginationParams]" = dc_field( - default=None, metadata=dc_config(field_name="members") + default=None, + metadata=dc_config(field_name="members"), ) messages: "Optional[MessagePaginationParams]" = dc_field( - default=None, metadata=dc_config(field_name="messages") + default=None, + metadata=dc_config(field_name="messages"), ) watchers: "Optional[PaginationParams]" = dc_field( - default=None, metadata=dc_config(field_name="watchers") + default=None, + metadata=dc_config(field_name="watchers"), ) @@ -3459,7 +3727,7 @@ class ChannelGetOrCreateRequest(DataClassJsonMixin): class ChannelHiddenEvent(DataClassJsonMixin): channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") + metadata=dc_config(field_name="channel_member_count"), ) channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) cid: str = dc_field(metadata=dc_config(field_name="cid")) @@ -3470,54 +3738,68 @@ class ChannelHiddenEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.hidden", metadata=dc_config(field_name="type") + default="channel.hidden", + metadata=dc_config(field_name="type"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class ChannelInput(DataClassJsonMixin): auto_translation_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="auto_translation_enabled") + default=None, + metadata=dc_config(field_name="auto_translation_enabled"), ) auto_translation_language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="auto_translation_language") + default=None, + metadata=dc_config(field_name="auto_translation_language"), ) created_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="created_by_id") + default=None, + metadata=dc_config(field_name="created_by_id"), ) disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="disabled") + default=None, + metadata=dc_config(field_name="disabled"), ) frozen: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="frozen") + default=None, + metadata=dc_config(field_name="frozen"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) truncated_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="truncated_by_id") + default=None, + metadata=dc_config(field_name="truncated_by_id"), ) invites: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="invites") + default=None, + metadata=dc_config(field_name="invites"), ) members: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="members") + default=None, + metadata=dc_config(field_name="members"), ) config_overrides: "Optional[ChannelConfig]" = dc_field( - default=None, metadata=dc_config(field_name="config_overrides") + default=None, + metadata=dc_config(field_name="config_overrides"), ) created_by: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -3531,10 +3813,10 @@ class ChannelMember(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) notifications_muted: bool = dc_field( - metadata=dc_config(field_name="notifications_muted") + metadata=dc_config(field_name="notifications_muted"), ) shadow_banned: bool = dc_field(metadata=dc_config(field_name="shadow_banned")) updated_at: datetime = dc_field( @@ -3543,7 +3825,7 @@ class ChannelMember(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) archived_at: Optional[datetime] = dc_field( @@ -3592,10 +3874,12 @@ class ChannelMember(DataClassJsonMixin): ), ) invited: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invited") + default=None, + metadata=dc_config(field_name="invited"), ) is_moderator: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_moderator") + default=None, + metadata=dc_config(field_name="is_moderator"), ) pinned_at: Optional[datetime] = dc_field( default=None, @@ -3608,13 +3892,16 @@ class ChannelMember(DataClassJsonMixin): ) role: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="role")) status: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="status") + default=None, + metadata=dc_config(field_name="status"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -3628,10 +3915,10 @@ class ChannelMemberResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) notifications_muted: bool = dc_field( - metadata=dc_config(field_name="notifications_muted") + metadata=dc_config(field_name="notifications_muted"), ) shadow_banned: bool = dc_field(metadata=dc_config(field_name="shadow_banned")) updated_at: datetime = dc_field( @@ -3640,7 +3927,7 @@ class ChannelMemberResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) archived_at: Optional[datetime] = dc_field( @@ -3689,10 +3976,12 @@ class ChannelMemberResponse(DataClassJsonMixin): ), ) invited: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invited") + default=None, + metadata=dc_config(field_name="invited"), ) is_moderator: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_moderator") + default=None, + metadata=dc_config(field_name="is_moderator"), ) pinned_at: Optional[datetime] = dc_field( default=None, @@ -3705,13 +3994,16 @@ class ChannelMemberResponse(DataClassJsonMixin): ) role: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="role")) status: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="status") + default=None, + metadata=dc_config(field_name="status"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -3719,7 +4011,8 @@ class ChannelMemberResponse(DataClassJsonMixin): class ChannelMessages(DataClassJsonMixin): messages: "List[Message]" = dc_field(metadata=dc_config(field_name="messages")) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) @@ -3731,7 +4024,7 @@ class ChannelMute(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) updated_at: datetime = dc_field( metadata=dc_config( @@ -3739,7 +4032,7 @@ class ChannelMute(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) expires: Optional[datetime] = dc_field( default=None, @@ -3751,10 +4044,12 @@ class ChannelMute(DataClassJsonMixin): ), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -3766,7 +4061,7 @@ class ChannelMutedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(default="channel.muted", metadata=dc_config(field_name="type")) @@ -3818,7 +4113,8 @@ class ChannelOwnCapability: @dataclass class ChannelPushPreferences(DataClassJsonMixin): chat_level: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="chat_level") + default=None, + metadata=dc_config(field_name="chat_level"), ) disabled_until: Optional[datetime] = dc_field( default=None, @@ -3840,7 +4136,7 @@ class ChannelResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) disabled: bool = dc_field(metadata=dc_config(field_name="disabled")) frozen: bool = dc_field(metadata=dc_config(field_name="frozen")) @@ -3852,20 +4148,24 @@ class ChannelResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) auto_translation_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="auto_translation_enabled") + default=None, + metadata=dc_config(field_name="auto_translation_enabled"), ) auto_translation_language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="auto_translation_language") + default=None, + metadata=dc_config(field_name="auto_translation_language"), ) blocked: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="blocked") + default=None, + metadata=dc_config(field_name="blocked"), ) cooldown: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="cooldown") + default=None, + metadata=dc_config(field_name="cooldown"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -3877,7 +4177,8 @@ class ChannelResponse(DataClassJsonMixin): ), ) hidden: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hidden") + default=None, + metadata=dc_config(field_name="hidden"), ) hide_messages_before: Optional[datetime] = dc_field( default=None, @@ -3898,7 +4199,8 @@ class ChannelResponse(DataClassJsonMixin): ), ) member_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="member_count") + default=None, + metadata=dc_config(field_name="member_count"), ) mute_expires_at: Optional[datetime] = dc_field( default=None, @@ -3910,7 +4212,8 @@ class ChannelResponse(DataClassJsonMixin): ), ) muted: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="muted") + default=None, + metadata=dc_config(field_name="muted"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) truncated_at: Optional[datetime] = dc_field( @@ -3923,19 +4226,24 @@ class ChannelResponse(DataClassJsonMixin): ), ) members: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="members") + default=None, + metadata=dc_config(field_name="members"), ) own_capabilities: "Optional[List[ChannelOwnCapability]]" = dc_field( - default=None, metadata=dc_config(field_name="own_capabilities") + default=None, + metadata=dc_config(field_name="own_capabilities"), ) config: "Optional[ChannelConfigWithInfo]" = dc_field( - default=None, metadata=dc_config(field_name="config") + default=None, + metadata=dc_config(field_name="config"), ) created_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) truncated_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="truncated_by") + default=None, + metadata=dc_config(field_name="truncated_by"), ) @@ -3944,16 +4252,17 @@ class ChannelStateResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) members: "List[ChannelMember]" = dc_field(metadata=dc_config(field_name="members")) messages: "List[MessageResponse]" = dc_field( - metadata=dc_config(field_name="messages") + metadata=dc_config(field_name="messages"), ) pinned_messages: "List[MessageResponse]" = dc_field( - metadata=dc_config(field_name="pinned_messages") + metadata=dc_config(field_name="pinned_messages"), ) threads: "List[ThreadStateResponse]" = dc_field( - metadata=dc_config(field_name="threads") + metadata=dc_config(field_name="threads"), ) hidden: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hidden") + default=None, + metadata=dc_config(field_name="hidden"), ) hide_messages_before: Optional[datetime] = dc_field( default=None, @@ -3965,31 +4274,40 @@ class ChannelStateResponse(DataClassJsonMixin): ), ) watcher_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="watcher_count") + default=None, + metadata=dc_config(field_name="watcher_count"), ) active_live_locations: "Optional[List[SharedLocationResponseData]]" = dc_field( - default=None, metadata=dc_config(field_name="active_live_locations") + default=None, + metadata=dc_config(field_name="active_live_locations"), ) pending_messages: "Optional[List[PendingMessageResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="pending_messages") + default=None, + metadata=dc_config(field_name="pending_messages"), ) read: "Optional[List[ReadStateResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="read") + default=None, + metadata=dc_config(field_name="read"), ) watchers: "Optional[List[UserResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="watchers") + default=None, + metadata=dc_config(field_name="watchers"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) draft: "Optional[DraftResponse]" = dc_field( - default=None, metadata=dc_config(field_name="draft") + default=None, + metadata=dc_config(field_name="draft"), ) membership: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="membership") + default=None, + metadata=dc_config(field_name="membership"), ) push_preferences: "Optional[ChannelPushPreferences]" = dc_field( - default=None, metadata=dc_config(field_name="push_preferences") + default=None, + metadata=dc_config(field_name="push_preferences"), ) @@ -3997,16 +4315,17 @@ class ChannelStateResponse(DataClassJsonMixin): class ChannelStateResponseFields(DataClassJsonMixin): members: "List[ChannelMember]" = dc_field(metadata=dc_config(field_name="members")) messages: "List[MessageResponse]" = dc_field( - metadata=dc_config(field_name="messages") + metadata=dc_config(field_name="messages"), ) pinned_messages: "List[MessageResponse]" = dc_field( - metadata=dc_config(field_name="pinned_messages") + metadata=dc_config(field_name="pinned_messages"), ) threads: "List[ThreadStateResponse]" = dc_field( - metadata=dc_config(field_name="threads") + metadata=dc_config(field_name="threads"), ) hidden: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hidden") + default=None, + metadata=dc_config(field_name="hidden"), ) hide_messages_before: Optional[datetime] = dc_field( default=None, @@ -4018,31 +4337,40 @@ class ChannelStateResponseFields(DataClassJsonMixin): ), ) watcher_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="watcher_count") + default=None, + metadata=dc_config(field_name="watcher_count"), ) active_live_locations: "Optional[List[SharedLocationResponseData]]" = dc_field( - default=None, metadata=dc_config(field_name="active_live_locations") + default=None, + metadata=dc_config(field_name="active_live_locations"), ) pending_messages: "Optional[List[PendingMessageResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="pending_messages") + default=None, + metadata=dc_config(field_name="pending_messages"), ) read: "Optional[List[ReadStateResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="read") + default=None, + metadata=dc_config(field_name="read"), ) watchers: "Optional[List[UserResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="watchers") + default=None, + metadata=dc_config(field_name="watchers"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) draft: "Optional[DraftResponse]" = dc_field( - default=None, metadata=dc_config(field_name="draft") + default=None, + metadata=dc_config(field_name="draft"), ) membership: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="membership") + default=None, + metadata=dc_config(field_name="membership"), ) push_preferences: "Optional[ChannelPushPreferences]" = dc_field( - default=None, metadata=dc_config(field_name="push_preferences") + default=None, + metadata=dc_config(field_name="push_preferences"), ) @@ -4050,7 +4378,7 @@ class ChannelStateResponseFields(DataClassJsonMixin): class ChannelTruncatedEvent(DataClassJsonMixin): channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") + metadata=dc_config(field_name="channel_member_count"), ) channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) cid: str = dc_field(metadata=dc_config(field_name="cid")) @@ -4060,13 +4388,15 @@ class ChannelTruncatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.truncated", metadata=dc_config(field_name="type") + default="channel.truncated", + metadata=dc_config(field_name="type"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) @@ -4081,20 +4411,20 @@ class ChannelTypeConfig(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom_events: bool = dc_field(metadata=dc_config(field_name="custom_events")) mark_messages_pending: bool = dc_field( - metadata=dc_config(field_name="mark_messages_pending") + metadata=dc_config(field_name="mark_messages_pending"), ) max_message_length: int = dc_field( - metadata=dc_config(field_name="max_message_length") + metadata=dc_config(field_name="max_message_length"), ) mutes: bool = dc_field(metadata=dc_config(field_name="mutes")) name: str = dc_field(metadata=dc_config(field_name="name")) polls: bool = dc_field(metadata=dc_config(field_name="polls")) push_notifications: bool = dc_field( - metadata=dc_config(field_name="push_notifications") + metadata=dc_config(field_name="push_notifications"), ) quotes: bool = dc_field(metadata=dc_config(field_name="quotes")) reactions: bool = dc_field(metadata=dc_config(field_name="reactions")) @@ -4104,7 +4434,7 @@ class ChannelTypeConfig(DataClassJsonMixin): search: bool = dc_field(metadata=dc_config(field_name="search")) shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( - metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") + metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), ) typing_events: bool = dc_field(metadata=dc_config(field_name="typing_events")) updated_at: datetime = dc_field( @@ -4113,38 +4443,45 @@ class ChannelTypeConfig(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) uploads: bool = dc_field(metadata=dc_config(field_name="uploads")) url_enrichment: bool = dc_field(metadata=dc_config(field_name="url_enrichment")) user_message_reminders: bool = dc_field( - metadata=dc_config(field_name="user_message_reminders") + metadata=dc_config(field_name="user_message_reminders"), ) commands: "List[Command]" = dc_field(metadata=dc_config(field_name="commands")) permissions: "List[PolicyRequest]" = dc_field( - metadata=dc_config(field_name="permissions") + metadata=dc_config(field_name="permissions"), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) partition_size: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_size") + default=None, + metadata=dc_config(field_name="partition_size"), ) partition_ttl: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="partition_ttl") + default=None, + metadata=dc_config(field_name="partition_ttl"), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) blocklists: "Optional[List[BlockListOptions]]" = dc_field( - default=None, metadata=dc_config(field_name="blocklists") + default=None, + metadata=dc_config(field_name="blocklists"), ) automod_thresholds: "Optional[Thresholds]" = dc_field( - default=None, metadata=dc_config(field_name="automod_thresholds") + default=None, + metadata=dc_config(field_name="automod_thresholds"), ) @@ -4159,10 +4496,11 @@ class ChannelUnFrozenEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.unfrozen", metadata=dc_config(field_name="type") + default="channel.unfrozen", + metadata=dc_config(field_name="type"), ) @@ -4174,10 +4512,11 @@ class ChannelUnmutedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.unmuted", metadata=dc_config(field_name="type") + default="channel.unmuted", + metadata=dc_config(field_name="type"), ) @@ -4185,7 +4524,7 @@ class ChannelUnmutedEvent(DataClassJsonMixin): class ChannelUpdatedEvent(DataClassJsonMixin): channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") + metadata=dc_config(field_name="channel_member_count"), ) channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) cid: str = dc_field(metadata=dc_config(field_name="cid")) @@ -4195,20 +4534,24 @@ class ChannelUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.updated", metadata=dc_config(field_name="type") + default="channel.updated", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -4223,20 +4566,23 @@ class ChannelVisibleEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="channel.visible", metadata=dc_config(field_name="type") + default="channel.visible", + metadata=dc_config(field_name="type"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class ChatActivityStatsResponse(DataClassJsonMixin): messages: "Optional[MessageStatsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="Messages") + default=None, + metadata=dc_config(field_name="Messages"), ) @@ -4249,34 +4595,44 @@ class CheckExternalStorageResponse(DataClassJsonMixin): @dataclass class CheckPushRequest(DataClassJsonMixin): apn_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_template") + default=None, + metadata=dc_config(field_name="apn_template"), ) event_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="event_type") + default=None, + metadata=dc_config(field_name="event_type"), ) firebase_data_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_data_template") + default=None, + metadata=dc_config(field_name="firebase_data_template"), ) firebase_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_template") + default=None, + metadata=dc_config(field_name="firebase_template"), ) message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="message_id") + default=None, + metadata=dc_config(field_name="message_id"), ) push_provider_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="push_provider_name") + default=None, + metadata=dc_config(field_name="push_provider_name"), ) push_provider_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="push_provider_type") + default=None, + metadata=dc_config(field_name="push_provider_type"), ) skip_devices: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_devices") + default=None, + metadata=dc_config(field_name="skip_devices"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -4284,25 +4640,32 @@ class CheckPushRequest(DataClassJsonMixin): class CheckPushResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) event_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="event_type") + default=None, + metadata=dc_config(field_name="event_type"), ) rendered_apn_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="rendered_apn_template") + default=None, + metadata=dc_config(field_name="rendered_apn_template"), ) rendered_firebase_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="rendered_firebase_template") + default=None, + metadata=dc_config(field_name="rendered_firebase_template"), ) skip_devices: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_devices") + default=None, + metadata=dc_config(field_name="skip_devices"), ) general_errors: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="general_errors") + default=None, + metadata=dc_config(field_name="general_errors"), ) device_errors: "Optional[Dict[str, DeviceErrorInfo]]" = dc_field( - default=None, metadata=dc_config(field_name="device_errors") + default=None, + metadata=dc_config(field_name="device_errors"), ) rendered_message: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="rendered_message") + default=None, + metadata=dc_config(field_name="rendered_message"), ) @@ -4310,27 +4673,33 @@ class CheckPushResponse(DataClassJsonMixin): class CheckRequest(DataClassJsonMixin): config_key: str = dc_field(metadata=dc_config(field_name="config_key")) entity_creator_id: str = dc_field( - metadata=dc_config(field_name="entity_creator_id") + metadata=dc_config(field_name="entity_creator_id"), ) entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) config_team: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="config_team") + default=None, + metadata=dc_config(field_name="config_team"), ) test_mode: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="test_mode") + default=None, + metadata=dc_config(field_name="test_mode"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") + default=None, + metadata=dc_config(field_name="moderation_payload"), ) options: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="options") + default=None, + metadata=dc_config(field_name="options"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -4338,27 +4707,32 @@ class CheckRequest(DataClassJsonMixin): class CheckResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) recommended_action: str = dc_field( - metadata=dc_config(field_name="recommended_action") + metadata=dc_config(field_name="recommended_action"), ) status: str = dc_field(metadata=dc_config(field_name="status")) task_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="task_id") + default=None, + metadata=dc_config(field_name="task_id"), ) item: "Optional[ReviewQueueItemResponse]" = dc_field( - default=None, metadata=dc_config(field_name="item") + default=None, + metadata=dc_config(field_name="item"), ) @dataclass class CheckSNSRequest(DataClassJsonMixin): sns_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_key") + default=None, + metadata=dc_config(field_name="sns_key"), ) sns_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_secret") + default=None, + metadata=dc_config(field_name="sns_secret"), ) sns_topic_arn: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_topic_arn") + default=None, + metadata=dc_config(field_name="sns_topic_arn"), ) @@ -4367,23 +4741,28 @@ class CheckSNSResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) status: str = dc_field(metadata=dc_config(field_name="status")) error: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="error") + default=None, + metadata=dc_config(field_name="error"), ) data: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) @dataclass class CheckSQSRequest(DataClassJsonMixin): sqs_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_key") + default=None, + metadata=dc_config(field_name="sqs_key"), ) sqs_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_secret") + default=None, + metadata=dc_config(field_name="sqs_secret"), ) sqs_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_url") + default=None, + metadata=dc_config(field_name="sqs_url"), ) @@ -4392,21 +4771,25 @@ class CheckSQSResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) status: str = dc_field(metadata=dc_config(field_name="status")) error: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="error") + default=None, + metadata=dc_config(field_name="error"), ) data: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) @dataclass class ClientOSDataResponse(DataClassJsonMixin): architecture: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="architecture") + default=None, + metadata=dc_config(field_name="architecture"), ) name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) version: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="version") + default=None, + metadata=dc_config(field_name="version"), ) @@ -4419,13 +4802,14 @@ class ClosedCaptionEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) closed_caption: "CallClosedCaption" = dc_field( - metadata=dc_config(field_name="closed_caption") + metadata=dc_config(field_name="closed_caption"), ) type: str = dc_field( - default="call.closed_caption", metadata=dc_config(field_name="type") + default="call.closed_caption", + metadata=dc_config(field_name="type"), ) @@ -4435,13 +4819,16 @@ class CollectUserFeedbackRequest(DataClassJsonMixin): sdk: str = dc_field(metadata=dc_config(field_name="sdk")) sdk_version: str = dc_field(metadata=dc_config(field_name="sdk_version")) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) user_session_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_session_id") + default=None, + metadata=dc_config(field_name="user_session_id"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -4484,7 +4871,8 @@ class CommitMessageRequest(DataClassJsonMixin): @dataclass class CompositeAppSettings(DataClassJsonMixin): json_encoded_settings: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="json_encoded_settings") + default=None, + metadata=dc_config(field_name="json_encoded_settings"), ) url: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="url")) @@ -4494,37 +4882,48 @@ class ConfigOverrides(DataClassJsonMixin): commands: List[str] = dc_field(metadata=dc_config(field_name="commands")) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) max_message_length: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_message_length") + default=None, + metadata=dc_config(field_name="max_message_length"), ) quotes: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="quotes") + default=None, + metadata=dc_config(field_name="quotes"), ) reactions: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="reactions") + default=None, + metadata=dc_config(field_name="reactions"), ) replies: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="replies") + default=None, + metadata=dc_config(field_name="replies"), ) shared_locations: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="shared_locations") + default=None, + metadata=dc_config(field_name="shared_locations"), ) typing_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="typing_events") + default=None, + metadata=dc_config(field_name="typing_events"), ) uploads: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="uploads") + default=None, + metadata=dc_config(field_name="uploads"), ) url_enrichment: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="url_enrichment") + default=None, + metadata=dc_config(field_name="url_enrichment"), ) user_message_reminders: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="user_message_reminders") + default=None, + metadata=dc_config(field_name="user_message_reminders"), ) @@ -4537,7 +4936,7 @@ class ConfigResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) key: str = dc_field(metadata=dc_config(field_name="key")) team: str = dc_field(metadata=dc_config(field_name="team")) @@ -4547,16 +4946,19 @@ class ConfigResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) ai_image_config: "Optional[AIImageConfig]" = dc_field( - default=None, metadata=dc_config(field_name="ai_image_config") + default=None, + metadata=dc_config(field_name="ai_image_config"), ) ai_text_config: "Optional[AITextConfig]" = dc_field( - default=None, metadata=dc_config(field_name="ai_text_config") + default=None, + metadata=dc_config(field_name="ai_text_config"), ) ai_video_config: "Optional[AIVideoConfig]" = dc_field( - default=None, metadata=dc_config(field_name="ai_video_config") + default=None, + metadata=dc_config(field_name="ai_video_config"), ) automod_platform_circumvention_config: "Optional[AutomodPlatformCircumventionConfig]" = dc_field( default=None, @@ -4569,16 +4971,20 @@ class ConfigResponse(DataClassJsonMixin): ) ) automod_toxicity_config: "Optional[AutomodToxicityConfig]" = dc_field( - default=None, metadata=dc_config(field_name="automod_toxicity_config") + default=None, + metadata=dc_config(field_name="automod_toxicity_config"), ) block_list_config: "Optional[BlockListConfig]" = dc_field( - default=None, metadata=dc_config(field_name="block_list_config") + default=None, + metadata=dc_config(field_name="block_list_config"), ) velocity_filter_config: "Optional[VelocityFilterConfig]" = dc_field( - default=None, metadata=dc_config(field_name="velocity_filter_config") + default=None, + metadata=dc_config(field_name="velocity_filter_config"), ) video_call_rule_config: "Optional[VideoCallRuleConfig]" = dc_field( - default=None, metadata=dc_config(field_name="video_call_rule_config") + default=None, + metadata=dc_config(field_name="video_call_rule_config"), ) @@ -4591,7 +4997,7 @@ class CountByMinuteResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) @@ -4607,7 +5013,8 @@ class CreateBlockListRequest(DataClassJsonMixin): class CreateBlockListResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) blocklist: "Optional[BlockListResponse]" = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) @@ -4615,16 +5022,20 @@ class CreateBlockListResponse(DataClassJsonMixin): class CreateCallTypeRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) grants: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="grants") + default=None, + metadata=dc_config(field_name="grants"), ) notification_settings: "Optional[NotificationSettings]" = dc_field( - default=None, metadata=dc_config(field_name="notification_settings") + default=None, + metadata=dc_config(field_name="notification_settings"), ) settings: "Optional[CallSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="settings") + default=None, + metadata=dc_config(field_name="settings"), ) @@ -4636,7 +5047,7 @@ class CreateCallTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) duration: str = dc_field(metadata=dc_config(field_name="duration")) name: str = dc_field(metadata=dc_config(field_name="name")) @@ -4646,17 +5057,18 @@ class CreateCallTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) notification_settings: "NotificationSettings" = dc_field( - metadata=dc_config(field_name="notification_settings") + metadata=dc_config(field_name="notification_settings"), ) settings: "CallSettingsResponse" = dc_field( - metadata=dc_config(field_name="settings") + metadata=dc_config(field_name="settings"), ) external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) @@ -4665,84 +5077,108 @@ class CreateChannelTypeRequest(DataClassJsonMixin): automod: str = dc_field(metadata=dc_config(field_name="automod")) automod_behavior: str = dc_field(metadata=dc_config(field_name="automod_behavior")) max_message_length: int = dc_field( - metadata=dc_config(field_name="max_message_length") + metadata=dc_config(field_name="max_message_length"), ) name: str = dc_field(metadata=dc_config(field_name="name")) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) connect_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="connect_events") + default=None, + metadata=dc_config(field_name="connect_events"), ) custom_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="custom_events") + default=None, + metadata=dc_config(field_name="custom_events"), ) mark_messages_pending: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mark_messages_pending") + default=None, + metadata=dc_config(field_name="mark_messages_pending"), ) message_retention: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="message_retention") + default=None, + metadata=dc_config(field_name="message_retention"), ) mutes: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mutes") + default=None, + metadata=dc_config(field_name="mutes"), ) partition_size: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_size") + default=None, + metadata=dc_config(field_name="partition_size"), ) partition_ttl: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="partition_ttl") + default=None, + metadata=dc_config(field_name="partition_ttl"), ) polls: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="polls") + default=None, + metadata=dc_config(field_name="polls"), ) push_notifications: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="push_notifications") + default=None, + metadata=dc_config(field_name="push_notifications"), ) reactions: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="reactions") + default=None, + metadata=dc_config(field_name="reactions"), ) read_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="read_events") + default=None, + metadata=dc_config(field_name="read_events"), ) replies: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="replies") + default=None, + metadata=dc_config(field_name="replies"), ) search: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="search") + default=None, + metadata=dc_config(field_name="search"), ) shared_locations: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="shared_locations") + default=None, + metadata=dc_config(field_name="shared_locations"), ) skip_last_msg_update_for_system_msgs: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), ) typing_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="typing_events") + default=None, + metadata=dc_config(field_name="typing_events"), ) uploads: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="uploads") + default=None, + metadata=dc_config(field_name="uploads"), ) url_enrichment: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="url_enrichment") + default=None, + metadata=dc_config(field_name="url_enrichment"), ) user_message_reminders: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="user_message_reminders") + default=None, + metadata=dc_config(field_name="user_message_reminders"), ) blocklists: "Optional[List[BlockListOptions]]" = dc_field( - default=None, metadata=dc_config(field_name="blocklists") + default=None, + metadata=dc_config(field_name="blocklists"), ) commands: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="commands") + default=None, + metadata=dc_config(field_name="commands"), ) permissions: "Optional[List[PolicyRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="permissions") + default=None, + metadata=dc_config(field_name="permissions"), ) grants: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="grants") + default=None, + metadata=dc_config(field_name="grants"), ) @@ -4757,21 +5193,21 @@ class CreateChannelTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom_events: bool = dc_field(metadata=dc_config(field_name="custom_events")) duration: str = dc_field(metadata=dc_config(field_name="duration")) mark_messages_pending: bool = dc_field( - metadata=dc_config(field_name="mark_messages_pending") + metadata=dc_config(field_name="mark_messages_pending"), ) max_message_length: int = dc_field( - metadata=dc_config(field_name="max_message_length") + metadata=dc_config(field_name="max_message_length"), ) mutes: bool = dc_field(metadata=dc_config(field_name="mutes")) name: str = dc_field(metadata=dc_config(field_name="name")) polls: bool = dc_field(metadata=dc_config(field_name="polls")) push_notifications: bool = dc_field( - metadata=dc_config(field_name="push_notifications") + metadata=dc_config(field_name="push_notifications"), ) quotes: bool = dc_field(metadata=dc_config(field_name="quotes")) reactions: bool = dc_field(metadata=dc_config(field_name="reactions")) @@ -4781,7 +5217,7 @@ class CreateChannelTypeResponse(DataClassJsonMixin): search: bool = dc_field(metadata=dc_config(field_name="search")) shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( - metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") + metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), ) typing_events: bool = dc_field(metadata=dc_config(field_name="typing_events")) updated_at: datetime = dc_field( @@ -4790,38 +5226,45 @@ class CreateChannelTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) uploads: bool = dc_field(metadata=dc_config(field_name="uploads")) url_enrichment: bool = dc_field(metadata=dc_config(field_name="url_enrichment")) user_message_reminders: bool = dc_field( - metadata=dc_config(field_name="user_message_reminders") + metadata=dc_config(field_name="user_message_reminders"), ) commands: List[str] = dc_field(metadata=dc_config(field_name="commands")) permissions: "List[PolicyRequest]" = dc_field( - metadata=dc_config(field_name="permissions") + metadata=dc_config(field_name="permissions"), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) partition_size: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_size") + default=None, + metadata=dc_config(field_name="partition_size"), ) partition_ttl: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="partition_ttl") + default=None, + metadata=dc_config(field_name="partition_ttl"), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) blocklists: "Optional[List[BlockListOptions]]" = dc_field( - default=None, metadata=dc_config(field_name="blocklists") + default=None, + metadata=dc_config(field_name="blocklists"), ) automod_thresholds: "Optional[Thresholds]" = dc_field( - default=None, metadata=dc_config(field_name="automod_thresholds") + default=None, + metadata=dc_config(field_name="automod_thresholds"), ) @@ -4837,7 +5280,8 @@ class CreateCommandRequest(DataClassJsonMixin): class CreateCommandResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) command: "Optional[Command]" = dc_field( - default=None, metadata=dc_config(field_name="command") + default=None, + metadata=dc_config(field_name="command"), ) @@ -4846,16 +5290,20 @@ class CreateDeviceRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) push_provider: str = dc_field(metadata=dc_config(field_name="push_provider")) push_provider_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="push_provider_name") + default=None, + metadata=dc_config(field_name="push_provider_name"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) voip_token: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="voip_token") + default=None, + metadata=dc_config(field_name="voip_token"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -4865,14 +5313,17 @@ class CreateExternalStorageRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) storage_type: str = dc_field(metadata=dc_config(field_name="storage_type")) gcs_credentials: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="gcs_credentials") + default=None, + metadata=dc_config(field_name="gcs_credentials"), ) path: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="path")) aws_s3: "Optional[S3Request]" = dc_field( - default=None, metadata=dc_config(field_name="aws_s3") + default=None, + metadata=dc_config(field_name="aws_s3"), ) azure_blob: "Optional[AzureRequest]" = dc_field( - default=None, metadata=dc_config(field_name="azure_blob") + default=None, + metadata=dc_config(field_name="azure_blob"), ) @@ -4903,14 +5354,16 @@ class CreateImportRequest(DataClassJsonMixin): class CreateImportResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) import_task: "Optional[ImportTask]" = dc_field( - default=None, metadata=dc_config(field_name="import_task") + default=None, + metadata=dc_config(field_name="import_task"), ) @dataclass class CreateImportURLRequest(DataClassJsonMixin): filename: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="filename") + default=None, + metadata=dc_config(field_name="filename"), ) @@ -4925,16 +5378,20 @@ class CreateImportURLResponse(DataClassJsonMixin): class CreatePollOptionRequest(DataClassJsonMixin): text: str = dc_field(metadata=dc_config(field_name="text")) position: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="position") + default=None, + metadata=dc_config(field_name="position"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="Custom") + default=None, + metadata=dc_config(field_name="Custom"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -4942,38 +5399,49 @@ class CreatePollOptionRequest(DataClassJsonMixin): class CreatePollRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) allow_answers: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="allow_answers") + default=None, + metadata=dc_config(field_name="allow_answers"), ) allow_user_suggested_options: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="allow_user_suggested_options") + default=None, + metadata=dc_config(field_name="allow_user_suggested_options"), ) description: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="description") + default=None, + metadata=dc_config(field_name="description"), ) enforce_unique_vote: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enforce_unique_vote") + default=None, + metadata=dc_config(field_name="enforce_unique_vote"), ) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) is_closed: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_closed") + default=None, + metadata=dc_config(field_name="is_closed"), ) max_votes_allowed: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_votes_allowed") + default=None, + metadata=dc_config(field_name="max_votes_allowed"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) voting_visibility: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="voting_visibility") + default=None, + metadata=dc_config(field_name="voting_visibility"), ) options: "Optional[List[PollOptionInput]]" = dc_field( - default=None, metadata=dc_config(field_name="options") + default=None, + metadata=dc_config(field_name="options"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="Custom") + default=None, + metadata=dc_config(field_name="Custom"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -4989,10 +5457,12 @@ class CreateReminderRequest(DataClassJsonMixin): ), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -5011,7 +5481,8 @@ class CreateRoleResponse(DataClassJsonMixin): class CustomActionRequest(DataClassJsonMixin): id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) options: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="options") + default=None, + metadata=dc_config(field_name="options"), ) @@ -5019,13 +5490,16 @@ class CustomActionRequest(DataClassJsonMixin): class CustomCheckFlag(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="labels") + default=None, + metadata=dc_config(field_name="labels"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -5035,16 +5509,20 @@ class CustomCheckRequest(DataClassJsonMixin): entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) flags: "List[CustomCheckFlag]" = dc_field(metadata=dc_config(field_name="flags")) entity_creator_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="entity_creator_id") + default=None, + metadata=dc_config(field_name="entity_creator_id"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") + default=None, + metadata=dc_config(field_name="moderation_payload"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -5054,7 +5532,8 @@ class CustomCheckResponse(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) status: str = dc_field(metadata=dc_config(field_name="status")) item: "Optional[ReviewQueueItemResponse]" = dc_field( - default=None, metadata=dc_config(field_name="item") + default=None, + metadata=dc_config(field_name="item"), ) @@ -5067,7 +5546,7 @@ class CustomVideoEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) @@ -5084,7 +5563,7 @@ class DailyAggregateCallDurationReportResponse(DataClassJsonMixin): class DailyAggregateCallParticipantCountReportResponse(DataClassJsonMixin): date: str = dc_field(metadata=dc_config(field_name="date")) report: "CallParticipantCountReport" = dc_field( - metadata=dc_config(field_name="report") + metadata=dc_config(field_name="report"), ) @@ -5120,10 +5599,12 @@ class Data(DataClassJsonMixin): @dataclass class DataDogInfo(DataClassJsonMixin): api_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="api_key") + default=None, + metadata=dc_config(field_name="api_key"), ) enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) site: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="site")) @@ -5131,10 +5612,12 @@ class DataDogInfo(DataClassJsonMixin): @dataclass class DeactivateUserRequest(DataClassJsonMixin): created_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="created_by_id") + default=None, + metadata=dc_config(field_name="created_by_id"), ) mark_messages_deleted: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mark_messages_deleted") + default=None, + metadata=dc_config(field_name="mark_messages_deleted"), ) @@ -5142,7 +5625,8 @@ class DeactivateUserRequest(DataClassJsonMixin): class DeactivateUserResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -5150,13 +5634,16 @@ class DeactivateUserResponse(DataClassJsonMixin): class DeactivateUsersRequest(DataClassJsonMixin): user_ids: List[str] = dc_field(metadata=dc_config(field_name="user_ids")) created_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="created_by_id") + default=None, + metadata=dc_config(field_name="created_by_id"), ) mark_channels_deleted: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mark_channels_deleted") + default=None, + metadata=dc_config(field_name="mark_channels_deleted"), ) mark_messages_deleted: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mark_messages_deleted") + default=None, + metadata=dc_config(field_name="mark_messages_deleted"), ) @@ -5169,7 +5656,8 @@ class DeactivateUsersResponse(DataClassJsonMixin): @dataclass class DeleteActivityRequest(DataClassJsonMixin): hard_delete: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hard_delete") + default=None, + metadata=dc_config(field_name="hard_delete"), ) @@ -5183,7 +5671,8 @@ class DeleteCallResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) task_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="task_id") + default=None, + metadata=dc_config(field_name="task_id"), ) @@ -5191,7 +5680,8 @@ class DeleteCallResponse(DataClassJsonMixin): class DeleteChannelResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) @@ -5199,7 +5689,8 @@ class DeleteChannelResponse(DataClassJsonMixin): class DeleteChannelsRequest(DataClassJsonMixin): cids: List[str] = dc_field(metadata=dc_config(field_name="cids")) hard_delete: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hard_delete") + default=None, + metadata=dc_config(field_name="hard_delete"), ) @@ -5207,10 +5698,12 @@ class DeleteChannelsRequest(DataClassJsonMixin): class DeleteChannelsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) task_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="task_id") + default=None, + metadata=dc_config(field_name="task_id"), ) result: "Optional[Dict[str, Optional[DeleteChannelsResultResponse]]]" = dc_field( - default=None, metadata=dc_config(field_name="result") + default=None, + metadata=dc_config(field_name="result"), ) @@ -5218,7 +5711,8 @@ class DeleteChannelsResponse(DataClassJsonMixin): class DeleteChannelsResultResponse(DataClassJsonMixin): status: str = dc_field(metadata=dc_config(field_name="status")) error: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="error") + default=None, + metadata=dc_config(field_name="error"), ) @@ -5236,7 +5730,8 @@ class DeleteExternalStorageResponse(DataClassJsonMixin): @dataclass class DeleteMessageRequest(DataClassJsonMixin): hard_delete: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hard_delete") + default=None, + metadata=dc_config(field_name="hard_delete"), ) @@ -5259,7 +5754,8 @@ class DeleteModerationTemplateResponse(DataClassJsonMixin): @dataclass class DeleteReactionRequest(DataClassJsonMixin): hard_delete: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hard_delete") + default=None, + metadata=dc_config(field_name="hard_delete"), ) @@ -5293,16 +5789,20 @@ class DeleteTranscriptionResponse(DataClassJsonMixin): @dataclass class DeleteUserRequest(DataClassJsonMixin): delete_conversation_channels: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="delete_conversation_channels") + default=None, + metadata=dc_config(field_name="delete_conversation_channels"), ) delete_feeds_content: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="delete_feeds_content") + default=None, + metadata=dc_config(field_name="delete_feeds_content"), ) hard_delete: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hard_delete") + default=None, + metadata=dc_config(field_name="hard_delete"), ) mark_messages_deleted: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mark_messages_deleted") + default=None, + metadata=dc_config(field_name="mark_messages_deleted"), ) @@ -5310,19 +5810,24 @@ class DeleteUserRequest(DataClassJsonMixin): class DeleteUsersRequest(DataClassJsonMixin): user_ids: List[str] = dc_field(metadata=dc_config(field_name="user_ids")) calls: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="calls") + default=None, + metadata=dc_config(field_name="calls"), ) conversations: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="conversations") + default=None, + metadata=dc_config(field_name="conversations"), ) messages: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="messages") + default=None, + metadata=dc_config(field_name="messages"), ) new_call_owner_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="new_call_owner_id") + default=None, + metadata=dc_config(field_name="new_call_owner_id"), ) new_channel_owner_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="new_channel_owner_id") + default=None, + metadata=dc_config(field_name="new_channel_owner_id"), ) user: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="user")) @@ -5341,19 +5846,22 @@ class Device(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) push_provider: str = dc_field(metadata=dc_config(field_name="push_provider")) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="disabled") + default=None, + metadata=dc_config(field_name="disabled"), ) disabled_reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="disabled_reason") + default=None, + metadata=dc_config(field_name="disabled_reason"), ) push_provider_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="push_provider_name") + default=None, + metadata=dc_config(field_name="push_provider_name"), ) voip: Optional[bool] = dc_field(default=None, metadata=dc_config(field_name="voip")) @@ -5362,7 +5870,8 @@ class Device(DataClassJsonMixin): class DeviceDataResponse(DataClassJsonMixin): name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) version: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="version") + default=None, + metadata=dc_config(field_name="version"), ) @@ -5381,19 +5890,22 @@ class DeviceResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) push_provider: str = dc_field(metadata=dc_config(field_name="push_provider")) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="disabled") + default=None, + metadata=dc_config(field_name="disabled"), ) disabled_reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="disabled_reason") + default=None, + metadata=dc_config(field_name="disabled_reason"), ) push_provider_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="push_provider_name") + default=None, + metadata=dc_config(field_name="push_provider_name"), ) voip: Optional[bool] = dc_field(default=None, metadata=dc_config(field_name="voip")) @@ -5406,26 +5918,33 @@ class DraftPayloadResponse(DataClassJsonMixin): html: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="html")) mml: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mml")) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) poll_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="poll_id") + default=None, + metadata=dc_config(field_name="poll_id"), ) quoted_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quoted_message_id") + default=None, + metadata=dc_config(field_name="quoted_message_id"), ) show_in_channel: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="show_in_channel") + default=None, + metadata=dc_config(field_name="show_in_channel"), ) silent: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="silent") + default=None, + metadata=dc_config(field_name="silent"), ) type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) attachments: "Optional[List[Attachment]]" = dc_field( - default=None, metadata=dc_config(field_name="attachments") + default=None, + metadata=dc_config(field_name="attachments"), ) mentioned_users: "Optional[List[UserResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="mentioned_users") + default=None, + metadata=dc_config(field_name="mentioned_users"), ) @@ -5438,20 +5957,24 @@ class DraftResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message: "DraftPayloadResponse" = dc_field(metadata=dc_config(field_name="message")) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) parent_message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="parent_message") + default=None, + metadata=dc_config(field_name="parent_message"), ) quoted_message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="quoted_message") + default=None, + metadata=dc_config(field_name="quoted_message"), ) @@ -5466,7 +5989,7 @@ class EdgeResponse(DataClassJsonMixin): longitude: float = dc_field(metadata=dc_config(field_name="longitude")) red: int = dc_field(metadata=dc_config(field_name="red")) subdivision_iso_code: str = dc_field( - metadata=dc_config(field_name="subdivision_iso_code") + metadata=dc_config(field_name="subdivision_iso_code"), ) yellow: int = dc_field(metadata=dc_config(field_name="yellow")) @@ -5486,13 +6009,15 @@ class EgressRTMPResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) stream_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="stream_key") + default=None, + metadata=dc_config(field_name="stream_key"), ) stream_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="stream_url") + default=None, + metadata=dc_config(field_name="stream_url"), ) @@ -5501,39 +6026,48 @@ class EgressResponse(DataClassJsonMixin): broadcasting: bool = dc_field(metadata=dc_config(field_name="broadcasting")) rtmps: "List[EgressRTMPResponse]" = dc_field(metadata=dc_config(field_name="rtmps")) frame_recording: "Optional[FrameRecordingResponse]" = dc_field( - default=None, metadata=dc_config(field_name="frame_recording") + default=None, + metadata=dc_config(field_name="frame_recording"), ) hls: "Optional[EgressHLSResponse]" = dc_field( - default=None, metadata=dc_config(field_name="hls") + default=None, + metadata=dc_config(field_name="hls"), ) @dataclass class EgressTaskConfig(DataClassJsonMixin): egress_user: "Optional[EgressUser]" = dc_field( - default=None, metadata=dc_config(field_name="egress_user") + default=None, + metadata=dc_config(field_name="egress_user"), ) frame_recording_egress_config: "Optional[FrameRecordingEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="frame_recording_egress_config") + default=None, + metadata=dc_config(field_name="frame_recording_egress_config"), ) hls_egress_config: "Optional[HLSEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="hls_egress_config") + default=None, + metadata=dc_config(field_name="hls_egress_config"), ) recording_egress_config: "Optional[RecordingEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="recording_egress_config") + default=None, + metadata=dc_config(field_name="recording_egress_config"), ) rtmp_egress_config: "Optional[RTMPEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="rtmp_egress_config") + default=None, + metadata=dc_config(field_name="rtmp_egress_config"), ) stt_egress_config: "Optional[STTEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="stt_egress_config") + default=None, + metadata=dc_config(field_name="stt_egress_config"), ) @dataclass class EgressUser(DataClassJsonMixin): token: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="token") + default=None, + metadata=dc_config(field_name="token"), ) @@ -5550,36 +6084,46 @@ class EndCallResponse(DataClassJsonMixin): @dataclass class EnrichedActivity(DataClassJsonMixin): foreign_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="foreign_id") + default=None, + metadata=dc_config(field_name="foreign_id"), ) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) score: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="score") + default=None, + metadata=dc_config(field_name="score"), ) verb: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="verb")) to: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="to") + default=None, + metadata=dc_config(field_name="to"), ) actor: "Optional[Data]" = dc_field( - default=None, metadata=dc_config(field_name="actor") + default=None, + metadata=dc_config(field_name="actor"), ) latest_reactions: "Optional[Dict[str, List[EnrichedReaction]]]" = dc_field( - default=None, metadata=dc_config(field_name="latest_reactions") + default=None, + metadata=dc_config(field_name="latest_reactions"), ) object: "Optional[Data]" = dc_field( - default=None, metadata=dc_config(field_name="object") + default=None, + metadata=dc_config(field_name="object"), ) origin: "Optional[Data]" = dc_field( - default=None, metadata=dc_config(field_name="origin") + default=None, + metadata=dc_config(field_name="origin"), ) own_reactions: "Optional[Dict[str, List[EnrichedReaction]]]" = dc_field( - default=None, metadata=dc_config(field_name="own_reactions") + default=None, + metadata=dc_config(field_name="own_reactions"), ) reaction_counts: "Optional[Dict[str, int]]" = dc_field( - default=None, metadata=dc_config(field_name="reaction_counts") + default=None, + metadata=dc_config(field_name="reaction_counts"), ) target: "Optional[Data]" = dc_field( - default=None, metadata=dc_config(field_name="target") + default=None, + metadata=dc_config(field_name="target"), ) @@ -5590,31 +6134,40 @@ class EnrichedReaction(DataClassJsonMixin): user_id: str = dc_field(metadata=dc_config(field_name="user_id")) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) parent: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent") + default=None, + metadata=dc_config(field_name="parent"), ) target_feeds: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="target_feeds") + default=None, + metadata=dc_config(field_name="target_feeds"), ) children_counts: "Optional[Dict[str, int]]" = dc_field( - default=None, metadata=dc_config(field_name="children_counts") + default=None, + metadata=dc_config(field_name="children_counts"), ) created_at: "Optional[Time]" = dc_field( - default=None, metadata=dc_config(field_name="created_at") + default=None, + metadata=dc_config(field_name="created_at"), ) data: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) latest_children: "Optional[Dict[str, List[EnrichedReaction]]]" = dc_field( - default=None, metadata=dc_config(field_name="latest_children") + default=None, + metadata=dc_config(field_name="latest_children"), ) own_children: "Optional[Dict[str, List[EnrichedReaction]]]" = dc_field( - default=None, metadata=dc_config(field_name="own_children") + default=None, + metadata=dc_config(field_name="own_children"), ) updated_at: "Optional[Time]" = dc_field( - default=None, metadata=dc_config(field_name="updated_at") + default=None, + metadata=dc_config(field_name="updated_at"), ) user: "Optional[Data]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -5623,7 +6176,7 @@ class EntityCreator(DataClassJsonMixin): ban_count: int = dc_field(metadata=dc_config(field_name="ban_count")) banned: bool = dc_field(metadata=dc_config(field_name="banned")) deleted_content_count: int = dc_field( - metadata=dc_config(field_name="deleted_content_count") + metadata=dc_config(field_name="deleted_content_count"), ) id: str = dc_field(metadata=dc_config(field_name="id")) online: bool = dc_field(metadata=dc_config(field_name="online")) @@ -5667,10 +6220,12 @@ class EntityCreator(DataClassJsonMixin): ), ) invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") + default=None, + metadata=dc_config(field_name="invisible"), ) language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + default=None, + metadata=dc_config(field_name="language"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -5709,10 +6264,12 @@ class EntityCreator(DataClassJsonMixin): ), ) teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") + default=None, + metadata=dc_config(field_name="teams"), ) privacy_settings: "Optional[PrivacySettings]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) @@ -5726,10 +6283,10 @@ class EntityCreatorResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) deleted_content_count: int = dc_field( - metadata=dc_config(field_name="deleted_content_count") + metadata=dc_config(field_name="deleted_content_count"), ) flagged_count: int = dc_field(metadata=dc_config(field_name="flagged_count")) id: str = dc_field(metadata=dc_config(field_name="id")) @@ -5744,10 +6301,10 @@ class EntityCreatorResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) blocked_user_ids: List[str] = dc_field( - metadata=dc_config(field_name="blocked_user_ids") + metadata=dc_config(field_name="blocked_user_ids"), ) teams: List[str] = dc_field(metadata=dc_config(field_name="teams")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) @@ -5779,7 +6336,8 @@ class EntityCreatorResponse(DataClassJsonMixin): ), ) image: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image") + default=None, + metadata=dc_config(field_name="image"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -5801,16 +6359,20 @@ class EntityCreatorResponse(DataClassJsonMixin): ), ) devices: "Optional[List[DeviceResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="devices") + default=None, + metadata=dc_config(field_name="devices"), ) privacy_settings: "Optional[PrivacySettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) push_notifications: "Optional[PushNotificationSettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="push_notifications") + default=None, + metadata=dc_config(field_name="push_notifications"), ) teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + default=None, + metadata=dc_config(field_name="teams_role"), ) @@ -5818,10 +6380,12 @@ class EntityCreatorResponse(DataClassJsonMixin): class ErrorResult(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) stacktrace: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="stacktrace") + default=None, + metadata=dc_config(field_name="stacktrace"), ) version: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="version") + default=None, + metadata=dc_config(field_name="version"), ) @@ -5837,50 +6401,65 @@ class EventHook(DataClassJsonMixin): ), ) enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) hook_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="hook_type") + default=None, + metadata=dc_config(field_name="hook_type"), ) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) sns_auth_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_auth_type") + default=None, + metadata=dc_config(field_name="sns_auth_type"), ) sns_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_key") + default=None, + metadata=dc_config(field_name="sns_key"), ) sns_region: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_region") + default=None, + metadata=dc_config(field_name="sns_region"), ) sns_role_arn: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_role_arn") + default=None, + metadata=dc_config(field_name="sns_role_arn"), ) sns_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_secret") + default=None, + metadata=dc_config(field_name="sns_secret"), ) sns_topic_arn: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_topic_arn") + default=None, + metadata=dc_config(field_name="sns_topic_arn"), ) sqs_auth_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_auth_type") + default=None, + metadata=dc_config(field_name="sqs_auth_type"), ) sqs_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_key") + default=None, + metadata=dc_config(field_name="sqs_key"), ) sqs_queue_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_queue_url") + default=None, + metadata=dc_config(field_name="sqs_queue_url"), ) sqs_region: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_region") + default=None, + metadata=dc_config(field_name="sqs_region"), ) sqs_role_arn: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_role_arn") + default=None, + metadata=dc_config(field_name="sqs_role_arn"), ) sqs_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_secret") + default=None, + metadata=dc_config(field_name="sqs_secret"), ) timeout_ms: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="timeout_ms") + default=None, + metadata=dc_config(field_name="timeout_ms"), ) updated_at: Optional[datetime] = dc_field( default=None, @@ -5892,13 +6471,16 @@ class EventHook(DataClassJsonMixin): ), ) webhook_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="webhook_url") + default=None, + metadata=dc_config(field_name="webhook_url"), ) event_types: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="event_types") + default=None, + metadata=dc_config(field_name="event_types"), ) callback: "Optional[AsyncModerationCallbackConfig]" = dc_field( - default=None, metadata=dc_config(field_name="callback") + default=None, + metadata=dc_config(field_name="callback"), ) @@ -5913,16 +6495,20 @@ class EventNotificationSettings(DataClassJsonMixin): class EventRequest(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -5935,22 +6521,27 @@ class EventResponse(DataClassJsonMixin): @dataclass class ExportChannelsRequest(DataClassJsonMixin): channels: "List[ChannelExport]" = dc_field( - metadata=dc_config(field_name="channels") + metadata=dc_config(field_name="channels"), ) clear_deleted_message_text: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="clear_deleted_message_text") + default=None, + metadata=dc_config(field_name="clear_deleted_message_text"), ) export_users: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="export_users") + default=None, + metadata=dc_config(field_name="export_users"), ) include_soft_deleted_channels: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="include_soft_deleted_channels") + default=None, + metadata=dc_config(field_name="include_soft_deleted_channels"), ) include_truncated_messages: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="include_truncated_messages") + default=None, + metadata=dc_config(field_name="include_truncated_messages"), ) version: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="version") + default=None, + metadata=dc_config(field_name="version"), ) @@ -5964,13 +6555,16 @@ class ExportChannelsResponse(DataClassJsonMixin): class ExportUserResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) messages: "Optional[List[MessageResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="messages") + default=None, + metadata=dc_config(field_name="messages"), ) reactions: "Optional[List[ReactionResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="reactions") + default=None, + metadata=dc_config(field_name="reactions"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -5988,41 +6582,53 @@ class ExportUsersResponse(DataClassJsonMixin): @dataclass class ExternalStorage(DataClassJsonMixin): abs_account_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="abs_account_name") + default=None, + metadata=dc_config(field_name="abs_account_name"), ) abs_client_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="abs_client_id") + default=None, + metadata=dc_config(field_name="abs_client_id"), ) abs_client_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="abs_client_secret") + default=None, + metadata=dc_config(field_name="abs_client_secret"), ) abs_tenant_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="abs_tenant_id") + default=None, + metadata=dc_config(field_name="abs_tenant_id"), ) bucket: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="bucket") + default=None, + metadata=dc_config(field_name="bucket"), ) gcs_credentials: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="gcs_credentials") + default=None, + metadata=dc_config(field_name="gcs_credentials"), ) path: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="path")) s3_api_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_api_key") + default=None, + metadata=dc_config(field_name="s3_api_key"), ) s3_custom_endpoint: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_custom_endpoint") + default=None, + metadata=dc_config(field_name="s3_custom_endpoint"), ) s3_region: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_region") + default=None, + metadata=dc_config(field_name="s3_region"), ) s3_secret_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_secret_key") + default=None, + metadata=dc_config(field_name="s3_secret_key"), ) storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="storage_name") + default=None, + metadata=dc_config(field_name="storage_name"), ) storage_type: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="storage_type") + default=None, + metadata=dc_config(field_name="storage_type"), ) @@ -6037,7 +6643,8 @@ class ExternalStorageResponse(DataClassJsonMixin): @dataclass class FCM(DataClassJsonMixin): data: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) @@ -6058,16 +6665,20 @@ class Field(DataClassJsonMixin): class FileUploadConfig(DataClassJsonMixin): size_limit: int = dc_field(metadata=dc_config(field_name="size_limit")) allowed_file_extensions: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_file_extensions") + default=None, + metadata=dc_config(field_name="allowed_file_extensions"), ) allowed_mime_types: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_mime_types") + default=None, + metadata=dc_config(field_name="allowed_mime_types"), ) blocked_file_extensions: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="blocked_file_extensions") + default=None, + metadata=dc_config(field_name="blocked_file_extensions"), ) blocked_mime_types: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="blocked_mime_types") + default=None, + metadata=dc_config(field_name="blocked_mime_types"), ) @@ -6075,7 +6686,8 @@ class FileUploadConfig(DataClassJsonMixin): class FileUploadRequest(DataClassJsonMixin): file: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="file")) user: "Optional[OnlyUserID]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -6084,29 +6696,36 @@ class FileUploadResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) file: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="file")) thumb_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thumb_url") + default=None, + metadata=dc_config(field_name="thumb_url"), ) @dataclass class FirebaseConfig(DataClassJsonMixin): apn_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_template") + default=None, + metadata=dc_config(field_name="apn_template"), ) credentials_json: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="credentials_json") + default=None, + metadata=dc_config(field_name="credentials_json"), ) data_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="data_template") + default=None, + metadata=dc_config(field_name="data_template"), ) disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="Disabled") + default=None, + metadata=dc_config(field_name="Disabled"), ) notification_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="notification_template") + default=None, + metadata=dc_config(field_name="notification_template"), ) server_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="server_key") + default=None, + metadata=dc_config(field_name="server_key"), ) @@ -6114,19 +6733,24 @@ class FirebaseConfig(DataClassJsonMixin): class FirebaseConfigFields(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) apn_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_template") + default=None, + metadata=dc_config(field_name="apn_template"), ) credentials_json: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="credentials_json") + default=None, + metadata=dc_config(field_name="credentials_json"), ) data_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="data_template") + default=None, + metadata=dc_config(field_name="data_template"), ) notification_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="notification_template") + default=None, + metadata=dc_config(field_name="notification_template"), ) server_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="server_key") + default=None, + metadata=dc_config(field_name="server_key"), ) @@ -6138,7 +6762,7 @@ class Flag(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) @@ -6148,41 +6772,51 @@ class Flag(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) result: "List[Dict[str, object]]" = dc_field( - metadata=dc_config(field_name="result") + metadata=dc_config(field_name="result"), ) entity_creator_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="entity_creator_id") + default=None, + metadata=dc_config(field_name="entity_creator_id"), ) is_streamed_content: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_streamed_content") + default=None, + metadata=dc_config(field_name="is_streamed_content"), ) moderation_payload_hash: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload_hash") + default=None, + metadata=dc_config(field_name="moderation_payload_hash"), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) review_queue_item_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item_id") + default=None, + metadata=dc_config(field_name="review_queue_item_id"), ) type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="labels") + default=None, + metadata=dc_config(field_name="labels"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") + default=None, + metadata=dc_config(field_name="moderation_payload"), ) review_queue_item: "Optional[ReviewQueueItem]" = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item") + default=None, + metadata=dc_config(field_name="review_queue_item"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -6191,7 +6825,8 @@ class FlagDetails(DataClassJsonMixin): original_text: str = dc_field(metadata=dc_config(field_name="original_text")) extra: Dict[str, object] = dc_field(metadata=dc_config(field_name="Extra")) automod: "Optional[AutomodDetails]" = dc_field( - default=None, metadata=dc_config(field_name="automod") + default=None, + metadata=dc_config(field_name="automod"), ) @@ -6203,7 +6838,7 @@ class FlagFeedback(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) labels: "List[Label]" = dc_field(metadata=dc_config(field_name="labels")) @@ -6212,16 +6847,20 @@ class FlagFeedback(DataClassJsonMixin): @dataclass class FlagMessageDetails(DataClassJsonMixin): pin_changed: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="pin_changed") + default=None, + metadata=dc_config(field_name="pin_changed"), ) should_enrich: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="should_enrich") + default=None, + metadata=dc_config(field_name="should_enrich"), ) skip_push: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_push") + default=None, + metadata=dc_config(field_name="skip_push"), ) updated_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="updated_by_id") + default=None, + metadata=dc_config(field_name="updated_by_id"), ) @@ -6230,22 +6869,28 @@ class FlagRequest(DataClassJsonMixin): entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) entity_creator_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="entity_creator_id") + default=None, + metadata=dc_config(field_name="entity_creator_id"), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") + default=None, + metadata=dc_config(field_name="moderation_payload"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -6263,7 +6908,7 @@ class FlagUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field(default="flag.updated", metadata=dc_config(field_name="type")) @@ -6277,40 +6922,48 @@ class FlagUpdatedEvent(DataClassJsonMixin): ), ) created_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="CreatedBy") + default=None, + metadata=dc_config(field_name="CreatedBy"), ) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="Message") + default=None, + metadata=dc_config(field_name="Message"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="User") + default=None, + metadata=dc_config(field_name="User"), ) @dataclass class FrameRecordSettings(DataClassJsonMixin): capture_interval_in_seconds: int = dc_field( - metadata=dc_config(field_name="capture_interval_in_seconds") + metadata=dc_config(field_name="capture_interval_in_seconds"), ) mode: str = dc_field(metadata=dc_config(field_name="mode")) quality: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) @dataclass class FrameRecordingEgressConfig(DataClassJsonMixin): capture_interval_in_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="capture_interval_in_seconds") + default=None, + metadata=dc_config(field_name="capture_interval_in_seconds"), ) storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="storage_name") + default=None, + metadata=dc_config(field_name="storage_name"), ) external_storage: "Optional[ExternalStorage]" = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) quality: "Optional[Quality]" = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) @@ -6322,22 +6975,24 @@ class FrameRecordingResponse(DataClassJsonMixin): @dataclass class FrameRecordingSettingsRequest(DataClassJsonMixin): capture_interval_in_seconds: int = dc_field( - metadata=dc_config(field_name="capture_interval_in_seconds") + metadata=dc_config(field_name="capture_interval_in_seconds"), ) mode: str = dc_field(metadata=dc_config(field_name="mode")) quality: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) @dataclass class FrameRecordingSettingsResponse(DataClassJsonMixin): capture_interval_in_seconds: int = dc_field( - metadata=dc_config(field_name="capture_interval_in_seconds") + metadata=dc_config(field_name="capture_interval_in_seconds"), ) mode: str = dc_field(metadata=dc_config(field_name="mode")) quality: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) @@ -6350,7 +7005,7 @@ class FullUserResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) invisible: bool = dc_field(metadata=dc_config(field_name="invisible")) @@ -6359,7 +7014,7 @@ class FullUserResponse(DataClassJsonMixin): role: str = dc_field(metadata=dc_config(field_name="role")) shadow_banned: bool = dc_field(metadata=dc_config(field_name="shadow_banned")) total_unread_count: int = dc_field( - metadata=dc_config(field_name="total_unread_count") + metadata=dc_config(field_name="total_unread_count"), ) unread_channels: int = dc_field(metadata=dc_config(field_name="unread_channels")) unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) @@ -6370,13 +7025,13 @@ class FullUserResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) blocked_user_ids: List[str] = dc_field( - metadata=dc_config(field_name="blocked_user_ids") + metadata=dc_config(field_name="blocked_user_ids"), ) channel_mutes: "List[ChannelMute]" = dc_field( - metadata=dc_config(field_name="channel_mutes") + metadata=dc_config(field_name="channel_mutes"), ) devices: "List[DeviceResponse]" = dc_field(metadata=dc_config(field_name="devices")) mutes: "List[UserMuteResponse]" = dc_field(metadata=dc_config(field_name="mutes")) @@ -6410,7 +7065,8 @@ class FullUserResponse(DataClassJsonMixin): ), ) image: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image") + default=None, + metadata=dc_config(field_name="image"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -6432,13 +7088,16 @@ class FullUserResponse(DataClassJsonMixin): ), ) latest_hidden_channels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="latest_hidden_channels") + default=None, + metadata=dc_config(field_name="latest_hidden_channels"), ) privacy_settings: "Optional[PrivacySettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + default=None, + metadata=dc_config(field_name="teams_role"), ) @@ -6446,11 +7105,13 @@ class FullUserResponse(DataClassJsonMixin): class GeofenceResponse(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) description: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="description") + default=None, + metadata=dc_config(field_name="description"), ) type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) country_codes: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="country_codes") + default=None, + metadata=dc_config(field_name="country_codes"), ) @@ -6462,7 +7123,8 @@ class GeofenceSettings(DataClassJsonMixin): @dataclass class GeofenceSettingsRequest(DataClassJsonMixin): names: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="names") + default=None, + metadata=dc_config(field_name="names"), ) @@ -6481,7 +7143,8 @@ class GetApplicationResponse(DataClassJsonMixin): class GetBlockListResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) blocklist: "Optional[BlockListResponse]" = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) @@ -6489,7 +7152,7 @@ class GetBlockListResponse(DataClassJsonMixin): class GetBlockedUsersResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) blocks: "List[BlockedUserResponse]" = dc_field( - metadata=dc_config(field_name="blocks") + metadata=dc_config(field_name="blocks"), ) @@ -6499,10 +7162,12 @@ class GetCallReportResponse(DataClassJsonMixin): session_id: str = dc_field(metadata=dc_config(field_name="session_id")) report: "ReportResponse" = dc_field(metadata=dc_config(field_name="report")) video_reactions: "Optional[List[VideoReactionsResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="video_reactions") + default=None, + metadata=dc_config(field_name="video_reactions"), ) chat_activity: "Optional[ChatActivityStatsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="chat_activity") + default=None, + metadata=dc_config(field_name="chat_activity"), ) @@ -6511,7 +7176,7 @@ class GetCallResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) own_capabilities: "List[OwnCapability]" = dc_field( - metadata=dc_config(field_name="own_capabilities") + metadata=dc_config(field_name="own_capabilities"), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) @@ -6524,7 +7189,7 @@ class GetCallTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) duration: str = dc_field(metadata=dc_config(field_name="duration")) name: str = dc_field(metadata=dc_config(field_name="name")) @@ -6534,17 +7199,18 @@ class GetCallTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) notification_settings: "NotificationSettings" = dc_field( - metadata=dc_config(field_name="notification_settings") + metadata=dc_config(field_name="notification_settings"), ) settings: "CallSettingsResponse" = dc_field( - metadata=dc_config(field_name="settings") + metadata=dc_config(field_name="settings"), ) external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) @@ -6552,10 +7218,12 @@ class GetCallTypeResponse(DataClassJsonMixin): class GetCampaignResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) campaign: "Optional[CampaignResponse]" = dc_field( - default=None, metadata=dc_config(field_name="campaign") + default=None, + metadata=dc_config(field_name="campaign"), ) users: "Optional[PagerResponse]" = dc_field( - default=None, metadata=dc_config(field_name="users") + default=None, + metadata=dc_config(field_name="users"), ) @@ -6570,21 +7238,21 @@ class GetChannelTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom_events: bool = dc_field(metadata=dc_config(field_name="custom_events")) duration: str = dc_field(metadata=dc_config(field_name="duration")) mark_messages_pending: bool = dc_field( - metadata=dc_config(field_name="mark_messages_pending") + metadata=dc_config(field_name="mark_messages_pending"), ) max_message_length: int = dc_field( - metadata=dc_config(field_name="max_message_length") + metadata=dc_config(field_name="max_message_length"), ) mutes: bool = dc_field(metadata=dc_config(field_name="mutes")) name: str = dc_field(metadata=dc_config(field_name="name")) polls: bool = dc_field(metadata=dc_config(field_name="polls")) push_notifications: bool = dc_field( - metadata=dc_config(field_name="push_notifications") + metadata=dc_config(field_name="push_notifications"), ) quotes: bool = dc_field(metadata=dc_config(field_name="quotes")) reactions: bool = dc_field(metadata=dc_config(field_name="reactions")) @@ -6594,7 +7262,7 @@ class GetChannelTypeResponse(DataClassJsonMixin): search: bool = dc_field(metadata=dc_config(field_name="search")) shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( - metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") + metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), ) typing_events: bool = dc_field(metadata=dc_config(field_name="typing_events")) updated_at: datetime = dc_field( @@ -6603,38 +7271,45 @@ class GetChannelTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) uploads: bool = dc_field(metadata=dc_config(field_name="uploads")) url_enrichment: bool = dc_field(metadata=dc_config(field_name="url_enrichment")) user_message_reminders: bool = dc_field( - metadata=dc_config(field_name="user_message_reminders") + metadata=dc_config(field_name="user_message_reminders"), ) commands: "List[Command]" = dc_field(metadata=dc_config(field_name="commands")) permissions: "List[PolicyRequest]" = dc_field( - metadata=dc_config(field_name="permissions") + metadata=dc_config(field_name="permissions"), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) partition_size: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_size") + default=None, + metadata=dc_config(field_name="partition_size"), ) partition_ttl: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="partition_ttl") + default=None, + metadata=dc_config(field_name="partition_ttl"), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) blocklists: "Optional[List[BlockListOptions]]" = dc_field( - default=None, metadata=dc_config(field_name="blocklists") + default=None, + metadata=dc_config(field_name="blocklists"), ) automod_thresholds: "Optional[Thresholds]" = dc_field( - default=None, metadata=dc_config(field_name="automod_thresholds") + default=None, + metadata=dc_config(field_name="automod_thresholds"), ) @@ -6669,7 +7344,8 @@ class GetCommandResponse(DataClassJsonMixin): class GetConfigResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) config: "Optional[ConfigResponse]" = dc_field( - default=None, metadata=dc_config(field_name="config") + default=None, + metadata=dc_config(field_name="config"), ) @@ -6695,7 +7371,8 @@ class GetEdgesResponse(DataClassJsonMixin): class GetImportResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) import_task: "Optional[ImportTask]" = dc_field( - default=None, metadata=dc_config(field_name="import_task") + default=None, + metadata=dc_config(field_name="import_task"), ) @@ -6703,7 +7380,7 @@ class GetImportResponse(DataClassJsonMixin): class GetManyMessagesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) messages: "List[MessageResponse]" = dc_field( - metadata=dc_config(field_name="messages") + metadata=dc_config(field_name="messages"), ) @@ -6711,10 +7388,11 @@ class GetManyMessagesResponse(DataClassJsonMixin): class GetMessageResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) message: "MessageWithChannelResponse" = dc_field( - metadata=dc_config(field_name="message") + metadata=dc_config(field_name="message"), ) pending_message_metadata: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="pending_message_metadata") + default=None, + metadata=dc_config(field_name="pending_message_metadata"), ) @@ -6723,80 +7401,103 @@ class GetOGResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) asset_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="asset_url") + default=None, + metadata=dc_config(field_name="asset_url"), ) author_icon: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="author_icon") + default=None, + metadata=dc_config(field_name="author_icon"), ) author_link: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="author_link") + default=None, + metadata=dc_config(field_name="author_link"), ) author_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="author_name") + default=None, + metadata=dc_config(field_name="author_name"), ) color: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="color") + default=None, + metadata=dc_config(field_name="color"), ) fallback: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="fallback") + default=None, + metadata=dc_config(field_name="fallback"), ) footer: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="footer") + default=None, + metadata=dc_config(field_name="footer"), ) footer_icon: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="footer_icon") + default=None, + metadata=dc_config(field_name="footer_icon"), ) image_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image_url") + default=None, + metadata=dc_config(field_name="image_url"), ) og_scrape_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="og_scrape_url") + default=None, + metadata=dc_config(field_name="og_scrape_url"), ) original_height: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="original_height") + default=None, + metadata=dc_config(field_name="original_height"), ) original_width: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="original_width") + default=None, + metadata=dc_config(field_name="original_width"), ) pretext: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="pretext") + default=None, + metadata=dc_config(field_name="pretext"), ) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) thumb_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thumb_url") + default=None, + metadata=dc_config(field_name="thumb_url"), ) title: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="title") + default=None, + metadata=dc_config(field_name="title"), ) title_link: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="title_link") + default=None, + metadata=dc_config(field_name="title_link"), ) type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) actions: "Optional[List[Action]]" = dc_field( - default=None, metadata=dc_config(field_name="actions") + default=None, + metadata=dc_config(field_name="actions"), ) fields: "Optional[List[Field]]" = dc_field( - default=None, metadata=dc_config(field_name="fields") + default=None, + metadata=dc_config(field_name="fields"), ) giphy: "Optional[Images]" = dc_field( - default=None, metadata=dc_config(field_name="giphy") + default=None, + metadata=dc_config(field_name="giphy"), ) @dataclass class GetOrCreateCallRequest(DataClassJsonMixin): members_limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="members_limit") + default=None, + metadata=dc_config(field_name="members_limit"), ) notify: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="notify") + default=None, + metadata=dc_config(field_name="notify"), ) ring: Optional[bool] = dc_field(default=None, metadata=dc_config(field_name="ring")) video: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="video") + default=None, + metadata=dc_config(field_name="video"), ) data: "Optional[CallRequest]" = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) @@ -6806,7 +7507,7 @@ class GetOrCreateCallResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) own_capabilities: "List[OwnCapability]" = dc_field( - metadata=dc_config(field_name="own_capabilities") + metadata=dc_config(field_name="own_capabilities"), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) @@ -6815,7 +7516,7 @@ class GetOrCreateCallResponse(DataClassJsonMixin): class GetPushTemplatesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) templates: "List[PushTemplate]" = dc_field( - metadata=dc_config(field_name="templates") + metadata=dc_config(field_name="templates"), ) @@ -6823,16 +7524,20 @@ class GetPushTemplatesResponse(DataClassJsonMixin): class GetRateLimitsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) android: "Optional[Dict[str, LimitInfo]]" = dc_field( - default=None, metadata=dc_config(field_name="android") + default=None, + metadata=dc_config(field_name="android"), ) ios: "Optional[Dict[str, LimitInfo]]" = dc_field( - default=None, metadata=dc_config(field_name="ios") + default=None, + metadata=dc_config(field_name="ios"), ) server_side: "Optional[Dict[str, LimitInfo]]" = dc_field( - default=None, metadata=dc_config(field_name="server_side") + default=None, + metadata=dc_config(field_name="server_side"), ) web: "Optional[Dict[str, LimitInfo]]" = dc_field( - default=None, metadata=dc_config(field_name="web") + default=None, + metadata=dc_config(field_name="web"), ) @@ -6846,7 +7551,7 @@ class GetReactionsResponse(DataClassJsonMixin): class GetRepliesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) messages: "List[MessageResponse]" = dc_field( - metadata=dc_config(field_name="messages") + metadata=dc_config(field_name="messages"), ) @@ -6854,7 +7559,8 @@ class GetRepliesResponse(DataClassJsonMixin): class GetReviewQueueItemResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) item: "Optional[ReviewQueueItemResponse]" = dc_field( - default=None, metadata=dc_config(field_name="item") + default=None, + metadata=dc_config(field_name="item"), ) @@ -6862,7 +7568,8 @@ class GetReviewQueueItemResponse(DataClassJsonMixin): class GetSegmentResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) segment: "Optional[SegmentResponse]" = dc_field( - default=None, metadata=dc_config(field_name="segment") + default=None, + metadata=dc_config(field_name="segment"), ) @@ -6874,7 +7581,7 @@ class GetTaskResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) duration: str = dc_field(metadata=dc_config(field_name="duration")) status: str = dc_field(metadata=dc_config(field_name="status")) @@ -6885,13 +7592,15 @@ class GetTaskResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) error: "Optional[ErrorResult]" = dc_field( - default=None, metadata=dc_config(field_name="error") + default=None, + metadata=dc_config(field_name="error"), ) result: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="result") + default=None, + metadata=dc_config(field_name="result"), ) @@ -6904,22 +7613,28 @@ class GetThreadResponse(DataClassJsonMixin): @dataclass class GoLiveRequest(DataClassJsonMixin): recording_storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="recording_storage_name") + default=None, + metadata=dc_config(field_name="recording_storage_name"), ) start_closed_caption: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="start_closed_caption") + default=None, + metadata=dc_config(field_name="start_closed_caption"), ) start_hls: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="start_hls") + default=None, + metadata=dc_config(field_name="start_hls"), ) start_recording: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="start_recording") + default=None, + metadata=dc_config(field_name="start_recording"), ) start_transcription: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="start_transcription") + default=None, + metadata=dc_config(field_name="start_transcription"), ) transcription_storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="transcription_storage_name") + default=None, + metadata=dc_config(field_name="transcription_storage_name"), ) @@ -6932,7 +7647,8 @@ class GoLiveResponse(DataClassJsonMixin): @dataclass class GoogleVisionConfig(DataClassJsonMixin): enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) @@ -6945,16 +7661,20 @@ class GroupedStatsResponse(DataClassJsonMixin): @dataclass class HLSEgressConfig(DataClassJsonMixin): playlist_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="playlist_url") + default=None, + metadata=dc_config(field_name="playlist_url"), ) start_unix_nano: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="start_unix_nano") + default=None, + metadata=dc_config(field_name="start_unix_nano"), ) qualities: "Optional[List[Quality]]" = dc_field( - default=None, metadata=dc_config(field_name="qualities") + default=None, + metadata=dc_config(field_name="qualities"), ) composite_app_settings: "Optional[CompositeAppSettings]" = dc_field( - default=None, metadata=dc_config(field_name="composite_app_settings") + default=None, + metadata=dc_config(field_name="composite_app_settings"), ) @@ -6963,26 +7683,30 @@ class HLSSettings(DataClassJsonMixin): auto_on: bool = dc_field(metadata=dc_config(field_name="auto_on")) enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) quality_tracks: List[str] = dc_field( - metadata=dc_config(field_name="quality_tracks") + metadata=dc_config(field_name="quality_tracks"), ) layout: "Optional[LayoutSettings]" = dc_field( - default=None, metadata=dc_config(field_name="layout") + default=None, + metadata=dc_config(field_name="layout"), ) @dataclass class HLSSettingsRequest(DataClassJsonMixin): quality_tracks: List[str] = dc_field( - metadata=dc_config(field_name="quality_tracks") + metadata=dc_config(field_name="quality_tracks"), ) auto_on: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="auto_on") + default=None, + metadata=dc_config(field_name="auto_on"), ) enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) layout: "Optional[LayoutSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="layout") + default=None, + metadata=dc_config(field_name="layout"), ) @@ -6991,7 +7715,7 @@ class HLSSettingsResponse(DataClassJsonMixin): auto_on: bool = dc_field(metadata=dc_config(field_name="auto_on")) enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) quality_tracks: List[str] = dc_field( - metadata=dc_config(field_name="quality_tracks") + metadata=dc_config(field_name="quality_tracks"), ) layout: "LayoutSettingsResponse" = dc_field(metadata=dc_config(field_name="layout")) @@ -7000,20 +7724,23 @@ class HLSSettingsResponse(DataClassJsonMixin): class HarmConfig(DataClassJsonMixin): severity: int = dc_field(metadata=dc_config(field_name="severity")) action_sequences: "List[ActionSequence]" = dc_field( - metadata=dc_config(field_name="action_sequences") + metadata=dc_config(field_name="action_sequences"), ) @dataclass class HideChannelRequest(DataClassJsonMixin): clear_history: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="clear_history") + default=None, + metadata=dc_config(field_name="clear_history"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7025,11 +7752,13 @@ class HideChannelResponse(DataClassJsonMixin): @dataclass class HuaweiConfig(DataClassJsonMixin): disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="Disabled") + default=None, + metadata=dc_config(field_name="Disabled"), ) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="secret") + default=None, + metadata=dc_config(field_name="secret"), ) @@ -7038,7 +7767,8 @@ class HuaweiConfigFields(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="secret") + default=None, + metadata=dc_config(field_name="secret"), ) @@ -7055,13 +7785,16 @@ class ImageData(DataClassJsonMixin): class ImageSize(DataClassJsonMixin): crop: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="crop")) height: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="height") + default=None, + metadata=dc_config(field_name="height"), ) resize: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="resize") + default=None, + metadata=dc_config(field_name="resize"), ) width: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="width") + default=None, + metadata=dc_config(field_name="width"), ) @@ -7069,10 +7802,12 @@ class ImageSize(DataClassJsonMixin): class ImageUploadRequest(DataClassJsonMixin): file: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="file")) upload_sizes: "Optional[List[ImageSize]]" = dc_field( - default=None, metadata=dc_config(field_name="upload_sizes") + default=None, + metadata=dc_config(field_name="upload_sizes"), ) user: "Optional[OnlyUserID]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7081,10 +7816,12 @@ class ImageUploadResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) file: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="file")) thumb_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thumb_url") + default=None, + metadata=dc_config(field_name="thumb_url"), ) upload_sizes: "Optional[List[ImageSize]]" = dc_field( - default=None, metadata=dc_config(field_name="upload_sizes") + default=None, + metadata=dc_config(field_name="upload_sizes"), ) @@ -7092,17 +7829,17 @@ class ImageUploadResponse(DataClassJsonMixin): class Images(DataClassJsonMixin): fixed_height: "ImageData" = dc_field(metadata=dc_config(field_name="fixed_height")) fixed_height_downsampled: "ImageData" = dc_field( - metadata=dc_config(field_name="fixed_height_downsampled") + metadata=dc_config(field_name="fixed_height_downsampled"), ) fixed_height_still: "ImageData" = dc_field( - metadata=dc_config(field_name="fixed_height_still") + metadata=dc_config(field_name="fixed_height_still"), ) fixed_width: "ImageData" = dc_field(metadata=dc_config(field_name="fixed_width")) fixed_width_downsampled: "ImageData" = dc_field( - metadata=dc_config(field_name="fixed_width_downsampled") + metadata=dc_config(field_name="fixed_width_downsampled"), ) fixed_width_still: "ImageData" = dc_field( - metadata=dc_config(field_name="fixed_width_still") + metadata=dc_config(field_name="fixed_width_still"), ) original: "ImageData" = dc_field(metadata=dc_config(field_name="original")) @@ -7115,7 +7852,7 @@ class ImportTask(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) mode: str = dc_field(metadata=dc_config(field_name="mode")) @@ -7127,10 +7864,10 @@ class ImportTask(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) history: "List[ImportTaskHistory]" = dc_field( - metadata=dc_config(field_name="history") + metadata=dc_config(field_name="history"), ) size: Optional[int] = dc_field(default=None, metadata=dc_config(field_name="size")) @@ -7143,7 +7880,7 @@ class ImportTaskHistory(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) next_state: str = dc_field(metadata=dc_config(field_name="next_state")) prev_state: str = dc_field(metadata=dc_config(field_name="prev_state")) @@ -7153,20 +7890,24 @@ class ImportTaskHistory(DataClassJsonMixin): class Label(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) harm_labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="harm_labels") + default=None, + metadata=dc_config(field_name="harm_labels"), ) phrase_list_ids: "Optional[List[int]]" = dc_field( - default=None, metadata=dc_config(field_name="phrase_list_ids") + default=None, + metadata=dc_config(field_name="phrase_list_ids"), ) @dataclass class LabelThresholds(DataClassJsonMixin): block: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="block") + default=None, + metadata=dc_config(field_name="block"), ) flag: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="flag") + default=None, + metadata=dc_config(field_name="flag"), ) @@ -7176,10 +7917,12 @@ class LayoutSettings(DataClassJsonMixin): external_css_url: str = dc_field(metadata=dc_config(field_name="external_css_url")) name: str = dc_field(metadata=dc_config(field_name="name")) detect_orientation: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="detect_orientation") + default=None, + metadata=dc_config(field_name="detect_orientation"), ) options: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="options") + default=None, + metadata=dc_config(field_name="options"), ) @@ -7187,16 +7930,20 @@ class LayoutSettings(DataClassJsonMixin): class LayoutSettingsRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) detect_orientation: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="detect_orientation") + default=None, + metadata=dc_config(field_name="detect_orientation"), ) external_app_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_app_url") + default=None, + metadata=dc_config(field_name="external_app_url"), ) external_css_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_css_url") + default=None, + metadata=dc_config(field_name="external_css_url"), ) options: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="options") + default=None, + metadata=dc_config(field_name="options"), ) @@ -7206,10 +7953,12 @@ class LayoutSettingsResponse(DataClassJsonMixin): external_css_url: str = dc_field(metadata=dc_config(field_name="external_css_url")) name: str = dc_field(metadata=dc_config(field_name="name")) detect_orientation: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="detect_orientation") + default=None, + metadata=dc_config(field_name="detect_orientation"), ) options: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="options") + default=None, + metadata=dc_config(field_name="options"), ) @@ -7223,48 +7972,58 @@ class LimitInfo(DataClassJsonMixin): @dataclass class LimitsSettings(DataClassJsonMixin): max_participants_exclude_roles: List[str] = dc_field( - metadata=dc_config(field_name="max_participants_exclude_roles") + metadata=dc_config(field_name="max_participants_exclude_roles"), ) max_duration_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_duration_seconds") + default=None, + metadata=dc_config(field_name="max_duration_seconds"), ) max_participants: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_participants") + default=None, + metadata=dc_config(field_name="max_participants"), ) max_participants_exclude_owner: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="max_participants_exclude_owner") + default=None, + metadata=dc_config(field_name="max_participants_exclude_owner"), ) @dataclass class LimitsSettingsRequest(DataClassJsonMixin): max_duration_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_duration_seconds") + default=None, + metadata=dc_config(field_name="max_duration_seconds"), ) max_participants: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_participants") + default=None, + metadata=dc_config(field_name="max_participants"), ) max_participants_exclude_owner: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="max_participants_exclude_owner") + default=None, + metadata=dc_config(field_name="max_participants_exclude_owner"), ) max_participants_exclude_roles: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="max_participants_exclude_roles") + default=None, + metadata=dc_config(field_name="max_participants_exclude_roles"), ) @dataclass class LimitsSettingsResponse(DataClassJsonMixin): max_participants_exclude_roles: List[str] = dc_field( - metadata=dc_config(field_name="max_participants_exclude_roles") + metadata=dc_config(field_name="max_participants_exclude_roles"), ) max_duration_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_duration_seconds") + default=None, + metadata=dc_config(field_name="max_duration_seconds"), ) max_participants: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_participants") + default=None, + metadata=dc_config(field_name="max_participants"), ) max_participants_exclude_owner: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="max_participants_exclude_owner") + default=None, + metadata=dc_config(field_name="max_participants_exclude_owner"), ) @@ -7272,7 +8031,7 @@ class LimitsSettingsResponse(DataClassJsonMixin): class ListBlockListResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) blocklists: "List[BlockListResponse]" = dc_field( - metadata=dc_config(field_name="blocklists") + metadata=dc_config(field_name="blocklists"), ) @@ -7280,7 +8039,7 @@ class ListBlockListResponse(DataClassJsonMixin): class ListCallTypeResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) call_types: "Dict[str, CallTypeResponse]" = dc_field( - metadata=dc_config(field_name="call_types") + metadata=dc_config(field_name="call_types"), ) @@ -7288,7 +8047,7 @@ class ListCallTypeResponse(DataClassJsonMixin): class ListChannelTypesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) channel_types: "Dict[str, Optional[ChannelTypeConfig]]" = dc_field( - metadata=dc_config(field_name="channel_types") + metadata=dc_config(field_name="channel_types"), ) @@ -7308,7 +8067,7 @@ class ListDevicesResponse(DataClassJsonMixin): class ListExternalStorageResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) external_storages: "Dict[str, ExternalStorageResponse]" = dc_field( - metadata=dc_config(field_name="external_storages") + metadata=dc_config(field_name="external_storages"), ) @@ -7316,7 +8075,7 @@ class ListExternalStorageResponse(DataClassJsonMixin): class ListImportsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) import_tasks: "List[ImportTask]" = dc_field( - metadata=dc_config(field_name="import_tasks") + metadata=dc_config(field_name="import_tasks"), ) @@ -7324,7 +8083,7 @@ class ListImportsResponse(DataClassJsonMixin): class ListPermissionsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) permissions: "List[Permission]" = dc_field( - metadata=dc_config(field_name="permissions") + metadata=dc_config(field_name="permissions"), ) @@ -7332,7 +8091,7 @@ class ListPermissionsResponse(DataClassJsonMixin): class ListPushProvidersResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) push_providers: "List[PushProviderResponse]" = dc_field( - metadata=dc_config(field_name="push_providers") + metadata=dc_config(field_name="push_providers"), ) @@ -7340,7 +8099,7 @@ class ListPushProvidersResponse(DataClassJsonMixin): class ListRecordingsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) recordings: "List[CallRecording]" = dc_field( - metadata=dc_config(field_name="recordings") + metadata=dc_config(field_name="recordings"), ) @@ -7354,36 +8113,43 @@ class ListRolesResponse(DataClassJsonMixin): class ListTranscriptionsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) transcriptions: "List[CallTranscription]" = dc_field( - metadata=dc_config(field_name="transcriptions") + metadata=dc_config(field_name="transcriptions"), ) @dataclass class MarkChannelsReadRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) read_by_channel: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="read_by_channel") + default=None, + metadata=dc_config(field_name="read_by_channel"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class MarkReadRequest(DataClassJsonMixin): message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="message_id") + default=None, + metadata=dc_config(field_name="message_id"), ) thread_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thread_id") + default=None, + metadata=dc_config(field_name="thread_id"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7391,14 +8157,16 @@ class MarkReadRequest(DataClassJsonMixin): class MarkReadResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) event: "Optional[MessageReadEvent]" = dc_field( - default=None, metadata=dc_config(field_name="event") + default=None, + metadata=dc_config(field_name="event"), ) @dataclass class MarkReviewedRequest(DataClassJsonMixin): content_to_mark_as_reviewed_limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="content_to_mark_as_reviewed_limit") + default=None, + metadata=dc_config(field_name="content_to_mark_as_reviewed_limit"), ) disable_marking_content_as_reviewed: Optional[bool] = dc_field( default=None, @@ -7409,16 +8177,20 @@ class MarkReviewedRequest(DataClassJsonMixin): @dataclass class MarkUnreadRequest(DataClassJsonMixin): message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="message_id") + default=None, + metadata=dc_config(field_name="message_id"), ) thread_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thread_id") + default=None, + metadata=dc_config(field_name="thread_id"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7433,15 +8205,17 @@ class MemberAddedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(default="member.added", metadata=dc_config(field_name="type")) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) member: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="member") + default=None, + metadata=dc_config(field_name="member"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7461,16 +8235,19 @@ class MemberRemovedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="member.removed", metadata=dc_config(field_name="type") + default="member.removed", + metadata=dc_config(field_name="type"), ) member: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="member") + default=None, + metadata=dc_config(field_name="member"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7479,7 +8256,8 @@ class MemberRequest(DataClassJsonMixin): user_id: str = dc_field(metadata=dc_config(field_name="user_id")) role: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="role")) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -7491,7 +8269,7 @@ class MemberResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) updated_at: datetime = dc_field( metadata=dc_config( @@ -7499,7 +8277,7 @@ class MemberResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) @@ -7527,17 +8305,20 @@ class MemberUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="member.updated", metadata=dc_config(field_name="type") + default="member.updated", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) member: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="member") + default=None, + metadata=dc_config(field_name="member"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7556,10 +8337,10 @@ class Message(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) deleted_reply_count: int = dc_field( - metadata=dc_config(field_name="deleted_reply_count") + metadata=dc_config(field_name="deleted_reply_count"), ) html: str = dc_field(metadata=dc_config(field_name="html")) id: str = dc_field(metadata=dc_config(field_name="id")) @@ -7575,38 +8356,40 @@ class Message(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) attachments: "List[Attachment]" = dc_field( - metadata=dc_config(field_name="attachments") + metadata=dc_config(field_name="attachments"), ) latest_reactions: "List[Reaction]" = dc_field( - metadata=dc_config(field_name="latest_reactions") + metadata=dc_config(field_name="latest_reactions"), ) mentioned_users: "List[User]" = dc_field( - metadata=dc_config(field_name="mentioned_users") + metadata=dc_config(field_name="mentioned_users"), ) own_reactions: "List[Reaction]" = dc_field( - metadata=dc_config(field_name="own_reactions") + metadata=dc_config(field_name="own_reactions"), ) restricted_visibility: List[str] = dc_field( - metadata=dc_config(field_name="restricted_visibility") + metadata=dc_config(field_name="restricted_visibility"), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) reaction_counts: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_counts") + metadata=dc_config(field_name="reaction_counts"), ) reaction_groups: "Dict[str, Optional[ReactionGroupResponse]]" = dc_field( - metadata=dc_config(field_name="reaction_groups") + metadata=dc_config(field_name="reaction_groups"), ) reaction_scores: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_scores") + metadata=dc_config(field_name="reaction_scores"), ) before_message_send_failed: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="before_message_send_failed") + default=None, + metadata=dc_config(field_name="before_message_send_failed"), ) command: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="command") + default=None, + metadata=dc_config(field_name="command"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -7628,7 +8411,8 @@ class Message(DataClassJsonMixin): ) mml: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mml")) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) pin_expires: Optional[datetime] = dc_field( default=None, @@ -7649,43 +8433,56 @@ class Message(DataClassJsonMixin): ), ) poll_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="poll_id") + default=None, + metadata=dc_config(field_name="poll_id"), ) quoted_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quoted_message_id") + default=None, + metadata=dc_config(field_name="quoted_message_id"), ) show_in_channel: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="show_in_channel") + default=None, + metadata=dc_config(field_name="show_in_channel"), ) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) i18n: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="i18n") + default=None, + metadata=dc_config(field_name="i18n"), ) image_labels: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="image_labels") + default=None, + metadata=dc_config(field_name="image_labels"), ) moderation: "Optional[ModerationV2Response]" = dc_field( - default=None, metadata=dc_config(field_name="moderation") + default=None, + metadata=dc_config(field_name="moderation"), ) pinned_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="pinned_by") + default=None, + metadata=dc_config(field_name="pinned_by"), ) poll: "Optional[Poll]" = dc_field( - default=None, metadata=dc_config(field_name="poll") + default=None, + metadata=dc_config(field_name="poll"), ) quoted_message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="quoted_message") + default=None, + metadata=dc_config(field_name="quoted_message"), ) reminder: "Optional[MessageReminder]" = dc_field( - default=None, metadata=dc_config(field_name="reminder") + default=None, + metadata=dc_config(field_name="reminder"), ) shared_location: "Optional[SharedLocation]" = dc_field( - default=None, metadata=dc_config(field_name="shared_location") + default=None, + metadata=dc_config(field_name="shared_location"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7693,10 +8490,12 @@ class Message(DataClassJsonMixin): class MessageActionRequest(DataClassJsonMixin): form_data: "Dict[str, str]" = dc_field(metadata=dc_config(field_name="form_data")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7706,12 +8505,12 @@ class MessageChangeSet(DataClassJsonMixin): custom: bool = dc_field(metadata=dc_config(field_name="custom")) html: bool = dc_field(metadata=dc_config(field_name="html")) mentioned_user_ids: bool = dc_field( - metadata=dc_config(field_name="mentioned_user_ids") + metadata=dc_config(field_name="mentioned_user_ids"), ) mml: bool = dc_field(metadata=dc_config(field_name="mml")) pin: bool = dc_field(metadata=dc_config(field_name="pin")) quoted_message_id: bool = dc_field( - metadata=dc_config(field_name="quoted_message_id") + metadata=dc_config(field_name="quoted_message_id"), ) silent: bool = dc_field(metadata=dc_config(field_name="silent")) text: bool = dc_field(metadata=dc_config(field_name="text")) @@ -7728,21 +8527,25 @@ class MessageDeletedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) hard_delete: bool = dc_field(metadata=dc_config(field_name="hard_delete")) type: str = dc_field( - default="message.deleted", metadata=dc_config(field_name="type") + default="message.deleted", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7754,10 +8557,10 @@ class MessageFlagResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_automod: bool = dc_field( - metadata=dc_config(field_name="created_by_automod") + metadata=dc_config(field_name="created_by_automod"), ) updated_at: datetime = dc_field( metadata=dc_config( @@ -7765,7 +8568,7 @@ class MessageFlagResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) approved_at: Optional[datetime] = dc_field( default=None, @@ -7777,7 +8580,8 @@ class MessageFlagResponse(DataClassJsonMixin): ), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) rejected_at: Optional[datetime] = dc_field( default=None, @@ -7798,25 +8602,32 @@ class MessageFlagResponse(DataClassJsonMixin): ), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) details: "Optional[FlagDetails]" = dc_field( - default=None, metadata=dc_config(field_name="details") + default=None, + metadata=dc_config(field_name="details"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) moderation_feedback: "Optional[FlagFeedback]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_feedback") + default=None, + metadata=dc_config(field_name="moderation_feedback"), ) moderation_result: "Optional[MessageModerationResult]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_result") + default=None, + metadata=dc_config(field_name="moderation_result"), ) reviewed_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="reviewed_by") + default=None, + metadata=dc_config(field_name="reviewed_by"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7829,22 +8640,27 @@ class MessageFlaggedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="message.flagged", metadata=dc_config(field_name="type") + default="message.flagged", + metadata=dc_config(field_name="type"), ) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) flag: "Optional[Flag]" = dc_field( - default=None, metadata=dc_config(field_name="flag") + default=None, + metadata=dc_config(field_name="flag"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7858,14 +8674,14 @@ class MessageHistoryEntryResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message_updated_by_id: str = dc_field( - metadata=dc_config(field_name="message_updated_by_id") + metadata=dc_config(field_name="message_updated_by_id"), ) text: str = dc_field(metadata=dc_config(field_name="text")) attachments: "List[Attachment]" = dc_field( - metadata=dc_config(field_name="attachments") + metadata=dc_config(field_name="attachments"), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="Custom")) @@ -7879,7 +8695,7 @@ class MessageModerationResult(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) updated_at: datetime = dc_field( @@ -7888,24 +8704,29 @@ class MessageModerationResult(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_bad_karma: bool = dc_field(metadata=dc_config(field_name="user_bad_karma")) user_karma: float = dc_field(metadata=dc_config(field_name="user_karma")) blocked_word: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocked_word") + default=None, + metadata=dc_config(field_name="blocked_word"), ) blocklist_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_name") + default=None, + metadata=dc_config(field_name="blocklist_name"), ) moderated_by: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="moderated_by") + default=None, + metadata=dc_config(field_name="moderated_by"), ) ai_moderation_response: "Optional[ModerationResponse]" = dc_field( - default=None, metadata=dc_config(field_name="ai_moderation_response") + default=None, + metadata=dc_config(field_name="ai_moderation_response"), ) moderation_thresholds: "Optional[Thresholds]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_thresholds") + default=None, + metadata=dc_config(field_name="moderation_thresholds"), ) @@ -7920,28 +8741,33 @@ class MessageNewEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) watcher_count: int = dc_field(metadata=dc_config(field_name="watcher_count")) type: str = dc_field( - default="notification.thread_message_new", metadata=dc_config(field_name="type") + default="notification.thread_message_new", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class MessageOptions(DataClassJsonMixin): include_thread_participants: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="include_thread_participants") + default=None, + metadata=dc_config(field_name="include_thread_participants"), ) @@ -7961,7 +8787,7 @@ class MessageReadEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(default="message.read", metadata=dc_config(field_name="type")) channel_last_message_at: Optional[datetime] = dc_field( @@ -7974,14 +8800,17 @@ class MessageReadEvent(DataClassJsonMixin): ), ) last_read_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="last_read_message_id") + default=None, + metadata=dc_config(field_name="last_read_message_id"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread: "Optional[ThreadResponse]" = dc_field( - default=None, metadata=dc_config(field_name="thread") + default=None, + metadata=dc_config(field_name="thread"), ) user: "Optional[UserResponseCommonFields]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -7994,7 +8823,7 @@ class MessageReminder(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) task_id: str = dc_field(metadata=dc_config(field_name="task_id")) @@ -8004,7 +8833,7 @@ class MessageReminder(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) remind_at: Optional[datetime] = dc_field( @@ -8017,13 +8846,16 @@ class MessageReminder(DataClassJsonMixin): ), ) channel: "Optional[Channel]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8033,7 +8865,8 @@ class MessageRequest(DataClassJsonMixin): id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) mml: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mml")) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) pin_expires: Optional[datetime] = dc_field( default=None, @@ -8045,7 +8878,8 @@ class MessageRequest(DataClassJsonMixin): ), ) pinned: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="pinned") + default=None, + metadata=dc_config(field_name="pinned"), ) pinned_at: Optional[datetime] = dc_field( default=None, @@ -8057,39 +8891,50 @@ class MessageRequest(DataClassJsonMixin): ), ) poll_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="poll_id") + default=None, + metadata=dc_config(field_name="poll_id"), ) quoted_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quoted_message_id") + default=None, + metadata=dc_config(field_name="quoted_message_id"), ) show_in_channel: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="show_in_channel") + default=None, + metadata=dc_config(field_name="show_in_channel"), ) silent: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="silent") + default=None, + metadata=dc_config(field_name="silent"), ) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) attachments: "Optional[List[Attachment]]" = dc_field( - default=None, metadata=dc_config(field_name="attachments") + default=None, + metadata=dc_config(field_name="attachments"), ) mentioned_users: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="mentioned_users") + default=None, + metadata=dc_config(field_name="mentioned_users"), ) restricted_visibility: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="restricted_visibility") + default=None, + metadata=dc_config(field_name="restricted_visibility"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) shared_location: "Optional[SharedLocation]" = dc_field( - default=None, metadata=dc_config(field_name="shared_location") + default=None, + metadata=dc_config(field_name="shared_location"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8102,10 +8947,10 @@ class MessageResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) deleted_reply_count: int = dc_field( - metadata=dc_config(field_name="deleted_reply_count") + metadata=dc_config(field_name="deleted_reply_count"), ) html: str = dc_field(metadata=dc_config(field_name="html")) id: str = dc_field(metadata=dc_config(field_name="id")) @@ -8121,33 +8966,34 @@ class MessageResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) attachments: "List[Attachment]" = dc_field( - metadata=dc_config(field_name="attachments") + metadata=dc_config(field_name="attachments"), ) latest_reactions: "List[ReactionResponse]" = dc_field( - metadata=dc_config(field_name="latest_reactions") + metadata=dc_config(field_name="latest_reactions"), ) mentioned_users: "List[UserResponse]" = dc_field( - metadata=dc_config(field_name="mentioned_users") + metadata=dc_config(field_name="mentioned_users"), ) own_reactions: "List[ReactionResponse]" = dc_field( - metadata=dc_config(field_name="own_reactions") + metadata=dc_config(field_name="own_reactions"), ) restricted_visibility: List[str] = dc_field( - metadata=dc_config(field_name="restricted_visibility") + metadata=dc_config(field_name="restricted_visibility"), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) reaction_counts: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_counts") + metadata=dc_config(field_name="reaction_counts"), ) reaction_scores: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_scores") + metadata=dc_config(field_name="reaction_scores"), ) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) command: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="command") + default=None, + metadata=dc_config(field_name="command"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -8169,7 +9015,8 @@ class MessageResponse(DataClassJsonMixin): ) mml: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mml")) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) pin_expires: Optional[datetime] = dc_field( default=None, @@ -8190,53 +9037,68 @@ class MessageResponse(DataClassJsonMixin): ), ) poll_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="poll_id") + default=None, + metadata=dc_config(field_name="poll_id"), ) quoted_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quoted_message_id") + default=None, + metadata=dc_config(field_name="quoted_message_id"), ) show_in_channel: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="show_in_channel") + default=None, + metadata=dc_config(field_name="show_in_channel"), ) thread_participants: "Optional[List[UserResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) draft: "Optional[DraftResponse]" = dc_field( - default=None, metadata=dc_config(field_name="draft") + default=None, + metadata=dc_config(field_name="draft"), ) i18n: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="i18n") + default=None, + metadata=dc_config(field_name="i18n"), ) image_labels: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="image_labels") + default=None, + metadata=dc_config(field_name="image_labels"), ) moderation: "Optional[ModerationV2Response]" = dc_field( - default=None, metadata=dc_config(field_name="moderation") + default=None, + metadata=dc_config(field_name="moderation"), ) pinned_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="pinned_by") + default=None, + metadata=dc_config(field_name="pinned_by"), ) poll: "Optional[PollResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="poll") + default=None, + metadata=dc_config(field_name="poll"), ) quoted_message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="quoted_message") + default=None, + metadata=dc_config(field_name="quoted_message"), ) reaction_groups: "Optional[Dict[str, Optional[ReactionGroupResponse]]]" = dc_field( - default=None, metadata=dc_config(field_name="reaction_groups") + default=None, + metadata=dc_config(field_name="reaction_groups"), ) reminder: "Optional[ReminderResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="reminder") + default=None, + metadata=dc_config(field_name="reminder"), ) shared_location: "Optional[SharedLocationResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="shared_location") + default=None, + metadata=dc_config(field_name="shared_location"), ) @dataclass class MessageStatsResponse(DataClassJsonMixin): count_over_time: "Optional[List[CountByMinuteResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="count_over_time") + default=None, + metadata=dc_config(field_name="count_over_time"), ) @@ -8249,19 +9111,23 @@ class MessageUnblockedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="message.unblocked", metadata=dc_config(field_name="type") + default="message.unblocked", + metadata=dc_config(field_name="type"), ) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8276,30 +9142,36 @@ class MessageUndeletedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="message.undeleted", metadata=dc_config(field_name="type") + default="message.undeleted", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class MessageUpdate(DataClassJsonMixin): old_text: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="old_text") + default=None, + metadata=dc_config(field_name="old_text"), ) change_set: "Optional[MessageChangeSet]" = dc_field( - default=None, metadata=dc_config(field_name="change_set") + default=None, + metadata=dc_config(field_name="change_set"), ) @@ -8314,20 +9186,24 @@ class MessageUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="message.updated", metadata=dc_config(field_name="type") + default="message.updated", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8340,10 +9216,10 @@ class MessageWithChannelResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) deleted_reply_count: int = dc_field( - metadata=dc_config(field_name="deleted_reply_count") + metadata=dc_config(field_name="deleted_reply_count"), ) html: str = dc_field(metadata=dc_config(field_name="html")) id: str = dc_field(metadata=dc_config(field_name="id")) @@ -8359,34 +9235,35 @@ class MessageWithChannelResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) attachments: "List[Attachment]" = dc_field( - metadata=dc_config(field_name="attachments") + metadata=dc_config(field_name="attachments"), ) latest_reactions: "List[ReactionResponse]" = dc_field( - metadata=dc_config(field_name="latest_reactions") + metadata=dc_config(field_name="latest_reactions"), ) mentioned_users: "List[UserResponse]" = dc_field( - metadata=dc_config(field_name="mentioned_users") + metadata=dc_config(field_name="mentioned_users"), ) own_reactions: "List[ReactionResponse]" = dc_field( - metadata=dc_config(field_name="own_reactions") + metadata=dc_config(field_name="own_reactions"), ) restricted_visibility: List[str] = dc_field( - metadata=dc_config(field_name="restricted_visibility") + metadata=dc_config(field_name="restricted_visibility"), ) channel: "ChannelResponse" = dc_field(metadata=dc_config(field_name="channel")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) reaction_counts: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_counts") + metadata=dc_config(field_name="reaction_counts"), ) reaction_scores: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_scores") + metadata=dc_config(field_name="reaction_scores"), ) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) command: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="command") + default=None, + metadata=dc_config(field_name="command"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -8408,7 +9285,8 @@ class MessageWithChannelResponse(DataClassJsonMixin): ) mml: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mml")) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) pin_expires: Optional[datetime] = dc_field( default=None, @@ -8429,46 +9307,60 @@ class MessageWithChannelResponse(DataClassJsonMixin): ), ) poll_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="poll_id") + default=None, + metadata=dc_config(field_name="poll_id"), ) quoted_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quoted_message_id") + default=None, + metadata=dc_config(field_name="quoted_message_id"), ) show_in_channel: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="show_in_channel") + default=None, + metadata=dc_config(field_name="show_in_channel"), ) thread_participants: "Optional[List[UserResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) draft: "Optional[DraftResponse]" = dc_field( - default=None, metadata=dc_config(field_name="draft") + default=None, + metadata=dc_config(field_name="draft"), ) i18n: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="i18n") + default=None, + metadata=dc_config(field_name="i18n"), ) image_labels: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="image_labels") + default=None, + metadata=dc_config(field_name="image_labels"), ) moderation: "Optional[ModerationV2Response]" = dc_field( - default=None, metadata=dc_config(field_name="moderation") + default=None, + metadata=dc_config(field_name="moderation"), ) pinned_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="pinned_by") + default=None, + metadata=dc_config(field_name="pinned_by"), ) poll: "Optional[PollResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="poll") + default=None, + metadata=dc_config(field_name="poll"), ) quoted_message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="quoted_message") + default=None, + metadata=dc_config(field_name="quoted_message"), ) reaction_groups: "Optional[Dict[str, Optional[ReactionGroupResponse]]]" = dc_field( - default=None, metadata=dc_config(field_name="reaction_groups") + default=None, + metadata=dc_config(field_name="reaction_groups"), ) reminder: "Optional[ReminderResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="reminder") + default=None, + metadata=dc_config(field_name="reminder"), ) shared_location: "Optional[SharedLocationResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="shared_location") + default=None, + metadata=dc_config(field_name="shared_location"), ) @@ -8490,19 +9382,20 @@ class ModerationCheckCompletedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) recommended_action: str = dc_field( - metadata=dc_config(field_name="recommended_action") + metadata=dc_config(field_name="recommended_action"), ) review_queue_item_id: str = dc_field( - metadata=dc_config(field_name="review_queue_item_id") + metadata=dc_config(field_name="review_queue_item_id"), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="moderation_check.completed", metadata=dc_config(field_name="type") + default="moderation_check.completed", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -8523,26 +9416,31 @@ class ModerationCustomActionEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="moderation.custom_action", metadata=dc_config(field_name="type") + default="moderation.custom_action", + metadata=dc_config(field_name="type"), ) item: "Optional[ReviewQueueItem]" = dc_field( - default=None, metadata=dc_config(field_name="item") + default=None, + metadata=dc_config(field_name="item"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class ModerationDashboardPreferences(DataClassJsonMixin): media_queue_blur_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="media_queue_blur_enabled") + default=None, + metadata=dc_config(field_name="media_queue_blur_enabled"), ) @@ -8555,31 +9453,40 @@ class ModerationFlagResponse(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) updated_at: str = dc_field(metadata=dc_config(field_name="updated_at")) entity_creator_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="entity_creator_id") + default=None, + metadata=dc_config(field_name="entity_creator_id"), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) review_queue_item_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item_id") + default=None, + metadata=dc_config(field_name="review_queue_item_id"), ) labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="labels") + default=None, + metadata=dc_config(field_name="labels"), ) result: "Optional[List[Dict[str, object]]]" = dc_field( - default=None, metadata=dc_config(field_name="result") + default=None, + metadata=dc_config(field_name="result"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") + default=None, + metadata=dc_config(field_name="moderation_payload"), ) review_queue_item: "Optional[ReviewQueueItemResponse]" = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item") + default=None, + metadata=dc_config(field_name="review_queue_item"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8591,17 +9498,20 @@ class ModerationFlaggedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="moderation.flagged", metadata=dc_config(field_name="type") + default="moderation.flagged", + metadata=dc_config(field_name="type"), ) item: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="item")) object_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="object_id") + default=None, + metadata=dc_config(field_name="object_id"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8613,35 +9523,43 @@ class ModerationMarkReviewedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="moderation.mark_reviewed", metadata=dc_config(field_name="type") + default="moderation.mark_reviewed", + metadata=dc_config(field_name="type"), ) item: "Optional[ReviewQueueItem]" = dc_field( - default=None, metadata=dc_config(field_name="item") + default=None, + metadata=dc_config(field_name="item"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class ModerationPayload(DataClassJsonMixin): images: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="images") + default=None, + metadata=dc_config(field_name="images"), ) texts: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="texts") + default=None, + metadata=dc_config(field_name="texts"), ) videos: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="videos") + default=None, + metadata=dc_config(field_name="videos"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -8658,35 +9576,44 @@ class ModerationV2Response(DataClassJsonMixin): action: str = dc_field(metadata=dc_config(field_name="action")) original_text: str = dc_field(metadata=dc_config(field_name="original_text")) blocklist_matched: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_matched") + default=None, + metadata=dc_config(field_name="blocklist_matched"), ) platform_circumvented: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="platform_circumvented") + default=None, + metadata=dc_config(field_name="platform_circumvented"), ) semantic_filter_matched: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="semantic_filter_matched") + default=None, + metadata=dc_config(field_name="semantic_filter_matched"), ) image_harms: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="image_harms") + default=None, + metadata=dc_config(field_name="image_harms"), ) text_harms: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="text_harms") + default=None, + metadata=dc_config(field_name="text_harms"), ) @dataclass class MuteChannelRequest(DataClassJsonMixin): expiration: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="expiration") + default=None, + metadata=dc_config(field_name="expiration"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) channel_cids: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="channel_cids") + default=None, + metadata=dc_config(field_name="channel_cids"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8694,13 +9621,16 @@ class MuteChannelRequest(DataClassJsonMixin): class MuteChannelResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) channel_mutes: "Optional[List[ChannelMute]]" = dc_field( - default=None, metadata=dc_config(field_name="channel_mutes") + default=None, + metadata=dc_config(field_name="channel_mutes"), ) channel_mute: "Optional[ChannelMute]" = dc_field( - default=None, metadata=dc_config(field_name="channel_mute") + default=None, + metadata=dc_config(field_name="channel_mute"), ) own_user: "Optional[OwnUser]" = dc_field( - default=None, metadata=dc_config(field_name="own_user") + default=None, + metadata=dc_config(field_name="own_user"), ) @@ -8708,13 +9638,16 @@ class MuteChannelResponse(DataClassJsonMixin): class MuteRequest(DataClassJsonMixin): target_ids: List[str] = dc_field(metadata=dc_config(field_name="target_ids")) timeout: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="timeout") + default=None, + metadata=dc_config(field_name="timeout"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8722,41 +9655,52 @@ class MuteRequest(DataClassJsonMixin): class MuteResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) mutes: "Optional[List[UserMute]]" = dc_field( - default=None, metadata=dc_config(field_name="mutes") + default=None, + metadata=dc_config(field_name="mutes"), ) non_existing_users: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="non_existing_users") + default=None, + metadata=dc_config(field_name="non_existing_users"), ) own_user: "Optional[OwnUser]" = dc_field( - default=None, metadata=dc_config(field_name="own_user") + default=None, + metadata=dc_config(field_name="own_user"), ) @dataclass class MuteUsersRequest(DataClassJsonMixin): audio: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="audio") + default=None, + metadata=dc_config(field_name="audio"), ) mute_all_users: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mute_all_users") + default=None, + metadata=dc_config(field_name="mute_all_users"), ) muted_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="muted_by_id") + default=None, + metadata=dc_config(field_name="muted_by_id"), ) screenshare: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="screenshare") + default=None, + metadata=dc_config(field_name="screenshare"), ) screenshare_audio: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="screenshare_audio") + default=None, + metadata=dc_config(field_name="screenshare_audio"), ) video: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="video") + default=None, + metadata=dc_config(field_name="video"), ) user_ids: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="user_ids") + default=None, + metadata=dc_config(field_name="user_ids"), ) muted_by: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="muted_by") + default=None, + metadata=dc_config(field_name="muted_by"), ) @@ -8768,16 +9712,20 @@ class MuteUsersResponse(DataClassJsonMixin): @dataclass class NetworkMetricsReportResponse(DataClassJsonMixin): average_connection_time: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="average_connection_time") + default=None, + metadata=dc_config(field_name="average_connection_time"), ) average_jitter: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="average_jitter") + default=None, + metadata=dc_config(field_name="average_jitter"), ) average_latency: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="average_latency") + default=None, + metadata=dc_config(field_name="average_latency"), ) average_time_to_reconnect: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="average_time_to_reconnect") + default=None, + metadata=dc_config(field_name="average_time_to_reconnect"), ) @@ -8790,7 +9738,7 @@ class NoiseCancellationSettings(DataClassJsonMixin): class NotificationMarkUnreadEvent(DataClassJsonMixin): channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) channel_member_count: int = dc_field( - metadata=dc_config(field_name="channel_member_count") + metadata=dc_config(field_name="channel_member_count"), ) channel_type: str = dc_field(metadata=dc_config(field_name="channel_type")) cid: str = dc_field(metadata=dc_config(field_name="cid")) @@ -8800,10 +9748,10 @@ class NotificationMarkUnreadEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) first_unread_message_id: str = dc_field( - metadata=dc_config(field_name="first_unread_message_id") + metadata=dc_config(field_name="first_unread_message_id"), ) last_read_at: datetime = dc_field( metadata=dc_config( @@ -8811,30 +9759,35 @@ class NotificationMarkUnreadEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) total_unread_count: int = dc_field( - metadata=dc_config(field_name="total_unread_count") + metadata=dc_config(field_name="total_unread_count"), ) unread_channels: int = dc_field(metadata=dc_config(field_name="unread_channels")) unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) unread_messages: int = dc_field(metadata=dc_config(field_name="unread_messages")) unread_threads: int = dc_field(metadata=dc_config(field_name="unread_threads")) type: str = dc_field( - default="notification.mark_unread", metadata=dc_config(field_name="type") + default="notification.mark_unread", + metadata=dc_config(field_name="type"), ) last_read_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="last_read_message_id") + default=None, + metadata=dc_config(field_name="last_read_message_id"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thread_id") + default=None, + metadata=dc_config(field_name="thread_id"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -8842,19 +9795,19 @@ class NotificationMarkUnreadEvent(DataClassJsonMixin): class NotificationSettings(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) call_live_started: "EventNotificationSettings" = dc_field( - metadata=dc_config(field_name="call_live_started") + metadata=dc_config(field_name="call_live_started"), ) call_missed: "EventNotificationSettings" = dc_field( - metadata=dc_config(field_name="call_missed") + metadata=dc_config(field_name="call_missed"), ) call_notification: "EventNotificationSettings" = dc_field( - metadata=dc_config(field_name="call_notification") + metadata=dc_config(field_name="call_notification"), ) call_ring: "EventNotificationSettings" = dc_field( - metadata=dc_config(field_name="call_ring") + metadata=dc_config(field_name="call_ring"), ) session_started: "EventNotificationSettings" = dc_field( - metadata=dc_config(field_name="session_started") + metadata=dc_config(field_name="session_started"), ) @@ -8919,14 +9872,14 @@ class OwnUser(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) language: str = dc_field(metadata=dc_config(field_name="language")) online: bool = dc_field(metadata=dc_config(field_name="online")) role: str = dc_field(metadata=dc_config(field_name="role")) total_unread_count: int = dc_field( - metadata=dc_config(field_name="total_unread_count") + metadata=dc_config(field_name="total_unread_count"), ) unread_channels: int = dc_field(metadata=dc_config(field_name="unread_channels")) unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) @@ -8937,10 +9890,10 @@ class OwnUser(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) channel_mutes: "List[ChannelMute]" = dc_field( - metadata=dc_config(field_name="channel_mutes") + metadata=dc_config(field_name="channel_mutes"), ) devices: "List[Device]" = dc_field(metadata=dc_config(field_name="devices")) mutes: "List[UserMute]" = dc_field(metadata=dc_config(field_name="mutes")) @@ -8964,7 +9917,8 @@ class OwnUser(DataClassJsonMixin): ), ) invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") + default=None, + metadata=dc_config(field_name="invisible"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -8985,22 +9939,28 @@ class OwnUser(DataClassJsonMixin): ), ) blocked_user_ids: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="blocked_user_ids") + default=None, + metadata=dc_config(field_name="blocked_user_ids"), ) latest_hidden_channels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="latest_hidden_channels") + default=None, + metadata=dc_config(field_name="latest_hidden_channels"), ) teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") + default=None, + metadata=dc_config(field_name="teams"), ) privacy_settings: "Optional[PrivacySettings]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) push_preferences: "Optional[PushPreferences]" = dc_field( - default=None, metadata=dc_config(field_name="push_preferences") + default=None, + metadata=dc_config(field_name="push_preferences"), ) teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + default=None, + metadata=dc_config(field_name="teams_role"), ) @@ -9013,7 +9973,7 @@ class OwnUserResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) invisible: bool = dc_field(metadata=dc_config(field_name="invisible")) @@ -9021,7 +9981,7 @@ class OwnUserResponse(DataClassJsonMixin): online: bool = dc_field(metadata=dc_config(field_name="online")) role: str = dc_field(metadata=dc_config(field_name="role")) total_unread_count: int = dc_field( - metadata=dc_config(field_name="total_unread_count") + metadata=dc_config(field_name="total_unread_count"), ) unread_channels: int = dc_field(metadata=dc_config(field_name="unread_channels")) unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) @@ -9032,10 +9992,10 @@ class OwnUserResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) channel_mutes: "List[ChannelMute]" = dc_field( - metadata=dc_config(field_name="channel_mutes") + metadata=dc_config(field_name="channel_mutes"), ) devices: "List[DeviceResponse]" = dc_field(metadata=dc_config(field_name="devices")) mutes: "List[UserMuteResponse]" = dc_field(metadata=dc_config(field_name="mutes")) @@ -9060,7 +10020,8 @@ class OwnUserResponse(DataClassJsonMixin): ), ) image: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image") + default=None, + metadata=dc_config(field_name="image"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -9082,19 +10043,24 @@ class OwnUserResponse(DataClassJsonMixin): ), ) blocked_user_ids: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="blocked_user_ids") + default=None, + metadata=dc_config(field_name="blocked_user_ids"), ) latest_hidden_channels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="latest_hidden_channels") + default=None, + metadata=dc_config(field_name="latest_hidden_channels"), ) privacy_settings: "Optional[PrivacySettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) push_preferences: "Optional[PushPreferences]" = dc_field( - default=None, metadata=dc_config(field_name="push_preferences") + default=None, + metadata=dc_config(field_name="push_preferences"), ) teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + default=None, + metadata=dc_config(field_name="teams_role"), ) @@ -9107,10 +10073,12 @@ class PagerResponse(DataClassJsonMixin): @dataclass class PaginationParams(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) offset: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="offset") + default=None, + metadata=dc_config(field_name="offset"), ) @@ -9126,14 +10094,15 @@ class ParticipantCountByMinuteResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) @dataclass class ParticipantCountOverTimeResponse(DataClassJsonMixin): by_minute: "Optional[List[ParticipantCountByMinuteResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="by_minute") + default=None, + metadata=dc_config(field_name="by_minute"), ) @@ -9142,44 +10111,56 @@ class ParticipantReportResponse(DataClassJsonMixin): sum: int = dc_field(metadata=dc_config(field_name="sum")) unique: int = dc_field(metadata=dc_config(field_name="unique")) max_concurrent: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_concurrent") + default=None, + metadata=dc_config(field_name="max_concurrent"), ) by_browser: "Optional[List[GroupedStatsResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="by_browser") + default=None, + metadata=dc_config(field_name="by_browser"), ) by_country: "Optional[List[GroupedStatsResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="by_country") + default=None, + metadata=dc_config(field_name="by_country"), ) by_device: "Optional[List[GroupedStatsResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="by_device") + default=None, + metadata=dc_config(field_name="by_device"), ) by_operating_system: "Optional[List[GroupedStatsResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="by_operating_system") + default=None, + metadata=dc_config(field_name="by_operating_system"), ) count_over_time: "Optional[ParticipantCountOverTimeResponse]" = dc_field( - default=None, metadata=dc_config(field_name="count_over_time") + default=None, + metadata=dc_config(field_name="count_over_time"), ) publishers: "Optional[PublisherStatsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="publishers") + default=None, + metadata=dc_config(field_name="publishers"), ) subscribers: "Optional[SubscriberStatsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="subscribers") + default=None, + metadata=dc_config(field_name="subscribers"), ) @dataclass class PendingMessageResponse(DataClassJsonMixin): channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) metadata: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="metadata") + default=None, + metadata=dc_config(field_name="metadata"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -9201,7 +10182,8 @@ class Permission(DataClassJsonMixin): same_team: bool = dc_field(metadata=dc_config(field_name="same_team")) tags: List[str] = dc_field(metadata=dc_config(field_name="tags")) condition: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="condition") + default=None, + metadata=dc_config(field_name="condition"), ) @@ -9214,12 +10196,13 @@ class PermissionRequestEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) permissions: List[str] = dc_field(metadata=dc_config(field_name="permissions")) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field( - default="call.permission_request", metadata=dc_config(field_name="type") + default="call.permission_request", + metadata=dc_config(field_name="type"), ) @@ -9250,7 +10233,7 @@ class Policy(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) name: str = dc_field(metadata=dc_config(field_name="name")) owner: bool = dc_field(metadata=dc_config(field_name="owner")) @@ -9261,7 +10244,7 @@ class Policy(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) resources: List[str] = dc_field(metadata=dc_config(field_name="resources")) roles: List[str] = dc_field(metadata=dc_config(field_name="roles")) @@ -9281,7 +10264,7 @@ class PolicyRequest(DataClassJsonMixin): class Poll(DataClassJsonMixin): allow_answers: bool = dc_field(metadata=dc_config(field_name="allow_answers")) allow_user_suggested_options: bool = dc_field( - metadata=dc_config(field_name="allow_user_suggested_options") + metadata=dc_config(field_name="allow_user_suggested_options"), ) answers_count: int = dc_field(metadata=dc_config(field_name="answers_count")) created_at: datetime = dc_field( @@ -9290,12 +10273,12 @@ class Poll(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_id: str = dc_field(metadata=dc_config(field_name="created_by_id")) description: str = dc_field(metadata=dc_config(field_name="description")) enforce_unique_vote: bool = dc_field( - metadata=dc_config(field_name="enforce_unique_vote") + metadata=dc_config(field_name="enforce_unique_vote"), ) id: str = dc_field(metadata=dc_config(field_name="id")) name: str = dc_field(metadata=dc_config(field_name="name")) @@ -9305,32 +10288,36 @@ class Poll(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) vote_count: int = dc_field(metadata=dc_config(field_name="vote_count")) latest_answers: "List[PollVote]" = dc_field( - metadata=dc_config(field_name="latest_answers") + metadata=dc_config(field_name="latest_answers"), ) options: "List[PollOption]" = dc_field(metadata=dc_config(field_name="options")) own_votes: "List[PollVote]" = dc_field(metadata=dc_config(field_name="own_votes")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="Custom")) latest_votes_by_option: "Dict[str, List[PollVote]]" = dc_field( - metadata=dc_config(field_name="latest_votes_by_option") + metadata=dc_config(field_name="latest_votes_by_option"), ) vote_counts_by_option: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="vote_counts_by_option") + metadata=dc_config(field_name="vote_counts_by_option"), ) is_closed: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_closed") + default=None, + metadata=dc_config(field_name="is_closed"), ) max_votes_allowed: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_votes_allowed") + default=None, + metadata=dc_config(field_name="max_votes_allowed"), ) voting_visibility: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="voting_visibility") + default=None, + metadata=dc_config(field_name="voting_visibility"), ) created_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) @@ -9345,7 +10332,8 @@ class PollOption(DataClassJsonMixin): class PollOptionInput(DataClassJsonMixin): text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -9354,7 +10342,8 @@ class PollOptionRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) text: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="text")) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -9362,7 +10351,7 @@ class PollOptionRequest(DataClassJsonMixin): class PollOptionResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) poll_option: "PollOptionResponseData" = dc_field( - metadata=dc_config(field_name="poll_option") + metadata=dc_config(field_name="poll_option"), ) @@ -9383,7 +10372,7 @@ class PollResponse(DataClassJsonMixin): class PollResponseData(DataClassJsonMixin): allow_answers: bool = dc_field(metadata=dc_config(field_name="allow_answers")) allow_user_suggested_options: bool = dc_field( - metadata=dc_config(field_name="allow_user_suggested_options") + metadata=dc_config(field_name="allow_user_suggested_options"), ) answers_count: int = dc_field(metadata=dc_config(field_name="answers_count")) created_at: datetime = dc_field( @@ -9392,12 +10381,12 @@ class PollResponseData(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_id: str = dc_field(metadata=dc_config(field_name="created_by_id")) description: str = dc_field(metadata=dc_config(field_name="description")) enforce_unique_vote: bool = dc_field( - metadata=dc_config(field_name="enforce_unique_vote") + metadata=dc_config(field_name="enforce_unique_vote"), ) id: str = dc_field(metadata=dc_config(field_name="id")) name: str = dc_field(metadata=dc_config(field_name="name")) @@ -9407,36 +10396,39 @@ class PollResponseData(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) vote_count: int = dc_field(metadata=dc_config(field_name="vote_count")) voting_visibility: str = dc_field( - metadata=dc_config(field_name="voting_visibility") + metadata=dc_config(field_name="voting_visibility"), ) latest_answers: "List[PollVoteResponseData]" = dc_field( - metadata=dc_config(field_name="latest_answers") + metadata=dc_config(field_name="latest_answers"), ) options: "List[PollOptionResponseData]" = dc_field( - metadata=dc_config(field_name="options") + metadata=dc_config(field_name="options"), ) own_votes: "List[PollVoteResponseData]" = dc_field( - metadata=dc_config(field_name="own_votes") + metadata=dc_config(field_name="own_votes"), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="Custom")) latest_votes_by_option: "Dict[str, List[PollVoteResponseData]]" = dc_field( - metadata=dc_config(field_name="latest_votes_by_option") + metadata=dc_config(field_name="latest_votes_by_option"), ) vote_counts_by_option: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="vote_counts_by_option") + metadata=dc_config(field_name="vote_counts_by_option"), ) is_closed: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_closed") + default=None, + metadata=dc_config(field_name="is_closed"), ) max_votes_allowed: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_votes_allowed") + default=None, + metadata=dc_config(field_name="max_votes_allowed"), ) created_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) @@ -9448,7 +10440,7 @@ class PollVote(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) option_id: str = dc_field(metadata=dc_config(field_name="option_id")) @@ -9459,19 +10451,23 @@ class PollVote(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) answer_text: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="answer_text") + default=None, + metadata=dc_config(field_name="answer_text"), ) is_answer: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_answer") + default=None, + metadata=dc_config(field_name="is_answer"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -9479,7 +10475,8 @@ class PollVote(DataClassJsonMixin): class PollVoteResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) vote: "Optional[PollVoteResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="vote") + default=None, + metadata=dc_config(field_name="vote"), ) @@ -9491,7 +10488,7 @@ class PollVoteResponseData(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) option_id: str = dc_field(metadata=dc_config(field_name="option_id")) @@ -9502,19 +10499,23 @@ class PollVoteResponseData(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) answer_text: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="answer_text") + default=None, + metadata=dc_config(field_name="answer_text"), ) is_answer: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_answer") + default=None, + metadata=dc_config(field_name="is_answer"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -9522,7 +10523,7 @@ class PollVoteResponseData(DataClassJsonMixin): class PollVotesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) votes: "List[PollVoteResponseData]" = dc_field( - metadata=dc_config(field_name="votes") + metadata=dc_config(field_name="votes"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -9531,20 +10532,24 @@ class PollVotesResponse(DataClassJsonMixin): @dataclass class PrivacySettings(DataClassJsonMixin): read_receipts: "Optional[ReadReceipts]" = dc_field( - default=None, metadata=dc_config(field_name="read_receipts") + default=None, + metadata=dc_config(field_name="read_receipts"), ) typing_indicators: "Optional[TypingIndicators]" = dc_field( - default=None, metadata=dc_config(field_name="typing_indicators") + default=None, + metadata=dc_config(field_name="typing_indicators"), ) @dataclass class PrivacySettingsResponse(DataClassJsonMixin): read_receipts: "Optional[ReadReceiptsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="read_receipts") + default=None, + metadata=dc_config(field_name="read_receipts"), ) typing_indicators: "Optional[TypingIndicatorsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="typing_indicators") + default=None, + metadata=dc_config(field_name="typing_indicators"), ) @@ -9553,7 +10558,8 @@ class PublisherStatsResponse(DataClassJsonMixin): total: int = dc_field(metadata=dc_config(field_name="total")) unique: int = dc_field(metadata=dc_config(field_name="unique")) by_track: "Optional[List[TrackStatsResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="by_track") + default=None, + metadata=dc_config(field_name="by_track"), ) @@ -9561,7 +10567,8 @@ class PublisherStatsResponse(DataClassJsonMixin): class PushConfig(DataClassJsonMixin): version: str = dc_field(metadata=dc_config(field_name="version")) offline_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="offline_only") + default=None, + metadata=dc_config(field_name="offline_only"), ) @@ -9571,19 +10578,21 @@ class PushNotificationFields(DataClassJsonMixin): version: str = dc_field(metadata=dc_config(field_name="version")) apn: "APNConfigFields" = dc_field(metadata=dc_config(field_name="apn")) firebase: "FirebaseConfigFields" = dc_field( - metadata=dc_config(field_name="firebase") + metadata=dc_config(field_name="firebase"), ) huawei: "HuaweiConfigFields" = dc_field(metadata=dc_config(field_name="huawei")) xiaomi: "XiaomiConfigFields" = dc_field(metadata=dc_config(field_name="xiaomi")) providers: "Optional[List[PushProvider]]" = dc_field( - default=None, metadata=dc_config(field_name="providers") + default=None, + metadata=dc_config(field_name="providers"), ) @dataclass class PushNotificationSettingsResponse(DataClassJsonMixin): disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="disabled") + default=None, + metadata=dc_config(field_name="disabled"), ) disabled_until: Optional[datetime] = dc_field( default=None, @@ -9599,10 +10608,12 @@ class PushNotificationSettingsResponse(DataClassJsonMixin): @dataclass class PushPreferenceInput(DataClassJsonMixin): channel_cid: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_cid") + default=None, + metadata=dc_config(field_name="channel_cid"), ) chat_level: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="chat_level") + default=None, + metadata=dc_config(field_name="chat_level"), ) disabled_until: Optional[datetime] = dc_field( default=None, @@ -9614,20 +10625,24 @@ class PushPreferenceInput(DataClassJsonMixin): ), ) remove_disable: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="remove_disable") + default=None, + metadata=dc_config(field_name="remove_disable"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) @dataclass class PushPreferences(DataClassJsonMixin): call_level: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="call_level") + default=None, + metadata=dc_config(field_name="call_level"), ) chat_level: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="chat_level") + default=None, + metadata=dc_config(field_name="chat_level"), ) disabled_until: Optional[datetime] = dc_field( default=None, @@ -9648,7 +10663,7 @@ class PushProvider(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) name: str = dc_field(metadata=dc_config(field_name="name")) type: str = dc_field(metadata=dc_config(field_name="type")) @@ -9658,37 +10673,47 @@ class PushProvider(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) apn_auth_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_auth_key") + default=None, + metadata=dc_config(field_name="apn_auth_key"), ) apn_auth_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_auth_type") + default=None, + metadata=dc_config(field_name="apn_auth_type"), ) apn_development: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="apn_development") + default=None, + metadata=dc_config(field_name="apn_development"), ) apn_host: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_host") + default=None, + metadata=dc_config(field_name="apn_host"), ) apn_key_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_key_id") + default=None, + metadata=dc_config(field_name="apn_key_id"), ) apn_notification_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_notification_template") + default=None, + metadata=dc_config(field_name="apn_notification_template"), ) apn_p12_cert: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_p12_cert") + default=None, + metadata=dc_config(field_name="apn_p12_cert"), ) apn_team_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_team_id") + default=None, + metadata=dc_config(field_name="apn_team_id"), ) apn_topic: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_topic") + default=None, + metadata=dc_config(field_name="apn_topic"), ) description: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="description") + default=None, + metadata=dc_config(field_name="description"), ) disabled_at: Optional[datetime] = dc_field( default=None, @@ -9700,40 +10725,52 @@ class PushProvider(DataClassJsonMixin): ), ) disabled_reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="disabled_reason") + default=None, + metadata=dc_config(field_name="disabled_reason"), ) firebase_apn_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_apn_template") + default=None, + metadata=dc_config(field_name="firebase_apn_template"), ) firebase_credentials: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_credentials") + default=None, + metadata=dc_config(field_name="firebase_credentials"), ) firebase_data_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_data_template") + default=None, + metadata=dc_config(field_name="firebase_data_template"), ) firebase_host: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_host") + default=None, + metadata=dc_config(field_name="firebase_host"), ) firebase_notification_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_notification_template") + default=None, + metadata=dc_config(field_name="firebase_notification_template"), ) firebase_server_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_server_key") + default=None, + metadata=dc_config(field_name="firebase_server_key"), ) huawei_app_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="huawei_app_id") + default=None, + metadata=dc_config(field_name="huawei_app_id"), ) huawei_app_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="huawei_app_secret") + default=None, + metadata=dc_config(field_name="huawei_app_secret"), ) xiaomi_app_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="xiaomi_app_secret") + default=None, + metadata=dc_config(field_name="xiaomi_app_secret"), ) xiaomi_package_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="xiaomi_package_name") + default=None, + metadata=dc_config(field_name="xiaomi_package_name"), ) push_templates: "Optional[List[PushTemplate]]" = dc_field( - default=None, metadata=dc_config(field_name="push_templates") + default=None, + metadata=dc_config(field_name="push_templates"), ) @@ -9745,7 +10782,7 @@ class PushProviderResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) name: str = dc_field(metadata=dc_config(field_name="name")) type: str = dc_field(metadata=dc_config(field_name="type")) @@ -9755,43 +10792,55 @@ class PushProviderResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) apn_auth_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_auth_key") + default=None, + metadata=dc_config(field_name="apn_auth_key"), ) apn_auth_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_auth_type") + default=None, + metadata=dc_config(field_name="apn_auth_type"), ) apn_development: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="apn_development") + default=None, + metadata=dc_config(field_name="apn_development"), ) apn_host: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_host") + default=None, + metadata=dc_config(field_name="apn_host"), ) apn_key_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_key_id") + default=None, + metadata=dc_config(field_name="apn_key_id"), ) apn_p12_cert: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_p12_cert") + default=None, + metadata=dc_config(field_name="apn_p12_cert"), ) apn_sandbox_certificate: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="apn_sandbox_certificate") + default=None, + metadata=dc_config(field_name="apn_sandbox_certificate"), ) apn_supports_remote_notifications: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="apn_supports_remote_notifications") + default=None, + metadata=dc_config(field_name="apn_supports_remote_notifications"), ) apn_supports_voip_notifications: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="apn_supports_voip_notifications") + default=None, + metadata=dc_config(field_name="apn_supports_voip_notifications"), ) apn_team_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_team_id") + default=None, + metadata=dc_config(field_name="apn_team_id"), ) apn_topic: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="apn_topic") + default=None, + metadata=dc_config(field_name="apn_topic"), ) description: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="description") + default=None, + metadata=dc_config(field_name="description"), ) disabled_at: Optional[datetime] = dc_field( default=None, @@ -9803,37 +10852,48 @@ class PushProviderResponse(DataClassJsonMixin): ), ) disabled_reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="disabled_reason") + default=None, + metadata=dc_config(field_name="disabled_reason"), ) firebase_apn_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_apn_template") + default=None, + metadata=dc_config(field_name="firebase_apn_template"), ) firebase_credentials: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_credentials") + default=None, + metadata=dc_config(field_name="firebase_credentials"), ) firebase_data_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_data_template") + default=None, + metadata=dc_config(field_name="firebase_data_template"), ) firebase_host: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_host") + default=None, + metadata=dc_config(field_name="firebase_host"), ) firebase_notification_template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_notification_template") + default=None, + metadata=dc_config(field_name="firebase_notification_template"), ) firebase_server_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="firebase_server_key") + default=None, + metadata=dc_config(field_name="firebase_server_key"), ) huawei_app_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="huawei_app_id") + default=None, + metadata=dc_config(field_name="huawei_app_id"), ) huawei_app_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="huawei_app_secret") + default=None, + metadata=dc_config(field_name="huawei_app_secret"), ) xiaomi_app_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="xiaomi_app_secret") + default=None, + metadata=dc_config(field_name="xiaomi_app_secret"), ) xiaomi_package_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="xiaomi_package_name") + default=None, + metadata=dc_config(field_name="xiaomi_package_name"), ) @@ -9845,7 +10905,7 @@ class PushTemplate(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) enable_push: bool = dc_field(metadata=dc_config(field_name="enable_push")) event_type: str = dc_field(metadata=dc_config(field_name="event_type")) @@ -9855,44 +10915,50 @@ class PushTemplate(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="template") + default=None, + metadata=dc_config(field_name="template"), ) @dataclass class Quality(DataClassJsonMixin): bitdepth: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="bitdepth") + default=None, + metadata=dc_config(field_name="bitdepth"), ) framerate: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="framerate") + default=None, + metadata=dc_config(field_name="framerate"), ) height: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="height") + default=None, + metadata=dc_config(field_name="height"), ) name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) video_bitrate: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="video_bitrate") + default=None, + metadata=dc_config(field_name="video_bitrate"), ) width: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="width") + default=None, + metadata=dc_config(field_name="width"), ) @dataclass class QualityScoreReport(DataClassJsonMixin): histogram: "List[ReportByHistogramBucket]" = dc_field( - metadata=dc_config(field_name="histogram") + metadata=dc_config(field_name="histogram"), ) @dataclass class QualityScoreReportResponse(DataClassJsonMixin): daily: "List[DailyAggregateQualityScoreReportResponse]" = dc_field( - metadata=dc_config(field_name="daily") + metadata=dc_config(field_name="daily"), ) @@ -9901,7 +10967,8 @@ class QueryAggregateCallStatsRequest(DataClassJsonMixin): _from: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="from")) to: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="to")) report_types: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="report_types") + default=None, + metadata=dc_config(field_name="report_types"), ) @@ -9909,52 +10976,65 @@ class QueryAggregateCallStatsRequest(DataClassJsonMixin): class QueryAggregateCallStatsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) call_duration_report: "Optional[CallDurationReportResponse]" = dc_field( - default=None, metadata=dc_config(field_name="call_duration_report") + default=None, + metadata=dc_config(field_name="call_duration_report"), ) call_participant_count_report: "Optional[CallParticipantCountReportResponse]" = ( dc_field( - default=None, metadata=dc_config(field_name="call_participant_count_report") + default=None, + metadata=dc_config(field_name="call_participant_count_report"), ) ) calls_per_day_report: "Optional[CallsPerDayReportResponse]" = dc_field( - default=None, metadata=dc_config(field_name="calls_per_day_report") + default=None, + metadata=dc_config(field_name="calls_per_day_report"), ) network_metrics_report: "Optional[NetworkMetricsReportResponse]" = dc_field( - default=None, metadata=dc_config(field_name="network_metrics_report") + default=None, + metadata=dc_config(field_name="network_metrics_report"), ) quality_score_report: "Optional[QualityScoreReportResponse]" = dc_field( - default=None, metadata=dc_config(field_name="quality_score_report") + default=None, + metadata=dc_config(field_name="quality_score_report"), ) sdk_usage_report: "Optional[SDKUsageReportResponse]" = dc_field( - default=None, metadata=dc_config(field_name="sdk_usage_report") + default=None, + metadata=dc_config(field_name="sdk_usage_report"), ) user_feedback_report: "Optional[UserFeedbackReportResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user_feedback_report") + default=None, + metadata=dc_config(field_name="user_feedback_report"), ) @dataclass class QueryBannedUsersPayload(DataClassJsonMixin): filter_conditions: Dict[str, object] = dc_field( - metadata=dc_config(field_name="filter_conditions") + metadata=dc_config(field_name="filter_conditions"), ) exclude_expired_bans: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="exclude_expired_bans") + default=None, + metadata=dc_config(field_name="exclude_expired_bans"), ) limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) offset: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="offset") + default=None, + metadata=dc_config(field_name="offset"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -9969,15 +11049,18 @@ class QueryCallMembersRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) type: str = dc_field(metadata=dc_config(field_name="type")) limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter_conditions: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter_conditions") + default=None, + metadata=dc_config(field_name="filter_conditions"), ) @@ -9992,7 +11075,8 @@ class QueryCallMembersResponse(DataClassJsonMixin): @dataclass class QueryCallParticipantsRequest(DataClassJsonMixin): filter_conditions: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter_conditions") + default=None, + metadata=dc_config(field_name="filter_conditions"), ) @@ -10000,14 +11084,14 @@ class QueryCallParticipantsRequest(DataClassJsonMixin): class QueryCallParticipantsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) total_participants: int = dc_field( - metadata=dc_config(field_name="total_participants") + metadata=dc_config(field_name="total_participants"), ) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) own_capabilities: "List[OwnCapability]" = dc_field( - metadata=dc_config(field_name="own_capabilities") + metadata=dc_config(field_name="own_capabilities"), ) participants: "List[CallParticipantResponse]" = dc_field( - metadata=dc_config(field_name="participants") + metadata=dc_config(field_name="participants"), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) @@ -10015,15 +11099,18 @@ class QueryCallParticipantsResponse(DataClassJsonMixin): @dataclass class QueryCallStatsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter_conditions: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter_conditions") + default=None, + metadata=dc_config(field_name="filter_conditions"), ) @@ -10031,7 +11118,7 @@ class QueryCallStatsRequest(DataClassJsonMixin): class QueryCallStatsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) reports: "List[CallStatsReportSummaryResponse]" = dc_field( - metadata=dc_config(field_name="reports") + metadata=dc_config(field_name="reports"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10040,15 +11127,18 @@ class QueryCallStatsResponse(DataClassJsonMixin): @dataclass class QueryCallsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter_conditions: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter_conditions") + default=None, + metadata=dc_config(field_name="filter_conditions"), ) @@ -10056,7 +11146,7 @@ class QueryCallsRequest(DataClassJsonMixin): class QueryCallsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) calls: "List[CallStateResponseFields]" = dc_field( - metadata=dc_config(field_name="calls") + metadata=dc_config(field_name="calls"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10065,18 +11155,22 @@ class QueryCallsResponse(DataClassJsonMixin): @dataclass class QueryCampaignsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) user_limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="user_limit") + default=None, + metadata=dc_config(field_name="user_limit"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) @@ -10084,7 +11178,7 @@ class QueryCampaignsRequest(DataClassJsonMixin): class QueryCampaignsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) campaigns: "List[CampaignResponse]" = dc_field( - metadata=dc_config(field_name="campaigns") + metadata=dc_config(field_name="campaigns"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10093,31 +11187,40 @@ class QueryCampaignsResponse(DataClassJsonMixin): @dataclass class QueryChannelsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) member_limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="member_limit") + default=None, + metadata=dc_config(field_name="member_limit"), ) message_limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="message_limit") + default=None, + metadata=dc_config(field_name="message_limit"), ) offset: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="offset") + default=None, + metadata=dc_config(field_name="offset"), ) state: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="state") + default=None, + metadata=dc_config(field_name="state"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter_conditions: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter_conditions") + default=None, + metadata=dc_config(field_name="filter_conditions"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10125,28 +11228,33 @@ class QueryChannelsRequest(DataClassJsonMixin): class QueryChannelsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) channels: "List[ChannelStateResponseFields]" = dc_field( - metadata=dc_config(field_name="channels") + metadata=dc_config(field_name="channels"), ) @dataclass class QueryDraftsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10166,7 +11274,7 @@ class QueryFeedModerationTemplate(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) name: str = dc_field(metadata=dc_config(field_name="name")) updated_at: datetime = dc_field( @@ -10175,10 +11283,11 @@ class QueryFeedModerationTemplate(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) config: "Optional[FeedsModerationTemplateConfig]" = dc_field( - default=None, metadata=dc_config(field_name="config") + default=None, + metadata=dc_config(field_name="config"), ) @@ -10186,7 +11295,7 @@ class QueryFeedModerationTemplate(DataClassJsonMixin): class QueryFeedModerationTemplatesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) templates: "List[QueryFeedModerationTemplate]" = dc_field( - metadata=dc_config(field_name="templates") + metadata=dc_config(field_name="templates"), ) @@ -10194,51 +11303,64 @@ class QueryFeedModerationTemplatesResponse(DataClassJsonMixin): class QueryMembersPayload(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) filter_conditions: Dict[str, object] = dc_field( - metadata=dc_config(field_name="filter_conditions") + metadata=dc_config(field_name="filter_conditions"), ) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) offset: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="offset") + default=None, + metadata=dc_config(field_name="offset"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) members: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="members") + default=None, + metadata=dc_config(field_name="members"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class QueryMessageFlagsPayload(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) offset: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="offset") + default=None, + metadata=dc_config(field_name="offset"), ) show_deleted_messages: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="show_deleted_messages") + default=None, + metadata=dc_config(field_name="show_deleted_messages"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter_conditions: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter_conditions") + default=None, + metadata=dc_config(field_name="filter_conditions"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10246,7 +11368,7 @@ class QueryMessageFlagsPayload(DataClassJsonMixin): class QueryMessageFlagsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) flags: "List[MessageFlagResponse]" = dc_field( - metadata=dc_config(field_name="flags") + metadata=dc_config(field_name="flags"), ) @@ -10254,12 +11376,14 @@ class QueryMessageFlagsResponse(DataClassJsonMixin): class QueryMessageHistoryRequest(DataClassJsonMixin): filter: Dict[str, object] = dc_field(metadata=dc_config(field_name="filter")) limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) @@ -10267,7 +11391,7 @@ class QueryMessageHistoryRequest(DataClassJsonMixin): class QueryMessageHistoryResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) message_history: "List[MessageHistoryEntryResponse]" = dc_field( - metadata=dc_config(field_name="message_history") + metadata=dc_config(field_name="message_history"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10276,21 +11400,26 @@ class QueryMessageHistoryResponse(DataClassJsonMixin): @dataclass class QueryModerationConfigsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10305,15 +11434,18 @@ class QueryModerationConfigsResponse(DataClassJsonMixin): @dataclass class QueryModerationFlagsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParam]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) @@ -10321,7 +11453,7 @@ class QueryModerationFlagsRequest(DataClassJsonMixin): class QueryModerationFlagsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) flags: "List[ModerationFlagResponse]" = dc_field( - metadata=dc_config(field_name="flags") + metadata=dc_config(field_name="flags"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10330,21 +11462,26 @@ class QueryModerationFlagsResponse(DataClassJsonMixin): @dataclass class QueryModerationLogsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10359,30 +11496,36 @@ class QueryModerationLogsResponse(DataClassJsonMixin): @dataclass class QueryPollVotesRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) @dataclass class QueryPollsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) @@ -10397,21 +11540,26 @@ class QueryPollsResponse(DataClassJsonMixin): @dataclass class QueryReactionsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10419,7 +11567,7 @@ class QueryReactionsRequest(DataClassJsonMixin): class QueryReactionsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) reactions: "List[ReactionResponse]" = dc_field( - metadata=dc_config(field_name="reactions") + metadata=dc_config(field_name="reactions"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10428,21 +11576,26 @@ class QueryReactionsResponse(DataClassJsonMixin): @dataclass class QueryRemindersRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10450,7 +11603,7 @@ class QueryRemindersRequest(DataClassJsonMixin): class QueryRemindersResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) reminders: "List[ReminderResponseData]" = dc_field( - metadata=dc_config(field_name="reminders") + metadata=dc_config(field_name="reminders"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10459,33 +11612,42 @@ class QueryRemindersResponse(DataClassJsonMixin): @dataclass class QueryReviewQueueRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) lock_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="lock_count") + default=None, + metadata=dc_config(field_name="lock_count"), ) lock_duration: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="lock_duration") + default=None, + metadata=dc_config(field_name="lock_duration"), ) lock_items: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="lock_items") + default=None, + metadata=dc_config(field_name="lock_items"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) stats_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="stats_only") + default=None, + metadata=dc_config(field_name="stats_only"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10493,10 +11655,10 @@ class QueryReviewQueueRequest(DataClassJsonMixin): class QueryReviewQueueResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) items: "List[ReviewQueueItemResponse]" = dc_field( - metadata=dc_config(field_name="items") + metadata=dc_config(field_name="items"), ) action_config: "Dict[str, List[ModerationActionConfig]]" = dc_field( - metadata=dc_config(field_name="action_config") + metadata=dc_config(field_name="action_config"), ) stats: Dict[str, object] = dc_field(metadata=dc_config(field_name="stats")) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) @@ -10506,15 +11668,18 @@ class QueryReviewQueueResponse(DataClassJsonMixin): @dataclass class QuerySegmentTargetsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="Sort") + default=None, + metadata=dc_config(field_name="Sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="Filter") + default=None, + metadata=dc_config(field_name="Filter"), ) @@ -10522,7 +11687,7 @@ class QuerySegmentTargetsRequest(DataClassJsonMixin): class QuerySegmentTargetsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) targets: "List[SegmentTargetResponse]" = dc_field( - metadata=dc_config(field_name="targets") + metadata=dc_config(field_name="targets"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10532,12 +11697,14 @@ class QuerySegmentTargetsResponse(DataClassJsonMixin): class QuerySegmentsRequest(DataClassJsonMixin): filter: Dict[str, object] = dc_field(metadata=dc_config(field_name="filter")) limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) @@ -10545,7 +11712,7 @@ class QuerySegmentsRequest(DataClassJsonMixin): class QuerySegmentsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) segments: "List[SegmentResponse]" = dc_field( - metadata=dc_config(field_name="segments") + metadata=dc_config(field_name="segments"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10554,30 +11721,38 @@ class QuerySegmentsResponse(DataClassJsonMixin): @dataclass class QueryThreadsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) member_limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="member_limit") + default=None, + metadata=dc_config(field_name="member_limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) participant_limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="participant_limit") + default=None, + metadata=dc_config(field_name="participant_limit"), ) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) reply_limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="reply_limit") + default=None, + metadata=dc_config(field_name="reply_limit"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10585,7 +11760,7 @@ class QueryThreadsRequest(DataClassJsonMixin): class QueryThreadsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) threads: "List[ThreadStateResponse]" = dc_field( - metadata=dc_config(field_name="threads") + metadata=dc_config(field_name="threads"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10594,15 +11769,18 @@ class QueryThreadsResponse(DataClassJsonMixin): @dataclass class QueryUserFeedbackRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) filter_conditions: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter_conditions") + default=None, + metadata=dc_config(field_name="filter_conditions"), ) @@ -10610,7 +11788,7 @@ class QueryUserFeedbackRequest(DataClassJsonMixin): class QueryUserFeedbackResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) user_feedback: "List[UserFeedbackResponse]" = dc_field( - metadata=dc_config(field_name="user_feedback") + metadata=dc_config(field_name="user_feedback"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) @@ -10619,28 +11797,35 @@ class QueryUserFeedbackResponse(DataClassJsonMixin): @dataclass class QueryUsersPayload(DataClassJsonMixin): filter_conditions: Dict[str, object] = dc_field( - metadata=dc_config(field_name="filter_conditions") + metadata=dc_config(field_name="filter_conditions"), ) include_deactivated_users: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="include_deactivated_users") + default=None, + metadata=dc_config(field_name="include_deactivated_users"), ) limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) offset: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="offset") + default=None, + metadata=dc_config(field_name="offset"), ) presence: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="presence") + default=None, + metadata=dc_config(field_name="presence"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10655,26 +11840,32 @@ class RTMPBroadcastRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) stream_url: str = dc_field(metadata=dc_config(field_name="stream_url")) quality: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) stream_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="stream_key") + default=None, + metadata=dc_config(field_name="stream_key"), ) layout: "Optional[LayoutSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="layout") + default=None, + metadata=dc_config(field_name="layout"), ) @dataclass class RTMPEgressConfig(DataClassJsonMixin): rtmp_location: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="rtmp_location") + default=None, + metadata=dc_config(field_name="rtmp_location"), ) composite_app_settings: "Optional[CompositeAppSettings]" = dc_field( - default=None, metadata=dc_config(field_name="composite_app_settings") + default=None, + metadata=dc_config(field_name="composite_app_settings"), ) quality: "Optional[Quality]" = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) @@ -10694,26 +11885,32 @@ class RTMPLocation(DataClassJsonMixin): class RTMPSettings(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) quality_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quality_name") + default=None, + metadata=dc_config(field_name="quality_name"), ) layout: "Optional[LayoutSettings]" = dc_field( - default=None, metadata=dc_config(field_name="layout") + default=None, + metadata=dc_config(field_name="layout"), ) location: "Optional[RTMPLocation]" = dc_field( - default=None, metadata=dc_config(field_name="location") + default=None, + metadata=dc_config(field_name="location"), ) @dataclass class RTMPSettingsRequest(DataClassJsonMixin): enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) quality: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) layout: "Optional[LayoutSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="layout") + default=None, + metadata=dc_config(field_name="layout"), ) @@ -10732,7 +11929,7 @@ class Reaction(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) score: int = dc_field(metadata=dc_config(field_name="score")) @@ -10743,14 +11940,16 @@ class Reaction(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10765,23 +11964,28 @@ class ReactionDeletedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="reaction.deleted", metadata=dc_config(field_name="type") + default="reaction.deleted", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) reaction: "Optional[Reaction]" = dc_field( - default=None, metadata=dc_config(field_name="reaction") + default=None, + metadata=dc_config(field_name="reaction"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10794,7 +11998,7 @@ class ReactionGroupResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) last_reaction_at: datetime = dc_field( metadata=dc_config( @@ -10802,7 +12006,7 @@ class ReactionGroupResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) sum_scores: int = dc_field(metadata=dc_config(field_name="sum_scores")) @@ -10818,21 +12022,25 @@ class ReactionNewEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(default="reaction.new", metadata=dc_config(field_name="type")) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_participants: "Optional[List[User]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) reaction: "Optional[Reaction]" = dc_field( - default=None, metadata=dc_config(field_name="reaction") + default=None, + metadata=dc_config(field_name="reaction"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10849,7 +12057,8 @@ class ReactionRequest(DataClassJsonMixin): ), ) score: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="score") + default=None, + metadata=dc_config(field_name="score"), ) updated_at: Optional[datetime] = dc_field( default=None, @@ -10861,13 +12070,16 @@ class ReactionRequest(DataClassJsonMixin): ), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10879,7 +12091,7 @@ class ReactionResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) score: int = dc_field(metadata=dc_config(field_name="score")) @@ -10890,7 +12102,7 @@ class ReactionResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) @@ -10908,27 +12120,31 @@ class ReactionUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message: "Message" = dc_field(metadata=dc_config(field_name="message")) reaction: "Reaction" = dc_field(metadata=dc_config(field_name="reaction")) type: str = dc_field( - default="reaction.updated", metadata=dc_config(field_name="type") + default="reaction.updated", + metadata=dc_config(field_name="type"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class ReactivateUserRequest(DataClassJsonMixin): created_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="created_by_id") + default=None, + metadata=dc_config(field_name="created_by_id"), ) name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) restore_messages: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="restore_messages") + default=None, + metadata=dc_config(field_name="restore_messages"), ) @@ -10936,7 +12152,8 @@ class ReactivateUserRequest(DataClassJsonMixin): class ReactivateUserResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -10944,13 +12161,16 @@ class ReactivateUserResponse(DataClassJsonMixin): class ReactivateUsersRequest(DataClassJsonMixin): user_ids: List[str] = dc_field(metadata=dc_config(field_name="user_ids")) created_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="created_by_id") + default=None, + metadata=dc_config(field_name="created_by_id"), ) restore_channels: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="restore_channels") + default=None, + metadata=dc_config(field_name="restore_channels"), ) restore_messages: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="restore_messages") + default=None, + metadata=dc_config(field_name="restore_messages"), ) @@ -10968,7 +12188,8 @@ class ReadReceipts(DataClassJsonMixin): @dataclass class ReadReceiptsResponse(DataClassJsonMixin): enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) @@ -10980,12 +12201,13 @@ class ReadStateResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) unread_messages: int = dc_field(metadata=dc_config(field_name="unread_messages")) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) last_read_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="last_read_message_id") + default=None, + metadata=dc_config(field_name="last_read_message_id"), ) @@ -10993,13 +12215,16 @@ class ReadStateResponse(DataClassJsonMixin): class RecordSettings(DataClassJsonMixin): mode: str = dc_field(metadata=dc_config(field_name="mode")) audio_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="audio_only") + default=None, + metadata=dc_config(field_name="audio_only"), ) quality: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) layout: "Optional[LayoutSettings]" = dc_field( - default=None, metadata=dc_config(field_name="layout") + default=None, + metadata=dc_config(field_name="layout"), ) @@ -11007,13 +12232,16 @@ class RecordSettings(DataClassJsonMixin): class RecordSettingsRequest(DataClassJsonMixin): mode: str = dc_field(metadata=dc_config(field_name="mode")) audio_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="audio_only") + default=None, + metadata=dc_config(field_name="audio_only"), ) quality: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) layout: "Optional[LayoutSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="layout") + default=None, + metadata=dc_config(field_name="layout"), ) @@ -11028,22 +12256,28 @@ class RecordSettingsResponse(DataClassJsonMixin): @dataclass class RecordingEgressConfig(DataClassJsonMixin): audio_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="audio_only") + default=None, + metadata=dc_config(field_name="audio_only"), ) storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="storage_name") + default=None, + metadata=dc_config(field_name="storage_name"), ) composite_app_settings: "Optional[CompositeAppSettings]" = dc_field( - default=None, metadata=dc_config(field_name="composite_app_settings") + default=None, + metadata=dc_config(field_name="composite_app_settings"), ) external_storage: "Optional[ExternalStorage]" = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) quality: "Optional[Quality]" = dc_field( - default=None, metadata=dc_config(field_name="quality") + default=None, + metadata=dc_config(field_name="quality"), ) video_orientation_hint: "Optional[VideoOrientation]" = dc_field( - default=None, metadata=dc_config(field_name="video_orientation_hint") + default=None, + metadata=dc_config(field_name="video_orientation_hint"), ) @@ -11056,7 +12290,7 @@ class ReminderResponseData(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) updated_at: datetime = dc_field( @@ -11065,7 +12299,7 @@ class ReminderResponseData(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) remind_at: Optional[datetime] = dc_field( @@ -11078,13 +12312,16 @@ class ReminderResponseData(DataClassJsonMixin): ), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -11094,10 +12331,12 @@ class ReportByHistogramBucket(DataClassJsonMixin): count: int = dc_field(metadata=dc_config(field_name="count")) sum: float = dc_field(metadata=dc_config(field_name="sum")) lower_bound: "Optional[Bound]" = dc_field( - default=None, metadata=dc_config(field_name="lower_bound") + default=None, + metadata=dc_config(field_name="lower_bound"), ) upper_bound: "Optional[Bound]" = dc_field( - default=None, metadata=dc_config(field_name="upper_bound") + default=None, + metadata=dc_config(field_name="upper_bound"), ) @@ -11105,10 +12344,10 @@ class ReportByHistogramBucket(DataClassJsonMixin): class ReportResponse(DataClassJsonMixin): call: "CallReportResponse" = dc_field(metadata=dc_config(field_name="call")) participants: "ParticipantReportResponse" = dc_field( - metadata=dc_config(field_name="participants") + metadata=dc_config(field_name="participants"), ) user_ratings: "UserRatingReportResponse" = dc_field( - metadata=dc_config(field_name="user_ratings") + metadata=dc_config(field_name="user_ratings"), ) @@ -11139,7 +12378,7 @@ class ReviewQueueItem(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) @@ -11149,10 +12388,10 @@ class ReviewQueueItem(DataClassJsonMixin): has_video: bool = dc_field(metadata=dc_config(field_name="has_video")) id: str = dc_field(metadata=dc_config(field_name="id")) moderation_payload_hash: str = dc_field( - metadata=dc_config(field_name="moderation_payload_hash") + metadata=dc_config(field_name="moderation_payload_hash"), ) recommended_action: str = dc_field( - metadata=dc_config(field_name="recommended_action") + metadata=dc_config(field_name="recommended_action"), ) reviewed_by: str = dc_field(metadata=dc_config(field_name="reviewed_by")) severity: int = dc_field(metadata=dc_config(field_name="severity")) @@ -11163,7 +12402,7 @@ class ReviewQueueItem(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) actions: "List[ActionLog]" = dc_field(metadata=dc_config(field_name="actions")) bans: "List[Ban]" = dc_field(metadata=dc_config(field_name="bans")) @@ -11173,31 +12412,40 @@ class ReviewQueueItem(DataClassJsonMixin): completed_at: "NullTime" = dc_field(metadata=dc_config(field_name="completed_at")) reviewed_at: "NullTime" = dc_field(metadata=dc_config(field_name="reviewed_at")) activity: "Optional[EnrichedActivity]" = dc_field( - default=None, metadata=dc_config(field_name="activity") + default=None, + metadata=dc_config(field_name="activity"), ) assigned_to: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="assigned_to") + default=None, + metadata=dc_config(field_name="assigned_to"), ) call: "Optional[Call]" = dc_field( - default=None, metadata=dc_config(field_name="call") + default=None, + metadata=dc_config(field_name="call"), ) entity_creator: "Optional[EntityCreator]" = dc_field( - default=None, metadata=dc_config(field_name="entity_creator") + default=None, + metadata=dc_config(field_name="entity_creator"), ) feeds_v2_activity: "Optional[EnrichedActivity]" = dc_field( - default=None, metadata=dc_config(field_name="feeds_v2_activity") + default=None, + metadata=dc_config(field_name="feeds_v2_activity"), ) feeds_v2_reaction: "Optional[Reaction]" = dc_field( - default=None, metadata=dc_config(field_name="feeds_v2_reaction") + default=None, + metadata=dc_config(field_name="feeds_v2_reaction"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") + default=None, + metadata=dc_config(field_name="moderation_payload"), ) reaction: "Optional[Reaction]" = dc_field( - default=None, metadata=dc_config(field_name="reaction") + default=None, + metadata=dc_config(field_name="reaction"), ) @@ -11209,11 +12457,12 @@ class ReviewQueueItemNewEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="review_queue_item.new", metadata=dc_config(field_name="type") + default="review_queue_item.new", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -11225,13 +12474,16 @@ class ReviewQueueItemNewEvent(DataClassJsonMixin): ), ) flags: "Optional[List[FlagResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="flags") + default=None, + metadata=dc_config(field_name="flags"), ) action: "Optional[ActionLogResponse]" = dc_field( - default=None, metadata=dc_config(field_name="action") + default=None, + metadata=dc_config(field_name="action"), ) review_queue_item: "Optional[ReviewQueueItemResponse]" = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item") + default=None, + metadata=dc_config(field_name="review_queue_item"), ) @@ -11244,14 +12496,14 @@ class ReviewQueueItemResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) flags_count: int = dc_field(metadata=dc_config(field_name="flags_count")) id: str = dc_field(metadata=dc_config(field_name="id")) recommended_action: str = dc_field( - metadata=dc_config(field_name="recommended_action") + metadata=dc_config(field_name="recommended_action"), ) reviewed_by: str = dc_field(metadata=dc_config(field_name="reviewed_by")) severity: int = dc_field(metadata=dc_config(field_name="severity")) @@ -11262,10 +12514,10 @@ class ReviewQueueItemResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) actions: "List[ActionLogResponse]" = dc_field( - metadata=dc_config(field_name="actions") + metadata=dc_config(field_name="actions"), ) bans: "List[Ban]" = dc_field(metadata=dc_config(field_name="bans")) flags: "List[FlagResponse]" = dc_field(metadata=dc_config(field_name="flags")) @@ -11280,10 +12532,12 @@ class ReviewQueueItemResponse(DataClassJsonMixin): ), ) config_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="config_key") + default=None, + metadata=dc_config(field_name="config_key"), ) entity_creator_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="entity_creator_id") + default=None, + metadata=dc_config(field_name="entity_creator_id"), ) reviewed_at: Optional[datetime] = dc_field( default=None, @@ -11295,34 +12549,44 @@ class ReviewQueueItemResponse(DataClassJsonMixin): ), ) teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") + default=None, + metadata=dc_config(field_name="teams"), ) activity: "Optional[EnrichedActivity]" = dc_field( - default=None, metadata=dc_config(field_name="activity") + default=None, + metadata=dc_config(field_name="activity"), ) assigned_to: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="assigned_to") + default=None, + metadata=dc_config(field_name="assigned_to"), ) call: "Optional[CallResponse]" = dc_field( - default=None, metadata=dc_config(field_name="call") + default=None, + metadata=dc_config(field_name="call"), ) entity_creator: "Optional[EntityCreatorResponse]" = dc_field( - default=None, metadata=dc_config(field_name="entity_creator") + default=None, + metadata=dc_config(field_name="entity_creator"), ) feeds_v2_activity: "Optional[EnrichedActivity]" = dc_field( - default=None, metadata=dc_config(field_name="feeds_v2_activity") + default=None, + metadata=dc_config(field_name="feeds_v2_activity"), ) feeds_v2_reaction: "Optional[Reaction]" = dc_field( - default=None, metadata=dc_config(field_name="feeds_v2_reaction") + default=None, + metadata=dc_config(field_name="feeds_v2_reaction"), ) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") + default=None, + metadata=dc_config(field_name="moderation_payload"), ) reaction: "Optional[Reaction]" = dc_field( - default=None, metadata=dc_config(field_name="reaction") + default=None, + metadata=dc_config(field_name="reaction"), ) @@ -11334,11 +12598,12 @@ class ReviewQueueItemUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="review_queue_item.updated", metadata=dc_config(field_name="type") + default="review_queue_item.updated", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -11350,52 +12615,56 @@ class ReviewQueueItemUpdatedEvent(DataClassJsonMixin): ), ) flags: "Optional[List[FlagResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="flags") + default=None, + metadata=dc_config(field_name="flags"), ) action: "Optional[ActionLogResponse]" = dc_field( - default=None, metadata=dc_config(field_name="action") + default=None, + metadata=dc_config(field_name="action"), ) review_queue_item: "Optional[ReviewQueueItemResponse]" = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item") + default=None, + metadata=dc_config(field_name="review_queue_item"), ) @dataclass class RingSettings(DataClassJsonMixin): auto_cancel_timeout_ms: int = dc_field( - metadata=dc_config(field_name="auto_cancel_timeout_ms") + metadata=dc_config(field_name="auto_cancel_timeout_ms"), ) incoming_call_timeout_ms: int = dc_field( - metadata=dc_config(field_name="incoming_call_timeout_ms") + metadata=dc_config(field_name="incoming_call_timeout_ms"), ) missed_call_timeout_ms: int = dc_field( - metadata=dc_config(field_name="missed_call_timeout_ms") + metadata=dc_config(field_name="missed_call_timeout_ms"), ) @dataclass class RingSettingsRequest(DataClassJsonMixin): auto_cancel_timeout_ms: int = dc_field( - metadata=dc_config(field_name="auto_cancel_timeout_ms") + metadata=dc_config(field_name="auto_cancel_timeout_ms"), ) incoming_call_timeout_ms: int = dc_field( - metadata=dc_config(field_name="incoming_call_timeout_ms") + metadata=dc_config(field_name="incoming_call_timeout_ms"), ) missed_call_timeout_ms: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="missed_call_timeout_ms") + default=None, + metadata=dc_config(field_name="missed_call_timeout_ms"), ) @dataclass class RingSettingsResponse(DataClassJsonMixin): auto_cancel_timeout_ms: int = dc_field( - metadata=dc_config(field_name="auto_cancel_timeout_ms") + metadata=dc_config(field_name="auto_cancel_timeout_ms"), ) incoming_call_timeout_ms: int = dc_field( - metadata=dc_config(field_name="incoming_call_timeout_ms") + metadata=dc_config(field_name="incoming_call_timeout_ms"), ) missed_call_timeout_ms: int = dc_field( - metadata=dc_config(field_name="missed_call_timeout_ms") + metadata=dc_config(field_name="missed_call_timeout_ms"), ) @@ -11407,7 +12676,7 @@ class Role(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: bool = dc_field(metadata=dc_config(field_name="custom")) name: str = dc_field(metadata=dc_config(field_name="name")) @@ -11417,7 +12686,7 @@ class Role(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) scopes: List[str] = dc_field(metadata=dc_config(field_name="scopes")) @@ -11425,16 +12694,20 @@ class Role(DataClassJsonMixin): @dataclass class RuleBuilderAction(DataClassJsonMixin): duration: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="duration") + default=None, + metadata=dc_config(field_name="duration"), ) ip_ban: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="ip_ban") + default=None, + metadata=dc_config(field_name="ip_ban"), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) shadow_ban: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="shadow_ban") + default=None, + metadata=dc_config(field_name="shadow_ban"), ) type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) @@ -11442,44 +12715,54 @@ class RuleBuilderAction(DataClassJsonMixin): @dataclass class RuleBuilderCondition(DataClassJsonMixin): provider: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="provider") + default=None, + metadata=dc_config(field_name="provider"), ) threshold: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="threshold") + default=None, + metadata=dc_config(field_name="threshold"), ) time_window: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="time_window") + default=None, + metadata=dc_config(field_name="time_window"), ) labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="labels") + default=None, + metadata=dc_config(field_name="labels"), ) @dataclass class RuleBuilderConfig(DataClassJsonMixin): _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) rules: "Optional[List[RuleBuilderRule]]" = dc_field( - default=None, metadata=dc_config(field_name="rules") + default=None, + metadata=dc_config(field_name="rules"), ) @dataclass class RuleBuilderRule(DataClassJsonMixin): enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) conditions: "Optional[List[RuleBuilderCondition]]" = dc_field( - default=None, metadata=dc_config(field_name="conditions") + default=None, + metadata=dc_config(field_name="conditions"), ) action: "Optional[RuleBuilderAction]" = dc_field( - default=None, metadata=dc_config(field_name="action") + default=None, + metadata=dc_config(field_name="action"), ) @@ -11487,27 +12770,30 @@ class RuleBuilderRule(DataClassJsonMixin): class S3Request(DataClassJsonMixin): s3_region: str = dc_field(metadata=dc_config(field_name="s3_region")) s3_api_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_api_key") + default=None, + metadata=dc_config(field_name="s3_api_key"), ) s3_custom_endpoint_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_custom_endpoint_url") + default=None, + metadata=dc_config(field_name="s3_custom_endpoint_url"), ) s3_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_secret") + default=None, + metadata=dc_config(field_name="s3_secret"), ) @dataclass class SDKUsageReport(DataClassJsonMixin): per_sdk_usage: "Dict[str, Optional[PerSDKUsageReport]]" = dc_field( - metadata=dc_config(field_name="per_sdk_usage") + metadata=dc_config(field_name="per_sdk_usage"), ) @dataclass class SDKUsageReportResponse(DataClassJsonMixin): daily: "List[DailyAggregateSDKUsageReportResponse]" = dc_field( - metadata=dc_config(field_name="daily") + metadata=dc_config(field_name="daily"), ) @@ -11520,99 +12806,118 @@ class SFUIDLastSeen(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) process_start_time: int = dc_field( - metadata=dc_config(field_name="process_start_time") + metadata=dc_config(field_name="process_start_time"), ) @dataclass class STTEgressConfig(DataClassJsonMixin): closed_captions_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="closed_captions_enabled") + default=None, + metadata=dc_config(field_name="closed_captions_enabled"), ) language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + default=None, + metadata=dc_config(field_name="language"), ) storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="storage_name") + default=None, + metadata=dc_config(field_name="storage_name"), ) translations_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="translations_enabled") + default=None, + metadata=dc_config(field_name="translations_enabled"), ) upload_transcriptions: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="upload_transcriptions") + default=None, + metadata=dc_config(field_name="upload_transcriptions"), ) whisper_server_base_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="whisper_server_base_url") + default=None, + metadata=dc_config(field_name="whisper_server_base_url"), ) translation_languages: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="translation_languages") + default=None, + metadata=dc_config(field_name="translation_languages"), ) external_storage: "Optional[ExternalStorage]" = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) @dataclass class ScreensharingSettings(DataClassJsonMixin): access_request_enabled: bool = dc_field( - metadata=dc_config(field_name="access_request_enabled") + metadata=dc_config(field_name="access_request_enabled"), ) enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) target_resolution: "Optional[TargetResolution]" = dc_field( - default=None, metadata=dc_config(field_name="target_resolution") + default=None, + metadata=dc_config(field_name="target_resolution"), ) @dataclass class ScreensharingSettingsRequest(DataClassJsonMixin): access_request_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="access_request_enabled") + default=None, + metadata=dc_config(field_name="access_request_enabled"), ) enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) target_resolution: "Optional[TargetResolution]" = dc_field( - default=None, metadata=dc_config(field_name="target_resolution") + default=None, + metadata=dc_config(field_name="target_resolution"), ) @dataclass class ScreensharingSettingsResponse(DataClassJsonMixin): access_request_enabled: bool = dc_field( - metadata=dc_config(field_name="access_request_enabled") + metadata=dc_config(field_name="access_request_enabled"), ) enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) target_resolution: "Optional[TargetResolution]" = dc_field( - default=None, metadata=dc_config(field_name="target_resolution") + default=None, + metadata=dc_config(field_name="target_resolution"), ) @dataclass class SearchPayload(DataClassJsonMixin): filter_conditions: Dict[str, object] = dc_field( - metadata=dc_config(field_name="filter_conditions") + metadata=dc_config(field_name="filter_conditions"), ) limit: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="limit") + default=None, + metadata=dc_config(field_name="limit"), ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) offset: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="offset") + default=None, + metadata=dc_config(field_name="offset"), ) query: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="query") + default=None, + metadata=dc_config(field_name="query"), ) sort: "Optional[List[SortParamRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="sort") + default=None, + metadata=dc_config(field_name="sort"), ) message_filter_conditions: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="message_filter_conditions") + default=None, + metadata=dc_config(field_name="message_filter_conditions"), ) message_options: "Optional[MessageOptions]" = dc_field( - default=None, metadata=dc_config(field_name="message_options") + default=None, + metadata=dc_config(field_name="message_options"), ) @@ -11622,17 +12927,20 @@ class SearchResponse(DataClassJsonMixin): results: "List[SearchResult]" = dc_field(metadata=dc_config(field_name="results")) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) previous: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="previous") + default=None, + metadata=dc_config(field_name="previous"), ) results_warning: "Optional[SearchWarning]" = dc_field( - default=None, metadata=dc_config(field_name="results_warning") + default=None, + metadata=dc_config(field_name="results_warning"), ) @dataclass class SearchResult(DataClassJsonMixin): message: "Optional[SearchResultMessage]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) @@ -11645,10 +12953,10 @@ class SearchResultMessage(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) deleted_reply_count: int = dc_field( - metadata=dc_config(field_name="deleted_reply_count") + metadata=dc_config(field_name="deleted_reply_count"), ) html: str = dc_field(metadata=dc_config(field_name="html")) id: str = dc_field(metadata=dc_config(field_name="id")) @@ -11664,33 +12972,34 @@ class SearchResultMessage(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) attachments: "List[Attachment]" = dc_field( - metadata=dc_config(field_name="attachments") + metadata=dc_config(field_name="attachments"), ) latest_reactions: "List[ReactionResponse]" = dc_field( - metadata=dc_config(field_name="latest_reactions") + metadata=dc_config(field_name="latest_reactions"), ) mentioned_users: "List[UserResponse]" = dc_field( - metadata=dc_config(field_name="mentioned_users") + metadata=dc_config(field_name="mentioned_users"), ) own_reactions: "List[ReactionResponse]" = dc_field( - metadata=dc_config(field_name="own_reactions") + metadata=dc_config(field_name="own_reactions"), ) restricted_visibility: List[str] = dc_field( - metadata=dc_config(field_name="restricted_visibility") + metadata=dc_config(field_name="restricted_visibility"), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) reaction_counts: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_counts") + metadata=dc_config(field_name="reaction_counts"), ) reaction_scores: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="reaction_scores") + metadata=dc_config(field_name="reaction_scores"), ) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) command: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="command") + default=None, + metadata=dc_config(field_name="command"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -11712,7 +13021,8 @@ class SearchResultMessage(DataClassJsonMixin): ) mml: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="mml")) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) pin_expires: Optional[datetime] = dc_field( default=None, @@ -11733,49 +13043,64 @@ class SearchResultMessage(DataClassJsonMixin): ), ) poll_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="poll_id") + default=None, + metadata=dc_config(field_name="poll_id"), ) quoted_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="quoted_message_id") + default=None, + metadata=dc_config(field_name="quoted_message_id"), ) show_in_channel: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="show_in_channel") + default=None, + metadata=dc_config(field_name="show_in_channel"), ) thread_participants: "Optional[List[UserResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) draft: "Optional[DraftResponse]" = dc_field( - default=None, metadata=dc_config(field_name="draft") + default=None, + metadata=dc_config(field_name="draft"), ) i18n: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="i18n") + default=None, + metadata=dc_config(field_name="i18n"), ) image_labels: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="image_labels") + default=None, + metadata=dc_config(field_name="image_labels"), ) moderation: "Optional[ModerationV2Response]" = dc_field( - default=None, metadata=dc_config(field_name="moderation") + default=None, + metadata=dc_config(field_name="moderation"), ) pinned_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="pinned_by") + default=None, + metadata=dc_config(field_name="pinned_by"), ) poll: "Optional[PollResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="poll") + default=None, + metadata=dc_config(field_name="poll"), ) quoted_message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="quoted_message") + default=None, + metadata=dc_config(field_name="quoted_message"), ) reaction_groups: "Optional[Dict[str, Optional[ReactionGroupResponse]]]" = dc_field( - default=None, metadata=dc_config(field_name="reaction_groups") + default=None, + metadata=dc_config(field_name="reaction_groups"), ) reminder: "Optional[ReminderResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="reminder") + default=None, + metadata=dc_config(field_name="reminder"), ) shared_location: "Optional[SharedLocationResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="shared_location") + default=None, + metadata=dc_config(field_name="shared_location"), ) @@ -11783,20 +13108,22 @@ class SearchResultMessage(DataClassJsonMixin): class SearchWarning(DataClassJsonMixin): warning_code: int = dc_field(metadata=dc_config(field_name="warning_code")) warning_description: str = dc_field( - metadata=dc_config(field_name="warning_description") + metadata=dc_config(field_name="warning_description"), ) channel_search_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="channel_search_count") + default=None, + metadata=dc_config(field_name="channel_search_count"), ) channel_search_cids: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="channel_search_cids") + default=None, + metadata=dc_config(field_name="channel_search_cids"), ) @dataclass class Segment(DataClassJsonMixin): all_sender_channels: bool = dc_field( - metadata=dc_config(field_name="all_sender_channels") + metadata=dc_config(field_name="all_sender_channels"), ) all_users: bool = dc_field(metadata=dc_config(field_name="all_users")) created_at: datetime = dc_field( @@ -11805,7 +13132,7 @@ class Segment(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) name: str = dc_field(metadata=dc_config(field_name="name")) @@ -11817,7 +13144,7 @@ class Segment(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -11829,20 +13156,23 @@ class Segment(DataClassJsonMixin): ), ) description: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="description") + default=None, + metadata=dc_config(field_name="description"), ) task_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="task_id") + default=None, + metadata=dc_config(field_name="task_id"), ) filter: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="filter") + default=None, + metadata=dc_config(field_name="filter"), ) @dataclass class SegmentResponse(DataClassJsonMixin): all_sender_channels: bool = dc_field( - metadata=dc_config(field_name="all_sender_channels") + metadata=dc_config(field_name="all_sender_channels"), ) all_users: bool = dc_field(metadata=dc_config(field_name="all_users")) created_at: datetime = dc_field( @@ -11851,7 +13181,7 @@ class SegmentResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) deleted_at: datetime = dc_field( metadata=dc_config( @@ -11859,7 +13189,7 @@ class SegmentResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) description: str = dc_field(metadata=dc_config(field_name="description")) id: str = dc_field(metadata=dc_config(field_name="id")) @@ -11872,7 +13202,7 @@ class SegmentResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) filter: Dict[str, object] = dc_field(metadata=dc_config(field_name="filter")) @@ -11886,7 +13216,7 @@ class SegmentTargetResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) segment_id: str = dc_field(metadata=dc_config(field_name="segment_id")) target_id: str = dc_field(metadata=dc_config(field_name="target_id")) @@ -11895,13 +13225,16 @@ class SegmentTargetResponse(DataClassJsonMixin): @dataclass class SendCallEventRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -11919,22 +13252,28 @@ class SendEventRequest(DataClassJsonMixin): class SendMessageRequest(DataClassJsonMixin): message: "MessageRequest" = dc_field(metadata=dc_config(field_name="message")) force_moderation: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="force_moderation") + default=None, + metadata=dc_config(field_name="force_moderation"), ) keep_channel_hidden: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="keep_channel_hidden") + default=None, + metadata=dc_config(field_name="keep_channel_hidden"), ) pending: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="pending") + default=None, + metadata=dc_config(field_name="pending"), ) skip_enrich_url: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_enrich_url") + default=None, + metadata=dc_config(field_name="skip_enrich_url"), ) skip_push: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_push") + default=None, + metadata=dc_config(field_name="skip_push"), ) pending_message_metadata: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="pending_message_metadata") + default=None, + metadata=dc_config(field_name="pending_message_metadata"), ) @@ -11943,7 +13282,8 @@ class SendMessageResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) pending_message_metadata: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="pending_message_metadata") + default=None, + metadata=dc_config(field_name="pending_message_metadata"), ) @@ -11951,10 +13291,12 @@ class SendMessageResponse(DataClassJsonMixin): class SendReactionRequest(DataClassJsonMixin): reaction: "ReactionRequest" = dc_field(metadata=dc_config(field_name="reaction")) enforce_unique: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enforce_unique") + default=None, + metadata=dc_config(field_name="enforce_unique"), ) skip_push: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_push") + default=None, + metadata=dc_config(field_name="skip_push"), ) @@ -11973,21 +13315,21 @@ class SendUserCustomEventRequest(DataClassJsonMixin): @dataclass class SessionSettings(DataClassJsonMixin): inactivity_timeout_seconds: int = dc_field( - metadata=dc_config(field_name="inactivity_timeout_seconds") + metadata=dc_config(field_name="inactivity_timeout_seconds"), ) @dataclass class SessionSettingsRequest(DataClassJsonMixin): inactivity_timeout_seconds: int = dc_field( - metadata=dc_config(field_name="inactivity_timeout_seconds") + metadata=dc_config(field_name="inactivity_timeout_seconds"), ) @dataclass class SessionSettingsResponse(DataClassJsonMixin): inactivity_timeout_seconds: int = dc_field( - metadata=dc_config(field_name="inactivity_timeout_seconds") + metadata=dc_config(field_name="inactivity_timeout_seconds"), ) @@ -12005,10 +13347,10 @@ class SharedLocation(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_device_id: str = dc_field( - metadata=dc_config(field_name="created_by_device_id") + metadata=dc_config(field_name="created_by_device_id"), ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) updated_at: datetime = dc_field( @@ -12017,7 +13359,7 @@ class SharedLocation(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) end_at: Optional[datetime] = dc_field( @@ -12030,16 +13372,20 @@ class SharedLocation(DataClassJsonMixin): ), ) latitude: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="latitude") + default=None, + metadata=dc_config(field_name="latitude"), ) longitude: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="longitude") + default=None, + metadata=dc_config(field_name="longitude"), ) channel: "Optional[Channel]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) @@ -12052,10 +13398,10 @@ class SharedLocationResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_device_id: str = dc_field( - metadata=dc_config(field_name="created_by_device_id") + metadata=dc_config(field_name="created_by_device_id"), ) duration: str = dc_field(metadata=dc_config(field_name="duration")) latitude: float = dc_field(metadata=dc_config(field_name="latitude")) @@ -12067,7 +13413,7 @@ class SharedLocationResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) end_at: Optional[datetime] = dc_field( @@ -12080,10 +13426,12 @@ class SharedLocationResponse(DataClassJsonMixin): ), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) @@ -12096,10 +13444,10 @@ class SharedLocationResponseData(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_device_id: str = dc_field( - metadata=dc_config(field_name="created_by_device_id") + metadata=dc_config(field_name="created_by_device_id"), ) latitude: float = dc_field(metadata=dc_config(field_name="latitude")) longitude: float = dc_field(metadata=dc_config(field_name="longitude")) @@ -12110,7 +13458,7 @@ class SharedLocationResponseData(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) end_at: Optional[datetime] = dc_field( @@ -12123,10 +13471,12 @@ class SharedLocationResponseData(DataClassJsonMixin): ), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) @@ -12134,17 +13484,19 @@ class SharedLocationResponseData(DataClassJsonMixin): class SharedLocationsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) active_live_locations: "List[SharedLocationResponseData]" = dc_field( - metadata=dc_config(field_name="active_live_locations") + metadata=dc_config(field_name="active_live_locations"), ) @dataclass class ShowChannelRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -12156,20 +13508,24 @@ class ShowChannelResponse(DataClassJsonMixin): @dataclass class SortParam(DataClassJsonMixin): direction: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="direction") + default=None, + metadata=dc_config(field_name="direction"), ) field: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="field") + default=None, + metadata=dc_config(field_name="field"), ) @dataclass class SortParamRequest(DataClassJsonMixin): direction: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="direction") + default=None, + metadata=dc_config(field_name="direction"), ) field: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="field") + default=None, + metadata=dc_config(field_name="field"), ) @@ -12199,23 +13555,28 @@ class StartCampaignRequest(DataClassJsonMixin): class StartCampaignResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) campaign: "Optional[CampaignResponse]" = dc_field( - default=None, metadata=dc_config(field_name="campaign") + default=None, + metadata=dc_config(field_name="campaign"), ) users: "Optional[PagerResponse]" = dc_field( - default=None, metadata=dc_config(field_name="users") + default=None, + metadata=dc_config(field_name="users"), ) @dataclass class StartClosedCaptionsRequest(DataClassJsonMixin): enable_transcription: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enable_transcription") + default=None, + metadata=dc_config(field_name="enable_transcription"), ) external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + default=None, + metadata=dc_config(field_name="language"), ) @@ -12227,7 +13588,8 @@ class StartClosedCaptionsResponse(DataClassJsonMixin): @dataclass class StartFrameRecordingRequest(DataClassJsonMixin): recording_external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="recording_external_storage") + default=None, + metadata=dc_config(field_name="recording_external_storage"), ) @@ -12250,7 +13612,7 @@ class StartHLSBroadcastingResponse(DataClassJsonMixin): @dataclass class StartRTMPBroadcastsRequest(DataClassJsonMixin): broadcasts: "List[RTMPBroadcastRequest]" = dc_field( - metadata=dc_config(field_name="broadcasts") + metadata=dc_config(field_name="broadcasts"), ) @@ -12262,7 +13624,8 @@ class StartRTMPBroadcastsResponse(DataClassJsonMixin): @dataclass class StartRecordingRequest(DataClassJsonMixin): recording_external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="recording_external_storage") + default=None, + metadata=dc_config(field_name="recording_external_storage"), ) @@ -12274,13 +13637,16 @@ class StartRecordingResponse(DataClassJsonMixin): @dataclass class StartTranscriptionRequest(DataClassJsonMixin): enable_closed_captions: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enable_closed_captions") + default=None, + metadata=dc_config(field_name="enable_closed_captions"), ) language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + default=None, + metadata=dc_config(field_name="language"), ) transcription_external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="transcription_external_storage") + default=None, + metadata=dc_config(field_name="transcription_external_storage"), ) @@ -12307,7 +13673,8 @@ class StopCampaignRequest(DataClassJsonMixin): @dataclass class StopClosedCaptionsRequest(DataClassJsonMixin): stop_transcription: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="stop_transcription") + default=None, + metadata=dc_config(field_name="stop_transcription"), ) @@ -12339,19 +13706,24 @@ class StopHLSBroadcastingResponse(DataClassJsonMixin): @dataclass class StopLiveRequest(DataClassJsonMixin): continue_closed_caption: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="continue_closed_caption") + default=None, + metadata=dc_config(field_name="continue_closed_caption"), ) continue_hls: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="continue_hls") + default=None, + metadata=dc_config(field_name="continue_hls"), ) continue_recording: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="continue_recording") + default=None, + metadata=dc_config(field_name="continue_recording"), ) continue_rtmp_broadcasts: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="continue_rtmp_broadcasts") + default=None, + metadata=dc_config(field_name="continue_rtmp_broadcasts"), ) continue_transcription: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="continue_transcription") + default=None, + metadata=dc_config(field_name="continue_transcription"), ) @@ -12384,7 +13756,8 @@ class StopRecordingResponse(DataClassJsonMixin): @dataclass class StopTranscriptionRequest(DataClassJsonMixin): stop_closed_captions: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="stop_closed_captions") + default=None, + metadata=dc_config(field_name="stop_closed_captions"), ) @@ -12398,34 +13771,44 @@ class SubmitActionRequest(DataClassJsonMixin): action_type: str = dc_field(metadata=dc_config(field_name="action_type")) item_id: str = dc_field(metadata=dc_config(field_name="item_id")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) ban: "Optional[BanActionRequest]" = dc_field( - default=None, metadata=dc_config(field_name="ban") + default=None, + metadata=dc_config(field_name="ban"), ) custom: "Optional[CustomActionRequest]" = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) delete_activity: "Optional[DeleteActivityRequest]" = dc_field( - default=None, metadata=dc_config(field_name="delete_activity") + default=None, + metadata=dc_config(field_name="delete_activity"), ) delete_message: "Optional[DeleteMessageRequest]" = dc_field( - default=None, metadata=dc_config(field_name="delete_message") + default=None, + metadata=dc_config(field_name="delete_message"), ) delete_reaction: "Optional[DeleteReactionRequest]" = dc_field( - default=None, metadata=dc_config(field_name="delete_reaction") + default=None, + metadata=dc_config(field_name="delete_reaction"), ) delete_user: "Optional[DeleteUserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="delete_user") + default=None, + metadata=dc_config(field_name="delete_user"), ) mark_reviewed: "Optional[MarkReviewedRequest]" = dc_field( - default=None, metadata=dc_config(field_name="mark_reviewed") + default=None, + metadata=dc_config(field_name="mark_reviewed"), ) unban: "Optional[UnbanActionRequest]" = dc_field( - default=None, metadata=dc_config(field_name="unban") + default=None, + metadata=dc_config(field_name="unban"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -12433,7 +13816,8 @@ class SubmitActionRequest(DataClassJsonMixin): class SubmitActionResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) item: "Optional[ReviewQueueItemResponse]" = dc_field( - default=None, metadata=dc_config(field_name="item") + default=None, + metadata=dc_config(field_name="item"), ) @@ -12441,7 +13825,7 @@ class SubmitActionResponse(DataClassJsonMixin): class SubscriberStatsResponse(DataClassJsonMixin): total: int = dc_field(metadata=dc_config(field_name="total")) total_subscribed_duration_seconds: int = dc_field( - metadata=dc_config(field_name="total_subscribed_duration_seconds") + metadata=dc_config(field_name="total_subscribed_duration_seconds"), ) unique: int = dc_field(metadata=dc_config(field_name="unique")) @@ -12463,7 +13847,7 @@ class ThreadParticipant(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) last_read_at: datetime = dc_field( metadata=dc_config( @@ -12471,7 +13855,7 @@ class ThreadParticipant(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) last_thread_message_at: Optional[datetime] = dc_field( @@ -12493,13 +13877,16 @@ class ThreadParticipant(DataClassJsonMixin): ), ) thread_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thread_id") + default=None, + metadata=dc_config(field_name="thread_id"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -12512,13 +13899,13 @@ class ThreadResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_user_id: str = dc_field( - metadata=dc_config(field_name="created_by_user_id") + metadata=dc_config(field_name="created_by_user_id"), ) parent_message_id: str = dc_field( - metadata=dc_config(field_name="parent_message_id") + metadata=dc_config(field_name="parent_message_id"), ) title: str = dc_field(metadata=dc_config(field_name="title")) updated_at: datetime = dc_field( @@ -12527,11 +13914,12 @@ class ThreadResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) active_participant_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="active_participant_count") + default=None, + metadata=dc_config(field_name="active_participant_count"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -12552,22 +13940,28 @@ class ThreadResponse(DataClassJsonMixin): ), ) participant_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="participant_count") + default=None, + metadata=dc_config(field_name="participant_count"), ) reply_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="reply_count") + default=None, + metadata=dc_config(field_name="reply_count"), ) thread_participants: "Optional[List[ThreadParticipant]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) created_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) parent_message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="parent_message") + default=None, + metadata=dc_config(field_name="parent_message"), ) @@ -12580,13 +13974,13 @@ class ThreadStateResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by_user_id: str = dc_field( - metadata=dc_config(field_name="created_by_user_id") + metadata=dc_config(field_name="created_by_user_id"), ) parent_message_id: str = dc_field( - metadata=dc_config(field_name="parent_message_id") + metadata=dc_config(field_name="parent_message_id"), ) title: str = dc_field(metadata=dc_config(field_name="title")) updated_at: datetime = dc_field( @@ -12595,14 +13989,15 @@ class ThreadStateResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) latest_replies: "List[MessageResponse]" = dc_field( - metadata=dc_config(field_name="latest_replies") + metadata=dc_config(field_name="latest_replies"), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) active_participant_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="active_participant_count") + default=None, + metadata=dc_config(field_name="active_participant_count"), ) deleted_at: Optional[datetime] = dc_field( default=None, @@ -12623,28 +14018,36 @@ class ThreadStateResponse(DataClassJsonMixin): ), ) participant_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="participant_count") + default=None, + metadata=dc_config(field_name="participant_count"), ) reply_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="reply_count") + default=None, + metadata=dc_config(field_name="reply_count"), ) read: "Optional[List[ReadStateResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="read") + default=None, + metadata=dc_config(field_name="read"), ) thread_participants: "Optional[List[ThreadParticipant]]" = dc_field( - default=None, metadata=dc_config(field_name="thread_participants") + default=None, + metadata=dc_config(field_name="thread_participants"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) created_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) draft: "Optional[DraftResponse]" = dc_field( - default=None, metadata=dc_config(field_name="draft") + default=None, + metadata=dc_config(field_name="draft"), ) parent_message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="parent_message") + default=None, + metadata=dc_config(field_name="parent_message"), ) @@ -12659,29 +14062,35 @@ class ThreadUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="thread.updated", metadata=dc_config(field_name="type") + default="thread.updated", + metadata=dc_config(field_name="type"), ) thread: "Optional[ThreadResponse]" = dc_field( - default=None, metadata=dc_config(field_name="thread") + default=None, + metadata=dc_config(field_name="thread"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class Thresholds(DataClassJsonMixin): explicit: "Optional[LabelThresholds]" = dc_field( - default=None, metadata=dc_config(field_name="explicit") + default=None, + metadata=dc_config(field_name="explicit"), ) spam: "Optional[LabelThresholds]" = dc_field( - default=None, metadata=dc_config(field_name="spam") + default=None, + metadata=dc_config(field_name="spam"), ) toxic: "Optional[LabelThresholds]" = dc_field( - default=None, metadata=dc_config(field_name="toxic") + default=None, + metadata=dc_config(field_name="toxic"), ) @@ -12698,7 +14107,8 @@ class ThumbnailsSettings(DataClassJsonMixin): @dataclass class ThumbnailsSettingsRequest(DataClassJsonMixin): enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) @@ -12721,7 +14131,7 @@ class TrackStatsResponse(DataClassJsonMixin): @dataclass class TranscriptionSettings(DataClassJsonMixin): closed_caption_mode: str = dc_field( - metadata=dc_config(field_name="closed_caption_mode") + metadata=dc_config(field_name="closed_caption_mode"), ) language: str = dc_field(metadata=dc_config(field_name="language")) mode: str = dc_field(metadata=dc_config(field_name="mode")) @@ -12731,17 +14141,19 @@ class TranscriptionSettings(DataClassJsonMixin): class TranscriptionSettingsRequest(DataClassJsonMixin): mode: str = dc_field(metadata=dc_config(field_name="mode")) closed_caption_mode: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="closed_caption_mode") + default=None, + metadata=dc_config(field_name="closed_caption_mode"), ) language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + default=None, + metadata=dc_config(field_name="language"), ) @dataclass class TranscriptionSettingsResponse(DataClassJsonMixin): closed_caption_mode: str = dc_field( - metadata=dc_config(field_name="closed_caption_mode") + metadata=dc_config(field_name="closed_caption_mode"), ) language: str = dc_field(metadata=dc_config(field_name="language")) mode: str = dc_field(metadata=dc_config(field_name="mode")) @@ -12755,10 +14167,12 @@ class TranslateMessageRequest(DataClassJsonMixin): @dataclass class TruncateChannelRequest(DataClassJsonMixin): hard_delete: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hard_delete") + default=None, + metadata=dc_config(field_name="hard_delete"), ) skip_push: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_push") + default=None, + metadata=dc_config(field_name="skip_push"), ) truncated_at: Optional[datetime] = dc_field( default=None, @@ -12770,16 +14184,20 @@ class TruncateChannelRequest(DataClassJsonMixin): ), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) member_ids: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="member_ids") + default=None, + metadata=dc_config(field_name="member_ids"), ) message: "Optional[MessageRequest]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -12787,10 +14205,12 @@ class TruncateChannelRequest(DataClassJsonMixin): class TruncateChannelResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) @@ -12802,7 +14222,8 @@ class TypingIndicators(DataClassJsonMixin): @dataclass class TypingIndicatorsResponse(DataClassJsonMixin): enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) @@ -12814,10 +14235,12 @@ class UnbanActionRequest(DataClassJsonMixin): @dataclass class UnbanRequest(DataClassJsonMixin): unbanned_by_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="unbanned_by_id") + default=None, + metadata=dc_config(field_name="unbanned_by_id"), ) unbanned_by: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="unbanned_by") + default=None, + metadata=dc_config(field_name="unbanned_by"), ) @@ -12845,10 +14268,12 @@ class UnblockUserResponse(DataClassJsonMixin): class UnblockUsersRequest(DataClassJsonMixin): blocked_user_id: str = dc_field(metadata=dc_config(field_name="blocked_user_id")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -12866,27 +14291,32 @@ class UnblockedUserEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field( - default="call.unblocked_user", metadata=dc_config(field_name="type") + default="call.unblocked_user", + metadata=dc_config(field_name="type"), ) @dataclass class UnmuteChannelRequest(DataClassJsonMixin): expiration: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="expiration") + default=None, + metadata=dc_config(field_name="expiration"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) channel_cids: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="channel_cids") + default=None, + metadata=dc_config(field_name="channel_cids"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -12894,10 +14324,12 @@ class UnmuteChannelRequest(DataClassJsonMixin): class UnmuteRequest(DataClassJsonMixin): target_ids: List[str] = dc_field(metadata=dc_config(field_name="target_ids")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -12905,7 +14337,8 @@ class UnmuteRequest(DataClassJsonMixin): class UnmuteResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) non_existing_users: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="non_existing_users") + default=None, + metadata=dc_config(field_name="non_existing_users"), ) @@ -12929,7 +14362,7 @@ class UnreadCountsBatchRequest(DataClassJsonMixin): class UnreadCountsBatchResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) counts_by_user: "Dict[str, Optional[UnreadCountsResponse]]" = dc_field( - metadata=dc_config(field_name="counts_by_user") + metadata=dc_config(field_name="counts_by_user"), ) @@ -12942,7 +14375,7 @@ class UnreadCountsChannel(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) @@ -12957,22 +14390,22 @@ class UnreadCountsChannelType(DataClassJsonMixin): @dataclass class UnreadCountsResponse(DataClassJsonMixin): total_unread_count: int = dc_field( - metadata=dc_config(field_name="total_unread_count") + metadata=dc_config(field_name="total_unread_count"), ) total_unread_threads_count: int = dc_field( - metadata=dc_config(field_name="total_unread_threads_count") + metadata=dc_config(field_name="total_unread_threads_count"), ) channel_type: "List[UnreadCountsChannelType]" = dc_field( - metadata=dc_config(field_name="channel_type") + metadata=dc_config(field_name="channel_type"), ) channels: "List[UnreadCountsChannel]" = dc_field( - metadata=dc_config(field_name="channels") + metadata=dc_config(field_name="channels"), ) threads: "List[UnreadCountsThread]" = dc_field( - metadata=dc_config(field_name="threads") + metadata=dc_config(field_name="threads"), ) total_unread_count_by_team: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="total_unread_count_by_team") + metadata=dc_config(field_name="total_unread_count_by_team"), ) @@ -12984,13 +14417,13 @@ class UnreadCountsThread(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) last_read_message_id: str = dc_field( - metadata=dc_config(field_name="last_read_message_id") + metadata=dc_config(field_name="last_read_message_id"), ) parent_message_id: str = dc_field( - metadata=dc_config(field_name="parent_message_id") + metadata=dc_config(field_name="parent_message_id"), ) unread_count: int = dc_field(metadata=dc_config(field_name="unread_count")) @@ -12998,64 +14431,84 @@ class UnreadCountsThread(DataClassJsonMixin): @dataclass class UpdateAppRequest(DataClassJsonMixin): async_url_enrich_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async_url_enrich_enabled") + default=None, + metadata=dc_config(field_name="async_url_enrich_enabled"), ) auto_translation_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="auto_translation_enabled") + default=None, + metadata=dc_config(field_name="auto_translation_enabled"), ) before_message_send_hook_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="before_message_send_hook_url") + default=None, + metadata=dc_config(field_name="before_message_send_hook_url"), ) cdn_expiration_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="cdn_expiration_seconds") + default=None, + metadata=dc_config(field_name="cdn_expiration_seconds"), ) channel_hide_members_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="channel_hide_members_only") + default=None, + metadata=dc_config(field_name="channel_hide_members_only"), ) custom_action_handler_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="custom_action_handler_url") + default=None, + metadata=dc_config(field_name="custom_action_handler_url"), ) disable_auth_checks: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="disable_auth_checks") + default=None, + metadata=dc_config(field_name="disable_auth_checks"), ) disable_permissions_checks: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="disable_permissions_checks") + default=None, + metadata=dc_config(field_name="disable_permissions_checks"), ) enforce_unique_usernames: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="enforce_unique_usernames") + default=None, + metadata=dc_config(field_name="enforce_unique_usernames"), ) feeds_moderation_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="feeds_moderation_enabled") + default=None, + metadata=dc_config(field_name="feeds_moderation_enabled"), ) feeds_v2_region: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="feeds_v2_region") + default=None, + metadata=dc_config(field_name="feeds_v2_region"), ) guest_user_creation_disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="guest_user_creation_disabled") + default=None, + metadata=dc_config(field_name="guest_user_creation_disabled"), ) image_moderation_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="image_moderation_enabled") + default=None, + metadata=dc_config(field_name="image_moderation_enabled"), ) migrate_permissions_to_v2: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="migrate_permissions_to_v2") + default=None, + metadata=dc_config(field_name="migrate_permissions_to_v2"), ) moderation_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="moderation_enabled") + default=None, + metadata=dc_config(field_name="moderation_enabled"), ) moderation_webhook_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="moderation_webhook_url") + default=None, + metadata=dc_config(field_name="moderation_webhook_url"), ) multi_tenant_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="multi_tenant_enabled") + default=None, + metadata=dc_config(field_name="multi_tenant_enabled"), ) permission_version: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="permission_version") + default=None, + metadata=dc_config(field_name="permission_version"), ) reminders_interval: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="reminders_interval") + default=None, + metadata=dc_config(field_name="reminders_interval"), ) reminders_max_members: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="reminders_max_members") + default=None, + metadata=dc_config(field_name="reminders_max_members"), ) revoke_tokens_issued_before: Optional[datetime] = dc_field( default=None, @@ -13067,67 +14520,88 @@ class UpdateAppRequest(DataClassJsonMixin): ), ) sns_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_key") + default=None, + metadata=dc_config(field_name="sns_key"), ) sns_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_secret") + default=None, + metadata=dc_config(field_name="sns_secret"), ) sns_topic_arn: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sns_topic_arn") + default=None, + metadata=dc_config(field_name="sns_topic_arn"), ) sqs_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_key") + default=None, + metadata=dc_config(field_name="sqs_key"), ) sqs_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_secret") + default=None, + metadata=dc_config(field_name="sqs_secret"), ) sqs_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_url") + default=None, + metadata=dc_config(field_name="sqs_url"), ) webhook_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="webhook_url") + default=None, + metadata=dc_config(field_name="webhook_url"), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) event_hooks: "Optional[List[EventHook]]" = dc_field( - default=None, metadata=dc_config(field_name="event_hooks") + default=None, + metadata=dc_config(field_name="event_hooks"), ) image_moderation_block_labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="image_moderation_block_labels") + default=None, + metadata=dc_config(field_name="image_moderation_block_labels"), ) image_moderation_labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="image_moderation_labels") + default=None, + metadata=dc_config(field_name="image_moderation_labels"), ) user_search_disallowed_roles: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="user_search_disallowed_roles") + default=None, + metadata=dc_config(field_name="user_search_disallowed_roles"), ) webhook_events: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="webhook_events") + default=None, + metadata=dc_config(field_name="webhook_events"), ) apn_config: "Optional[APNConfig]" = dc_field( - default=None, metadata=dc_config(field_name="apn_config") + default=None, + metadata=dc_config(field_name="apn_config"), ) async_moderation_config: "Optional[AsyncModerationConfiguration]" = dc_field( - default=None, metadata=dc_config(field_name="async_moderation_config") + default=None, + metadata=dc_config(field_name="async_moderation_config"), ) datadog_info: "Optional[DataDogInfo]" = dc_field( - default=None, metadata=dc_config(field_name="datadog_info") + default=None, + metadata=dc_config(field_name="datadog_info"), ) file_upload_config: "Optional[FileUploadConfig]" = dc_field( - default=None, metadata=dc_config(field_name="file_upload_config") + default=None, + metadata=dc_config(field_name="file_upload_config"), ) firebase_config: "Optional[FirebaseConfig]" = dc_field( - default=None, metadata=dc_config(field_name="firebase_config") + default=None, + metadata=dc_config(field_name="firebase_config"), ) grants: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="grants") + default=None, + metadata=dc_config(field_name="grants"), ) huawei_config: "Optional[HuaweiConfig]" = dc_field( - default=None, metadata=dc_config(field_name="huawei_config") + default=None, + metadata=dc_config(field_name="huawei_config"), ) image_upload_config: "Optional[FileUploadConfig]" = dc_field( - default=None, metadata=dc_config(field_name="image_upload_config") + default=None, + metadata=dc_config(field_name="image_upload_config"), ) moderation_dashboard_preferences: "Optional[ModerationDashboardPreferences]" = ( dc_field( @@ -13136,10 +14610,12 @@ class UpdateAppRequest(DataClassJsonMixin): ) ) push_config: "Optional[PushConfig]" = dc_field( - default=None, metadata=dc_config(field_name="push_config") + default=None, + metadata=dc_config(field_name="push_config"), ) xiaomi_config: "Optional[XiaomiConfig]" = dc_field( - default=None, metadata=dc_config(field_name="xiaomi_config") + default=None, + metadata=dc_config(field_name="xiaomi_config"), ) @@ -13147,7 +14623,8 @@ class UpdateAppRequest(DataClassJsonMixin): class UpdateBlockListRequest(DataClassJsonMixin): team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) words: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="words") + default=None, + metadata=dc_config(field_name="words"), ) @@ -13155,17 +14632,20 @@ class UpdateBlockListRequest(DataClassJsonMixin): class UpdateBlockListResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) blocklist: "Optional[BlockListResponse]" = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) @dataclass class UpdateCallMembersRequest(DataClassJsonMixin): remove_members: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="remove_members") + default=None, + metadata=dc_config(field_name="remove_members"), ) update_members: "Optional[List[MemberRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="update_members") + default=None, + metadata=dc_config(field_name="update_members"), ) @@ -13187,10 +14667,12 @@ class UpdateCallRequest(DataClassJsonMixin): ), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) settings_override: "Optional[CallSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="settings_override") + default=None, + metadata=dc_config(field_name="settings_override"), ) @@ -13199,7 +14681,7 @@ class UpdateCallResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) own_capabilities: "List[OwnCapability]" = dc_field( - metadata=dc_config(field_name="own_capabilities") + metadata=dc_config(field_name="own_capabilities"), ) call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) @@ -13207,16 +14689,20 @@ class UpdateCallResponse(DataClassJsonMixin): @dataclass class UpdateCallTypeRequest(DataClassJsonMixin): external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) grants: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="grants") + default=None, + metadata=dc_config(field_name="grants"), ) notification_settings: "Optional[NotificationSettings]" = dc_field( - default=None, metadata=dc_config(field_name="notification_settings") + default=None, + metadata=dc_config(field_name="notification_settings"), ) settings: "Optional[CallSettingsRequest]" = dc_field( - default=None, metadata=dc_config(field_name="settings") + default=None, + metadata=dc_config(field_name="settings"), ) @@ -13228,7 +14714,7 @@ class UpdateCallTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) duration: str = dc_field(metadata=dc_config(field_name="duration")) name: str = dc_field(metadata=dc_config(field_name="name")) @@ -13238,33 +14724,38 @@ class UpdateCallTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) notification_settings: "NotificationSettings" = dc_field( - metadata=dc_config(field_name="notification_settings") + metadata=dc_config(field_name="notification_settings"), ) settings: "CallSettingsResponse" = dc_field( - metadata=dc_config(field_name="settings") + metadata=dc_config(field_name="settings"), ) external_storage: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="external_storage") + default=None, + metadata=dc_config(field_name="external_storage"), ) @dataclass class UpdateChannelPartialRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) unset: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="unset") + default=None, + metadata=dc_config(field_name="unset"), ) set: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="set") + default=None, + metadata=dc_config(field_name="set"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -13272,59 +14763,75 @@ class UpdateChannelPartialRequest(DataClassJsonMixin): class UpdateChannelPartialResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) members: "List[ChannelMemberResponse]" = dc_field( - metadata=dc_config(field_name="members") + metadata=dc_config(field_name="members"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) @dataclass class UpdateChannelRequest(DataClassJsonMixin): accept_invite: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="accept_invite") + default=None, + metadata=dc_config(field_name="accept_invite"), ) cooldown: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="cooldown") + default=None, + metadata=dc_config(field_name="cooldown"), ) hide_history: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="hide_history") + default=None, + metadata=dc_config(field_name="hide_history"), ) reject_invite: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="reject_invite") + default=None, + metadata=dc_config(field_name="reject_invite"), ) skip_push: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_push") + default=None, + metadata=dc_config(field_name="skip_push"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) add_members: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="add_members") + default=None, + metadata=dc_config(field_name="add_members"), ) add_moderators: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="add_moderators") + default=None, + metadata=dc_config(field_name="add_moderators"), ) assign_roles: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="assign_roles") + default=None, + metadata=dc_config(field_name="assign_roles"), ) demote_moderators: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="demote_moderators") + default=None, + metadata=dc_config(field_name="demote_moderators"), ) invites: "Optional[List[ChannelMember]]" = dc_field( - default=None, metadata=dc_config(field_name="invites") + default=None, + metadata=dc_config(field_name="invites"), ) remove_members: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="remove_members") + default=None, + metadata=dc_config(field_name="remove_members"), ) data: "Optional[ChannelInput]" = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) message: "Optional[MessageRequest]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -13333,10 +14840,12 @@ class UpdateChannelResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) members: "List[ChannelMember]" = dc_field(metadata=dc_config(field_name="members")) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) @@ -13345,92 +14854,119 @@ class UpdateChannelTypeRequest(DataClassJsonMixin): automod: str = dc_field(metadata=dc_config(field_name="automod")) automod_behavior: str = dc_field(metadata=dc_config(field_name="automod_behavior")) max_message_length: int = dc_field( - metadata=dc_config(field_name="max_message_length") + metadata=dc_config(field_name="max_message_length"), ) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) connect_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="connect_events") + default=None, + metadata=dc_config(field_name="connect_events"), ) custom_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="custom_events") + default=None, + metadata=dc_config(field_name="custom_events"), ) mark_messages_pending: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mark_messages_pending") + default=None, + metadata=dc_config(field_name="mark_messages_pending"), ) mutes: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="mutes") + default=None, + metadata=dc_config(field_name="mutes"), ) partition_size: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_size") + default=None, + metadata=dc_config(field_name="partition_size"), ) partition_ttl: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="partition_ttl") + default=None, + metadata=dc_config(field_name="partition_ttl"), ) polls: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="polls") + default=None, + metadata=dc_config(field_name="polls"), ) push_notifications: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="push_notifications") + default=None, + metadata=dc_config(field_name="push_notifications"), ) quotes: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="quotes") + default=None, + metadata=dc_config(field_name="quotes"), ) reactions: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="reactions") + default=None, + metadata=dc_config(field_name="reactions"), ) read_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="read_events") + default=None, + metadata=dc_config(field_name="read_events"), ) reminders: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="reminders") + default=None, + metadata=dc_config(field_name="reminders"), ) replies: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="replies") + default=None, + metadata=dc_config(field_name="replies"), ) search: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="search") + default=None, + metadata=dc_config(field_name="search"), ) shared_locations: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="shared_locations") + default=None, + metadata=dc_config(field_name="shared_locations"), ) skip_last_msg_update_for_system_msgs: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), ) typing_events: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="typing_events") + default=None, + metadata=dc_config(field_name="typing_events"), ) uploads: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="uploads") + default=None, + metadata=dc_config(field_name="uploads"), ) url_enrichment: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="url_enrichment") + default=None, + metadata=dc_config(field_name="url_enrichment"), ) user_message_reminders: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="user_message_reminders") + default=None, + metadata=dc_config(field_name="user_message_reminders"), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) blocklists: "Optional[List[BlockListOptions]]" = dc_field( - default=None, metadata=dc_config(field_name="blocklists") + default=None, + metadata=dc_config(field_name="blocklists"), ) commands: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="commands") + default=None, + metadata=dc_config(field_name="commands"), ) permissions: "Optional[List[PolicyRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="permissions") + default=None, + metadata=dc_config(field_name="permissions"), ) automod_thresholds: "Optional[Thresholds]" = dc_field( - default=None, metadata=dc_config(field_name="automod_thresholds") + default=None, + metadata=dc_config(field_name="automod_thresholds"), ) grants: "Optional[Dict[str, List[str]]]" = dc_field( - default=None, metadata=dc_config(field_name="grants") + default=None, + metadata=dc_config(field_name="grants"), ) @@ -13445,21 +14981,21 @@ class UpdateChannelTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom_events: bool = dc_field(metadata=dc_config(field_name="custom_events")) duration: str = dc_field(metadata=dc_config(field_name="duration")) mark_messages_pending: bool = dc_field( - metadata=dc_config(field_name="mark_messages_pending") + metadata=dc_config(field_name="mark_messages_pending"), ) max_message_length: int = dc_field( - metadata=dc_config(field_name="max_message_length") + metadata=dc_config(field_name="max_message_length"), ) mutes: bool = dc_field(metadata=dc_config(field_name="mutes")) name: str = dc_field(metadata=dc_config(field_name="name")) polls: bool = dc_field(metadata=dc_config(field_name="polls")) push_notifications: bool = dc_field( - metadata=dc_config(field_name="push_notifications") + metadata=dc_config(field_name="push_notifications"), ) quotes: bool = dc_field(metadata=dc_config(field_name="quotes")) reactions: bool = dc_field(metadata=dc_config(field_name="reactions")) @@ -13469,7 +15005,7 @@ class UpdateChannelTypeResponse(DataClassJsonMixin): search: bool = dc_field(metadata=dc_config(field_name="search")) shared_locations: bool = dc_field(metadata=dc_config(field_name="shared_locations")) skip_last_msg_update_for_system_msgs: bool = dc_field( - metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs") + metadata=dc_config(field_name="skip_last_msg_update_for_system_msgs"), ) typing_events: bool = dc_field(metadata=dc_config(field_name="typing_events")) updated_at: datetime = dc_field( @@ -13478,38 +15014,45 @@ class UpdateChannelTypeResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) uploads: bool = dc_field(metadata=dc_config(field_name="uploads")) url_enrichment: bool = dc_field(metadata=dc_config(field_name="url_enrichment")) user_message_reminders: bool = dc_field( - metadata=dc_config(field_name="user_message_reminders") + metadata=dc_config(field_name="user_message_reminders"), ) commands: List[str] = dc_field(metadata=dc_config(field_name="commands")) permissions: "List[PolicyRequest]" = dc_field( - metadata=dc_config(field_name="permissions") + metadata=dc_config(field_name="permissions"), ) grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) blocklist: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist") + default=None, + metadata=dc_config(field_name="blocklist"), ) blocklist_behavior: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="blocklist_behavior") + default=None, + metadata=dc_config(field_name="blocklist_behavior"), ) partition_size: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="partition_size") + default=None, + metadata=dc_config(field_name="partition_size"), ) partition_ttl: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="partition_ttl") + default=None, + metadata=dc_config(field_name="partition_ttl"), ) allowed_flag_reasons: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="allowed_flag_reasons") + default=None, + metadata=dc_config(field_name="allowed_flag_reasons"), ) blocklists: "Optional[List[BlockListOptions]]" = dc_field( - default=None, metadata=dc_config(field_name="blocklists") + default=None, + metadata=dc_config(field_name="blocklists"), ) automod_thresholds: "Optional[Thresholds]" = dc_field( - default=None, metadata=dc_config(field_name="automod_thresholds") + default=None, + metadata=dc_config(field_name="automod_thresholds"), ) @@ -13524,7 +15067,8 @@ class UpdateCommandRequest(DataClassJsonMixin): class UpdateCommandResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) command: "Optional[Command]" = dc_field( - default=None, metadata=dc_config(field_name="command") + default=None, + metadata=dc_config(field_name="command"), ) @@ -13533,14 +15077,17 @@ class UpdateExternalStorageRequest(DataClassJsonMixin): bucket: str = dc_field(metadata=dc_config(field_name="bucket")) storage_type: str = dc_field(metadata=dc_config(field_name="storage_type")) gcs_credentials: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="gcs_credentials") + default=None, + metadata=dc_config(field_name="gcs_credentials"), ) path: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="path")) aws_s3: "Optional[S3Request]" = dc_field( - default=None, metadata=dc_config(field_name="aws_s3") + default=None, + metadata=dc_config(field_name="aws_s3"), ) azure_blob: "Optional[AzureRequest]" = dc_field( - default=None, metadata=dc_config(field_name="azure_blob") + default=None, + metadata=dc_config(field_name="azure_blob"), ) @@ -13556,7 +15103,7 @@ class UpdateExternalStorageResponse(DataClassJsonMixin): @dataclass class UpdateLiveLocationRequest(DataClassJsonMixin): created_by_device_id: str = dc_field( - metadata=dc_config(field_name="created_by_device_id") + metadata=dc_config(field_name="created_by_device_id"), ) message_id: str = dc_field(metadata=dc_config(field_name="message_id")) end_at: Optional[datetime] = dc_field( @@ -13569,20 +15116,24 @@ class UpdateLiveLocationRequest(DataClassJsonMixin): ), ) latitude: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="latitude") + default=None, + metadata=dc_config(field_name="latitude"), ) longitude: Optional[float] = dc_field( - default=None, metadata=dc_config(field_name="longitude") + default=None, + metadata=dc_config(field_name="longitude"), ) @dataclass class UpdateMemberPartialRequest(DataClassJsonMixin): unset: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="unset") + default=None, + metadata=dc_config(field_name="unset"), ) set: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="set") + default=None, + metadata=dc_config(field_name="set"), ) @@ -13590,26 +15141,32 @@ class UpdateMemberPartialRequest(DataClassJsonMixin): class UpdateMemberPartialResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) channel_member: "Optional[ChannelMemberResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel_member") + default=None, + metadata=dc_config(field_name="channel_member"), ) @dataclass class UpdateMessagePartialRequest(DataClassJsonMixin): skip_enrich_url: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_enrich_url") + default=None, + metadata=dc_config(field_name="skip_enrich_url"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) unset: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="unset") + default=None, + metadata=dc_config(field_name="unset"), ) set: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="set") + default=None, + metadata=dc_config(field_name="set"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -13617,10 +15174,12 @@ class UpdateMessagePartialRequest(DataClassJsonMixin): class UpdateMessagePartialResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) pending_message_metadata: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="pending_message_metadata") + default=None, + metadata=dc_config(field_name="pending_message_metadata"), ) @@ -13628,10 +15187,12 @@ class UpdateMessagePartialResponse(DataClassJsonMixin): class UpdateMessageRequest(DataClassJsonMixin): message: "MessageRequest" = dc_field(metadata=dc_config(field_name="message")) skip_enrich_url: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_enrich_url") + default=None, + metadata=dc_config(field_name="skip_enrich_url"), ) skip_push: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="skip_push") + default=None, + metadata=dc_config(field_name="skip_push"), ) @@ -13640,7 +15201,8 @@ class UpdateMessageResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) message: "MessageResponse" = dc_field(metadata=dc_config(field_name="message")) pending_message_metadata: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="pending_message_metadata") + default=None, + metadata=dc_config(field_name="pending_message_metadata"), ) @@ -13649,29 +15211,36 @@ class UpdatePollOptionRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) text: str = dc_field(metadata=dc_config(field_name="text")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="Custom") + default=None, + metadata=dc_config(field_name="Custom"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @dataclass class UpdatePollPartialRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) unset: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="unset") + default=None, + metadata=dc_config(field_name="unset"), ) set: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="set") + default=None, + metadata=dc_config(field_name="set"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -13680,37 +15249,48 @@ class UpdatePollRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) name: str = dc_field(metadata=dc_config(field_name="name")) allow_answers: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="allow_answers") + default=None, + metadata=dc_config(field_name="allow_answers"), ) allow_user_suggested_options: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="allow_user_suggested_options") + default=None, + metadata=dc_config(field_name="allow_user_suggested_options"), ) description: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="description") + default=None, + metadata=dc_config(field_name="description"), ) enforce_unique_vote: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enforce_unique_vote") + default=None, + metadata=dc_config(field_name="enforce_unique_vote"), ) is_closed: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_closed") + default=None, + metadata=dc_config(field_name="is_closed"), ) max_votes_allowed: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="max_votes_allowed") + default=None, + metadata=dc_config(field_name="max_votes_allowed"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) voting_visibility: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="voting_visibility") + default=None, + metadata=dc_config(field_name="voting_visibility"), ) options: "Optional[List[PollOptionRequest]]" = dc_field( - default=None, metadata=dc_config(field_name="options") + default=None, + metadata=dc_config(field_name="options"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="Custom") + default=None, + metadata=dc_config(field_name="Custom"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -13726,10 +15306,12 @@ class UpdateReminderRequest(DataClassJsonMixin): ), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -13737,23 +15319,27 @@ class UpdateReminderRequest(DataClassJsonMixin): class UpdateReminderResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) reminder: "ReminderResponseData" = dc_field( - metadata=dc_config(field_name="reminder") + metadata=dc_config(field_name="reminder"), ) @dataclass class UpdateThreadPartialRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) unset: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="unset") + default=None, + metadata=dc_config(field_name="unset"), ) set: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="set") + default=None, + metadata=dc_config(field_name="set"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -13767,10 +15353,12 @@ class UpdateThreadPartialResponse(DataClassJsonMixin): class UpdateUserPartialRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) unset: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="unset") + default=None, + metadata=dc_config(field_name="unset"), ) set: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="set") + default=None, + metadata=dc_config(field_name="set"), ) @@ -13778,10 +15366,12 @@ class UpdateUserPartialRequest(DataClassJsonMixin): class UpdateUserPermissionsRequest(DataClassJsonMixin): user_id: str = dc_field(metadata=dc_config(field_name="user_id")) grant_permissions: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="grant_permissions") + default=None, + metadata=dc_config(field_name="grant_permissions"), ) revoke_permissions: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="revoke_permissions") + default=None, + metadata=dc_config(field_name="revoke_permissions"), ) @@ -13793,7 +15383,7 @@ class UpdateUserPermissionsResponse(DataClassJsonMixin): @dataclass class UpdateUsersPartialRequest(DataClassJsonMixin): users: "List[UpdateUserPartialRequest]" = dc_field( - metadata=dc_config(field_name="users") + metadata=dc_config(field_name="users"), ) @@ -13806,10 +15396,10 @@ class UpdateUsersRequest(DataClassJsonMixin): class UpdateUsersResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) membership_deletion_task_id: str = dc_field( - metadata=dc_config(field_name="membership_deletion_task_id") + metadata=dc_config(field_name="membership_deletion_task_id"), ) users: "Dict[str, FullUserResponse]" = dc_field( - metadata=dc_config(field_name="users") + metadata=dc_config(field_name="users"), ) @@ -13822,14 +15412,15 @@ class UpdatedCallPermissionsEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) own_capabilities: "List[OwnCapability]" = dc_field( - metadata=dc_config(field_name="own_capabilities") + metadata=dc_config(field_name="own_capabilities"), ) user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) type: str = dc_field( - default="call.permissions_updated", metadata=dc_config(field_name="type") + default="call.permissions_updated", + metadata=dc_config(field_name="type"), ) @@ -13837,20 +15428,25 @@ class UpdatedCallPermissionsEvent(DataClassJsonMixin): class UpsertConfigRequest(DataClassJsonMixin): key: str = dc_field(metadata=dc_config(field_name="key")) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) ai_image_config: "Optional[AIImageConfig]" = dc_field( - default=None, metadata=dc_config(field_name="ai_image_config") + default=None, + metadata=dc_config(field_name="ai_image_config"), ) ai_text_config: "Optional[AITextConfig]" = dc_field( - default=None, metadata=dc_config(field_name="ai_text_config") + default=None, + metadata=dc_config(field_name="ai_text_config"), ) ai_video_config: "Optional[AIVideoConfig]" = dc_field( - default=None, metadata=dc_config(field_name="ai_video_config") + default=None, + metadata=dc_config(field_name="ai_video_config"), ) automod_platform_circumvention_config: "Optional[AutomodPlatformCircumventionConfig]" = dc_field( default=None, @@ -13863,31 +15459,40 @@ class UpsertConfigRequest(DataClassJsonMixin): ) ) automod_toxicity_config: "Optional[AutomodToxicityConfig]" = dc_field( - default=None, metadata=dc_config(field_name="automod_toxicity_config") + default=None, + metadata=dc_config(field_name="automod_toxicity_config"), ) aws_rekognition_config: "Optional[AIImageConfig]" = dc_field( - default=None, metadata=dc_config(field_name="aws_rekognition_config") + default=None, + metadata=dc_config(field_name="aws_rekognition_config"), ) block_list_config: "Optional[BlockListConfig]" = dc_field( - default=None, metadata=dc_config(field_name="block_list_config") + default=None, + metadata=dc_config(field_name="block_list_config"), ) bodyguard_config: "Optional[AITextConfig]" = dc_field( - default=None, metadata=dc_config(field_name="bodyguard_config") + default=None, + metadata=dc_config(field_name="bodyguard_config"), ) google_vision_config: "Optional[GoogleVisionConfig]" = dc_field( - default=None, metadata=dc_config(field_name="google_vision_config") + default=None, + metadata=dc_config(field_name="google_vision_config"), ) rule_builder_config: "Optional[RuleBuilderConfig]" = dc_field( - default=None, metadata=dc_config(field_name="rule_builder_config") + default=None, + metadata=dc_config(field_name="rule_builder_config"), ) user: "Optional[UserRequest]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) velocity_filter_config: "Optional[VelocityFilterConfig]" = dc_field( - default=None, metadata=dc_config(field_name="velocity_filter_config") + default=None, + metadata=dc_config(field_name="velocity_filter_config"), ) video_call_rule_config: "Optional[VideoCallRuleConfig]" = dc_field( - default=None, metadata=dc_config(field_name="video_call_rule_config") + default=None, + metadata=dc_config(field_name="video_call_rule_config"), ) @@ -13895,7 +15500,8 @@ class UpsertConfigRequest(DataClassJsonMixin): class UpsertConfigResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) config: "Optional[ConfigResponse]" = dc_field( - default=None, metadata=dc_config(field_name="config") + default=None, + metadata=dc_config(field_name="config"), ) @@ -13903,7 +15509,7 @@ class UpsertConfigResponse(DataClassJsonMixin): class UpsertModerationTemplateRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) config: "FeedsModerationTemplateConfig" = dc_field( - metadata=dc_config(field_name="config") + metadata=dc_config(field_name="config"), ) @@ -13915,7 +15521,7 @@ class UpsertModerationTemplateResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) duration: str = dc_field(metadata=dc_config(field_name="duration")) name: str = dc_field(metadata=dc_config(field_name="name")) @@ -13925,17 +15531,18 @@ class UpsertModerationTemplateResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) config: "Optional[FeedsModerationTemplateConfig]" = dc_field( - default=None, metadata=dc_config(field_name="config") + default=None, + metadata=dc_config(field_name="config"), ) @dataclass class UpsertPushPreferencesRequest(DataClassJsonMixin): preferences: "List[PushPreferenceInput]" = dc_field( - metadata=dc_config(field_name="preferences") + metadata=dc_config(field_name="preferences"), ) @@ -13943,17 +15550,18 @@ class UpsertPushPreferencesRequest(DataClassJsonMixin): class UpsertPushPreferencesResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) user_channel_preferences: "Dict[str, Dict[str, ChannelPushPreferences]]" = dc_field( - metadata=dc_config(field_name="user_channel_preferences") + metadata=dc_config(field_name="user_channel_preferences"), ) user_preferences: "Dict[str, PushPreferences]" = dc_field( - metadata=dc_config(field_name="user_preferences") + metadata=dc_config(field_name="user_preferences"), ) @dataclass class UpsertPushProviderRequest(DataClassJsonMixin): push_provider: "Optional[PushProvider]" = dc_field( - default=None, metadata=dc_config(field_name="push_provider") + default=None, + metadata=dc_config(field_name="push_provider"), ) @@ -13961,7 +15569,7 @@ class UpsertPushProviderRequest(DataClassJsonMixin): class UpsertPushProviderResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) push_provider: "PushProviderResponse" = dc_field( - metadata=dc_config(field_name="push_provider") + metadata=dc_config(field_name="push_provider"), ) @@ -13969,16 +15577,19 @@ class UpsertPushProviderResponse(DataClassJsonMixin): class UpsertPushTemplateRequest(DataClassJsonMixin): event_type: str = dc_field(metadata=dc_config(field_name="event_type")) push_provider_type: str = dc_field( - metadata=dc_config(field_name="push_provider_type") + metadata=dc_config(field_name="push_provider_type"), ) enable_push: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enable_push") + default=None, + metadata=dc_config(field_name="enable_push"), ) push_provider_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="push_provider_name") + default=None, + metadata=dc_config(field_name="push_provider_name"), ) template: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="template") + default=None, + metadata=dc_config(field_name="template"), ) @@ -13986,7 +15597,8 @@ class UpsertPushTemplateRequest(DataClassJsonMixin): class UpsertPushTemplateResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) template: "Optional[PushTemplate]" = dc_field( - default=None, metadata=dc_config(field_name="template") + default=None, + metadata=dc_config(field_name="template"), ) @@ -14035,10 +15647,12 @@ class User(DataClassJsonMixin): ), ) invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") + default=None, + metadata=dc_config(field_name="invisible"), ) language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + default=None, + metadata=dc_config(field_name="language"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -14077,10 +15691,12 @@ class User(DataClassJsonMixin): ), ) teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") + default=None, + metadata=dc_config(field_name="teams"), ) privacy_settings: "Optional[PrivacySettings]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) @@ -14095,7 +15711,7 @@ class UserBannedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) shadow: bool = dc_field(metadata=dc_config(field_name="shadow")) created_by: "User" = dc_field(metadata=dc_config(field_name="created_by")) @@ -14110,11 +15726,13 @@ class UserBannedEvent(DataClassJsonMixin): ), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14122,7 +15740,8 @@ class UserBannedEvent(DataClassJsonMixin): class UserCustomEventRequest(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -14134,14 +15753,16 @@ class UserDeactivatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) created_by: "User" = dc_field(metadata=dc_config(field_name="created_by")) type: str = dc_field( - default="user.deactivated", metadata=dc_config(field_name="type") + default="user.deactivated", + metadata=dc_config(field_name="type"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14153,18 +15774,19 @@ class UserDeletedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) delete_conversation_channels: bool = dc_field( - metadata=dc_config(field_name="delete_conversation_channels") + metadata=dc_config(field_name="delete_conversation_channels"), ) hard_delete: bool = dc_field(metadata=dc_config(field_name="hard_delete")) mark_messages_deleted: bool = dc_field( - metadata=dc_config(field_name="mark_messages_deleted") + metadata=dc_config(field_name="mark_messages_deleted"), ) type: str = dc_field(default="user.deleted", metadata=dc_config(field_name="type")) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14172,14 +15794,14 @@ class UserDeletedEvent(DataClassJsonMixin): class UserFeedbackReport(DataClassJsonMixin): unreported_count: int = dc_field(metadata=dc_config(field_name="unreported_count")) count_by_rating: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="count_by_rating") + metadata=dc_config(field_name="count_by_rating"), ) @dataclass class UserFeedbackReportResponse(DataClassJsonMixin): daily: "List[DailyAggregateUserFeedbackReportResponse]" = dc_field( - metadata=dc_config(field_name="daily") + metadata=dc_config(field_name="daily"), ) @@ -14193,10 +15815,11 @@ class UserFeedbackResponse(DataClassJsonMixin): session_id: str = dc_field(metadata=dc_config(field_name="session_id")) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) platform: "PlatformDataResponse" = dc_field( - metadata=dc_config(field_name="platform") + metadata=dc_config(field_name="platform"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) @@ -14208,17 +15831,20 @@ class UserFlaggedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(default="user.flagged", metadata=dc_config(field_name="type")) target_user: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="target_user") + default=None, + metadata=dc_config(field_name="target_user"), ) target_users: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="target_users") + default=None, + metadata=dc_config(field_name="target_users"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14230,7 +15856,7 @@ class UserMute(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) updated_at: datetime = dc_field( metadata=dc_config( @@ -14238,7 +15864,7 @@ class UserMute(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) expires: Optional[datetime] = dc_field( default=None, @@ -14250,10 +15876,12 @@ class UserMute(DataClassJsonMixin): ), ) target: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="target") + default=None, + metadata=dc_config(field_name="target"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14265,7 +15893,7 @@ class UserMuteResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) updated_at: datetime = dc_field( metadata=dc_config( @@ -14273,7 +15901,7 @@ class UserMuteResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) expires: Optional[datetime] = dc_field( default=None, @@ -14285,10 +15913,12 @@ class UserMuteResponse(DataClassJsonMixin): ), ) target: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="target") + default=None, + metadata=dc_config(field_name="target"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14300,17 +15930,20 @@ class UserMutedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(default="user.muted", metadata=dc_config(field_name="type")) target_user: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="target_user") + default=None, + metadata=dc_config(field_name="target_user"), ) target_users: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="target_users") + default=None, + metadata=dc_config(field_name="target_users"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14328,13 +15961,15 @@ class UserReactivatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field( - default="user.reactivated", metadata=dc_config(field_name="type") + default="user.reactivated", + metadata=dc_config(field_name="type"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14342,27 +15977,34 @@ class UserReactivatedEvent(DataClassJsonMixin): class UserRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) image: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image") + default=None, + metadata=dc_config(field_name="image"), ) invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") + default=None, + metadata=dc_config(field_name="invisible"), ) language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") + default=None, + metadata=dc_config(field_name="language"), ) name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) role: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="role")) teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") + default=None, + metadata=dc_config(field_name="teams"), ) custom: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="custom") + default=None, + metadata=dc_config(field_name="custom"), ) privacy_settings: "Optional[PrivacySettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + default=None, + metadata=dc_config(field_name="teams_role"), ) @@ -14375,7 +16017,7 @@ class UserResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) invisible: bool = dc_field(metadata=dc_config(field_name="invisible")) @@ -14389,10 +16031,10 @@ class UserResponse(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) blocked_user_ids: List[str] = dc_field( - metadata=dc_config(field_name="blocked_user_ids") + metadata=dc_config(field_name="blocked_user_ids"), ) teams: List[str] = dc_field(metadata=dc_config(field_name="teams")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) @@ -14424,7 +16066,8 @@ class UserResponse(DataClassJsonMixin): ), ) image: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image") + default=None, + metadata=dc_config(field_name="image"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -14446,16 +16089,20 @@ class UserResponse(DataClassJsonMixin): ), ) devices: "Optional[List[DeviceResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="devices") + default=None, + metadata=dc_config(field_name="devices"), ) privacy_settings: "Optional[PrivacySettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) push_notifications: "Optional[PushNotificationSettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="push_notifications") + default=None, + metadata=dc_config(field_name="push_notifications"), ) teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + default=None, + metadata=dc_config(field_name="teams_role"), ) @@ -14468,7 +16115,7 @@ class UserResponseCommonFields(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) language: str = dc_field(metadata=dc_config(field_name="language")) @@ -14480,10 +16127,10 @@ class UserResponseCommonFields(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) blocked_user_ids: List[str] = dc_field( - metadata=dc_config(field_name="blocked_user_ids") + metadata=dc_config(field_name="blocked_user_ids"), ) teams: List[str] = dc_field(metadata=dc_config(field_name="teams")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) @@ -14506,7 +16153,8 @@ class UserResponseCommonFields(DataClassJsonMixin): ), ) image: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image") + default=None, + metadata=dc_config(field_name="image"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -14528,7 +16176,8 @@ class UserResponseCommonFields(DataClassJsonMixin): ), ) teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + default=None, + metadata=dc_config(field_name="teams_role"), ) @@ -14541,7 +16190,7 @@ class UserResponsePrivacyFields(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) id: str = dc_field(metadata=dc_config(field_name="id")) language: str = dc_field(metadata=dc_config(field_name="language")) @@ -14553,10 +16202,10 @@ class UserResponsePrivacyFields(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) blocked_user_ids: List[str] = dc_field( - metadata=dc_config(field_name="blocked_user_ids") + metadata=dc_config(field_name="blocked_user_ids"), ) teams: List[str] = dc_field(metadata=dc_config(field_name="teams")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) @@ -14579,10 +16228,12 @@ class UserResponsePrivacyFields(DataClassJsonMixin): ), ) image: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="image") + default=None, + metadata=dc_config(field_name="image"), ) invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") + default=None, + metadata=dc_config(field_name="invisible"), ) last_active: Optional[datetime] = dc_field( default=None, @@ -14604,10 +16255,12 @@ class UserResponsePrivacyFields(DataClassJsonMixin): ), ) privacy_settings: "Optional[PrivacySettingsResponse]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") + default=None, + metadata=dc_config(field_name="privacy_settings"), ) teams_role: "Optional[Dict[str, str]]" = dc_field( - default=None, metadata=dc_config(field_name="teams_role") + default=None, + metadata=dc_config(field_name="teams_role"), ) @@ -14622,13 +16275,14 @@ class UserUnbannedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) shadow: bool = dc_field(metadata=dc_config(field_name="shadow")) type: str = dc_field(default="user.unbanned", metadata=dc_config(field_name="type")) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14640,17 +16294,20 @@ class UserUnmutedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(default="user.unmuted", metadata=dc_config(field_name="type")) target_user: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="target_user") + default=None, + metadata=dc_config(field_name="target_user"), ) target_users: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="target_users") + default=None, + metadata=dc_config(field_name="target_users"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14662,16 +16319,18 @@ class UserUnreadReminderEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) channels: "Dict[str, Optional[ChannelMessages]]" = dc_field( - metadata=dc_config(field_name="channels") + metadata=dc_config(field_name="channels"), ) type: str = dc_field( - default="user.unread_message_reminder", metadata=dc_config(field_name="type") + default="user.unread_message_reminder", + metadata=dc_config(field_name="type"), ) user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14683,7 +16342,7 @@ class UserUpdatedEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) user: "UserResponsePrivacyFields" = dc_field(metadata=dc_config(field_name="user")) @@ -14703,18 +16362,19 @@ class UserUpdatedEvent(DataClassJsonMixin): class VelocityFilterConfig(DataClassJsonMixin): advanced_filters: bool = dc_field(metadata=dc_config(field_name="advanced_filters")) cascading_actions: bool = dc_field( - metadata=dc_config(field_name="cascading_actions") + metadata=dc_config(field_name="cascading_actions"), ) cids_per_user: int = dc_field(metadata=dc_config(field_name="cids_per_user")) enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) first_message_only: bool = dc_field( - metadata=dc_config(field_name="first_message_only") + metadata=dc_config(field_name="first_message_only"), ) rules: "List[VelocityFilterConfigRule]" = dc_field( - metadata=dc_config(field_name="rules") + metadata=dc_config(field_name="rules"), ) _async: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="async") + default=None, + metadata=dc_config(field_name="async"), ) @@ -14724,25 +16384,26 @@ class VelocityFilterConfigRule(DataClassJsonMixin): ban_duration: int = dc_field(metadata=dc_config(field_name="ban_duration")) cascading_action: str = dc_field(metadata=dc_config(field_name="cascading_action")) cascading_threshold: int = dc_field( - metadata=dc_config(field_name="cascading_threshold") + metadata=dc_config(field_name="cascading_threshold"), ) check_message_context: bool = dc_field( - metadata=dc_config(field_name="check_message_context") + metadata=dc_config(field_name="check_message_context"), ) fast_spam_threshold: int = dc_field( - metadata=dc_config(field_name="fast_spam_threshold") + metadata=dc_config(field_name="fast_spam_threshold"), ) fast_spam_ttl: int = dc_field(metadata=dc_config(field_name="fast_spam_ttl")) ip_ban: bool = dc_field(metadata=dc_config(field_name="ip_ban")) probation_period: int = dc_field(metadata=dc_config(field_name="probation_period")) shadow_ban: bool = dc_field(metadata=dc_config(field_name="shadow_ban")) slow_spam_threshold: int = dc_field( - metadata=dc_config(field_name="slow_spam_threshold") + metadata=dc_config(field_name="slow_spam_threshold"), ) slow_spam_ttl: int = dc_field(metadata=dc_config(field_name="slow_spam_ttl")) url_only: bool = dc_field(metadata=dc_config(field_name="url_only")) slow_spam_ban_duration: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="slow_spam_ban_duration") + default=None, + metadata=dc_config(field_name="slow_spam_ban_duration"), ) @@ -14764,14 +16425,16 @@ class VideoKickUserRequest(DataClassJsonMixin): @dataclass class VideoOrientation(DataClassJsonMixin): orientation: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="orientation") + default=None, + metadata=dc_config(field_name="orientation"), ) @dataclass class VideoReactionOverTimeResponse(DataClassJsonMixin): by_minute: "Optional[List[CountByMinuteResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="by_minute") + default=None, + metadata=dc_config(field_name="by_minute"), ) @@ -14779,69 +16442,78 @@ class VideoReactionOverTimeResponse(DataClassJsonMixin): class VideoReactionsResponse(DataClassJsonMixin): reaction: str = dc_field(metadata=dc_config(field_name="reaction")) count_over_time: "Optional[VideoReactionOverTimeResponse]" = dc_field( - default=None, metadata=dc_config(field_name="count_over_time") + default=None, + metadata=dc_config(field_name="count_over_time"), ) @dataclass class VideoSettings(DataClassJsonMixin): access_request_enabled: bool = dc_field( - metadata=dc_config(field_name="access_request_enabled") + metadata=dc_config(field_name="access_request_enabled"), ) camera_default_on: bool = dc_field( - metadata=dc_config(field_name="camera_default_on") + metadata=dc_config(field_name="camera_default_on"), ) camera_facing: str = dc_field(metadata=dc_config(field_name="camera_facing")) enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) target_resolution: "TargetResolution" = dc_field( - metadata=dc_config(field_name="target_resolution") + metadata=dc_config(field_name="target_resolution"), ) @dataclass class VideoSettingsRequest(DataClassJsonMixin): access_request_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="access_request_enabled") + default=None, + metadata=dc_config(field_name="access_request_enabled"), ) camera_default_on: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="camera_default_on") + default=None, + metadata=dc_config(field_name="camera_default_on"), ) camera_facing: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="camera_facing") + default=None, + metadata=dc_config(field_name="camera_facing"), ) enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") + default=None, + metadata=dc_config(field_name="enabled"), ) target_resolution: "Optional[TargetResolution]" = dc_field( - default=None, metadata=dc_config(field_name="target_resolution") + default=None, + metadata=dc_config(field_name="target_resolution"), ) @dataclass class VideoSettingsResponse(DataClassJsonMixin): access_request_enabled: bool = dc_field( - metadata=dc_config(field_name="access_request_enabled") + metadata=dc_config(field_name="access_request_enabled"), ) camera_default_on: bool = dc_field( - metadata=dc_config(field_name="camera_default_on") + metadata=dc_config(field_name="camera_default_on"), ) camera_facing: str = dc_field(metadata=dc_config(field_name="camera_facing")) enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) target_resolution: "TargetResolution" = dc_field( - metadata=dc_config(field_name="target_resolution") + metadata=dc_config(field_name="target_resolution"), ) @dataclass class VoteData(DataClassJsonMixin): answer_text: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="answer_text") + default=None, + metadata=dc_config(field_name="answer_text"), ) option_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="option_id") + default=None, + metadata=dc_config(field_name="option_id"), ) option: "Optional[PollOptionResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="Option") + default=None, + metadata=dc_config(field_name="Option"), ) @@ -14853,15 +16525,17 @@ class WSEvent(DataClassJsonMixin): encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) + ), ) type: str = dc_field(metadata=dc_config(field_name="type")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) automoderation: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="automoderation") + default=None, + metadata=dc_config(field_name="automoderation"), ) channel_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_id") + default=None, + metadata=dc_config(field_name="channel_id"), ) channel_last_message_at: Optional[datetime] = dc_field( default=None, @@ -14873,63 +16547,82 @@ class WSEvent(DataClassJsonMixin): ), ) channel_type: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_type") + default=None, + metadata=dc_config(field_name="channel_type"), ) cid: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="cid")) connection_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="connection_id") + default=None, + metadata=dc_config(field_name="connection_id"), ) parent_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="parent_id") + default=None, + metadata=dc_config(field_name="parent_id"), ) reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + default=None, + metadata=dc_config(field_name="reason"), ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="thread_id") + default=None, + metadata=dc_config(field_name="thread_id"), ) user_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="user_id") + default=None, + metadata=dc_config(field_name="user_id"), ) watcher_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="watcher_count") + default=None, + metadata=dc_config(field_name="watcher_count"), ) automoderation_scores: "Optional[ModerationResponse]" = dc_field( - default=None, metadata=dc_config(field_name="automoderation_scores") + default=None, + metadata=dc_config(field_name="automoderation_scores"), ) channel: "Optional[ChannelResponse]" = dc_field( - default=None, metadata=dc_config(field_name="channel") + default=None, + metadata=dc_config(field_name="channel"), ) created_by: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="created_by") + default=None, + metadata=dc_config(field_name="created_by"), ) me: "Optional[OwnUserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="me") + default=None, + metadata=dc_config(field_name="me"), ) member: "Optional[ChannelMember]" = dc_field( - default=None, metadata=dc_config(field_name="member") + default=None, + metadata=dc_config(field_name="member"), ) message: "Optional[MessageResponse]" = dc_field( - default=None, metadata=dc_config(field_name="message") + default=None, + metadata=dc_config(field_name="message"), ) message_update: "Optional[MessageUpdate]" = dc_field( - default=None, metadata=dc_config(field_name="message_update") + default=None, + metadata=dc_config(field_name="message_update"), ) poll: "Optional[PollResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="poll") + default=None, + metadata=dc_config(field_name="poll"), ) poll_vote: "Optional[PollVoteResponseData]" = dc_field( - default=None, metadata=dc_config(field_name="poll_vote") + default=None, + metadata=dc_config(field_name="poll_vote"), ) reaction: "Optional[ReactionResponse]" = dc_field( - default=None, metadata=dc_config(field_name="reaction") + default=None, + metadata=dc_config(field_name="reaction"), ) thread: "Optional[ThreadResponse]" = dc_field( - default=None, metadata=dc_config(field_name="thread") + default=None, + metadata=dc_config(field_name="thread"), ) user: "Optional[UserResponse]" = dc_field( - default=None, metadata=dc_config(field_name="user") + default=None, + metadata=dc_config(field_name="user"), ) @@ -14942,35 +16635,38 @@ class WebhookEvent(DataClassJsonMixin): class WrappedUnreadCountsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) total_unread_count: int = dc_field( - metadata=dc_config(field_name="total_unread_count") + metadata=dc_config(field_name="total_unread_count"), ) total_unread_threads_count: int = dc_field( - metadata=dc_config(field_name="total_unread_threads_count") + metadata=dc_config(field_name="total_unread_threads_count"), ) channel_type: "List[UnreadCountsChannelType]" = dc_field( - metadata=dc_config(field_name="channel_type") + metadata=dc_config(field_name="channel_type"), ) channels: "List[UnreadCountsChannel]" = dc_field( - metadata=dc_config(field_name="channels") + metadata=dc_config(field_name="channels"), ) threads: "List[UnreadCountsThread]" = dc_field( - metadata=dc_config(field_name="threads") + metadata=dc_config(field_name="threads"), ) total_unread_count_by_team: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="total_unread_count_by_team") + metadata=dc_config(field_name="total_unread_count_by_team"), ) @dataclass class XiaomiConfig(DataClassJsonMixin): disabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="Disabled") + default=None, + metadata=dc_config(field_name="Disabled"), ) package_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="package_name") + default=None, + metadata=dc_config(field_name="package_name"), ) secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="secret") + default=None, + metadata=dc_config(field_name="secret"), ) @@ -14978,8 +16674,10 @@ class XiaomiConfig(DataClassJsonMixin): class XiaomiConfigFields(DataClassJsonMixin): enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) package_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="package_name") + default=None, + metadata=dc_config(field_name="package_name"), ) secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="secret") + default=None, + metadata=dc_config(field_name="secret"), ) diff --git a/getstream/moderation/client.py b/getstream/moderation/client.py index 06f4ede7..b62e0b53 100644 --- a/getstream/moderation/client.py +++ b/getstream/moderation/client.py @@ -4,6 +4,9 @@ class ModerationClient(ModerationRestClient): def __init__(self, api_key: str, base_url, token, timeout, stream): super().__init__( - api_key=api_key, base_url=base_url, token=token, timeout=timeout + api_key=api_key, + base_url=base_url, + token=token, + timeout=timeout, ) self.stream = stream diff --git a/getstream/moderation/rest_client.py b/getstream/moderation/rest_client.py index c51448f7..06c8cd38 100644 --- a/getstream/moderation/rest_client.py +++ b/getstream/moderation/rest_client.py @@ -2,13 +2,12 @@ from getstream.base import BaseClient from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_body_dict, build_query_param class ModerationRestClient(BaseClient): def __init__(self, api_key: str, base_url: str, timeout: float, token: str): - """ - Initializes ModerationClient with BaseClient instance + """Initializes ModerationClient with BaseClient instance :param api_key: A string representing the client's API key :param base_url: A string representing the base uniform resource locator :param timeout: A number representing the time limit for a request @@ -48,7 +47,8 @@ def ban( return self.post("/api/v2/moderation/ban", BanResponse, json=json) def bulk_image_moderation( - self, csv_file: str + self, + csv_file: str, ) -> StreamResponse[BulkImageModerationResponse]: json = build_body_dict(csv_file=csv_file) @@ -133,7 +133,9 @@ def upsert_config( return self.post("/api/v2/moderation/config", UpsertConfigResponse, json=json) def delete_config( - self, key: str, team: Optional[str] = None + self, + key: str, + team: Optional[str] = None, ) -> StreamResponse[DeleteModerationConfigResponse]: query_params = build_query_param(team=team) path_params = { @@ -148,7 +150,9 @@ def delete_config( ) def get_config( - self, key: str, team: Optional[str] = None + self, + key: str, + team: Optional[str] = None, ) -> StreamResponse[GetConfigResponse]: query_params = build_query_param(team=team) path_params = { @@ -183,7 +187,9 @@ def query_moderation_configs( ) return self.post( - "/api/v2/moderation/configs", QueryModerationConfigsResponse, json=json + "/api/v2/moderation/configs", + QueryModerationConfigsResponse, + json=json, ) def custom_check( @@ -207,7 +213,9 @@ def custom_check( ) return self.post( - "/api/v2/moderation/custom_check", CustomCheckResponse, json=json + "/api/v2/moderation/custom_check", + CustomCheckResponse, + json=json, ) def v2_delete_template(self) -> StreamResponse[DeleteModerationTemplateResponse]: @@ -225,7 +233,9 @@ def v2_query_templates( ) def v2_upsert_template( - self, name: str, config: FeedsModerationTemplateConfig + self, + name: str, + config: FeedsModerationTemplateConfig, ) -> StreamResponse[UpsertModerationTemplateResponse]: json = build_body_dict(name=name, config=config) @@ -268,11 +278,17 @@ def query_moderation_flags( filter: Optional[Dict[str, object]] = None, ) -> StreamResponse[QueryModerationFlagsResponse]: json = build_body_dict( - limit=limit, next=next, prev=prev, sort=sort, filter=filter + limit=limit, + next=next, + prev=prev, + sort=sort, + filter=filter, ) return self.post( - "/api/v2/moderation/flags", QueryModerationFlagsResponse, json=json + "/api/v2/moderation/flags", + QueryModerationFlagsResponse, + json=json, ) def query_moderation_logs( @@ -296,7 +312,9 @@ def query_moderation_logs( ) return self.post( - "/api/v2/moderation/logs", QueryModerationLogsResponse, json=json + "/api/v2/moderation/logs", + QueryModerationLogsResponse, + json=json, ) def mute( @@ -307,7 +325,10 @@ def mute( user: Optional[UserRequest] = None, ) -> StreamResponse[MuteResponse]: json = build_body_dict( - target_ids=target_ids, timeout=timeout, user_id=user_id, user=user + target_ids=target_ids, + timeout=timeout, + user_id=user_id, + user=user, ) return self.post("/api/v2/moderation/mute", MuteResponse, json=json) @@ -341,11 +362,14 @@ def query_review_queue( ) return self.post( - "/api/v2/moderation/review_queue", QueryReviewQueueResponse, json=json + "/api/v2/moderation/review_queue", + QueryReviewQueueResponse, + json=json, ) def get_review_queue_item( - self, id: str + self, + id: str, ) -> StreamResponse[GetReviewQueueItemResponse]: path_params = { "id": id, @@ -388,7 +412,9 @@ def submit_action( ) return self.post( - "/api/v2/moderation/submit_action", SubmitActionResponse, json=json + "/api/v2/moderation/submit_action", + SubmitActionResponse, + json=json, ) def unban( diff --git a/getstream/plugins/assemblyai/README.md b/getstream/plugins/assemblyai/README.md new file mode 100644 index 00000000..960df827 --- /dev/null +++ b/getstream/plugins/assemblyai/README.md @@ -0,0 +1,74 @@ +# AssemblyAI Speech-to-Text Plugin + +A high-quality Speech-to-Text (STT) plugin for GetStream that uses the AssemblyAI API. + +## Installation + +```bash +pip install getstream-plugins-assemblyai +``` + +## Usage + +```python +from getstream.plugins.assemblyai import AssemblyAISTT + +# Initialize with API key from environment variable +stt = AssemblyAISTT() + +# Or specify API key directly +stt = AssemblyAISTT(api_key="your_assemblyai_api_key") + +# Register event handlers +@stt.on("transcript") +def on_transcript(text, user, metadata): + print(f"Final transcript from {user}: {text}") + +@stt.on("partial_transcript") +def on_partial(text, user, metadata): + print(f"Partial transcript from {user}: {text}") + +# Process audio +await stt.process_audio(pcm_data) + +# When done +await stt.close() +``` + +## Configuration Options + +- `api_key`: AssemblyAI API key (default: reads from ASSEMBLYAI_API_KEY environment variable) +- `sample_rate`: Sample rate of the audio in Hz (default: 48000) +- `language`: Language code for transcription (default: "en") +- `interim_results`: Whether to emit interim results (default: True) +- `enable_partials`: Whether to enable partial results (default: True) +- `enable_automatic_punctuation`: Whether to enable automatic punctuation (default: True) +- `enable_utterance_end_detection`: Whether to enable utterance end detection (default: True) + +## Requirements + +- Python 3.10+ +- assemblyai>=0.43.1 +- numpy>=2.2.6,<2.3 + +## Features + +- Real-time streaming transcription +- Partial and final transcript results +- Automatic punctuation +- Utterance end detection +- Multi-language support +- High-quality transcription models + +## Environment Variables + +Set the following environment variable for API access: + +```bash +export ASSEMBLYAI_API_KEY="your_api_key_here" +``` + +## API Reference + +For more information about AssemblyAI's API, visit: +https://www.assemblyai.com/docs diff --git a/getstream/plugins/assemblyai/pyproject.toml b/getstream/plugins/assemblyai/pyproject.toml new file mode 100644 index 00000000..722dfad4 --- /dev/null +++ b/getstream/plugins/assemblyai/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "getstream-plugins-assemblyai" +version = "0.1.0" +description = "AssemblyAI plugin for GetStream" +readme = "README.md" +requires-python = ">=3.10" +license = "MIT" +dependencies = [ + "getstream[webrtc]>=2.3.0a4", + "getstream-plugins-common>=0.1.1", + "assemblyai>=0.43.1", + "numpy>=2.2.6,<2.3", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] + +[tool.uv.sources] +getstream = { workspace = true } +getstream-plugins-common = { workspace = true } + +[dependency-groups] +dev = [ + "pytest>=8.4.1", + "pytest-asyncio>=1.0.0", + "soundfile>=0.13.1", + "torchaudio>=2.7.1", + "scipy>=1.15.3,<1.16", +] diff --git a/getstream/plugins/assemblyai/stt/__init__.py b/getstream/plugins/assemblyai/stt/__init__.py new file mode 100644 index 00000000..b7a8ddee --- /dev/null +++ b/getstream/plugins/assemblyai/stt/__init__.py @@ -0,0 +1,9 @@ +"""AssemblyAI Speech-to-Text plugin for GetStream. + +This module provides real-time speech-to-text transcription using AssemblyAI's +streaming API with WebSocket support. +""" + +from .stt import AssemblyAISTT + +__all__ = ["AssemblyAISTT"] diff --git a/getstream/plugins/assemblyai/stt/stt.py b/getstream/plugins/assemblyai/stt/stt.py new file mode 100644 index 00000000..a2b59819 --- /dev/null +++ b/getstream/plugins/assemblyai/stt/stt.py @@ -0,0 +1,380 @@ +import logging +from typing import Dict, Any, Optional, Tuple, List +import numpy as np +import os +import time + +# Conditional imports with error handling +try: + import assemblyai as aai + from assemblyai.streaming.v3 import ( + StreamingClient, + StreamingClientOptions, + StreamingEvents, + StreamingParameters, + ) + + _assemblyai_available = True +except ImportError: + aai = None # type: ignore + StreamingClient = None # type: ignore + StreamingClientOptions = None # type: ignore + StreamingEvents = None # type: ignore + StreamingParameters = None # type: ignore + _assemblyai_available = False + +from getstream.plugins.common import STT +from getstream.video.rtc.track_util import PcmData + +logger = logging.getLogger(__name__) + + +class AssemblyAISTT(STT): + """ + AssemblyAI-based Speech-to-Text implementation. + + This implementation operates in asynchronous mode - it receives streaming transcripts + from AssemblyAI's WebSocket connection and emits events immediately as they arrive, + providing real-time responsiveness for live transcription scenarios. + + Events: + - transcript: Emitted when a complete transcript is available. + Args: text (str), user_metadata (dict), metadata (dict) + - partial_transcript: Emitted when a partial transcript is available. + Args: text (str), user_metadata (dict), metadata (dict) + - error: Emitted when an error occurs during transcription. + Args: error (Exception) + """ + + def __init__( + self, + api_key: Optional[str] = None, + sample_rate: int = 48000, + language: str = "en", + interim_results: bool = True, + enable_partials: bool = True, + enable_automatic_punctuation: bool = True, + enable_utterance_end_detection: bool = True, + ): + """ + Initialize the AssemblyAI STT service. + + Args: + api_key: AssemblyAI API key. If not provided, the ASSEMBLYAI_API_KEY + environment variable will be used automatically. + sample_rate: Sample rate of the audio in Hz (default: 48000) + language: Language code for transcription (default: "en") + interim_results: Whether to emit interim results (partial transcripts) + enable_partials: Whether to enable partial results + enable_automatic_punctuation: Whether to enable automatic punctuation + enable_utterance_end_detection: Whether to enable utterance end detection + """ + super().__init__(sample_rate=sample_rate, provider_name="assemblyai") + + # Check if assemblyai is available + if not _assemblyai_available: + raise ImportError("assemblyai package not installed.") + + # If no API key was provided, check for ASSEMBLYAI_API_KEY in environment + if api_key is None: + api_key = os.environ.get("ASSEMBLYAI_API_KEY") + if not api_key: + logger.warning( + "No API key provided and ASSEMBLYAI_API_KEY environment variable not found." + ) + + # Set the API key globally for AssemblyAI + aai.settings.api_key = api_key + + # Initialize streaming client + logger.info("Initializing AssemblyAI streaming client") + self.streaming_client = None + self._running = False + self._setup_attempted = False + self._is_closed = False + + # Configuration options + self.language = language + self.interim_results = interim_results + self.enable_partials = enable_partials + self.enable_automatic_punctuation = enable_automatic_punctuation + self.enable_utterance_end_detection = enable_utterance_end_detection + + # Track current user context for associating transcripts with users + self._current_user = None + + # Audio buffering for AssemblyAI requirements (50-1000ms chunks) + self._audio_buffer = bytearray() + self._buffer_start_time = None + self._min_chunk_duration_ms = 100 # Minimum 100ms chunks + self._max_chunk_duration_ms = 500 # Maximum 500ms chunks + + self._setup_connection() + + def _setup_connection(self): + """Set up the AssemblyAI streaming connection with event handlers.""" + if self._is_closed: + logger.warning("Cannot setup connection - AssemblyAI instance is closed") + return + + if self.streaming_client is not None: + logger.debug("Connection already set up, skipping initialization") + return + + try: + # Create the streaming client + logger.debug("Setting up AssemblyAI streaming connection") + self.streaming_client = StreamingClient( + StreamingClientOptions( + api_key=aai.settings.api_key, + ) + ) + + # Register event handlers with proper method references + self.streaming_client.on(StreamingEvents.Begin, self._on_begin) + self.streaming_client.on(StreamingEvents.Turn, self._on_turn) + self.streaming_client.on(StreamingEvents.Termination, self._on_terminated) + self.streaming_client.on(StreamingEvents.Error, self._on_error) + + # Start the connection + logger.info("Starting AssemblyAI connection") + self.streaming_client.connect( + StreamingParameters( + sample_rate=self.sample_rate, + language=self.language, + enable_partials=self.enable_partials, + enable_automatic_punctuation=self.enable_automatic_punctuation, + enable_utterance_end_detection=self.enable_utterance_end_detection, + ) + ) + + # Mark as running + self._running = True + + except Exception as e: + # Log the error and set connection to None + logger.error("Error setting up AssemblyAI connection", exc_info=e) + self.streaming_client = None + # Emit error immediately + self._emit_error_event(e, "AssemblyAI connection setup") + + def _on_begin(self, client, event): + """Handler for session begin event.""" + logger.info(f"AssemblyAI session started: {event.id}") + + def _on_turn(self, client, event): + """Handler for transcript results.""" + try: + # Get the transcript text from the response + transcript_text = event.transcript + if not transcript_text: + return + + # Check what attributes are available on the event + logger.debug(f"TurnEvent attributes: {dir(event)}") + + # AssemblyAI TurnEvent doesn't have is_final, it's always final + # Partial results come through different events + is_final = True + + # Create metadata with useful information + metadata = { + "confidence": getattr(event, "confidence", 0), + "is_final": is_final, + "session_id": getattr(event, "session_id", ""), + "audio_start": getattr(event, "audio_start", 0), + "audio_end": getattr(event, "audio_end", 0), + } + + # Handle the result (both collect and emit) + self._handle_transcript_result(is_final, transcript_text, metadata) + + logger.debug( + "Received transcript", + extra={ + "is_final": is_final, + "text_length": len(transcript_text), + "confidence": metadata["confidence"], + }, + ) + except Exception as e: + logger.error("Error processing transcript", exc_info=e) + # Emit error immediately + self._emit_error_event(e, "AssemblyAI transcript processing") + + def _on_terminated(self, client, event): + """Handler for session termination event.""" + logger.info( + f"AssemblyAI session terminated: {event.audio_duration_seconds} seconds of audio processed" + ) + + def _on_error(self, client, error): + """Handler for error events.""" + error_text = str(error) if error is not None else "Unknown error" + logger.error(f"AssemblyAI error received: {error_text}") + + # Emit error immediately + error_obj = Exception(f"AssemblyAI error: {error_text}") + self._emit_error_event(error_obj, "AssemblyAI connection") + + def _handle_transcript_result( + self, is_final: bool, text: str, metadata: Dict[str, Any] + ): + """ + Handle a transcript result by emitting it immediately. + """ + # Emit immediately for real-time responsiveness + if is_final: + self._emit_transcript_event(text, self._current_user, metadata) + else: + self._emit_partial_transcript_event(text, self._current_user, metadata) + + logger.debug( + "Handled transcript result", + extra={ + "is_final": is_final, + "text_length": len(text), + }, + ) + + async def _process_audio_impl( + self, pcm_data: PcmData, user_metadata: Optional[Dict[str, Any]] = None + ) -> Optional[List[Tuple[bool, str, Dict[str, Any]]]]: + """ + Process audio data through AssemblyAI for transcription. + + Args: + pcm_data: The PCM audio data to process. + user_metadata: Additional metadata about the user or session. + + Returns: + None - AssemblyAI operates in asynchronous mode and emits events directly + when transcripts arrive from the streaming service. + """ + if self._is_closed: + logger.warning("AssemblyAI connection is closed, ignoring audio") + return None + + # Store the current user context for transcript events + self._current_user = user_metadata + + # Check if the input sample rate matches the expected sample rate + if pcm_data.sample_rate != self.sample_rate: + logger.warning( + "Input audio sample rate (%s Hz) does not match the expected sample rate (%s Hz). " + "This may result in incorrect transcriptions. Consider resampling the audio.", + pcm_data.sample_rate, + self.sample_rate, + ) + + # Ensure connection is set up + if not self.streaming_client and not self._setup_attempted: + logger.warning("AssemblyAI connection not initialized, attempting setup") + self._setup_connection() + self._setup_attempted = True + + if not self.streaming_client: + if not self._setup_attempted: + logger.info("No AssemblyAI connection available, retrying setup") + self._setup_connection() + self._setup_attempted = True + else: + logger.error("No AssemblyAI connection available after retry") + # Return an error result instead of emitting directly + raise Exception("No AssemblyAI connection available") + + # Mark that we've attempted setup + self._setup_attempted = True + + # Convert PCM data to bytes if needed + audio_data = pcm_data.samples + if not isinstance(audio_data, bytes): + # Convert numpy array to bytes + audio_data = audio_data.astype(np.int16).tobytes() + + # Initialize buffer start time if not set + if self._buffer_start_time is None: + self._buffer_start_time = time.time() + + # Add audio data to buffer + self._audio_buffer.extend(audio_data) + + # Calculate current buffer duration + buffer_duration_ms = ( + len(self._audio_buffer) / (self.sample_rate * 2) + ) * 1000 # 2 bytes per sample for int16 + + # Send buffer if it meets minimum duration requirement + if buffer_duration_ms >= self._min_chunk_duration_ms: + try: + logger.debug( + "Sending buffered audio to AssemblyAI", + extra={ + "audio_bytes": len(self._audio_buffer), + "duration_ms": buffer_duration_ms, + }, + ) + + # Send the buffered audio data + self.streaming_client.stream(bytes(self._audio_buffer)) + + # Clear buffer and reset timer + self._audio_buffer.clear() + self._buffer_start_time = time.time() + + except Exception as e: + logger.error("Error sending audio to AssemblyAI", exc_info=e) + # Clear buffer on error to prevent accumulation + self._audio_buffer.clear() + self._buffer_start_time = time.time() + raise Exception(f"AssemblyAI audio transmission error: {e}") + + # Return None for asynchronous mode - events are emitted when they arrive + return None + + async def close(self): + """Close the AssemblyAI connection and clean up resources.""" + if self._is_closed: + logger.debug("AssemblyAI STT service already closed") + return + + logger.info("Closing AssemblyAI STT service") + self._is_closed = True + self._running = False + + # Flush any remaining audio in buffer + if self._audio_buffer and self.streaming_client: + try: + logger.debug("Flushing remaining audio buffer") + self.streaming_client.stream(bytes(self._audio_buffer)) + self._audio_buffer.clear() + except Exception as e: + logger.warning("Error flushing audio buffer", exc_info=e) + + # Close the AssemblyAI connection if it exists + if self.streaming_client: + logger.debug("Closing AssemblyAI connection") + try: + self.streaming_client.disconnect() + self.streaming_client = None + except Exception as e: + logger.error("Error closing AssemblyAI connection", exc_info=e) + + async def __aenter__(self): + """Async context manager entry.""" + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Async context manager exit.""" + await self.close() + + def flush_buffer(self): + """Flush the current audio buffer immediately (for testing purposes).""" + if self.streaming_client and self._audio_buffer: + try: + self.streaming_client.stream(bytes(self._audio_buffer)) + self._audio_buffer.clear() + self._buffer_start_time = None + except Exception as e: + logger.error("Error flushing audio buffer", exc_info=e) + raise Exception(f"AssemblyAI buffer flush error: {e}") diff --git a/getstream/plugins/assemblyai/tests/__init__.py b/getstream/plugins/assemblyai/tests/__init__.py new file mode 100644 index 00000000..87c26d4c --- /dev/null +++ b/getstream/plugins/assemblyai/tests/__init__.py @@ -0,0 +1 @@ +# Tests for AssemblyAI plugin diff --git a/getstream/plugins/assemblyai/tests/conftest.py b/getstream/plugins/assemblyai/tests/conftest.py new file mode 100644 index 00000000..06afb424 --- /dev/null +++ b/getstream/plugins/assemblyai/tests/conftest.py @@ -0,0 +1,108 @@ +import pytest +import os +import numpy as np +from getstream.video.rtc.track_util import PcmData + + +@pytest.fixture(scope="session") +def assemblyai_api_key(): + """Get the AssemblyAI API key from environment variables.""" + api_key = os.environ.get("ASSEMBLYAI_API_KEY") + if not api_key: + pytest.skip( + "ASSEMBLYAI_API_KEY environment variable not set. Add it to your .env file." + ) + return api_key + + +@pytest.fixture +def mock_assemblyai_api_key(): + """Provide a mock API key for unit tests that don't need real API access.""" + return "test_api_key_12345" + + +@pytest.fixture(scope="session") +def assemblyai_language(): + """Get the AssemblyAI language from environment variables.""" + return os.environ.get("ASSEMBLYAI_LANGUAGE", "en") + + +@pytest.fixture(scope="session") +def assemblyai_sample_rate(): + """Get the AssemblyAI sample rate from environment variables.""" + return int(os.environ.get("ASSEMBLYAI_SAMPLE_RATE", "48000")) + + +@pytest.fixture +def sample_audio_data(): + """Create realistic sample audio data for testing.""" + # Generate 1 second of audio at 48kHz (sine wave) + sample_rate = 48000 + duration = 1.0 # seconds + frequency = 440 # Hz (A note) + + t = np.linspace(0, duration, int(sample_rate * duration), False) + samples = (np.sin(2 * np.pi * frequency * t) * 16384).astype(np.int16) + + return PcmData(format="s16", samples=samples, sample_rate=sample_rate) + + +@pytest.fixture +def sample_audio_data_16k(): + """Create realistic sample audio data at 16kHz for testing.""" + # Generate 1 second of audio at 16kHz (sine wave) + sample_rate = 16000 + duration = 1.0 # seconds + frequency = 440 # Hz (A note) + + t = np.linspace(0, duration, int(sample_rate * duration), False) + samples = (np.sin(2 * np.pi * frequency * t) * 16384).astype(np.int16) + + return PcmData(format="s16", samples=samples, sample_rate=sample_rate) + + +@pytest.fixture +def mock_transcription_response(): + """Mock transcription response data.""" + return { + "text": "Hello world, this is a test transcription.", + "confidence": 0.95, + "words": [ + {"text": "Hello", "start": 0, "end": 0.5, "confidence": 0.98}, + {"text": "world", "start": 0.5, "end": 1.0, "confidence": 0.96}, + {"text": "this", "start": 1.0, "end": 1.3, "confidence": 0.94}, + {"text": "is", "start": 1.3, "end": 1.5, "confidence": 0.97}, + {"text": "a", "start": 1.5, "end": 1.6, "confidence": 0.99}, + {"text": "test", "start": 1.6, "end": 2.0, "confidence": 0.93}, + {"text": "transcription", "start": 2.0, "end": 2.8, "confidence": 0.92}, + ], + "audio_start": 0, + "audio_end": 2.8, + } + + +@pytest.fixture +def mock_partial_transcription(): + """Mock partial transcription response data.""" + return { + "text": "Hello world, this is", + "confidence": 0.94, + "words": [ + {"text": "Hello", "start": 0, "end": 0.5, "confidence": 0.98}, + {"text": "world", "start": 0.5, "end": 1.0, "confidence": 0.96}, + {"text": "this", "start": 1.0, "end": 1.3, "confidence": 0.94}, + {"text": "is", "start": 1.3, "end": 1.5, "confidence": 0.97}, + ], + "audio_start": 0, + "audio_end": 1.5, + } + + +@pytest.fixture +def mock_error_response(): + """Mock error response data.""" + return { + "error": "Invalid audio format", + "code": "INVALID_AUDIO_FORMAT", + "details": "The provided audio data is not in a supported format.", + } diff --git a/getstream/plugins/assemblyai/tests/test_stt.py b/getstream/plugins/assemblyai/tests/test_stt.py new file mode 100644 index 00000000..c1d50996 --- /dev/null +++ b/getstream/plugins/assemblyai/tests/test_stt.py @@ -0,0 +1,585 @@ +import pytest +import asyncio +import numpy as np +from unittest.mock import Mock, patch +from getstream.video.rtc.track_util import PcmData +from getstream.plugins.assemblyai.stt import AssemblyAISTT + + +@pytest.fixture +def mock_assemblyai_dependencies(): + """Mock all AssemblyAI dependencies to avoid import issues during testing.""" + with ( + patch("getstream.plugins.assemblyai.stt.stt._assemblyai_available", True), + patch("getstream.plugins.assemblyai.stt.stt.aai") as mock_aai, + patch( + "getstream.plugins.assemblyai.stt.stt.StreamingClient" + ) as mock_streaming_client, + patch( + "getstream.plugins.assemblyai.stt.stt.StreamingClientOptions" + ) as mock_options, + patch("getstream.plugins.assemblyai.stt.stt.StreamingEvents") as mock_events, + patch( + "getstream.plugins.assemblyai.stt.stt.StreamingParameters" + ) as mock_params, + patch( + "getstream.plugins.assemblyai.stt.stt.AssemblyAISTT._setup_connection" + ) as mock_setup, + ): + # Setup mock objects + mock_aai.settings.api_key = "test_key" + mock_streaming_client.return_value = Mock() + mock_options.return_value = Mock() + mock_events.Begin = Mock() + mock_events.Turn = Mock() + mock_events.Termination = Mock() + mock_events.Error = Mock() + mock_params.return_value = Mock() + mock_setup.return_value = None + + yield { + "aai": mock_aai, + "streaming_client": mock_streaming_client, + "options": mock_options, + "events": mock_events, + "params": mock_params, + "setup": mock_setup, + } + + +@pytest.fixture +def sample_pcm_data(): + """Create sample PCM data for testing.""" + # Create enough samples to meet the 100ms minimum chunk duration + # 100ms at 48kHz = 4800 samples + samples = np.array([1000, -1000, 500, -500, 750, -750] * 800, dtype=np.int16) + return PcmData(format="s16", samples=samples, sample_rate=48000) + + +@pytest.fixture +def sample_pcm_data_16k(): + """Create sample PCM data with 16kHz sample rate for testing.""" + samples = np.array([1000, -1000, 500, -500], dtype=np.int16) + return PcmData(format="s16", samples=samples, sample_rate=16000) + + +@pytest.fixture +def mock_streaming_client(): + """Create a mock streaming client with common methods.""" + client = Mock() + client.stream = Mock() + client.disconnect = Mock() + client.is_connected = Mock(return_value=True) + return client + + +class TestAssemblyAISTTInitialization: + """Test cases for AssemblyAI STT plugin initialization.""" + + def test_import_success(self): + """Test that the plugin can be imported successfully.""" + assert AssemblyAISTT is not None + + def test_init_with_api_key(self, assemblyai_api_key, mock_assemblyai_dependencies): + """Test initialization with API key.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key) + + assert stt.sample_rate == 48000 + assert stt.language == "en" + assert stt.interim_results is True + assert stt.enable_partials is True + assert stt.enable_automatic_punctuation is True + assert stt.enable_utterance_end_detection is True + + def test_init_with_custom_config(self, mock_assemblyai_dependencies): + """Test initialization with custom configuration.""" + stt = AssemblyAISTT( + sample_rate=16000, + language="es", + interim_results=False, + enable_partials=False, + enable_automatic_punctuation=False, + enable_utterance_end_detection=False, + ) + + assert stt.sample_rate == 16000 + assert stt.language == "es" + assert stt.interim_results is False + assert stt.enable_partials is False + assert stt.enable_automatic_punctuation is False + assert stt.enable_utterance_end_detection is False + + def test_init_without_api_key_logs_warning(self, mock_assemblyai_dependencies): + """Test that initialization without API key logs a warning.""" + + with ( + patch("getstream.plugins.assemblyai.stt.stt.logger") as mock_logger, + patch.dict("os.environ", {}, clear=True), + ): + stt = AssemblyAISTT() + # Should still initialize but with warning + assert stt is not None + # Check that warning was logged + mock_logger.warning.assert_called_with( + "No API key provided and ASSEMBLYAI_API_KEY environment variable not found." + ) + + def test_provider_name_setting( + self, assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test that the provider name is set correctly.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key) + assert stt.provider_name == "assemblyai" + + +class TestAssemblyAISTTConfiguration: + """Test cases for configuration and validation.""" + + @pytest.mark.parametrize( + "sample_rate,expected", + [ + (8000, 8000), + (16000, 16000), + (22050, 22050), + (44100, 44100), + (48000, 48000), + ], + ) + def test_sample_rate_configuration( + self, sample_rate, expected, mock_assemblyai_dependencies + ): + """Test different sample rate configurations.""" + stt = AssemblyAISTT(sample_rate=sample_rate) + assert stt.sample_rate == expected + + @pytest.mark.parametrize( + "language,expected", + [ + ("en", "en"), + ("es", "es"), + ("fr", "fr"), + ("de", "de"), + ("it", "it"), + ], + ) + def test_language_configuration( + self, language, expected, mock_assemblyai_dependencies + ): + """Test different language configurations.""" + stt = AssemblyAISTT(language=language) + assert stt.language == expected + + def test_invalid_sample_rate_handling(self, mock_assemblyai_dependencies): + """Test that invalid sample rate is handled gracefully.""" + + # The current implementation doesn't validate sample rate, so it should accept any value + stt = AssemblyAISTT(sample_rate=9999) + assert stt.sample_rate == 9999 + + +class TestAssemblyAISTTDataValidation: + """Test cases for data validation.""" + + def test_pcm_data_validation( + self, assemblyai_api_key, mock_assemblyai_dependencies, sample_pcm_data + ): + """Test that PCM data validation works correctly.""" + # Create instance to test initialization + _ = AssemblyAISTT(api_key=assemblyai_api_key) + + # Should not raise an exception + assert sample_pcm_data.samples is not None + assert sample_pcm_data.sample_rate == 48000 + assert len(sample_pcm_data.samples) > 0 + + def test_empty_pcm_data_handling( + self, assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test handling of empty PCM data.""" + # Create instance to test initialization + _ = AssemblyAISTT(api_key=assemblyai_api_key) + + # Create empty PCM data + empty_samples = np.array([], dtype=np.int16) + empty_pcm_data = PcmData(format="s16", samples=empty_samples, sample_rate=48000) + + # Should handle empty data gracefully + assert len(empty_pcm_data.samples) == 0 + + def test_pcm_data_with_different_dtypes( + self, assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test PCM data with different numpy dtypes.""" + # Create instance to test initialization + _ = AssemblyAISTT(api_key=assemblyai_api_key) + + # Test with float32 + float_samples = np.array([0.5, -0.5, 0.25, -0.25], dtype=np.float32) + float_pcm_data = PcmData(format="f32", samples=float_samples, sample_rate=48000) + assert float_pcm_data.samples.dtype == np.float32 + + # Test with int32 + int32_samples = np.array([1000, -1000, 500, -500], dtype=np.int32) + int32_pcm_data = PcmData(format="s32", samples=int32_samples, sample_rate=48000) + assert int32_pcm_data.samples.dtype == np.int32 + + +class TestAssemblyAISTTConnectionManagement: + """Test cases for connection management.""" + + @pytest.mark.asyncio + async def test_close_method( + self, assemblyai_api_key, mock_assemblyai_dependencies, mock_streaming_client + ): + """Test that the close method works correctly.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key) + + # Mock the streaming client + stt.streaming_client = mock_streaming_client + + await stt.close() + + assert stt._is_closed is True + assert stt._running is False + mock_streaming_client.disconnect.assert_called_once() + + @pytest.mark.asyncio + async def test_async_context_manager( + self, assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test that the plugin can be used as an async context manager.""" + + async with AssemblyAISTT(api_key=assemblyai_api_key) as stt: + assert stt is not None + assert stt._is_closed is False + + # Should be closed after context manager + assert stt._is_closed is True + + def test_connection_status_check( + self, assemblyai_api_key, mock_assemblyai_dependencies, mock_streaming_client + ): + """Test connection status checking.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key) + + # Initially no connection (but mock dependencies might set it up) + # We'll test the connection flow instead + + # Mock connection + stt.streaming_client = mock_streaming_client + mock_streaming_client.is_connected.return_value = True + + # Check that streaming client is set + assert stt.streaming_client is not None + assert stt.streaming_client.is_connected.return_value is True + + +class TestAssemblyAISTTAudioProcessing: + """Test cases for audio processing functionality.""" + + @pytest.mark.asyncio + async def test_process_audio_sample_rate_mismatch( + self, assemblyai_api_key, mock_assemblyai_dependencies, sample_pcm_data_16k + ): + """Test that sample rate mismatch warning is logged.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key, sample_rate=48000) + + # Mock the streaming client + stt.streaming_client = Mock() + stt.streaming_client.stream = Mock() + + with patch("getstream.plugins.assemblyai.stt.stt.logger") as mock_logger: + await stt._process_audio_impl(sample_pcm_data_16k) + mock_logger.warning.assert_called() + + @pytest.mark.asyncio + async def test_process_audio_connection_not_ready( + self, mock_assemblyai_api_key, mock_assemblyai_dependencies, sample_pcm_data + ): + """Test that audio processing fails when connection is not ready.""" + stt = AssemblyAISTT(api_key=mock_assemblyai_api_key) + + # Should raise exception when no connection + with pytest.raises(Exception, match="No AssemblyAI connection available"): + await stt._process_audio_impl(sample_pcm_data) + + @pytest.mark.asyncio + async def test_process_audio_success( + self, + mock_assemblyai_api_key, + mock_assemblyai_dependencies, + sample_pcm_data, + mock_streaming_client, + ): + """Test successful audio processing.""" + stt = AssemblyAISTT(api_key=mock_assemblyai_api_key) + + # Mock the streaming client + stt.streaming_client = mock_streaming_client + + # Process audio should not raise exception + await stt._process_audio_impl(sample_pcm_data) + + # Flush the buffer to ensure audio is sent immediately + stt.flush_buffer() + + # Verify that stream was called + mock_streaming_client.stream.assert_called_once() + + @pytest.mark.asyncio + async def test_process_audio_with_large_data( + self, + mock_assemblyai_api_key, + mock_assemblyai_dependencies, + mock_streaming_client, + ): + """Test audio processing with large PCM data.""" + stt = AssemblyAISTT(api_key=mock_assemblyai_api_key) + + # Create large PCM data + large_samples = np.random.randint(-32768, 32767, size=10000, dtype=np.int16) + large_pcm_data = PcmData(format="s16", samples=large_samples, sample_rate=48000) + + # Mock the streaming client + stt.streaming_client = mock_streaming_client + + # Should handle large data without issues + await stt._process_audio_impl(large_pcm_data) + + # Flush the buffer to ensure audio is sent immediately + stt.flush_buffer() + + mock_streaming_client.stream.assert_called_once() + + +class TestAssemblyAISTTEventHandling: + """Test cases for event handling and inheritance.""" + + def test_event_emitter_inheritance( + self, assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test that the plugin inherits from the correct base classes.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key) + + # Check that it has the expected methods + assert hasattr(stt, "on") + assert hasattr(stt, "emit") + assert hasattr(stt, "once") + assert hasattr(stt, "remove_listener") + + def test_event_emission(self, assemblyai_api_key, mock_assemblyai_dependencies): + """Test that events can be emitted and listened to.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key) + + # Test event emission + events_received = [] + + def event_handler(event_data): + events_received.append(event_data) + + stt.on("transcription", event_handler) + stt.emit("transcription", {"text": "test"}) + + assert len(events_received) == 1 + assert events_received[0]["text"] == "test" + + def test_multiple_event_listeners( + self, assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test multiple event listeners.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key) + + events_received = [] + + def handler1(event_data): + events_received.append(("handler1", event_data)) + + def handler2(event_data): + events_received.append(("handler2", event_data)) + + stt.on("transcription", handler1) + stt.on("transcription", handler2) + stt.emit("transcription", {"text": "test"}) + + assert len(events_received) == 2 + assert any("handler1" in event for event in events_received) + assert any("handler2" in event for event in events_received) + + +class TestAssemblyAISTTErrorHandling: + """Test cases for error handling scenarios.""" + + @pytest.mark.asyncio + async def test_network_error_handling( + self, mock_assemblyai_api_key, mock_assemblyai_dependencies, sample_pcm_data + ): + """Test handling of network errors during audio processing.""" + stt = AssemblyAISTT(api_key=mock_assemblyai_api_key) + + # Mock streaming client that raises network error + mock_client = Mock() + mock_client.stream.side_effect = ConnectionError("Network error") + stt.streaming_client = mock_client + + # Process audio - this should raise the error immediately since the buffer is large enough + with pytest.raises(Exception, match="AssemblyAI audio transmission error"): + await stt._process_audio_impl(sample_pcm_data) + + def test_empty_api_key_handling(self, mock_assemblyai_dependencies): + """Test handling of empty API key.""" + + # The current implementation accepts empty API keys without logging a warning + # (only logs warning when api_key is None) + stt = AssemblyAISTT(api_key="") + assert stt is not None + # Empty string is treated as a valid API key value + + @pytest.mark.asyncio + async def test_streaming_client_disconnect_error( + self, assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test handling of errors during client disconnection.""" + stt = AssemblyAISTT(api_key=assemblyai_api_key) + + # Mock streaming client that raises error on disconnect + mock_client = Mock() + mock_client.disconnect.side_effect = Exception("Disconnect error") + stt.streaming_client = mock_client + + # Should handle disconnect errors gracefully + await stt.close() + assert stt._is_closed is True + + +class TestAssemblyAISTTPerformance: + """Test cases for performance characteristics.""" + + @pytest.mark.asyncio + async def test_audio_processing_performance( + self, + mock_assemblyai_api_key, + mock_assemblyai_dependencies, + mock_streaming_client, + ): + """Test audio processing performance with timing.""" + import time + + stt = AssemblyAISTT(api_key=mock_assemblyai_api_key) + stt.streaming_client = mock_streaming_client + + # Create test data + samples = np.random.randint(-32768, 32767, size=1000, dtype=np.int16) + pcm_data = PcmData(format="s16", samples=samples, sample_rate=48000) + + # Measure processing time + start_time = time.time() + await stt._process_audio_impl(pcm_data) + end_time = time.time() + + processing_time = end_time - start_time + # Should process audio in reasonable time (less than 100ms for small data) + assert processing_time < 0.1 + + def test_memory_usage_with_large_data( + self, mock_assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test memory usage with large PCM data.""" + try: + import psutil + import os + except ImportError: + pytest.skip("psutil not available for memory testing") + + # Create instance to test initialization + _ = AssemblyAISTT(api_key=mock_assemblyai_api_key) + + # Get initial memory usage + process = psutil.Process(os.getpid()) + initial_memory = process.memory_info().rss + + # Create large PCM data + large_samples = np.random.randint(-32768, 32767, size=100000, dtype=np.int16) + large_pcm_data = PcmData(format="s16", samples=large_samples, sample_rate=48000) + + # Just create the object - no validation method exists in current implementation + assert large_pcm_data is not None + assert len(large_pcm_data.samples) == 100000 + + # Get final memory usage + final_memory = process.memory_info().rss + memory_increase = final_memory - initial_memory + + # Memory increase should be reasonable (less than 10MB for this operation) + assert memory_increase < 10 * 1024 * 1024 + + +# Integration test class for more realistic scenarios +@pytest.mark.integration +class TestAssemblyAISTTIntegration: + """Integration tests for AssemblyAI STT plugin.""" + + @pytest.mark.asyncio + async def test_full_audio_processing_workflow( + self, mock_assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test the complete audio processing workflow.""" + + async with AssemblyAISTT(api_key=mock_assemblyai_api_key) as stt: + # Mock streaming client with realistic behavior + mock_client = Mock() + mock_client.is_connected.return_value = True + mock_client.stream = Mock() + stt.streaming_client = mock_client + + # Set running flag manually since _setup_connection is patched + stt._running = True + + # Create realistic audio data + samples = np.random.randint( + -32768, 32767, size=4800, dtype=np.int16 + ) # 100ms at 48kHz + pcm_data = PcmData(format="s16", samples=samples, sample_rate=48000) + + # Process audio + await stt._process_audio_impl(pcm_data) + + # Flush buffer to ensure audio is sent + stt.flush_buffer() + + # Verify processing + assert mock_client.stream.called + + # Verify state + assert not stt._is_closed + assert stt._running + + @pytest.mark.asyncio + async def test_concurrent_audio_processing( + self, mock_assemblyai_api_key, mock_assemblyai_dependencies + ): + """Test concurrent audio processing.""" + + async with AssemblyAISTT(api_key=mock_assemblyai_api_key) as stt: + mock_client = Mock() + mock_client.is_connected.return_value = True + mock_client.stream = Mock() + stt.streaming_client = mock_client + + # Set running flag manually since _setup_connection is patched + stt._running = True + + # Create multiple audio chunks that are large enough to trigger immediate processing + audio_chunks = [] + for i in range(5): + # 120ms at 48kHz = 5760 samples (above the 100ms threshold) + samples = np.random.randint(-32768, 32767, size=5760, dtype=np.int16) + audio_chunks.append( + PcmData(format="s16", samples=samples, sample_rate=48000) + ) + + # Process all chunks concurrently + tasks = [stt._process_audio_impl(chunk) for chunk in audio_chunks] + await asyncio.gather(*tasks) + + # Verify all chunks were processed immediately (no need to flush) + assert mock_client.stream.call_count == 5 diff --git a/getstream/plugins/cartesia/tests/test_tts.py b/getstream/plugins/cartesia/tests/test_tts.py index 0db671f7..fb0b5ab5 100644 --- a/getstream/plugins/cartesia/tests/test_tts.py +++ b/getstream/plugins/cartesia/tests/test_tts.py @@ -1,13 +1,12 @@ -import os import asyncio -from unittest.mock import patch, MagicMock +import os +from unittest.mock import MagicMock, patch import pytest from getstream.plugins.cartesia.tts import CartesiaTTS from getstream.video.rtc.audio_track import AudioStreamTrack - ############################ # Test utilities & fixtures ############################ @@ -39,7 +38,7 @@ def __init__(self, api_key=None): mock_audio = [b"\x00\x00" * 1000, b"\x00\x00" * 1000] self.tts.bytes = MagicMock( - side_effect=lambda *_, **__: _AsyncBytesIterator(mock_audio.copy()) + side_effect=lambda *_, **__: _AsyncBytesIterator(mock_audio.copy()), ) diff --git a/getstream/plugins/cartesia/tts/tts.py b/getstream/plugins/cartesia/tts/tts.py index 37cfca51..2d3aebfa 100644 --- a/getstream/plugins/cartesia/tts/tts.py +++ b/getstream/plugins/cartesia/tts/tts.py @@ -29,15 +29,17 @@ def __init__( model_id: Which model to use (default ``sonic-2``). voice_id: Cartesia voice ID. When ``None`` the model default is used. sample_rate: PCM sample-rate you want back (must match output track). - """ + """ super().__init__() self.api_key = api_key or os.getenv("CARTESIA_API_KEY") if not self.api_key: raise ValueError("CARTESIA_API_KEY env var or api_key parameter required") - self.client = client if client is not None else AsyncCartesia(api_key=self.api_key) + self.client = ( + client if client is not None else AsyncCartesia(api_key=self.api_key) + ) self.model_id = model_id self.voice_id = voice_id self.sample_rate = sample_rate @@ -45,13 +47,12 @@ def __init__( def set_output_track(self, track: AudioStreamTrack) -> None: # noqa: D401 if track.framerate != self.sample_rate: raise TypeError( - f"Track framerate {track.framerate} β‰  expected {self.sample_rate}" + f"Track framerate {track.framerate} β‰  expected {self.sample_rate}", ) super().set_output_track(track) async def stream_audio(self, text: str, *_, **__) -> bytes: # noqa: D401 """Generate speech and yield raw PCM chunks.""" - output_format: OutputFormat_Raw = { "container": "raw", "encoding": "pcm_s16le", @@ -76,17 +77,16 @@ async def _audio_chunk_stream(): # noqa: D401 return _audio_chunk_stream() async def stop_audio(self) -> None: - """ - Clears the queue and stops playing audio. + """Clears the queue and stops playing audio. This method can be used manually or under the hood in response to turn events. Returns: None + """ try: - await self.track.flush(), + (await self.track.flush(),) logging.info("🎀 Stopping audio track for TTS") return except Exception as e: logging.error(f"Error flushing audio track: {e}") - diff --git a/getstream/plugins/common/__init__.py b/getstream/plugins/common/__init__.py index 4e17367b..ca05117c 100644 --- a/getstream/plugins/common/__init__.py +++ b/getstream/plugins/common/__init__.py @@ -4,62 +4,61 @@ this package for the canonical definitions of STT, TTS, VAD, … """ -from .stt import STT -from .tts import TTS -from .vad import VAD -from .sts import STS +from .event_metrics import ( + calculate_stt_metrics, + calculate_tts_metrics, + calculate_vad_metrics, +) +from .event_serialization import deserialize_event, serialize_event, serialize_events +from .event_utils import ( + EventFilter, + EventLogger, + EventRegistry, + get_global_logger, + get_global_registry, + register_global_event, +) from .events import ( - EventType, - ConnectionState, AudioFormat, BaseEvent, - + ConnectionState, + EventType, + PluginClosedEvent, + PluginErrorEvent, + # Generic Events + PluginInitializedEvent, + STSAudioInputEvent, + STSAudioOutputEvent, + # STS Events + STSConnectedEvent, + STSConversationItemEvent, + STSDisconnectedEvent, + STSErrorEvent, + STSResponseEvent, + STSTranscriptEvent, + STTConnectionEvent, + STTErrorEvent, + STTPartialTranscriptEvent, # STT Events STTTranscriptEvent, - STTPartialTranscriptEvent, - STTErrorEvent, - STTConnectionEvent, - # TTS Events TTSAudioEvent, - TTSSynthesisStartEvent, - TTSSynthesisCompleteEvent, - TTSErrorEvent, TTSConnectionEvent, - - # STS Events - STSConnectedEvent, - STSDisconnectedEvent, - STSAudioInputEvent, - STSAudioOutputEvent, - STSTranscriptEvent, - STSResponseEvent, - STSConversationItemEvent, - STSErrorEvent, - - # VAD Events - VADSpeechStartEvent, - VADSpeechEndEvent, + TTSErrorEvent, + TTSSynthesisCompleteEvent, + TTSSynthesisStartEvent, VADAudioEvent, - VADPartialEvent, VADErrorEvent, - - # Generic Events - PluginInitializedEvent, - PluginClosedEvent, - PluginErrorEvent, -) -from .event_utils import ( - EventFilter, - EventRegistry, - EventLogger, - register_global_event, - get_global_registry, - get_global_logger, + VADPartialEvent, + VADSpeechEndEvent, + # VAD Events + VADSpeechStartEvent, + create_event, ) -from .event_serialization import serialize_event, serialize_events, deserialize_event -from .event_metrics import calculate_stt_metrics, calculate_tts_metrics, calculate_vad_metrics -from .events import create_event +from .sts import STS +from .stt import STT +from .tts import TTS +from .vad import VAD __all__ = [ # Base classes @@ -67,26 +66,22 @@ "TTS", "VAD", "STS", - # Event system "EventType", "ConnectionState", "AudioFormat", "BaseEvent", - # STT Events "STTTranscriptEvent", "STTPartialTranscriptEvent", "STTErrorEvent", "STTConnectionEvent", - # TTS Events "TTSAudioEvent", "TTSSynthesisStartEvent", "TTSSynthesisCompleteEvent", "TTSErrorEvent", "TTSConnectionEvent", - # STS Events "STSConnectedEvent", "STSDisconnectedEvent", @@ -96,19 +91,16 @@ "STSResponseEvent", "STSConversationItemEvent", "STSErrorEvent", - # VAD Events "VADSpeechStartEvent", "VADSpeechEndEvent", "VADAudioEvent", "VADPartialEvent", "VADErrorEvent", - # Generic Events "PluginInitializedEvent", "PluginClosedEvent", "PluginErrorEvent", - # Event utilities "EventFilter", "EventRegistry", @@ -117,12 +109,10 @@ "get_global_registry", "get_global_logger", "create_event", - # Event serialization "serialize_event", "serialize_events", "deserialize_event", - # Event metrics "calculate_stt_metrics", "calculate_tts_metrics", diff --git a/getstream/plugins/common/event_metrics.py b/getstream/plugins/common/event_metrics.py index d2c35322..a4bdce23 100644 --- a/getstream/plugins/common/event_metrics.py +++ b/getstream/plugins/common/event_metrics.py @@ -1,23 +1,28 @@ -""" -Event metrics calculation utilities for GetStream AI plugins. +"""Event metrics calculation utilities for GetStream AI plugins. This module provides functions for calculating performance metrics from events across different plugin types. """ -from typing import Dict, Any, List +from typing import Any, Dict, List from .events import ( - BaseEvent, STTTranscriptEvent, STTPartialTranscriptEvent, - TTSAudioEvent, TTSSynthesisStartEvent, TTSSynthesisCompleteEvent, - VADAudioEvent, VADPartialEvent + BaseEvent, + STTPartialTranscriptEvent, + STTTranscriptEvent, + TTSAudioEvent, + TTSSynthesisCompleteEvent, + TTSSynthesisStartEvent, + VADAudioEvent, + VADPartialEvent, ) def calculate_stt_metrics(events: List[BaseEvent]) -> Dict[str, Any]: """Calculate STT-specific metrics.""" transcript_events = [ - e for e in events + e + for e in events if isinstance(e, (STTTranscriptEvent, STTPartialTranscriptEvent)) ] @@ -26,35 +31,45 @@ def calculate_stt_metrics(events: List[BaseEvent]) -> Dict[str, Any]: # Calculate processing time statistics processing_times = [ - e.processing_time_ms for e in transcript_events - if hasattr(e, 'processing_time_ms') and e.processing_time_ms + e.processing_time_ms + for e in transcript_events + if hasattr(e, "processing_time_ms") and e.processing_time_ms ] # Calculate confidence statistics confidences = [ - e.confidence for e in transcript_events - if hasattr(e, 'confidence') and e.confidence is not None + e.confidence + for e in transcript_events + if hasattr(e, "confidence") and e.confidence is not None ] metrics = { "total_transcripts": len(transcript_events), - "final_transcripts": len([e for e in transcript_events if getattr(e, 'is_final', True)]), - "partial_transcripts": len([e for e in transcript_events if not getattr(e, 'is_final', True)]), + "final_transcripts": len( + [e for e in transcript_events if getattr(e, "is_final", True)], + ), + "partial_transcripts": len( + [e for e in transcript_events if not getattr(e, "is_final", True)], + ), } if processing_times: - metrics.update({ - "avg_processing_time_ms": sum(processing_times) / len(processing_times), - "min_processing_time_ms": min(processing_times), - "max_processing_time_ms": max(processing_times), - }) + metrics.update( + { + "avg_processing_time_ms": sum(processing_times) / len(processing_times), + "min_processing_time_ms": min(processing_times), + "max_processing_time_ms": max(processing_times), + }, + ) if confidences: - metrics.update({ - "avg_confidence": sum(confidences) / len(confidences), - "min_confidence": min(confidences), - "max_confidence": max(confidences), - }) + metrics.update( + { + "avg_confidence": sum(confidences) / len(confidences), + "min_confidence": min(confidences), + "max_confidence": max(confidences), + }, + ) return metrics @@ -74,22 +89,28 @@ def calculate_tts_metrics(events: List[BaseEvent]) -> Dict[str, Any]: if completion_events: synthesis_times = [e.synthesis_time_ms for e in completion_events] real_time_factors = [ - e.real_time_factor for e in completion_events + e.real_time_factor + for e in completion_events if e.real_time_factor is not None ] - metrics.update({ - "avg_synthesis_time_ms": sum(synthesis_times) / len(synthesis_times), - "min_synthesis_time_ms": min(synthesis_times), - "max_synthesis_time_ms": max(synthesis_times), - }) + metrics.update( + { + "avg_synthesis_time_ms": sum(synthesis_times) / len(synthesis_times), + "min_synthesis_time_ms": min(synthesis_times), + "max_synthesis_time_ms": max(synthesis_times), + }, + ) if real_time_factors: - metrics.update({ - "avg_real_time_factor": sum(real_time_factors) / len(real_time_factors), - "min_real_time_factor": min(real_time_factors), - "max_real_time_factor": max(real_time_factors), - }) + metrics.update( + { + "avg_real_time_factor": sum(real_time_factors) + / len(real_time_factors), + "min_real_time_factor": min(real_time_factors), + "max_real_time_factor": max(real_time_factors), + }, + ) return metrics @@ -105,24 +126,26 @@ def calculate_vad_metrics(events: List[BaseEvent]) -> Dict[str, Any]: } if audio_events: - durations = [ - e.duration_ms for e in audio_events - if e.duration_ms is not None - ] + durations = [e.duration_ms for e in audio_events if e.duration_ms is not None] probabilities = [ - e.speech_probability for e in audio_events + e.speech_probability + for e in audio_events if e.speech_probability is not None ] if durations: - metrics.update({ - "avg_speech_duration_ms": sum(durations) / len(durations), - "total_speech_duration_ms": sum(durations), - }) + metrics.update( + { + "avg_speech_duration_ms": sum(durations) / len(durations), + "total_speech_duration_ms": sum(durations), + }, + ) if probabilities: - metrics.update({ - "avg_speech_probability": sum(probabilities) / len(probabilities), - }) + metrics.update( + { + "avg_speech_probability": sum(probabilities) / len(probabilities), + }, + ) return metrics diff --git a/getstream/plugins/common/event_serialization.py b/getstream/plugins/common/event_serialization.py index 75fcb413..9a8eeb40 100644 --- a/getstream/plugins/common/event_serialization.py +++ b/getstream/plugins/common/event_serialization.py @@ -1,13 +1,12 @@ -""" -Event serialization utilities for GetStream AI plugins. +"""Event serialization utilities for GetStream AI plugins. This module provides functions for serializing and deserializing events for storage or transmission. """ import json -from typing import Dict, Any, List from datetime import datetime +from typing import Any, Dict, List from .events import BaseEvent, EventType, create_event @@ -17,34 +16,29 @@ def serialize_event(event: BaseEvent) -> Dict[str, Any]: data = event.to_dict() # Handle special cases for non-serializable fields - if hasattr(event, 'error') and event.error: - data['error'] = { - 'type': type(event.error).__name__, - 'message': str(event.error), - 'args': getattr(event.error, 'args', []) + if hasattr(event, "error") and event.error: + data["error"] = { + "type": type(event.error).__name__, + "message": str(event.error), + "args": getattr(event.error, "args", []), } - if hasattr(event, 'audio_data') and event.audio_data: + if hasattr(event, "audio_data") and event.audio_data: # Don't serialize large audio data, just metadata - data['audio_data'] = { - 'size_bytes': len(event.audio_data), - 'type': 'bytes' - } + data["audio_data"] = {"size_bytes": len(event.audio_data), "type": "bytes"} return data def serialize_events(events: List[BaseEvent]) -> str: """Serialize a list of events to JSON string.""" - serialized_events = [ - serialize_event(event) for event in events - ] + serialized_events = [serialize_event(event) for event in events] return json.dumps(serialized_events, indent=2, default=str) def deserialize_event(data: Dict[str, Any]) -> BaseEvent: """Deserialize an event from a dictionary.""" - event_type_str = data.get('event_type') + event_type_str = data.get("event_type") if not event_type_str: raise ValueError("Event data missing event_type") @@ -55,19 +49,29 @@ def deserialize_event(data: Dict[str, Any]) -> BaseEvent: # Remove fields that shouldn't be passed to constructor constructor_data = data.copy() - constructor_data.pop('event_type', None) + constructor_data.pop("event_type", None) # Handle special fields - if 'timestamp' in constructor_data and isinstance(constructor_data['timestamp'], str): - constructor_data['timestamp'] = datetime.fromisoformat(constructor_data['timestamp']) + if "timestamp" in constructor_data and isinstance( + constructor_data["timestamp"], + str, + ): + constructor_data["timestamp"] = datetime.fromisoformat( + constructor_data["timestamp"], + ) # Handle error reconstruction (simplified) - if 'error' in constructor_data and isinstance(constructor_data['error'], dict): - error_info = constructor_data['error'] - constructor_data['error'] = Exception(error_info.get('message', 'Unknown error')) + if "error" in constructor_data and isinstance(constructor_data["error"], dict): + error_info = constructor_data["error"] + constructor_data["error"] = Exception( + error_info.get("message", "Unknown error"), + ) # Remove audio data placeholder - if 'audio_data' in constructor_data and isinstance(constructor_data['audio_data'], dict): - constructor_data.pop('audio_data') + if "audio_data" in constructor_data and isinstance( + constructor_data["audio_data"], + dict, + ): + constructor_data.pop("audio_data") return create_event(event_type, **constructor_data) diff --git a/getstream/plugins/common/event_utils.py b/getstream/plugins/common/event_utils.py index 2e6ede35..b6d8efaf 100644 --- a/getstream/plugins/common/event_utils.py +++ b/getstream/plugins/common/event_utils.py @@ -1,5 +1,4 @@ -""" -Event utilities and registry for GetStream AI plugins. +"""Event utilities and registry for GetStream AI plugins. This module provides utilities for event handling, filtering, serialization, and debugging across all plugin types. @@ -7,12 +6,10 @@ import logging import time -from typing import Dict, Any, List, Optional, Callable from collections import defaultdict, deque +from typing import Any, Callable, Dict, List, Optional -from .events import ( - BaseEvent, EventType -) +from .events import BaseEvent, EventType logger = logging.getLogger(__name__) @@ -26,10 +23,9 @@ def __init__( session_ids: Optional[List[str]] = None, plugin_names: Optional[List[str]] = None, time_window_ms: Optional[int] = None, - min_confidence: Optional[float] = None + min_confidence: Optional[float] = None, ): - """ - Initialize event filter. + """Initialize event filter. Args: event_types: List of event types to include @@ -37,6 +33,7 @@ def __init__( plugin_names: List of plugin names to include time_window_ms: Only include events within this time window (ms from now) min_confidence: Minimum confidence threshold for applicable events + """ self.event_types = set(event_types) if event_types else None self.session_ids = set(session_ids) if session_ids else None @@ -46,7 +43,6 @@ def __init__( def matches(self, event: BaseEvent) -> bool: """Check if an event matches the filter criteria.""" - # Check event type if self.event_types and event.event_type not in self.event_types: return False @@ -68,7 +64,7 @@ def matches(self, event: BaseEvent) -> bool: # Check confidence (strict filtering - events without confidence are excluded) if self.min_confidence is not None: - if not hasattr(event, 'confidence') or event.confidence is None: + if not hasattr(event, "confidence") or event.confidence is None: return False # Exclude events without confidence data if event.confidence < self.min_confidence: return False # Exclude low confidence events @@ -80,11 +76,11 @@ class EventRegistry: """Registry for tracking and analyzing events across all plugins.""" def __init__(self, max_events: int = 10000): - """ - Initialize event registry. + """Initialize event registry. Args: max_events: Maximum number of events to keep in memory + """ self.max_events = max_events self.events: deque = deque(maxlen=max_events) @@ -113,7 +109,11 @@ def register_event(self, event: BaseEvent): except Exception as e: logger.error(f"Error in event listener: {e}") - def add_listener(self, event_type: EventType, listener: Callable[[BaseEvent], None]): + def add_listener( + self, + event_type: EventType, + listener: Callable[[BaseEvent], None], + ): """Add a listener for a specific event type.""" self.listeners[event_type].append(listener) @@ -125,7 +125,7 @@ def remove_listener(self, event_type: EventType, listener: Callable): def get_events( self, filter_criteria: Optional[EventFilter] = None, - limit: Optional[int] = None + limit: Optional[int] = None, ) -> List[BaseEvent]: """Get events matching the filter criteria.""" events = list(self.events) @@ -155,8 +155,8 @@ def get_error_summary(self) -> Dict[str, Any]: "most_common_errors": sorted( self.error_counts.items(), key=lambda x: x[1], - reverse=True - )[:10] + reverse=True, + )[:10], } def get_statistics(self) -> Dict[str, Any]: @@ -165,8 +165,7 @@ def get_statistics(self) -> Dict[str, Any]: # Calculate event type distribution event_distribution = { - event_type.value: count - for event_type, count in self.event_counts.items() + event_type.value: count for event_type, count in self.event_counts.items() } # Calculate session statistics @@ -179,8 +178,12 @@ def get_statistics(self) -> Dict[str, Any]: if self.events: oldest_event = min(self.events, key=lambda e: e.timestamp) newest_event = max(self.events, key=lambda e: e.timestamp) - time_span_ms = (newest_event.timestamp - oldest_event.timestamp).total_seconds() * 1000 - events_per_second = total_events / (time_span_ms / 1000) if time_span_ms > 0 else 0 + time_span_ms = ( + newest_event.timestamp - oldest_event.timestamp + ).total_seconds() * 1000 + events_per_second = ( + total_events / (time_span_ms / 1000) if time_span_ms > 0 else 0 + ) else: time_span_ms = 0 events_per_second = 0 @@ -192,7 +195,7 @@ def get_statistics(self) -> Dict[str, Any]: "avg_events_per_session": avg_events_per_session, "time_span_ms": time_span_ms, "events_per_second": events_per_second, - "error_summary": self.get_error_summary() + "error_summary": self.get_error_summary(), } def clear(self): @@ -203,7 +206,6 @@ def clear(self): self.error_counts.clear() - class EventLogger: """Enhanced logging for events with structured output.""" @@ -225,24 +227,24 @@ def log_event(self, event: BaseEvent, log_level: int = logging.INFO): } # Add event-specific information - if hasattr(event, 'text'): + if hasattr(event, "text"): log_data["text_length"] = len(event.text) if event.text else 0 log_data["text_preview"] = event.text[:100] if event.text else "" - if hasattr(event, 'confidence'): + if hasattr(event, "confidence"): log_data["confidence"] = event.confidence - if hasattr(event, 'processing_time_ms'): + if hasattr(event, "processing_time_ms"): log_data["processing_time_ms"] = event.processing_time_ms - if hasattr(event, 'audio_data') and event.audio_data is not None: + if hasattr(event, "audio_data") and event.audio_data is not None: # Handle numpy arrays and other array-like objects - if hasattr(event.audio_data, '__len__'): + if hasattr(event.audio_data, "__len__"): log_data["audio_bytes"] = len(event.audio_data) else: log_data["audio_bytes"] = 0 - if hasattr(event, 'error'): + if hasattr(event, "error"): log_data["error_message"] = str(event.error) log_data["error_type"] = type(event.error).__name__ diff --git a/getstream/plugins/common/events.py b/getstream/plugins/common/events.py index f9fae00f..04049883 100644 --- a/getstream/plugins/common/events.py +++ b/getstream/plugins/common/events.py @@ -1,5 +1,4 @@ -""" -Structured event classes for all GetStream AI plugins. +"""Structured event classes for all GetStream AI plugins. This module provides type-safe, structured event classes for STT, TTS, STS, VAD, and other AI plugin types. These events ensure consistency across implementations @@ -16,8 +15,8 @@ import uuid from dataclasses import dataclass, field from datetime import datetime -from typing import Optional, Dict, Any, List from enum import Enum +from typing import Any, Dict, List, Optional class EventType(Enum): @@ -61,6 +60,7 @@ class EventType(Enum): class ConnectionState(Enum): """Connection states for streaming plugins.""" + DISCONNECTED = "disconnected" CONNECTING = "connecting" CONNECTED = "connected" @@ -70,6 +70,7 @@ class ConnectionState(Enum): class AudioFormat(Enum): """Supported audio formats.""" + PCM_S16 = "s16" PCM_F32 = "f32" WAV = "wav" @@ -80,6 +81,7 @@ class AudioFormat(Enum): @dataclass class BaseEvent: """Base class for all plugin events.""" + event_type: EventType event_id: str = field(default_factory=lambda: str(uuid.uuid4())) timestamp: datetime = field(default_factory=datetime.utcnow) @@ -91,12 +93,17 @@ class BaseEvent: def to_dict(self) -> Dict[str, Any]: """Convert event to dictionary for serialization.""" result = {} - + import dataclasses + for field_info in dataclasses.fields(self): field_value = getattr(self, field_info.name) if isinstance(field_value, (datetime, Enum)): - result[field_info.name] = field_value.value if isinstance(field_value, Enum) else str(field_value) + result[field_info.name] = ( + field_value.value + if isinstance(field_value, Enum) + else str(field_value) + ) else: result[field_info.name] = field_value return result @@ -106,9 +113,11 @@ def to_dict(self) -> Dict[str, Any]: # STT (Speech-to-Text) Events # ============================================================================ + @dataclass class STTTranscriptEvent(BaseEvent): """Event emitted when a complete transcript is available.""" + event_type: EventType = field(default=EventType.STT_TRANSCRIPT, init=False) text: str = "" confidence: Optional[float] = None @@ -127,6 +136,7 @@ def __post_init__(self): @dataclass class STTPartialTranscriptEvent(BaseEvent): """Event emitted when a partial transcript is available.""" + event_type: EventType = field(default=EventType.STT_PARTIAL_TRANSCRIPT, init=False) text: str = "" confidence: Optional[float] = None @@ -141,6 +151,7 @@ class STTPartialTranscriptEvent(BaseEvent): @dataclass class STTErrorEvent(BaseEvent): """Event emitted when an STT error occurs.""" + event_type: EventType = field(default=EventType.STT_ERROR, init=False) error: Optional[Exception] = None error_code: Optional[str] = None @@ -156,6 +167,7 @@ def error_message(self) -> str: @dataclass class STTConnectionEvent(BaseEvent): """Event emitted for STT connection state changes.""" + event_type: EventType = field(default=EventType.STT_CONNECTION, init=False) connection_state: Optional[ConnectionState] = None provider: Optional[str] = None @@ -167,9 +179,11 @@ class STTConnectionEvent(BaseEvent): # TTS (Text-to-Speech) Events # ============================================================================ + @dataclass class TTSAudioEvent(BaseEvent): """Event emitted when TTS audio data is available.""" + event_type: EventType = field(default=EventType.TTS_AUDIO, init=False) audio_data: Optional[bytes] = None audio_format: AudioFormat = AudioFormat.PCM_S16 @@ -184,6 +198,7 @@ class TTSAudioEvent(BaseEvent): @dataclass class TTSSynthesisStartEvent(BaseEvent): """Event emitted when TTS synthesis begins.""" + event_type: EventType = field(default=EventType.TTS_SYNTHESIS_START, init=False) text: Optional[str] = None synthesis_id: str = field(default_factory=lambda: str(uuid.uuid4())) @@ -195,6 +210,7 @@ class TTSSynthesisStartEvent(BaseEvent): @dataclass class TTSSynthesisCompleteEvent(BaseEvent): """Event emitted when TTS synthesis completes.""" + event_type: EventType = field(default=EventType.TTS_SYNTHESIS_COMPLETE, init=False) synthesis_id: Optional[str] = None text: Optional[str] = None @@ -208,6 +224,7 @@ class TTSSynthesisCompleteEvent(BaseEvent): @dataclass class TTSErrorEvent(BaseEvent): """Event emitted when a TTS error occurs.""" + event_type: EventType = field(default=EventType.TTS_ERROR, init=False) error: Optional[Exception] = None error_code: Optional[str] = None @@ -224,6 +241,7 @@ def error_message(self) -> str: @dataclass class TTSConnectionEvent(BaseEvent): """Event emitted for TTS connection state changes.""" + event_type: EventType = field(default=EventType.TTS_CONNECTION, init=False) connection_state: Optional[ConnectionState] = None provider: Optional[str] = None @@ -234,9 +252,11 @@ class TTSConnectionEvent(BaseEvent): # STS (Speech-to-Speech) Events # ============================================================================ + @dataclass class STSConnectedEvent(BaseEvent): """Event emitted when STS connection is established.""" + event_type: EventType = field(default=EventType.STS_CONNECTED, init=False) provider: Optional[str] = None session_config: Optional[Dict[str, Any]] = None @@ -246,6 +266,7 @@ class STSConnectedEvent(BaseEvent): @dataclass class STSDisconnectedEvent(BaseEvent): """Event emitted when STS connection is closed.""" + event_type: EventType = field(default=EventType.STS_DISCONNECTED, init=False) provider: Optional[str] = None reason: Optional[str] = None @@ -255,6 +276,7 @@ class STSDisconnectedEvent(BaseEvent): @dataclass class STSAudioInputEvent(BaseEvent): """Event emitted when audio input is sent to STS.""" + event_type: EventType = field(default=EventType.STS_AUDIO_INPUT, init=False) audio_data: Optional[bytes] = None audio_format: AudioFormat = AudioFormat.PCM_S16 @@ -265,6 +287,7 @@ class STSAudioInputEvent(BaseEvent): @dataclass class STSAudioOutputEvent(BaseEvent): """Event emitted when audio output is received from STS.""" + event_type: EventType = field(default=EventType.STS_AUDIO_OUTPUT, init=False) audio_data: Optional[bytes] = None audio_format: AudioFormat = AudioFormat.PCM_S16 @@ -276,6 +299,7 @@ class STSAudioOutputEvent(BaseEvent): @dataclass class STSTranscriptEvent(BaseEvent): """Event emitted when STS provides a transcript.""" + event_type: EventType = field(default=EventType.STS_TRANSCRIPT, init=False) text: Optional[str] = None is_user: bool = True @@ -286,6 +310,7 @@ class STSTranscriptEvent(BaseEvent): @dataclass class STSResponseEvent(BaseEvent): """Event emitted when STS provides a response.""" + event_type: EventType = field(default=EventType.STS_RESPONSE, init=False) text: Optional[str] = None response_id: str = field(default_factory=lambda: str(uuid.uuid4())) @@ -296,9 +321,12 @@ class STSResponseEvent(BaseEvent): @dataclass class STSConversationItemEvent(BaseEvent): """Event emitted for conversation item updates in STS.""" + event_type: EventType = field(default=EventType.STS_CONVERSATION_ITEM, init=False) item_id: Optional[str] = None - item_type: Optional[str] = None # "message", "function_call", "function_call_output" + item_type: Optional[str] = ( + None # "message", "function_call", "function_call_output" + ) status: Optional[str] = None # "completed", "in_progress", "incomplete" role: Optional[str] = None # "user", "assistant", "system" content: Optional[List[Dict[str, Any]]] = None @@ -307,6 +335,7 @@ class STSConversationItemEvent(BaseEvent): @dataclass class STSErrorEvent(BaseEvent): """Event emitted when an STS error occurs.""" + event_type: EventType = field(default=EventType.STS_ERROR, init=False) error: Optional[Exception] = None error_code: Optional[str] = None @@ -322,9 +351,11 @@ def error_message(self) -> str: # VAD (Voice Activity Detection) Events # ============================================================================ + @dataclass class VADSpeechStartEvent(BaseEvent): """Event emitted when speech begins.""" + event_type: EventType = field(default=EventType.VAD_SPEECH_START, init=False) speech_probability: float = 0.0 activation_threshold: float = 0.0 @@ -334,6 +365,7 @@ class VADSpeechStartEvent(BaseEvent): @dataclass class VADSpeechEndEvent(BaseEvent): """Event emitted when speech ends.""" + event_type: EventType = field(default=EventType.VAD_SPEECH_END, init=False) speech_probability: float = 0.0 deactivation_threshold: float = 0.0 @@ -344,6 +376,7 @@ class VADSpeechEndEvent(BaseEvent): @dataclass class VADAudioEvent(BaseEvent): """Event emitted when VAD detects complete speech segment.""" + event_type: EventType = field(default=EventType.VAD_AUDIO, init=False) audio_data: Optional[bytes] = None # PCM audio data sample_rate: int = 16000 @@ -357,6 +390,7 @@ class VADAudioEvent(BaseEvent): @dataclass class VADPartialEvent(BaseEvent): """Event emitted during ongoing speech detection.""" + event_type: EventType = field(default=EventType.VAD_PARTIAL, init=False) audio_data: Optional[bytes] = None # PCM audio data sample_rate: int = 16000 @@ -371,6 +405,7 @@ class VADPartialEvent(BaseEvent): @dataclass class VADErrorEvent(BaseEvent): """Event emitted when a VAD error occurs.""" + event_type: EventType = field(default=EventType.VAD_ERROR, init=False) error: Optional[Exception] = None error_code: Optional[str] = None @@ -386,9 +421,11 @@ def error_message(self) -> str: # Generic Plugin Events # ============================================================================ + @dataclass class PluginInitializedEvent(BaseEvent): """Event emitted when a plugin is successfully initialized.""" + event_type: EventType = field(default=EventType.PLUGIN_INITIALIZED, init=False) plugin_type: Optional[str] = None # "STT", "TTS", "STS", "VAD" provider: Optional[str] = None @@ -399,6 +436,7 @@ class PluginInitializedEvent(BaseEvent): @dataclass class PluginClosedEvent(BaseEvent): """Event emitted when a plugin is closed.""" + event_type: EventType = field(default=EventType.PLUGIN_CLOSED, init=False) plugin_type: Optional[str] = None # "STT", "STS", "VAD" provider: Optional[str] = None @@ -409,6 +447,7 @@ class PluginClosedEvent(BaseEvent): @dataclass class PluginErrorEvent(BaseEvent): """Event emitted when a generic plugin error occurs.""" + event_type: EventType = field(default=EventType.PLUGIN_ERROR, init=False) plugin_type: Optional[str] = None # "STT", "TTS", "STS", "VAD" provider: Optional[str] = None @@ -432,13 +471,11 @@ def error_message(self) -> str: EventType.STT_PARTIAL_TRANSCRIPT: STTPartialTranscriptEvent, EventType.STT_ERROR: STTErrorEvent, EventType.STT_CONNECTION: STTConnectionEvent, - EventType.TTS_AUDIO: TTSAudioEvent, EventType.TTS_SYNTHESIS_START: TTSSynthesisStartEvent, EventType.TTS_SYNTHESIS_COMPLETE: TTSSynthesisCompleteEvent, EventType.TTS_ERROR: TTSErrorEvent, EventType.TTS_CONNECTION: TTSConnectionEvent, - EventType.STS_CONNECTED: STSConnectedEvent, EventType.STS_DISCONNECTED: STSDisconnectedEvent, EventType.STS_AUDIO_INPUT: STSAudioInputEvent, @@ -447,24 +484,19 @@ def error_message(self) -> str: EventType.STS_RESPONSE: STSResponseEvent, EventType.STS_CONVERSATION_ITEM: STSConversationItemEvent, EventType.STS_ERROR: STSErrorEvent, - EventType.VAD_SPEECH_START: VADSpeechStartEvent, EventType.VAD_SPEECH_END: VADSpeechEndEvent, EventType.VAD_AUDIO: VADAudioEvent, EventType.VAD_PARTIAL: VADPartialEvent, EventType.VAD_ERROR: VADErrorEvent, - EventType.PLUGIN_INITIALIZED: PluginInitializedEvent, EventType.PLUGIN_CLOSED: PluginClosedEvent, EventType.PLUGIN_ERROR: PluginErrorEvent, } - - def create_event(event_type: EventType, **kwargs) -> BaseEvent: - """ - Create an event instance of the appropriate type. + """Create an event instance of the appropriate type. Args: event_type: The type of event to create @@ -475,6 +507,7 @@ def create_event(event_type: EventType, **kwargs) -> BaseEvent: Raises: ValueError: If the event type is not recognized + """ if event_type not in EVENT_CLASS_MAP: raise ValueError(f"No event class defined for type: {event_type}") @@ -488,23 +521,19 @@ def create_event(event_type: EventType, **kwargs) -> BaseEvent: "EventType", "ConnectionState", "AudioFormat", - # Base classes "BaseEvent", - # STT Events "STTTranscriptEvent", "STTPartialTranscriptEvent", "STTErrorEvent", "STTConnectionEvent", - # TTS Events "TTSAudioEvent", "TTSSynthesisStartEvent", "TTSSynthesisCompleteEvent", "TTSErrorEvent", "TTSConnectionEvent", - # STS Events "STSConnectedEvent", "STSDisconnectedEvent", @@ -514,19 +543,16 @@ def create_event(event_type: EventType, **kwargs) -> BaseEvent: "STSResponseEvent", "STSConversationItemEvent", "STSErrorEvent", - # VAD Events "VADSpeechStartEvent", "VADSpeechEndEvent", "VADAudioEvent", "VADPartialEvent", "VADErrorEvent", - # Generic Events "PluginInitializedEvent", "PluginClosedEvent", "PluginErrorEvent", - # Utilities "EVENT_CLASS_MAP", "create_event", diff --git a/getstream/plugins/common/sts.py b/getstream/plugins/common/sts.py index 248ab5fc..d5aef7e5 100644 --- a/getstream/plugins/common/sts.py +++ b/getstream/plugins/common/sts.py @@ -1,17 +1,23 @@ import abc import logging import uuid - from typing import Any, Dict, List, Optional from pyee.asyncio import AsyncIOEventEmitter +from .event_utils import register_global_event from .events import ( - STSConnectedEvent, STSDisconnectedEvent, STSAudioInputEvent, STSAudioOutputEvent, - STSTranscriptEvent, STSResponseEvent, STSConversationItemEvent, STSErrorEvent, - PluginInitializedEvent, PluginClosedEvent + PluginClosedEvent, + PluginInitializedEvent, + STSAudioInputEvent, + STSAudioOutputEvent, + STSConnectedEvent, + STSConversationItemEvent, + STSDisconnectedEvent, + STSErrorEvent, + STSResponseEvent, + STSTranscriptEvent, ) -from .event_utils import register_global_event logger = logging.getLogger(__name__) @@ -62,6 +68,7 @@ def __init__( provider_config: Provider-specific configuration (e.g., Gemini Live config, OpenAI session prefs). response_modalities: Optional response modalities passed to the session. tools: Optional tools passed to the session. + """ super().__init__() self._is_connected = False @@ -109,7 +116,7 @@ def _emit_connected_event(self, session_config=None, capabilities=None): plugin_name=self.provider_name, provider=self.provider_name, session_config=session_config, - capabilities=capabilities + capabilities=capabilities, ) register_global_event(event) self.emit("connected", event) # Structured event @@ -122,13 +129,16 @@ def _emit_disconnected_event(self, reason=None, was_clean=True): plugin_name=self.provider_name, provider=self.provider_name, reason=reason, - was_clean=was_clean + was_clean=was_clean, ) register_global_event(event) self.emit("disconnected", event) # Structured event def _emit_audio_input_event( - self, audio_data, sample_rate=16000, user_metadata=None + self, + audio_data, + sample_rate=16000, + user_metadata=None, ): """Emit a structured audio input event.""" event = STSAudioInputEvent( @@ -138,13 +148,17 @@ def _emit_audio_input_event( provider=self.provider_name, audio_data=audio_data, sample_rate=sample_rate, - user_metadata=user_metadata + user_metadata=user_metadata, ) register_global_event(event) self.emit("audio_input", event) def _emit_audio_output_event( - self, audio_data, sample_rate=16000, response_id=None, user_metadata=None + self, + audio_data, + sample_rate=16000, + response_id=None, + user_metadata=None, ): """Emit a structured audio output event.""" event = STSAudioOutputEvent( @@ -155,14 +169,18 @@ def _emit_audio_output_event( audio_data=audio_data, sample_rate=sample_rate, response_id=response_id, - user_metadata=user_metadata + user_metadata=user_metadata, ) register_global_event(event) self.emit("audio_output", event) def _emit_transcript_event( - self, text, is_user=True, confidence=None, - conversation_item_id=None, user_metadata=None + self, + text, + is_user=True, + confidence=None, + conversation_item_id=None, + user_metadata=None, ): """Emit a structured transcript event.""" event = STSTranscriptEvent( @@ -174,14 +192,18 @@ def _emit_transcript_event( is_user=is_user, confidence=confidence, conversation_item_id=conversation_item_id, - user_metadata=user_metadata + user_metadata=user_metadata, ) register_global_event(event) self.emit("transcript", event) def _emit_response_event( - self, text, response_id=None, is_complete=True, - conversation_item_id=None, user_metadata=None + self, + text, + response_id=None, + is_complete=True, + conversation_item_id=None, + user_metadata=None, ): """Emit a structured response event.""" event = STSResponseEvent( @@ -193,14 +215,19 @@ def _emit_response_event( response_id=response_id, is_complete=is_complete, conversation_item_id=conversation_item_id, - user_metadata=user_metadata + user_metadata=user_metadata, ) register_global_event(event) self.emit("response", event) def _emit_conversation_item_event( - self, item_id, item_type, status, role, - content=None, user_metadata=None + self, + item_id, + item_type, + status, + role, + content=None, + user_metadata=None, ): """Emit a structured conversation item event.""" event = STSConversationItemEvent( @@ -213,7 +240,7 @@ def _emit_conversation_item_event( status=status, role=role, content=content, - user_metadata=user_metadata + user_metadata=user_metadata, ) register_global_event(event) self.emit("conversation_item", event) @@ -225,7 +252,7 @@ def _emit_error_event(self, error, context="", user_metadata=None): plugin_name=self.provider_name, error=error, context=context, - user_metadata=user_metadata + user_metadata=user_metadata, ) register_global_event(event) self.emit("error", event) # Structured event @@ -241,7 +268,7 @@ async def close(self): plugin_name=self.provider_name, plugin_type="STS", provider=self.provider_name, - cleanup_successful=True + cleanup_successful=True, ) register_global_event(close_event) self.emit("closed", close_event) diff --git a/getstream/plugins/common/stt.py b/getstream/plugins/common/stt.py index bfa6716b..1b465272 100644 --- a/getstream/plugins/common/stt.py +++ b/getstream/plugins/common/stt.py @@ -1,24 +1,29 @@ import abc +import asyncio import logging import time import uuid -from typing import Optional, Dict, Any, Tuple, List -import asyncio from asyncio import AbstractEventLoop +from typing import Any, Dict, List, Optional, Tuple + from pyee.asyncio import AsyncIOEventEmitter + from getstream.video.rtc.track_util import PcmData +from .event_utils import register_global_event from .events import ( - STTTranscriptEvent, STTPartialTranscriptEvent, STTErrorEvent, PluginInitializedEvent, PluginClosedEvent + PluginClosedEvent, + PluginInitializedEvent, + STTErrorEvent, + STTPartialTranscriptEvent, + STTTranscriptEvent, ) -from .event_utils import register_global_event logger = logging.getLogger(__name__) class STT(AsyncIOEventEmitter, abc.ABC): - """ - Abstract base class for Speech-to-Text implementations. + """Abstract base class for Speech-to-Text implementations. This class provides a standardized interface for STT implementations with consistent event emission patterns and error handling. @@ -49,8 +54,7 @@ def __init__( loop: Optional[AbstractEventLoop] = None, provider_name: Optional[str] = None, ): - """ - Initialize the STT service. + """Initialize the STT service. Args: sample_rate: The sample rate of the audio to process, in Hz. @@ -66,8 +70,8 @@ def __init__( ``RuntimeError: There is no current event loop``. Capturing the running loop at instantiation guarantees the callbacks are always scheduled on the correct loop regardless of the calling thread. - """ + """ if loop is None: try: # Prefer the currently running loop if in async context @@ -106,22 +110,21 @@ def __init__( plugin_name=self.provider_name, plugin_type="STT", provider=self.provider_name, - configuration={"sample_rate": sample_rate} + configuration={"sample_rate": sample_rate}, ) register_global_event(init_event) self.emit("initialized", init_event) def _validate_pcm_data(self, pcm_data: PcmData) -> bool: - """ - Validate PCM data input for processing. + """Validate PCM data input for processing. Args: pcm_data: The PCM audio data to validate. Returns: True if the data is valid, False otherwise. - """ + """ if not hasattr(pcm_data, "samples") or pcm_data.samples is None: logger.warning("PCM data has no samples") return False @@ -143,13 +146,13 @@ def _emit_transcript_event( user_metadata: Optional[Dict[str, Any]], metadata: Dict[str, Any], ): - """ - Emit a final transcript event with structured data. + """Emit a final transcript event with structured data. Args: text: The transcribed text. user_metadata: User-specific metadata. metadata: Transcription metadata (processing time, confidence, etc.). + """ event = STTTranscriptEvent( session_id=self.session_id, @@ -185,13 +188,13 @@ def _emit_partial_transcript_event( user_metadata: Optional[Dict[str, Any]], metadata: Dict[str, Any], ): - """ - Emit a partial transcript event with structured data. + """Emit a partial transcript event with structured data. Args: text: The partial transcribed text. user_metadata: User-specific metadata. metadata: Transcription metadata (processing time, confidence, etc.). + """ event = STTPartialTranscriptEvent( session_id=self.session_id, @@ -220,14 +223,19 @@ def _emit_partial_transcript_event( register_global_event(event) self.emit("partial_transcript", event) # Structured event - def _emit_error_event(self, error: Exception, context: str = "", user_metadata: Optional[Dict[str, Any]] = None): - """ - Emit an error event with structured data. + def _emit_error_event( + self, + error: Exception, + context: str = "", + user_metadata: Optional[Dict[str, Any]] = None, + ): + """Emit an error event with structured data. Args: error: The exception that occurred. context: Additional context about where the error occurred. user_metadata: User-specific metadata. + """ event = STTErrorEvent( session_id=self.session_id, @@ -235,8 +243,8 @@ def _emit_error_event(self, error: Exception, context: str = "", user_metadata: error=error, context=context, user_metadata=user_metadata, - error_code=getattr(error, 'error_code', None), - is_recoverable=not isinstance(error, (SystemExit, KeyboardInterrupt)) + error_code=getattr(error, "error_code", None), + is_recoverable=not isinstance(error, (SystemExit, KeyboardInterrupt)), ) logger.error( @@ -254,14 +262,16 @@ def _emit_error_event(self, error: Exception, context: str = "", user_metadata: self.emit("error", event) # Structured event async def process_audio( - self, pcm_data: PcmData, user_metadata: Optional[Dict[str, Any]] = None + self, + pcm_data: PcmData, + user_metadata: Optional[Dict[str, Any]] = None, ): - """ - Process audio data for transcription and emit appropriate events. + """Process audio data for transcription and emit appropriate events. Args: pcm_data: The PCM audio data to process. user_metadata: Additional metadata about the user or session. + """ if self._is_closed: logger.debug("Ignoring audio processing request - STT is closed") @@ -317,10 +327,11 @@ async def process_audio( @abc.abstractmethod async def _process_audio_impl( - self, pcm_data: PcmData, user_metadata: Optional[Dict[str, Any]] = None + self, + pcm_data: PcmData, + user_metadata: Optional[Dict[str, Any]] = None, ) -> Optional[List[Tuple[bool, str, Dict[str, Any]]]]: - """ - Implementation-specific method to process audio data. + """Implementation-specific method to process audio data. This method must be implemented by all STT providers and should handle the core transcription logic. The base class handles event emission and error handling. @@ -339,13 +350,13 @@ async def _process_audio_impl( or duplicate events will be produced. Exceptions should bubble up; process_audio() will catch them and emit a single "error" event. + """ pass @abc.abstractmethod async def close(self): - """ - Close the STT service and release any resources. + """Close the STT service and release any resources. Implementations should: - Set self._is_closed = True @@ -362,7 +373,7 @@ async def close(self): plugin_name=self.provider_name, plugin_type="STT", provider=self.provider_name, - cleanup_successful=True + cleanup_successful=True, ) register_global_event(close_event) self.emit("closed", close_event) diff --git a/getstream/plugins/common/tests/test_base_consistency.py b/getstream/plugins/common/tests/test_base_consistency.py index 925583d7..dbfdd147 100644 --- a/getstream/plugins/common/tests/test_base_consistency.py +++ b/getstream/plugins/common/tests/test_base_consistency.py @@ -1,12 +1,12 @@ -""" -Test the base STT class consistency improvements. -""" +"""Test the base STT class consistency improvements.""" -import pytest from unittest.mock import Mock + +import numpy as np +import pytest + from getstream.plugins.common import STT from getstream.video.rtc.track_util import PcmData -import numpy as np class MockSTT(STT): diff --git a/getstream/plugins/common/tests/test_events.py b/getstream/plugins/common/tests/test_events.py index cb015119..dbc60b30 100644 --- a/getstream/plugins/common/tests/test_events.py +++ b/getstream/plugins/common/tests/test_events.py @@ -1,61 +1,56 @@ -import pytest import json from datetime import datetime +import pytest + +from getstream.plugins.common.event_serialization import ( + deserialize_event, + serialize_event, + serialize_events, +) from getstream.plugins.common.events import ( + AudioFormat, # Base events BaseEvent, - EventType, ConnectionState, - AudioFormat, - create_event, - + EventType, + PluginClosedEvent, + PluginErrorEvent, + # Generic Events + PluginInitializedEvent, + STSAudioInputEvent, + STSAudioOutputEvent, + # STS Events + STSConnectedEvent, + STSConversationItemEvent, + STSDisconnectedEvent, + STSErrorEvent, + STSResponseEvent, + STSTranscriptEvent, + STTConnectionEvent, + STTErrorEvent, + STTPartialTranscriptEvent, # STT Events STTTranscriptEvent, - STTPartialTranscriptEvent, - STTErrorEvent, - STTConnectionEvent, - # TTS Events TTSAudioEvent, - TTSSynthesisStartEvent, - TTSSynthesisCompleteEvent, - TTSErrorEvent, TTSConnectionEvent, - - # STS Events - STSConnectedEvent, - STSDisconnectedEvent, - STSAudioInputEvent, - STSAudioOutputEvent, - STSTranscriptEvent, - STSResponseEvent, - STSConversationItemEvent, - STSErrorEvent, - - # VAD Events - VADSpeechStartEvent, - VADSpeechEndEvent, + TTSErrorEvent, + TTSSynthesisCompleteEvent, + TTSSynthesisStartEvent, VADAudioEvent, - VADPartialEvent, VADErrorEvent, - - # Generic Events - PluginInitializedEvent, - PluginClosedEvent, - PluginErrorEvent, -) - -from getstream.plugins.common.event_serialization import ( - serialize_event, - serialize_events, - deserialize_event, + VADPartialEvent, + VADSpeechEndEvent, + # VAD Events + VADSpeechStartEvent, + create_event, ) class TestBaseEvent: """Test the base event functionality.""" - + def test_base_event_creation(self): """Test that base events can be created with minimal parameters.""" event = BaseEvent(event_type=EventType.STT_TRANSCRIPT) @@ -66,22 +61,22 @@ def test_base_event_creation(self): assert event.user_metadata is None assert event.plugin_name is None assert event.plugin_version is None - + def test_base_event_with_optional_params(self): """Test base event creation with optional parameters.""" session_id = "test-session-123" user_metadata = {"user_id": "user123"} plugin_name = "test_plugin" plugin_version = "1.0.0" - + event = BaseEvent( event_type=EventType.STT_TRANSCRIPT, session_id=session_id, user_metadata=user_metadata, plugin_name=plugin_name, - plugin_version=plugin_version + plugin_version=plugin_version, ) - + assert event.session_id == session_id assert event.user_metadata == user_metadata assert event.plugin_name == plugin_name @@ -90,7 +85,7 @@ def test_base_event_with_optional_params(self): class TestSTTEvents: """Test STT-related events.""" - + def test_stt_transcript_event_creation(self): """Test STT transcript event creation.""" event = STTTranscriptEvent(text="Hello world") @@ -103,7 +98,7 @@ def test_stt_transcript_event_creation(self): assert event.model_name is None assert event.words is None assert event.is_final is True - + def test_stt_transcript_event_with_confidence(self): """Test STT transcript event with confidence.""" event = STTTranscriptEvent( @@ -112,28 +107,28 @@ def test_stt_transcript_event_with_confidence(self): language="en", processing_time_ms=100.0, audio_duration_ms=2000.0, - model_name="test-model" + model_name="test-model", ) assert event.confidence == 0.95 assert event.language == "en" assert event.processing_time_ms == 100.0 assert event.audio_duration_ms == 2000.0 assert event.model_name == "test-model" - + def test_stt_transcript_event_validation(self): """Test STT transcript event validation.""" # Should raise ValueError for empty text with pytest.raises(ValueError, match="Transcript text cannot be empty"): STTTranscriptEvent(text="") - + # Whitespace-only text should pass validation (current behavior) event = STTTranscriptEvent(text=" ") assert event.text == " " - + # Test that non-empty text works event = STTTranscriptEvent(text="Valid text") assert event.text == "Valid text" - + def test_stt_partial_transcript_event(self): """Test STT partial transcript event.""" event = STTPartialTranscriptEvent(text="Hello") @@ -141,7 +136,7 @@ def test_stt_partial_transcript_event(self): assert event.text == "Hello" assert event.confidence is None assert event.is_final is False - + def test_stt_error_event(self): """Test STT error event.""" error = Exception("Test error") @@ -150,7 +145,7 @@ def test_stt_error_event(self): error_code="TEST_001", context="test context", retry_count=3, - is_recoverable=False + is_recoverable=False, ) assert event.event_type == EventType.STT_ERROR assert event.error == error @@ -159,14 +154,14 @@ def test_stt_error_event(self): assert event.retry_count == 3 assert event.is_recoverable is False assert event.error_message == "Test error" - + def test_stt_connection_event(self): """Test STT connection event.""" event = STTConnectionEvent( connection_state=ConnectionState.CONNECTED, provider="test_provider", details={"test": "detail"}, - reconnect_attempts=2 + reconnect_attempts=2, ) assert event.event_type == EventType.STT_CONNECTION assert event.connection_state == ConnectionState.CONNECTED @@ -177,7 +172,7 @@ def test_stt_connection_event(self): class TestTTSEvents: """Test TTS-related events.""" - + def test_tts_audio_event_creation(self): """Test TTS audio event creation.""" audio_data = b"test audio data" @@ -187,7 +182,7 @@ def test_tts_audio_event_creation(self): sample_rate=22050, channels=2, chunk_index=5, - is_final_chunk=False + is_final_chunk=False, ) assert event.event_type == EventType.TTS_AUDIO assert event.audio_data == audio_data @@ -196,14 +191,14 @@ def test_tts_audio_event_creation(self): assert event.channels == 2 assert event.chunk_index == 5 assert event.is_final_chunk is False - + def test_tts_synthesis_start_event(self): """Test TTS synthesis start event.""" event = TTSSynthesisStartEvent( text="Hello world", model_name="test-model", voice_id="voice123", - estimated_duration_ms=5000.0 + estimated_duration_ms=5000.0, ) assert event.event_type == EventType.TTS_SYNTHESIS_START assert event.text == "Hello world" @@ -211,7 +206,7 @@ def test_tts_synthesis_start_event(self): assert event.voice_id == "voice123" assert event.estimated_duration_ms == 5000.0 assert event.synthesis_id is not None - + def test_tts_synthesis_complete_event(self): """Test TTS synthesis complete event.""" event = TTSSynthesisCompleteEvent( @@ -221,7 +216,7 @@ def test_tts_synthesis_complete_event(self): synthesis_time_ms=2500.0, audio_duration_ms=5000.0, chunk_count=10, - real_time_factor=0.5 + real_time_factor=0.5, ) assert event.event_type == EventType.TTS_SYNTHESIS_COMPLETE assert event.synthesis_id == "synth123" @@ -231,7 +226,7 @@ def test_tts_synthesis_complete_event(self): assert event.audio_duration_ms == 5000.0 assert event.chunk_count == 10 assert event.real_time_factor == 0.5 - + def test_tts_error_event(self): """Test TTS error event.""" error = Exception("TTS synthesis failed") @@ -239,20 +234,20 @@ def test_tts_error_event(self): error=error, error_code="TTS_001", context="synthesis", - is_recoverable=True + is_recoverable=True, ) assert event.event_type == EventType.TTS_ERROR assert event.error == error assert event.error_code == "TTS_001" assert event.context == "synthesis" assert event.is_recoverable is True - + def test_tts_connection_event(self): """Test TTS connection event.""" event = TTSConnectionEvent( connection_state=ConnectionState.DISCONNECTED, provider="tts_provider", - details={"reason": "timeout"} + details={"reason": "timeout"}, ) assert event.event_type == EventType.TTS_CONNECTION assert event.connection_state == ConnectionState.DISCONNECTED @@ -262,98 +257,87 @@ def test_tts_connection_event(self): class TestSTSEvents: """Test STS-related events.""" - + def test_sts_connected_event(self): """Test STS connected event.""" event = STSConnectedEvent( provider="sts_provider", - session_config={"endpoint": "wss://test.com"} + session_config={"endpoint": "wss://test.com"}, ) assert event.event_type == EventType.STS_CONNECTED assert event.provider == "sts_provider" assert event.session_config == {"endpoint": "wss://test.com"} - + def test_sts_disconnected_event(self): """Test STS disconnected event.""" event = STSDisconnectedEvent( provider="sts_provider", reason="user_disconnect", - was_clean=True + was_clean=True, ) assert event.event_type == EventType.STS_DISCONNECTED assert event.provider == "sts_provider" assert event.reason == "user_disconnect" assert event.was_clean is True - + def test_sts_audio_input_event(self): """Test STS audio input event.""" event = STSAudioInputEvent( audio_data=b"input audio", sample_rate=16000, - channels=1 + channels=1, ) assert event.event_type == EventType.STS_AUDIO_INPUT assert event.audio_data == b"input audio" assert event.sample_rate == 16000 assert event.channels == 1 - + def test_sts_audio_output_event(self): """Test STS audio output event.""" event = STSAudioOutputEvent( audio_data=b"output audio", sample_rate=16000, - channels=1 + channels=1, ) assert event.event_type == EventType.STS_AUDIO_OUTPUT assert event.audio_data == b"output audio" assert event.sample_rate == 16000 assert event.channels == 1 - + def test_sts_transcript_event(self): """Test STS transcript event.""" - event = STSTranscriptEvent( - text="Hello world", - confidence=0.95, - is_user=True - ) + event = STSTranscriptEvent(text="Hello world", confidence=0.95, is_user=True) assert event.event_type == EventType.STS_TRANSCRIPT assert event.text == "Hello world" assert event.confidence == 0.95 assert event.is_user is True - + def test_sts_response_event(self): """Test STS response event.""" - event = STSResponseEvent( - text="Response text", - is_complete=True - ) + event = STSResponseEvent(text="Response text", is_complete=True) assert event.event_type == EventType.STS_RESPONSE assert event.text == "Response text" assert event.is_complete is True assert event.response_id is not None - + def test_sts_conversation_item_event(self): """Test STS conversation item event.""" event = STSConversationItemEvent( item_type="message", status="completed", role="user", - content=[{"type": "text", "text": "User message"}] + content=[{"type": "text", "text": "User message"}], ) assert event.event_type == EventType.STS_CONVERSATION_ITEM assert event.item_type == "message" assert event.status == "completed" assert event.role == "user" assert event.content == [{"type": "text", "text": "User message"}] - + def test_sts_error_event(self): """Test STS error event.""" error = Exception("STS error") - event = STSErrorEvent( - error=error, - error_code="STS_001", - context="conversation" - ) + event = STSErrorEvent(error=error, error_code="STS_001", context="conversation") assert event.event_type == EventType.STS_ERROR assert event.error == error assert event.error_code == "STS_001" @@ -362,33 +346,33 @@ def test_sts_error_event(self): class TestVADEvents: """Test VAD-related events.""" - + def test_vad_speech_start_event(self): """Test VAD speech start event.""" event = VADSpeechStartEvent( speech_probability=0.8, activation_threshold=0.5, - frame_count=10 + frame_count=10, ) assert event.event_type == EventType.VAD_SPEECH_START assert event.speech_probability == 0.8 assert event.activation_threshold == 0.5 assert event.frame_count == 10 - + def test_vad_speech_end_event(self): """Test VAD speech end event.""" event = VADSpeechEndEvent( speech_probability=0.2, deactivation_threshold=0.3, total_speech_duration_ms=2500.0, - total_frames=100 + total_frames=100, ) assert event.event_type == EventType.VAD_SPEECH_END assert event.speech_probability == 0.2 assert event.deactivation_threshold == 0.3 assert event.total_speech_duration_ms == 2500.0 assert event.total_frames == 100 - + def test_vad_audio_event(self): """Test VAD audio event.""" event = VADAudioEvent( @@ -398,7 +382,7 @@ def test_vad_audio_event(self): channels=1, duration_ms=1000.0, speech_probability=0.9, - frame_count=50 + frame_count=50, ) assert event.event_type == EventType.VAD_AUDIO assert event.audio_data == b"speech audio" @@ -408,19 +392,19 @@ def test_vad_audio_event(self): assert event.duration_ms == 1000.0 assert event.speech_probability == 0.9 assert event.frame_count == 50 - + def test_vad_partial_event(self): """Test VAD partial event.""" event = VADPartialEvent( speech_probability=0.7, frame_count=25, - is_speech_active=True + is_speech_active=True, ) assert event.event_type == EventType.VAD_PARTIAL assert event.speech_probability == 0.7 assert event.frame_count == 25 assert event.is_speech_active is True - + def test_vad_error_event(self): """Test VAD error event.""" error = Exception("VAD processing error") @@ -428,7 +412,7 @@ def test_vad_error_event(self): error=error, error_code="VAD_001", context="audio_processing", - frame_data_available=False + frame_data_available=False, ) assert event.event_type == EventType.VAD_ERROR assert event.error == error @@ -439,35 +423,35 @@ def test_vad_error_event(self): class TestGenericEvents: """Test generic plugin events.""" - + def test_plugin_initialized_event(self): """Test plugin initialized event.""" event = PluginInitializedEvent( plugin_type="STT", provider="test_provider", configuration={"setting": "value"}, - capabilities=["transcription", "language_detection"] + capabilities=["transcription", "language_detection"], ) assert event.event_type == EventType.PLUGIN_INITIALIZED assert event.plugin_type == "STT" assert event.provider == "test_provider" assert event.configuration == {"setting": "value"} assert event.capabilities == ["transcription", "language_detection"] - + def test_plugin_closed_event(self): """Test plugin closed event.""" event = PluginClosedEvent( plugin_type="STT", provider="test_provider", reason="shutdown", - cleanup_successful=True + cleanup_successful=True, ) assert event.event_type == EventType.PLUGIN_CLOSED assert event.plugin_type == "STT" assert event.provider == "test_provider" assert event.reason == "shutdown" assert event.cleanup_successful is True - + def test_plugin_error_event(self): """Test plugin error event.""" error = Exception("Plugin error") @@ -477,7 +461,7 @@ def test_plugin_error_event(self): error=error, error_code="PLUGIN_001", context="initialization", - is_fatal=False + is_fatal=False, ) assert event.event_type == EventType.PLUGIN_ERROR assert event.plugin_type == "STT" @@ -490,12 +474,12 @@ def test_plugin_error_event(self): class TestEventEnums: """Test event enums and constants.""" - + def test_event_types(self): """Test that all event types are defined.""" expected_types = [ "stt_transcript", - "stt_partial_transcript", + "stt_partial_transcript", "stt_error", "stt_connection", "tts_audio", @@ -518,19 +502,21 @@ def test_event_types(self): "vad_error", "plugin_initialized", "plugin_closed", - "plugin_error" + "plugin_error", ] - + for expected_type in expected_types: - assert hasattr(EventType, expected_type.upper().replace('-', '_')), f"Missing event type: {expected_type}" - + assert hasattr(EventType, expected_type.upper().replace("-", "_")), ( + f"Missing event type: {expected_type}" + ) + def test_connection_states(self): """Test connection state enum values.""" assert ConnectionState.CONNECTING.value == "connecting" assert ConnectionState.CONNECTED.value == "connected" assert ConnectionState.RECONNECTING.value == "reconnecting" assert ConnectionState.ERROR.value == "error" - + def test_audio_formats(self): """Test audio format enum values.""" assert AudioFormat.PCM_S16.value == "s16" @@ -542,347 +528,386 @@ def test_audio_formats(self): class TestEventEdgeCases: """Test edge cases and error conditions.""" - + def test_event_with_none_values(self): """Test that events can handle None values gracefully.""" event = STTTranscriptEvent( text="test", confidence=None, language=None, - processing_time_ms=None + processing_time_ms=None, ) assert event.confidence is None assert event.language is None assert event.processing_time_ms is None - + def test_event_with_empty_strings(self): """Test that events can handle empty strings.""" - event = STTTranscriptEvent( - text="test", - language="", - model_name="" - ) + event = STTTranscriptEvent(text="test", language="", model_name="") assert event.language == "" assert event.model_name == "" - + def test_event_with_zero_values(self): """Test that events can handle zero values.""" event = STTTranscriptEvent( text="test", confidence=0.0, processing_time_ms=0.0, - audio_duration_ms=0.0 + audio_duration_ms=0.0, ) assert event.confidence == 0.0 assert event.processing_time_ms == 0.0 assert event.audio_duration_ms == 0.0 - + def test_event_serialization_compatibility(self): """Test that events can be converted to dict for serialization.""" - event = STTTranscriptEvent( - text="test", - confidence=0.95, - language="en" - ) - + event = STTTranscriptEvent(text="test", confidence=0.95, language="en") + # Test that we can access all fields event_dict = { - 'text': event.text, - 'confidence': event.confidence, - 'language': event.language, - 'event_type': event.event_type.value, - 'event_id': event.event_id, - 'timestamp': event.timestamp.isoformat(), - 'is_final': event.is_final + "text": event.text, + "confidence": event.confidence, + "language": event.language, + "event_type": event.event_type.value, + "event_id": event.event_id, + "timestamp": event.timestamp.isoformat(), + "is_final": event.is_final, } - - assert event_dict['text'] == "test" - assert event_dict['confidence'] == 0.95 - assert event_dict['language'] == "en" - assert event_dict['event_type'] == "stt_transcript" - assert event_dict['is_final'] is True + + assert event_dict["text"] == "test" + assert event_dict["confidence"] == 0.95 + assert event_dict["language"] == "en" + assert event_dict["event_type"] == "stt_transcript" + assert event_dict["is_final"] is True class TestEventMetrics: """Test event metrics calculation functions.""" - + def test_calculate_stt_metrics_empty_events(self): """Test STT metrics calculation with no events.""" from getstream.plugins.common.event_metrics import calculate_stt_metrics - + metrics = calculate_stt_metrics([]) assert metrics == {"total_transcripts": 0} - + def test_calculate_stt_metrics_basic_counts(self): """Test STT metrics calculation with basic event counts.""" from getstream.plugins.common.event_metrics import calculate_stt_metrics - + events = [ STTTranscriptEvent(text="First transcript", is_final=True), STTTranscriptEvent(text="Second transcript", is_final=True), STTPartialTranscriptEvent(text="Partial transcript", is_final=False), - STTErrorEvent(error=Exception("Test error")) # Should be ignored + STTErrorEvent(error=Exception("Test error")), # Should be ignored ] - + metrics = calculate_stt_metrics(events) - + assert metrics["total_transcripts"] == 3 assert metrics["final_transcripts"] == 2 assert metrics["partial_transcripts"] == 1 - + def test_calculate_stt_metrics_with_processing_times(self): """Test STT metrics calculation with processing time data.""" from getstream.plugins.common.event_metrics import calculate_stt_metrics - + events = [ STTTranscriptEvent(text="Fast", processing_time_ms=50.0), STTTranscriptEvent(text="Medium", processing_time_ms=100.0), STTTranscriptEvent(text="Slow", processing_time_ms=200.0), - STTPartialTranscriptEvent(text="Partial", processing_time_ms=75.0) + STTPartialTranscriptEvent(text="Partial", processing_time_ms=75.0), ] - + metrics = calculate_stt_metrics(events) - + assert metrics["total_transcripts"] == 4 assert metrics["avg_processing_time_ms"] == 106.25 # (50+100+200+75)/4 assert metrics["min_processing_time_ms"] == 50.0 assert metrics["max_processing_time_ms"] == 200.0 - + def test_calculate_stt_metrics_with_confidence(self): """Test STT metrics calculation with confidence data.""" from getstream.plugins.common.event_metrics import calculate_stt_metrics - + events = [ STTTranscriptEvent(text="High confidence", confidence=0.95), STTTranscriptEvent(text="Medium confidence", confidence=0.75), STTTranscriptEvent(text="Low confidence", confidence=0.55), - STTPartialTranscriptEvent(text="Partial", confidence=0.80) + STTPartialTranscriptEvent(text="Partial", confidence=0.80), ] - + metrics = calculate_stt_metrics(events) - + assert metrics["total_transcripts"] == 4 assert metrics["avg_confidence"] == 0.7625 # (0.95+0.75+0.55+0.80)/4 assert metrics["min_confidence"] == 0.55 assert metrics["max_confidence"] == 0.95 - + def test_calculate_stt_metrics_mixed_data(self): """Test STT metrics calculation with mixed data (some missing values).""" from getstream.plugins.common.event_metrics import calculate_stt_metrics - + events = [ - STTTranscriptEvent(text="Complete", confidence=0.9, processing_time_ms=100.0), + STTTranscriptEvent( + text="Complete", + confidence=0.9, + processing_time_ms=100.0, + ), STTTranscriptEvent(text="No confidence", processing_time_ms=150.0), STTTranscriptEvent(text="No processing time", confidence=0.8), - STTPartialTranscriptEvent(text="Partial only") + STTPartialTranscriptEvent(text="Partial only"), ] - + metrics = calculate_stt_metrics(events) - + assert metrics["total_transcripts"] == 4 assert metrics["final_transcripts"] == 3 assert metrics["partial_transcripts"] == 1 - + # Only events with processing_time_ms should be included assert metrics["avg_processing_time_ms"] == 125.0 # (100+150)/2 assert metrics["min_processing_time_ms"] == 100.0 assert metrics["max_processing_time_ms"] == 150.0 - + # Only events with confidence should be included assert pytest.approx(metrics["avg_confidence"], 0.001) == 0.85 # (0.9+0.8)/2 assert metrics["min_confidence"] == 0.8 assert metrics["max_confidence"] == 0.9 - + def test_calculate_tts_metrics_empty_events(self): """Test TTS metrics calculation with no events.""" from getstream.plugins.common.event_metrics import calculate_tts_metrics - + metrics = calculate_tts_metrics([]) assert metrics == { "total_audio_chunks": 0, "total_syntheses": 0, - "completed_syntheses": 0 + "completed_syntheses": 0, } - + def test_calculate_tts_metrics_basic_counts(self): """Test TTS metrics calculation with basic event counts.""" from getstream.plugins.common.event_metrics import calculate_tts_metrics - + events = [ TTSAudioEvent(audio_data=b"chunk1"), TTSAudioEvent(audio_data=b"chunk2"), TTSSynthesisStartEvent(text="Start synthesis"), TTSSynthesisCompleteEvent(synthesis_id="synth1"), - TTSErrorEvent(error=Exception("TTS error")) # Should be ignored + TTSErrorEvent(error=Exception("TTS error")), # Should be ignored ] - + metrics = calculate_tts_metrics(events) - + assert metrics["total_audio_chunks"] == 2 assert metrics["total_syntheses"] == 1 assert metrics["completed_syntheses"] == 1 - + def test_calculate_tts_metrics_with_synthesis_times(self): """Test TTS metrics calculation with synthesis time data.""" from getstream.plugins.common.event_metrics import calculate_tts_metrics - + events = [ TTSSynthesisCompleteEvent(synthesis_id="fast", synthesis_time_ms=100.0), TTSSynthesisCompleteEvent(synthesis_id="medium", synthesis_time_ms=200.0), - TTSSynthesisCompleteEvent(synthesis_id="slow", synthesis_time_ms=300.0) + TTSSynthesisCompleteEvent(synthesis_id="slow", synthesis_time_ms=300.0), ] - + metrics = calculate_tts_metrics(events) - + assert metrics["completed_syntheses"] == 3 assert metrics["avg_synthesis_time_ms"] == 200.0 # (100+200+300)/3 assert metrics["min_synthesis_time_ms"] == 100.0 assert metrics["max_synthesis_time_ms"] == 300.0 - + def test_calculate_tts_metrics_with_real_time_factors(self): """Test TTS metrics calculation with real-time factor data.""" from getstream.plugins.common.event_metrics import calculate_tts_metrics - + events = [ TTSSynthesisCompleteEvent(synthesis_id="rt1", real_time_factor=0.5), TTSSynthesisCompleteEvent(synthesis_id="rt2", real_time_factor=1.0), TTSSynthesisCompleteEvent(synthesis_id="rt3", real_time_factor=1.5), - TTSSynthesisCompleteEvent(synthesis_id="rt4") # No real_time_factor + TTSSynthesisCompleteEvent(synthesis_id="rt4"), # No real_time_factor ] - + metrics = calculate_tts_metrics(events) - + assert metrics["completed_syntheses"] == 4 - assert metrics["avg_real_time_factor"] == 1.0 # (0.5+1.0+1.5)/3 (only 3 have values) + assert ( + metrics["avg_real_time_factor"] == 1.0 + ) # (0.5+1.0+1.5)/3 (only 3 have values) assert metrics["min_real_time_factor"] == 0.5 assert metrics["max_real_time_factor"] == 1.5 - + def test_calculate_vad_metrics_empty_events(self): """Test VAD metrics calculation with no events.""" from getstream.plugins.common.event_metrics import calculate_vad_metrics - + metrics = calculate_vad_metrics([]) - assert metrics == { - "total_speech_segments": 0, - "total_partial_events": 0 - } - + assert metrics == {"total_speech_segments": 0, "total_partial_events": 0} + def test_calculate_vad_metrics_basic_counts(self): """Test VAD metrics calculation with basic event counts.""" from getstream.plugins.common.event_metrics import calculate_vad_metrics - + events = [ VADAudioEvent(audio_data=b"speech1"), VADAudioEvent(audio_data=b"speech2"), VADPartialEvent(speech_probability=0.8), VADPartialEvent(speech_probability=0.9), - VADErrorEvent(error=Exception("VAD error")) # Should be ignored + VADErrorEvent(error=Exception("VAD error")), # Should be ignored ] - + metrics = calculate_vad_metrics(events) - + assert metrics["total_speech_segments"] == 2 assert metrics["total_partial_events"] == 2 - + def test_calculate_vad_metrics_with_durations_and_probabilities(self): """Test VAD metrics calculation with duration and probability data.""" from getstream.plugins.common.event_metrics import calculate_vad_metrics - + events = [ - VADAudioEvent(audio_data=b"short", duration_ms=500.0, speech_probability=0.8), - VADAudioEvent(audio_data=b"medium", duration_ms=1000.0, speech_probability=0.9), - VADAudioEvent(audio_data=b"long", duration_ms=2000.0, speech_probability=0.95) + VADAudioEvent( + audio_data=b"short", + duration_ms=500.0, + speech_probability=0.8, + ), + VADAudioEvent( + audio_data=b"medium", + duration_ms=1000.0, + speech_probability=0.9, + ), + VADAudioEvent( + audio_data=b"long", + duration_ms=2000.0, + speech_probability=0.95, + ), ] - + metrics = calculate_vad_metrics(events) - + assert metrics["total_speech_segments"] == 3 - assert pytest.approx(metrics["avg_speech_duration_ms"], 0.01) == 1166.67 # (500+1000+2000)/3 + assert ( + pytest.approx(metrics["avg_speech_duration_ms"], 0.01) == 1166.67 + ) # (500+1000+2000)/3 assert metrics["total_speech_duration_ms"] == 3500.0 # 500+1000+2000 - assert pytest.approx(metrics["avg_speech_probability"], 0.001) == 0.883 # (0.8+0.9+0.95)/3 - + assert ( + pytest.approx(metrics["avg_speech_probability"], 0.001) == 0.883 + ) # (0.8+0.9+0.95)/3 + def test_calculate_vad_metrics_mixed_data(self): """Test VAD metrics calculation with mixed data (some missing values).""" from getstream.plugins.common.event_metrics import calculate_vad_metrics - + events = [ - VADAudioEvent(audio_data=b"complete", duration_ms=1000.0, speech_probability=0.9), + VADAudioEvent( + audio_data=b"complete", + duration_ms=1000.0, + speech_probability=0.9, + ), VADAudioEvent(audio_data=b"no_duration", speech_probability=0.8), VADAudioEvent(audio_data=b"no_probability", duration_ms=1500.0), - VADPartialEvent(speech_probability=0.7) + VADPartialEvent(speech_probability=0.7), ] - + metrics = calculate_vad_metrics(events) - + assert metrics["total_speech_segments"] == 3 assert metrics["total_partial_events"] == 1 - + # Only events with duration_ms should be included in duration calculations assert metrics["avg_speech_duration_ms"] == 1250.0 # (1000+1500)/2 assert metrics["total_speech_duration_ms"] == 2500.0 # 1000+1500 - + # Only VADAudioEvent events with speech_probability are included (VADPartialEvent is ignored) - assert pytest.approx(metrics["avg_speech_probability"], 0.001) == 0.85 # (0.9+0.8)/2 = 1.7/2 = 0.85 - + assert ( + pytest.approx(metrics["avg_speech_probability"], 0.001) == 0.85 + ) # (0.9+0.8)/2 = 1.7/2 = 0.85 + def test_calculate_metrics_with_realistic_event_sequence(self): """Test metrics calculation with a realistic sequence of events.""" from getstream.plugins.common.event_metrics import ( - calculate_stt_metrics, calculate_tts_metrics, calculate_vad_metrics + calculate_stt_metrics, + calculate_tts_metrics, + calculate_vad_metrics, ) - + # Create a realistic sequence of events events = [ # STT Events STTTranscriptEvent(text="Hello", confidence=0.95, processing_time_ms=100.0), - STTPartialTranscriptEvent(text="Hello wo", confidence=0.85, processing_time_ms=50.0), - STTTranscriptEvent(text="Hello world", confidence=0.92, processing_time_ms=120.0), - + STTPartialTranscriptEvent( + text="Hello wo", + confidence=0.85, + processing_time_ms=50.0, + ), + STTTranscriptEvent( + text="Hello world", + confidence=0.92, + processing_time_ms=120.0, + ), # TTS Events TTSSynthesisStartEvent(text="Hello world"), TTSAudioEvent(audio_data=b"audio1"), TTSAudioEvent(audio_data=b"audio2"), TTSSynthesisCompleteEvent( - synthesis_id="synth1", - synthesis_time_ms=200.0, - real_time_factor=0.8 + synthesis_id="synth1", + synthesis_time_ms=200.0, + real_time_factor=0.8, ), - # VAD Events - VADAudioEvent(audio_data=b"speech1", duration_ms=800.0, speech_probability=0.9), + VADAudioEvent( + audio_data=b"speech1", + duration_ms=800.0, + speech_probability=0.9, + ), VADPartialEvent(speech_probability=0.85), - VADAudioEvent(audio_data=b"speech2", duration_ms=1200.0, speech_probability=0.95) + VADAudioEvent( + audio_data=b"speech2", + duration_ms=1200.0, + speech_probability=0.95, + ), ] - + # Calculate all metrics stt_metrics = calculate_stt_metrics(events) tts_metrics = calculate_tts_metrics(events) vad_metrics = calculate_vad_metrics(events) - + # Verify STT metrics assert stt_metrics["total_transcripts"] == 3 assert stt_metrics["final_transcripts"] == 2 assert stt_metrics["partial_transcripts"] == 1 assert stt_metrics["avg_processing_time_ms"] == 90.0 # (100+50+120)/3 - assert pytest.approx(stt_metrics["avg_confidence"], 0.001) == 0.907 # (0.95+0.85+0.92)/3 - + assert ( + pytest.approx(stt_metrics["avg_confidence"], 0.001) == 0.907 + ) # (0.95+0.85+0.92)/3 + # Verify TTS metrics assert tts_metrics["total_audio_chunks"] == 2 assert tts_metrics["total_syntheses"] == 1 assert tts_metrics["completed_syntheses"] == 1 assert tts_metrics["avg_synthesis_time_ms"] == 200.0 assert tts_metrics["avg_real_time_factor"] == 0.8 - + # Verify VAD metrics assert vad_metrics["total_speech_segments"] == 2 assert vad_metrics["total_partial_events"] == 1 - assert pytest.approx(vad_metrics["avg_speech_duration_ms"], 0.01) == 1000.0 # (800+1200)/2 + assert ( + pytest.approx(vad_metrics["avg_speech_duration_ms"], 0.01) == 1000.0 + ) # (800+1200)/2 assert vad_metrics["total_speech_duration_ms"] == 2000.0 # 800+1200 - assert pytest.approx(vad_metrics["avg_speech_probability"], 0.001) == 0.925 # (0.9+0.95)/2 + assert ( + pytest.approx(vad_metrics["avg_speech_probability"], 0.001) == 0.925 + ) # (0.9+0.95)/2 class TestEventSerialization: """Test event serialization and deserialization.""" - + def test_serialize_stt_transcript_event(self): """Test serializing STT transcript event.""" event = STTTranscriptEvent( @@ -891,164 +916,155 @@ def test_serialize_stt_transcript_event(self): language="en", processing_time_ms=100.0, session_id="test-session", - user_metadata={"user_id": "123"} + user_metadata={"user_id": "123"}, ) - + serialized = serialize_event(event) - - assert serialized['text'] == "Hello world" - assert serialized['confidence'] == 0.95 - assert serialized['language'] == "en" - assert serialized['processing_time_ms'] == 100.0 - assert serialized['session_id'] == "test-session" - assert serialized['user_metadata'] == {"user_id": "123"} - assert serialized['event_type'] == "stt_transcript" - assert 'event_id' in serialized - assert 'timestamp' in serialized - + + assert serialized["text"] == "Hello world" + assert serialized["confidence"] == 0.95 + assert serialized["language"] == "en" + assert serialized["processing_time_ms"] == 100.0 + assert serialized["session_id"] == "test-session" + assert serialized["user_metadata"] == {"user_id": "123"} + assert serialized["event_type"] == "stt_transcript" + assert "event_id" in serialized + assert "timestamp" in serialized + def test_serialize_event_with_error(self): """Test serializing event with error field.""" error = ValueError("Test error message") event = STTErrorEvent( error=error, error_code="TEST_001", - context="test context" + context="test context", ) - + serialized = serialize_event(event) - - assert serialized['error']['type'] == "ValueError" - assert serialized['error']['message'] == "Test error message" - assert serialized['error_code'] == "TEST_001" - assert serialized['context'] == "test context" - assert serialized['event_type'] == "stt_error" - + + assert serialized["error"]["type"] == "ValueError" + assert serialized["error"]["message"] == "Test error message" + assert serialized["error_code"] == "TEST_001" + assert serialized["context"] == "test context" + assert serialized["event_type"] == "stt_error" + def test_serialize_event_with_audio_data(self): """Test serializing event with audio data.""" audio_data = b"fake audio data" * 100 # Create some dummy audio data - event = TTSAudioEvent( - audio_data=audio_data, - sample_rate=22050, - channels=2 - ) - + event = TTSAudioEvent(audio_data=audio_data, sample_rate=22050, channels=2) + serialized = serialize_event(event) - + # Audio data should be replaced with metadata - assert serialized['audio_data']['size_bytes'] == len(audio_data) - assert serialized['audio_data']['type'] == 'bytes' - assert serialized['sample_rate'] == 22050 - assert serialized['channels'] == 2 - assert serialized['event_type'] == "tts_audio" - + assert serialized["audio_data"]["size_bytes"] == len(audio_data) + assert serialized["audio_data"]["type"] == "bytes" + assert serialized["sample_rate"] == 22050 + assert serialized["channels"] == 2 + assert serialized["event_type"] == "tts_audio" + def test_serialize_events_list(self): """Test serializing a list of events to JSON.""" events = [ STTTranscriptEvent(text="First transcript"), STTPartialTranscriptEvent(text="Partial"), - STTErrorEvent(error=Exception("Test error")) + STTErrorEvent(error=Exception("Test error")), ] - + json_string = serialize_events(events) - + # Should be valid JSON parsed = json.loads(json_string) assert len(parsed) == 3 - assert parsed[0]['text'] == "First transcript" - assert parsed[0]['event_type'] == "stt_transcript" - assert parsed[1]['text'] == "Partial" - assert parsed[1]['event_type'] == "stt_partial_transcript" - assert parsed[2]['event_type'] == "stt_error" - + assert parsed[0]["text"] == "First transcript" + assert parsed[0]["event_type"] == "stt_transcript" + assert parsed[1]["text"] == "Partial" + assert parsed[1]["event_type"] == "stt_partial_transcript" + assert parsed[2]["event_type"] == "stt_error" + def test_deserialize_stt_transcript_event(self): """Test deserializing STT transcript event.""" original_event = STTTranscriptEvent( text="Hello world", confidence=0.95, language="en", - processing_time_ms=100.0 + processing_time_ms=100.0, ) - + # Serialize and then deserialize serialized = serialize_event(original_event) deserialized = deserialize_event(serialized) - + assert isinstance(deserialized, STTTranscriptEvent) assert deserialized.text == "Hello world" assert deserialized.confidence == 0.95 assert deserialized.language == "en" assert deserialized.processing_time_ms == 100.0 assert deserialized.event_type == EventType.STT_TRANSCRIPT - + def test_deserialize_event_with_error(self): """Test deserializing event with error field.""" original_event = STTErrorEvent( error=ValueError("Original error"), error_code="TEST_001", - context="test context" + context="test context", ) - + serialized = serialize_event(original_event) deserialized = deserialize_event(serialized) - + assert isinstance(deserialized, STTErrorEvent) assert isinstance(deserialized.error, Exception) assert str(deserialized.error) == "Original error" assert deserialized.error_code == "TEST_001" assert deserialized.context == "test context" - + def test_deserialize_event_with_audio_data_placeholder(self): """Test deserializing event with audio data placeholder.""" original_event = TTSAudioEvent( audio_data=b"test audio", sample_rate=16000, - channels=1 + channels=1, ) - + serialized = serialize_event(original_event) deserialized = deserialize_event(serialized) - + assert isinstance(deserialized, TTSAudioEvent) assert deserialized.audio_data is None # Audio data should be removed assert deserialized.sample_rate == 16000 assert deserialized.channels == 1 - + def test_deserialize_event_with_timestamp_string(self): """Test deserializing event with string timestamp.""" data = { - 'event_type': 'stt_transcript', - 'text': 'Test text', - 'timestamp': '2023-12-01T10:30:00', - 'event_id': 'test-id-123' + "event_type": "stt_transcript", + "text": "Test text", + "timestamp": "2023-12-01T10:30:00", + "event_id": "test-id-123", } - + deserialized = deserialize_event(data) - + assert isinstance(deserialized, STTTranscriptEvent) - assert deserialized.text == 'Test text' + assert deserialized.text == "Test text" assert isinstance(deserialized.timestamp, datetime) assert deserialized.timestamp.year == 2023 assert deserialized.timestamp.month == 12 - + def test_deserialize_invalid_event_type(self): """Test deserializing with invalid event type.""" - data = { - 'event_type': 'invalid_event_type', - 'text': 'Test text' - } - + data = {"event_type": "invalid_event_type", "text": "Test text"} + with pytest.raises(ValueError, match="Unknown event type: invalid_event_type"): deserialize_event(data) - + def test_deserialize_missing_event_type(self): """Test deserializing with missing event type.""" - data = { - 'text': 'Test text' - } - + data = {"text": "Test text"} + with pytest.raises(ValueError, match="Event data missing event_type"): deserialize_event(data) - + def test_round_trip_serialization_all_event_types(self): """Test round-trip serialization for all major event types.""" test_events = [ @@ -1057,14 +1073,12 @@ def test_round_trip_serialization_all_event_types(self): STTPartialTranscriptEvent(text="Partial text"), STTErrorEvent(error=Exception("STT error")), STTConnectionEvent(connection_state=ConnectionState.CONNECTED), - - # TTS Events + # TTS Events TTSAudioEvent(audio_data=b"audio", sample_rate=16000), TTSSynthesisStartEvent(text="Synthesis text"), TTSSynthesisCompleteEvent(synthesis_id="synth-123"), TTSErrorEvent(error=Exception("TTS error")), TTSConnectionEvent(connection_state=ConnectionState.DISCONNECTED), - # STS Events STSConnectedEvent(provider="test_provider"), STSDisconnectedEvent(reason="user_disconnect"), @@ -1074,97 +1088,99 @@ def test_round_trip_serialization_all_event_types(self): STSResponseEvent(text="STS response"), STSConversationItemEvent(item_type="message"), STSErrorEvent(error=Exception("STS error")), - # VAD Events VADSpeechStartEvent(speech_probability=0.8), VADSpeechEndEvent(total_speech_duration_ms=1000.0), VADAudioEvent(audio_data=b"vad audio"), VADPartialEvent(speech_probability=0.7), VADErrorEvent(error=Exception("VAD error")), - # Generic Events PluginInitializedEvent(plugin_type="STT"), PluginClosedEvent(plugin_type="TTS"), - PluginErrorEvent(plugin_type="VAD", error=Exception("Plugin error")) + PluginErrorEvent(plugin_type="VAD", error=Exception("Plugin error")), ] - + for original_event in test_events: # Serialize serialized = serialize_event(original_event) - + # Deserialize deserialized = deserialize_event(serialized) - + # Verify type and basic properties assert isinstance(deserialized, type(original_event)) assert deserialized.event_type == original_event.event_type - + # Verify specific properties based on event type - if hasattr(original_event, 'text') and original_event.text: + if hasattr(original_event, "text") and original_event.text: assert deserialized.text == original_event.text - - if hasattr(original_event, 'error') and original_event.error: + + if hasattr(original_event, "error") and original_event.error: assert isinstance(deserialized.error, Exception) assert str(deserialized.error) == str(original_event.error) - + def test_create_event_function(self): """Test the create_event factory function.""" # Test creating different event types stt_event = create_event(EventType.STT_TRANSCRIPT, text="Test text") assert isinstance(stt_event, STTTranscriptEvent) assert stt_event.text == "Test text" - + tts_event = create_event(EventType.TTS_AUDIO, sample_rate=22050) assert isinstance(tts_event, TTSAudioEvent) assert tts_event.sample_rate == 22050 - + vad_event = create_event(EventType.VAD_SPEECH_START, speech_probability=0.9) assert isinstance(vad_event, VADSpeechStartEvent) assert vad_event.speech_probability == 0.9 - + def test_create_event_invalid_type(self): """Test create_event with invalid event type.""" # Test with None (which should raise an error) with pytest.raises((ValueError, TypeError)): create_event(None, text="test") - + def test_json_round_trip_serialization(self): """Test complete JSON round-trip serialization.""" events = [ STTTranscriptEvent(text="First", confidence=0.95), STTErrorEvent(error=ValueError("Test error"), error_code="E001"), - TTSAudioEvent(audio_data=b"audio data", sample_rate=16000) + TTSAudioEvent(audio_data=b"audio data", sample_rate=16000), ] - + # Serialize to JSON string json_string = serialize_events(events) - + # Parse JSON and deserialize each event events_data = json.loads(json_string) - deserialized_events = [deserialize_event(event_data) for event_data in events_data] - + deserialized_events = [ + deserialize_event(event_data) for event_data in events_data + ] + # Verify we got the same number and types of events assert len(deserialized_events) == 3 assert isinstance(deserialized_events[0], STTTranscriptEvent) assert isinstance(deserialized_events[1], STTErrorEvent) assert isinstance(deserialized_events[2], TTSAudioEvent) - + # Verify content assert deserialized_events[0].text == "First" assert deserialized_events[0].confidence == 0.95 assert str(deserialized_events[1].error) == "Test error" assert deserialized_events[1].error_code == "E001" assert deserialized_events[2].sample_rate == 16000 - assert deserialized_events[2].audio_data is None # Should be removed during serialization + assert ( + deserialized_events[2].audio_data is None + ) # Should be removed during serialization class TestEventFiltering: """Test event filtering functionality.""" - + def test_event_filter_creation(self): """Test EventFilter creation with various parameters.""" from getstream.plugins.common.event_utils import EventFilter - + # Test basic filter filter1 = EventFilter() assert filter1.event_types is None @@ -1172,14 +1188,14 @@ def test_event_filter_creation(self): assert filter1.plugin_names is None assert filter1.time_window_ms is None assert filter1.min_confidence is None - + # Test filter with specific criteria filter2 = EventFilter( event_types=[EventType.STT_TRANSCRIPT, EventType.STT_PARTIAL_TRANSCRIPT], session_ids=["session1", "session2"], plugin_names=["stt_plugin"], time_window_ms=60000, - min_confidence=0.8 + min_confidence=0.8, ) assert EventType.STT_TRANSCRIPT in filter2.event_types assert EventType.STT_PARTIAL_TRANSCRIPT in filter2.event_types @@ -1188,12 +1204,13 @@ def test_event_filter_creation(self): assert "stt_plugin" in filter2.plugin_names assert filter2.time_window_ms == 60000 assert filter2.min_confidence == 0.8 - + def test_event_filter_matching(self): """Test EventFilter.matches() method.""" - from getstream.plugins.common.event_utils import EventFilter from datetime import datetime, timedelta - + + from getstream.plugins.common.event_utils import EventFilter + # Create test events now = datetime.now() event1 = STTTranscriptEvent( @@ -1201,198 +1218,212 @@ def test_event_filter_matching(self): confidence=0.9, session_id="session1", plugin_name="stt_plugin", - timestamp=now + timestamp=now, ) event2 = STTTranscriptEvent( text="test2", confidence=0.7, session_id="session2", plugin_name="tts_plugin", - timestamp=now - timedelta(seconds=120) + timestamp=now - timedelta(seconds=120), ) - + # Test event type filtering type_filter = EventFilter(event_types=[EventType.STT_TRANSCRIPT]) assert type_filter.matches(event1) is True assert type_filter.matches(event2) is True - + type_filter = EventFilter(event_types=[EventType.TTS_AUDIO]) assert type_filter.matches(event1) is False assert type_filter.matches(event2) is False - + # Test session ID filtering session_filter = EventFilter(session_ids=["session1"]) assert session_filter.matches(event1) is True assert session_filter.matches(event2) is False - + # Test plugin name filtering plugin_filter = EventFilter(plugin_names=["stt_plugin"]) assert plugin_filter.matches(event1) is True assert plugin_filter.matches(event2) is False - + # Test time window filtering time_filter = EventFilter(time_window_ms=60000) # Last minute assert time_filter.matches(event1) is True # Recent assert time_filter.matches(event2) is False # 2 minutes ago - + # Test confidence filtering confidence_filter = EventFilter(min_confidence=0.8) assert confidence_filter.matches(event1) is True # 0.9 > 0.8 assert confidence_filter.matches(event2) is False # 0.7 < 0.8 - + # Test combined filtering combined_filter = EventFilter( event_types=[EventType.STT_TRANSCRIPT], session_ids=["session1"], plugin_names=["stt_plugin"], - min_confidence=0.8 + min_confidence=0.8, ) assert combined_filter.matches(event1) is True # All criteria match assert combined_filter.matches(event2) is False # Some criteria don't match - + def test_event_filter_edge_cases(self): """Test EventFilter edge cases.""" from getstream.plugins.common.event_utils import EventFilter - + # Test with None values event = STTTranscriptEvent(text="test") filter_none = EventFilter() assert filter_none.matches(event) is True # No filters = match everything - + # Test with empty sets (current implementation treats empty sets as "no filter") filter_empty = EventFilter(event_types=[], session_ids=[], plugin_names=[]) - assert filter_empty.matches(event) is True # Empty sets = no filtering = match everything - + assert ( + filter_empty.matches(event) is True + ) # Empty sets = no filtering = match everything + # Test with None event (current implementation doesn't validate event parameter) filter_any = EventFilter() - assert filter_any.matches(None) is True # None events currently pass through (could be improved) + assert ( + filter_any.matches(None) is True + ) # None events currently pass through (could be improved) class TestEventRegistry: """Test event registry functionality.""" - + def test_event_registry_creation(self): """Test EventRegistry creation and basic properties.""" from getstream.plugins.common.event_utils import EventRegistry - + registry = EventRegistry(max_events=100) assert registry.max_events == 100 assert len(registry.events) == 0 assert len(registry.event_counts) == 0 assert len(registry.session_events) == 0 assert len(registry.error_counts) == 0 - + def test_event_registration(self): """Test event registration and counting.""" from getstream.plugins.common.event_utils import EventRegistry - + registry = EventRegistry() - + # Register some events event1 = STTTranscriptEvent(text="test1", session_id="session1") event2 = STTTranscriptEvent(text="test2", session_id="session1") event3 = STTErrorEvent(error=Exception("test"), session_id="session2") - + registry.register_event(event1) registry.register_event(event2) registry.register_event(event3) - + # Check counts assert len(registry.events) == 3 assert registry.event_counts[EventType.STT_TRANSCRIPT] == 2 assert registry.event_counts[EventType.STT_ERROR] == 1 - + # Check session grouping assert len(registry.session_events["session1"]) == 2 assert len(registry.session_events["session2"]) == 1 - + # Check error counting assert registry.error_counts["None_stt_error"] == 1 - + def test_event_registry_max_events(self): """Test EventRegistry respects max_events limit.""" from getstream.plugins.common.event_utils import EventRegistry - + registry = EventRegistry(max_events=3) - + # Add 5 events for i in range(5): event = STTTranscriptEvent(text=f"test{i}") registry.register_event(event) - + # Should only keep the last 3 assert len(registry.events) == 3 assert registry.events[0].text == "test2" # First event should be dropped assert registry.events[2].text == "test4" # Last event should be kept - + def test_event_registry_filtering(self): """Test EventRegistry.get_events() with filtering.""" - from getstream.plugins.common.event_utils import EventRegistry, EventFilter - + from getstream.plugins.common.event_utils import EventFilter, EventRegistry + registry = EventRegistry() - + # Add events with different characteristics events = [ - STTTranscriptEvent(text="high_conf", confidence=0.95, session_id="session1"), + STTTranscriptEvent( + text="high_conf", + confidence=0.95, + session_id="session1", + ), STTTranscriptEvent(text="low_conf", confidence=0.75, session_id="session1"), - STTTranscriptEvent(text="other_session", confidence=0.9, session_id="session2"), - TTSAudioEvent(audio_data=b"audio", session_id="session1") + STTTranscriptEvent( + text="other_session", + confidence=0.9, + session_id="session2", + ), + TTSAudioEvent(audio_data=b"audio", session_id="session1"), ] - + for event in events: registry.register_event(event) - + # Test filtering by event type stt_filter = EventFilter(event_types=[EventType.STT_TRANSCRIPT]) stt_events = registry.get_events(stt_filter) assert len(stt_events) == 3 assert all(e.event_type == EventType.STT_TRANSCRIPT for e in stt_events) - + # Test filtering by session session_filter = EventFilter(session_ids=["session1"]) session_events = registry.get_events(session_filter) assert len(session_events) == 3 assert all(e.session_id == "session1" for e in session_events) - + # Test filtering by confidence (strict filtering - events without confidence are excluded) confidence_filter = EventFilter(min_confidence=0.8) high_conf_events = registry.get_events(confidence_filter) - assert len(high_conf_events) == 2 # Only STT events with confidence >= 0.8, TTSAudioEvent excluded + assert ( + len(high_conf_events) == 2 + ) # Only STT events with confidence >= 0.8, TTSAudioEvent excluded # Verify all returned events meet the confidence threshold assert all(e.confidence >= 0.8 for e in high_conf_events) - + # Test combined filtering combined_filter = EventFilter( event_types=[EventType.STT_TRANSCRIPT], session_ids=["session1"], - min_confidence=0.8 + min_confidence=0.8, ) combined_events = registry.get_events(combined_filter) assert len(combined_events) == 1 assert combined_events[0].text == "high_conf" - + def test_event_registry_statistics(self): """Test EventRegistry statistics methods.""" from getstream.plugins.common.event_utils import EventRegistry - + registry = EventRegistry() - + # Add events events = [ STTTranscriptEvent(text="test1", session_id="session1"), STTTranscriptEvent(text="test2", session_id="session1"), STTErrorEvent(error=Exception("error1"), session_id="session2"), - TTSAudioEvent(audio_data=b"audio", session_id="session3") + TTSAudioEvent(audio_data=b"audio", session_id="session3"), ] - + for event in events: registry.register_event(event) - + # Test error summary error_summary = registry.get_error_summary() assert "None_stt_error" in error_summary["error_breakdown"] assert error_summary["error_breakdown"]["None_stt_error"] == 1 - + # Test statistics stats = registry.get_statistics() assert stats["total_events"] == 4 @@ -1400,56 +1431,56 @@ def test_event_registry_statistics(self): assert stats["event_distribution"]["stt_transcript"] == 2 assert stats["event_distribution"]["stt_error"] == 1 assert stats["event_distribution"]["tts_audio"] == 1 - + def test_event_registry_listeners(self): """Test EventRegistry event listener functionality.""" from getstream.plugins.common.event_utils import EventRegistry - + registry = EventRegistry() received_events = [] - + def listener(event): received_events.append(event) - + # Add listener for STT events registry.add_listener(EventType.STT_TRANSCRIPT, listener) - + # Register an event event = STTTranscriptEvent(text="test") registry.register_event(event) - + # Check if listener was called assert len(received_events) == 1 assert received_events[0] == event - + # Remove listener registry.remove_listener(EventType.STT_TRANSCRIPT, listener) received_events.clear() - + # Register another event event2 = STTTranscriptEvent(text="test2") registry.register_event(event2) - + # Listener should not be called assert len(received_events) == 0 - + def test_event_registry_clear(self): """Test EventRegistry.clear() method.""" from getstream.plugins.common.event_utils import EventRegistry - + registry = EventRegistry() - + # Add some events event = STTTranscriptEvent(text="test") registry.register_event(event) - + # Verify events exist assert len(registry.events) == 1 assert len(registry.event_counts) > 0 - + # Clear registry registry.clear() - + # Verify everything is cleared assert len(registry.events) == 0 assert len(registry.event_counts) == 0 @@ -1459,64 +1490,65 @@ def test_event_registry_clear(self): class TestEventLogger: """Test event logging functionality.""" - + def test_event_logger_creation(self): """Test EventLogger creation.""" from getstream.plugins.common.event_utils import EventLogger - + logger = EventLogger() assert logger.registry is not None assert logger.logger is not None - + def test_event_logger_logging(self): """Test EventLogger event logging.""" - from getstream.plugins.common.event_utils import EventLogger import logging - + + from getstream.plugins.common.event_utils import EventLogger + # Set up logging capture log_records = [] - + def capture_logs(record): log_records.append(record) - + # Create logger and capture logs logger = EventLogger() logger.logger.handlers = [] # Remove default handlers logger.logger.addHandler(logging.StreamHandler()) - + # Log some events event1 = STTTranscriptEvent(text="test1") event2 = STTErrorEvent(error=Exception("test error")) - + logger.log_event(event1) logger.log_event(event2) - + # Check that events were registered assert len(logger.registry.events) == 2 - + # Check that events were logged (basic verification) assert len(logger.registry.events) == 2 - + def test_event_logger_batch_logging(self): """Test EventLogger batch logging.""" from getstream.plugins.common.event_utils import EventLogger - + logger = EventLogger() - + # Create multiple events events = [ STTTranscriptEvent(text="test1"), STTTranscriptEvent(text="test2"), - TTSAudioEvent(audio_data=b"audio") + TTSAudioEvent(audio_data=b"audio"), ] - + # Log events individually (EventLogger doesn't have batch method) for event in events: logger.log_event(event) - + # Check all events were registered assert len(logger.registry.events) == 3 - + # Check event types event_types = [e.event_type for e in logger.registry.events] assert EventType.STT_TRANSCRIPT in event_types @@ -1525,54 +1557,68 @@ def test_event_logger_batch_logging(self): class TestGlobalEventSystem: """Test global event system functionality.""" - + def test_global_registry_access(self): """Test global registry access functions.""" from getstream.plugins.common.event_utils import ( - get_global_registry, get_global_logger, register_global_event + get_global_logger, + get_global_registry, + register_global_event, ) - + # Get global instances registry = get_global_registry() logger = get_global_logger() - + assert registry is not None assert logger is not None - + # Test global event registration event = STTTranscriptEvent(text="global_test") register_global_event(event) - + # Check event was registered global_events = registry.get_events() assert len(global_events) > 0 - + # Find our event - filter by event type and text to avoid issues with other event types - test_events = [e for e in global_events if hasattr(e, 'text') and e.text == "global_test"] + test_events = [ + e for e in global_events if hasattr(e, "text") and e.text == "global_test" + ] assert len(test_events) == 1 - + def test_global_event_consistency(self): """Test that global registry and logger are consistent.""" from getstream.plugins.common.event_utils import ( - get_global_registry, get_global_logger, register_global_event + get_global_logger, + get_global_registry, + register_global_event, ) - + registry = get_global_registry() logger = get_global_logger() - + # They are separate instances but register_global_event ensures consistency assert logger.registry is not registry # Separate instances - + # Test consistency through global registration event = STTTranscriptEvent(text="consistency_test") register_global_event(event) - + # Check both registries have the event registry_events = registry.get_events() logger_events = logger.registry.get_events() - - registry_test_events = [e for e in registry_events if hasattr(e, 'text') and e.text == "consistency_test"] - logger_test_events = [e for e in logger_events if hasattr(e, 'text') and e.text == "consistency_test"] - + + registry_test_events = [ + e + for e in registry_events + if hasattr(e, "text") and e.text == "consistency_test" + ] + logger_test_events = [ + e + for e in logger_events + if hasattr(e, "text") and e.text == "consistency_test" + ] + assert len(registry_test_events) == 1 assert len(logger_test_events) == 1 diff --git a/getstream/plugins/common/tts.py b/getstream/plugins/common/tts.py index 2aef71c6..ed6b8701 100644 --- a/getstream/plugins/common/tts.py +++ b/getstream/plugins/common/tts.py @@ -1,25 +1,30 @@ import abc -import logging import inspect +import logging import time import uuid -from typing import Optional, Dict, Any, Union, Iterator, AsyncIterator +from collections.abc import AsyncIterator, Iterator +from typing import Any, Dict, Optional, Union from pyee.asyncio import AsyncIOEventEmitter + from getstream.video.rtc.audio_track import AudioStreamTrack +from .event_utils import register_global_event from .events import ( - TTSAudioEvent, TTSSynthesisStartEvent, TTSSynthesisCompleteEvent, TTSErrorEvent, - PluginInitializedEvent, PluginClosedEvent + PluginClosedEvent, + PluginInitializedEvent, + TTSAudioEvent, + TTSErrorEvent, + TTSSynthesisCompleteEvent, + TTSSynthesisStartEvent, ) -from .event_utils import register_global_event logger = logging.getLogger(__name__) class TTS(AsyncIOEventEmitter, abc.ABC): - """ - Text-to-Speech base class. + """Text-to-Speech base class. This abstract class provides the interface for text-to-speech implementations. It handles: @@ -37,11 +42,11 @@ class TTS(AsyncIOEventEmitter, abc.ABC): """ def __init__(self, provider_name: Optional[str] = None): - """ - Initialize the TTS base class. + """Initialize the TTS base class. Args: provider_name: Name of the TTS provider (e.g., "cartesia", "elevenlabs") + """ super().__init__() self._track: Optional[AudioStreamTrack] = None @@ -67,11 +72,11 @@ def __init__(self, provider_name: Optional[str] = None): self.emit("initialized", init_event) def set_output_track(self, track: AudioStreamTrack) -> None: - """ - Set the audio track to output speech to. + """Set the audio track to output speech to. Args: track: The audio track object that will receive speech audio + """ self._track = track @@ -82,10 +87,12 @@ def track(self): @abc.abstractmethod async def stream_audio( - self, text: str, *args, **kwargs + self, + text: str, + *args, + **kwargs, ) -> Union[bytes, Iterator[bytes], AsyncIterator[bytes]]: - """ - Convert text to speech audio data. + """Convert text to speech audio data. This method must be implemented by subclasses. @@ -96,13 +103,13 @@ async def stream_audio( Returns: Audio data as bytes, an iterator of audio chunks, or an async iterator of audio chunks + """ pass @abc.abstractmethod async def stop_audio(self) -> None: - """ - Clears the queue and stops playing audio. + """Clears the queue and stops playing audio. This method can be used manually or under the hood in response to turn events. This method must be implemented by subclasses. @@ -110,14 +117,18 @@ async def stop_audio(self) -> None: Returns: None + """ pass async def send( - self, text: str, user: Optional[Dict[str, Any]] = None, *args, **kwargs + self, + text: str, + user: Optional[Dict[str, Any]] = None, + *args, + **kwargs, ): - """ - Convert text to speech, send to the output track, and emit an audio event. + """Convert text to speech, send to the output track, and emit an audio event. Args: text: The text to convert to speech @@ -127,6 +138,7 @@ async def send( Raises: ValueError: If no output track has been set + """ if self._track is None: raise ValueError("No output track set. Call set_output_track() first.") @@ -137,7 +149,8 @@ async def send( synthesis_id = str(uuid.uuid4()) logger.debug( - "Starting text-to-speech synthesis", extra={"text_length": len(text)} + "Starting text-to-speech synthesis", + extra={"text_length": len(text)}, ) # Emit synthesis start event @@ -146,7 +159,7 @@ async def send( plugin_name=self.provider_name, text=text, synthesis_id=synthesis_id, - user_metadata=user + user_metadata=user, ) register_global_event(start_event) self.emit("synthesis_start", start_event) @@ -174,7 +187,7 @@ async def send( synthesis_id=synthesis_id, text_source=text, user_metadata=user, - sample_rate=self._track.framerate if self._track else 16000 + sample_rate=self._track.framerate if self._track else 16000, ) register_global_event(audio_event) self.emit("audio", audio_event) # Structured event @@ -195,7 +208,7 @@ async def send( user_metadata=user, chunk_index=audio_chunks - 1, is_final_chunk=False, # We don't know if it's final yet - sample_rate=self._track.framerate if self._track else 16000 + sample_rate=self._track.framerate if self._track else 16000, ) register_global_event(audio_event) self.emit("audio", audio_event) # Structured event @@ -214,12 +227,13 @@ async def send( user_metadata=user, chunk_index=audio_chunks - 1, is_final_chunk=False, # We don't know if it's final yet - sample_rate=self._track.framerate if self._track else 16000 + sample_rate=self._track.framerate if self._track else 16000, ) register_global_event(audio_event) self.emit("audio", audio_event) # Structured event elif hasattr(audio_data, "__iter__") and not isinstance( - audio_data, (str, bytes, bytearray) + audio_data, + (str, bytes, bytearray), ): for chunk in audio_data: total_audio_bytes += len(chunk) @@ -236,13 +250,13 @@ async def send( user_metadata=user, chunk_index=audio_chunks - 1, is_final_chunk=False, # We don't know if it's final yet - sample_rate=self._track.framerate if self._track else 16000 + sample_rate=self._track.framerate if self._track else 16000, ) register_global_event(audio_event) self.emit("audio", audio_event) # Structured event else: raise TypeError( - f"Unsupported return type from synthesize: {type(audio_data)}" + f"Unsupported return type from synthesize: {type(audio_data)}", ) # Log completion with timing information @@ -257,7 +271,8 @@ async def send( real_time_factor = ( (synthesis_time * 1000) / estimated_audio_duration_ms - if estimated_audio_duration_ms > 0 else None + if estimated_audio_duration_ms > 0 + else None ) # Emit synthesis completion event @@ -271,7 +286,7 @@ async def send( synthesis_time_ms=synthesis_time * 1000, audio_duration_ms=estimated_audio_duration_ms, chunk_count=audio_chunks, - real_time_factor=real_time_factor + real_time_factor=real_time_factor, ) register_global_event(completion_event) self.emit("synthesis_complete", completion_event) @@ -300,7 +315,7 @@ async def send( context="synthesis", text_source=text, synthesis_id=synthesis_id, - user_metadata=user + user_metadata=user, ) register_global_event(error_event) self.emit("error", error_event) # New structured event @@ -316,7 +331,7 @@ async def close(self): plugin_name=self.provider_name, plugin_type="TTS", provider=self.provider_name, - cleanup_successful=True + cleanup_successful=True, ) register_global_event(close_event) self.emit("closed", close_event) diff --git a/getstream/plugins/common/vad.py b/getstream/plugins/common/vad.py index 277d09a0..c4f5d054 100644 --- a/getstream/plugins/common/vad.py +++ b/getstream/plugins/common/vad.py @@ -2,26 +2,30 @@ import logging import time import uuid -from typing import Optional, Dict, Any +from typing import Any, Dict, Optional import numpy as np from pyee.asyncio import AsyncIOEventEmitter +from getstream.audio.pcm_utils import numpy_array_to_bytes, pcm_to_numpy_array from getstream.video.rtc.track_util import PcmData -from getstream.audio.pcm_utils import pcm_to_numpy_array, numpy_array_to_bytes +from .event_utils import register_global_event from .events import ( - VADSpeechStartEvent, VADSpeechEndEvent, VADAudioEvent, VADPartialEvent, VADErrorEvent, - PluginInitializedEvent, PluginClosedEvent + PluginClosedEvent, + PluginInitializedEvent, + VADAudioEvent, + VADErrorEvent, + VADPartialEvent, + VADSpeechEndEvent, + VADSpeechStartEvent, ) -from .event_utils import register_global_event logger = logging.getLogger(__name__) class VAD(AsyncIOEventEmitter, abc.ABC): - """ - Voice Activity Detection base class. + """Voice Activity Detection base class. This abstract class provides the interface for voice activity detection implementations. It handles: @@ -46,8 +50,7 @@ def __init__( partial_frames: int = 10, provider_name: Optional[str] = None, ): - """ - Initialize the VAD. + """Initialize the VAD. Args: sample_rate: Audio sample rate in Hz @@ -60,6 +63,7 @@ def __init__( max_speech_ms: Maximum milliseconds of speech before forced flush partial_frames: Number of frames to process before emitting a "partial" event provider_name: Name of the VAD provider (e.g., "silero") + """ super().__init__() @@ -110,38 +114,39 @@ def __init__( "deactivation_threshold": deactivation_th, "min_speech_ms": min_speech_ms, "max_speech_ms": max_speech_ms, - } + }, ) register_global_event(init_event) self.emit("initialized", init_event) @abc.abstractmethod async def is_speech(self, frame: PcmData) -> float: - """ - Determine if the audio frame contains speech. + """Determine if the audio frame contains speech. Args: frame: Audio frame data as PcmData Returns: Probability (0.0 to 1.0) that the frame contains speech + """ pass async def process_audio( - self, pcm_data: PcmData, user: Optional[Dict[str, Any]] = None + self, + pcm_data: PcmData, + user: Optional[Dict[str, Any]] = None, ) -> None: - """ - Process raw PCM audio data for voice activity detection. + """Process raw PCM audio data for voice activity detection. Args: pcm_data: Raw PCM audio data user: User metadata to include with emitted audio events - """ + """ if pcm_data.sample_rate != self.sample_rate: raise TypeError( - f"vad is initialized with sample rate {self.sample_rate} but pcm data has sample rate {pcm_data.sample_rate}" + f"vad is initialized with sample rate {self.sample_rate} but pcm data has sample rate {pcm_data.sample_rate}", ) # Convert samples to numpy array using shared utility @@ -183,14 +188,16 @@ async def process_audio( logger.debug(f"Keeping {len(self._leftover)} samples for next processing") async def _process_frame( - self, frame: PcmData, user: Optional[Dict[str, Any]] = None + self, + frame: PcmData, + user: Optional[Dict[str, Any]] = None, ) -> None: - """ - Process a single audio frame. + """Process a single audio frame. Args: frame: Audio frame as PcmData user: User metadata to include with emitted audio events + """ speech_prob = await self.is_speech(frame) @@ -217,7 +224,8 @@ async def _process_frame( if self.partial_counter >= self.partial_frames: # Create a copy of the current speech data current_samples = np.frombuffer( - self.speech_buffer, dtype=np.int16 + self.speech_buffer, + dtype=np.int16, ).copy() # Calculate current duration @@ -230,13 +238,13 @@ async def _process_frame( audio_data=current_samples, duration_ms=current_duration_ms, frame_count=len(current_samples) // self.frame_size, - user_metadata=user + user_metadata=user, ) register_global_event(partial_event) self.emit("partial", partial_event) # Structured event logger.debug( - f"Emitted partial event with {len(current_samples)} samples" + f"Emitted partial event with {len(current_samples)} samples", ) self.partial_counter = 0 @@ -249,7 +257,7 @@ async def _process_frame( # Calculate silence pad frames based on ms speech_pad_frames = int( - self.speech_pad_ms * self.sample_rate / 1000 / self.frame_size + self.speech_pad_ms * self.sample_rate / 1000 / self.frame_size, ) # If silence exceeds padding duration, emit audio and reset @@ -258,7 +266,7 @@ async def _process_frame( # Calculate max speech frames based on ms max_speech_frames = int( - self.max_speech_ms * self.sample_rate / 1000 / self.frame_size + self.max_speech_ms * self.sample_rate / 1000 / self.frame_size, ) # Force flush if speech duration exceeds maximum @@ -280,7 +288,7 @@ async def _process_frame( speech_probability=speech_prob, activation_threshold=self.activation_th, frame_count=1, - user_metadata=user + user_metadata=user, ) register_global_event(speech_start_event) self.emit("speech_start", speech_start_event) @@ -290,15 +298,15 @@ async def _process_frame( self.speech_buffer.extend(frame_bytes) async def _flush_speech_buffer(self, user: Optional[Dict[str, Any]] = None) -> None: - """ - Flush the accumulated speech buffer if it meets minimum length requirements. + """Flush the accumulated speech buffer if it meets minimum length requirements. Args: user: User metadata to include with emitted audio events + """ # Calculate min speech frames based on ms min_speech_frames = int( - self.min_speech_ms * self.sample_rate / 1000 / self.frame_size + self.min_speech_ms * self.sample_rate / 1000 / self.frame_size, ) # Convert bytearray to numpy array @@ -315,7 +323,7 @@ async def _flush_speech_buffer(self, user: Optional[Dict[str, Any]] = None) -> N audio_data=speech_data, duration_ms=speech_duration_ms, frame_count=len(speech_data) // self.frame_size, - user_metadata=user + user_metadata=user, ) register_global_event(audio_event) self.emit("audio", audio_event) # Structured event @@ -332,7 +340,7 @@ async def _flush_speech_buffer(self, user: Optional[Dict[str, Any]] = None) -> N deactivation_threshold=self.deactivation_th, total_speech_duration_ms=total_speech_duration, total_frames=self.total_speech_frames, - user_metadata=user + user_metadata=user, ) register_global_event(speech_end_event) self.emit("speech_end", speech_end_event) @@ -346,11 +354,11 @@ async def _flush_speech_buffer(self, user: Optional[Dict[str, Any]] = None) -> N self._speech_start_time = None async def flush(self, user: Optional[Dict[str, Any]] = None) -> None: - """ - Public method to flush any accumulated speech buffer. + """Public method to flush any accumulated speech buffer. Args: user: User metadata to include with emitted audio events + """ await self._flush_speech_buffer(user) @@ -364,7 +372,12 @@ async def reset(self) -> None: self._leftover = np.empty(0, np.int16) self._speech_start_time = None - def _emit_error_event(self, error: Exception, context: str = "", user_metadata: Optional[Dict[str, Any]] = None): + def _emit_error_event( + self, + error: Exception, + context: str = "", + user_metadata: Optional[Dict[str, Any]] = None, + ): """Emit a structured error event.""" error_event = VADErrorEvent( session_id=self.session_id, @@ -372,7 +385,7 @@ def _emit_error_event(self, error: Exception, context: str = "", user_metadata: error=error, context=context, user_metadata=user_metadata, - frame_data_available=len(self.speech_buffer) > 0 + frame_data_available=len(self.speech_buffer) > 0, ) register_global_event(error_event) self.emit("error", error_event) # Structured event @@ -389,7 +402,7 @@ async def close(self): plugin_name=self.provider_name, plugin_type="VAD", provider=self.provider_name, - cleanup_successful=True + cleanup_successful=True, ) register_global_event(close_event) self.emit("closed", close_event) diff --git a/getstream/plugins/deepgram/stt/stt.py b/getstream/plugins/deepgram/stt/stt.py index 5ac185cb..7339bd5c 100644 --- a/getstream/plugins/deepgram/stt/stt.py +++ b/getstream/plugins/deepgram/stt/stt.py @@ -1,14 +1,15 @@ import asyncio import json import logging -from typing import Dict, Any, Optional, Tuple, List -import numpy as np import os import time +from typing import Any, Dict, List, Optional, Tuple + +import numpy as np # Conditional imports with error handling try: - from deepgram import DeepgramClient, LiveTranscriptionEvents, LiveOptions + from deepgram import DeepgramClient, LiveOptions, LiveTranscriptionEvents _deepgram_available = True except ImportError: @@ -24,8 +25,7 @@ class DeepgramSTT(STT): - """ - Deepgram-based Speech-to-Text implementation. + """Deepgram-based Speech-to-Text implementation. This implementation operates in asynchronous mode - it receives streaming transcripts from Deepgram's WebSocket connection and emits events immediately as they arrive, @@ -50,8 +50,7 @@ def __init__( interim_results: bool = False, client: Optional[DeepgramClient] = None, ): - """ - Initialize the Deepgram STT service. + """Initialize the Deepgram STT service. Args: api_key: Deepgram API key. If not provided, the DEEPGRAM_API_KEY @@ -62,6 +61,7 @@ def __init__( keep_alive_interval: Interval in seconds to send keep-alive messages. Default is 5.0 seconds (recommended value by Deepgram) interim_results: Whether to emit interim results (partial transcripts with the partial_transcript event). + """ super().__init__(sample_rate=sample_rate) @@ -74,7 +74,7 @@ def __init__( api_key = os.environ.get("DEEPGRAM_API_KEY") if not api_key: logger.warning( - "No API key provided and DEEPGRAM_API_KEY environment variable not found." + "No API key provided and DEEPGRAM_API_KEY environment variable not found.", ) # Initialize DeepgramClient with the API key @@ -104,11 +104,12 @@ def __init__( self._setup_connection() def _handle_transcript_result( - self, is_final: bool, text: str, metadata: Dict[str, Any] + self, + is_final: bool, + text: str, + metadata: Dict[str, Any], ): - """ - Handle a transcript result by emitting it immediately. - """ + """Handle a transcript result by emitting it immediately.""" # Emit immediately for real-time responsiveness if is_final: self._emit_transcript_event(text, self._current_user, metadata) @@ -153,7 +154,8 @@ def handle_transcript(conn, result=None): transcript = json.loads(result) else: logger.warning( - "Unrecognized transcript format: %s", type(result) + "Unrecognized transcript format: %s", + type(result), ) return @@ -228,7 +230,8 @@ def _start_keep_alive_task(self): """Start the background task that sends keep-alive messages.""" if self.keep_alive_task is None and self._running: logger.debug( - "Starting keep-alive task with interval %ss", self.keep_alive_interval + "Starting keep-alive task with interval %ss", + self.keep_alive_interval, ) self.keep_alive_task = asyncio.create_task(self._keep_alive_loop()) @@ -292,10 +295,11 @@ async def send_keep_alive(self): return False async def _process_audio_impl( - self, pcm_data: PcmData, user_metadata: Optional[Dict[str, Any]] = None + self, + pcm_data: PcmData, + user_metadata: Optional[Dict[str, Any]] = None, ) -> Optional[List[Tuple[bool, str, Dict[str, Any]]]]: - """ - Process audio data through Deepgram for transcription. + """Process audio data through Deepgram for transcription. Args: pcm_data: The PCM audio data to process. @@ -304,6 +308,7 @@ async def _process_audio_impl( Returns: None - Deepgram operates in asynchronous mode and emits events directly when transcripts arrive from the streaming service. + """ if self._is_closed: logger.warning("Deepgram connection is closed, ignoring audio") diff --git a/getstream/plugins/deepgram/tests/conftest.py b/getstream/plugins/deepgram/tests/conftest.py index 3326e9e7..fb0c7c38 100644 --- a/getstream/plugins/deepgram/tests/conftest.py +++ b/getstream/plugins/deepgram/tests/conftest.py @@ -1,6 +1,7 @@ -import pytest import os +import pytest + @pytest.fixture(scope="session") def deepgram_api_key(): @@ -8,7 +9,7 @@ def deepgram_api_key(): api_key = os.environ.get("DEEPGRAM_API_KEY") if not api_key: pytest.skip( - "DEEPGRAM_API_KEY environment variable not set. Add it to your .env file." + "DEEPGRAM_API_KEY environment variable not set. Add it to your .env file.", ) return api_key diff --git a/getstream/plugins/deepgram/tests/test_realtime.py b/getstream/plugins/deepgram/tests/test_realtime.py index 267e2ff4..b7beb1ee 100644 --- a/getstream/plugins/deepgram/tests/test_realtime.py +++ b/getstream/plugins/deepgram/tests/test_realtime.py @@ -1,7 +1,8 @@ +import asyncio import json +from unittest.mock import MagicMock, patch + import pytest -import asyncio -from unittest.mock import patch, MagicMock from getstream.plugins.deepgram.stt import DeepgramSTT from getstream.video.rtc.track_util import PcmData @@ -54,8 +55,8 @@ def emit_transcript(self, text, is_final=True): } for i, word in enumerate(text.split()) ], - } - ] + }, + ], }, "channel_index": 0, } @@ -65,7 +66,8 @@ def emit_transcript(self, text, is_final=True): # Call the handler with the connection and the result self.event_handlers[LiveTranscriptionEvents.Transcript]( - self, result=transcript_json + self, + result=transcript_json, ) def emit_error(self, error_message): @@ -75,7 +77,8 @@ def emit_error(self, error_message): if LiveTranscriptionEvents.Error in self.event_handlers: # Call the error handler self.event_handlers[LiveTranscriptionEvents.Error]( - self, error=error_message + self, + error=error_message, ) @@ -90,8 +93,7 @@ def __init__(self, api_key=None): @pytest.mark.asyncio @patch("getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClient) async def test_real_time_transcript_emission(): - """ - Test that transcripts are emitted in real-time without needing a second audio chunk. + """Test that transcripts are emitted in real-time without needing a second audio chunk. This test verifies that: 1. A transcript can be emitted immediately after receiving it from the server @@ -113,7 +115,9 @@ def on_transcript(event): @stt.on("partial_transcript") def on_partial_transcript(event): - partial_transcript_events.append((event.text, event.user_metadata, {"is_final": False})) + partial_transcript_events.append( + (event.text, event.user_metadata, {"is_final": False}), + ) @stt.on("error") def on_error(event): @@ -145,9 +149,7 @@ def on_error(event): @pytest.mark.asyncio @patch("getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClient) async def test_real_time_partial_transcript_emission(): - """ - Test that partial transcripts are emitted in real-time. - """ + """Test that partial transcripts are emitted in real-time.""" # Create the Deepgram STT instance stt = DeepgramSTT(api_key="test-api-key") @@ -162,7 +164,9 @@ def on_transcript(event): @stt.on("partial_transcript") def on_partial_transcript(event): - partial_transcript_events.append((event.text, event.user_metadata, {"is_final": False})) + partial_transcript_events.append( + (event.text, event.user_metadata, {"is_final": False}), + ) # Send some audio data to ensure the connection is active pcm_data = PcmData(samples=b"\x00\x00" * 800, sample_rate=48000, format="s16") @@ -188,18 +192,18 @@ def on_partial_transcript(event): # Check that we received the partial transcript events assert len(partial_transcript_events) == 2, "Expected 2 partial transcript events" - assert ( - partial_transcript_events[0][0] == "typing in prog" - ), "Incorrect partial transcript text" - assert ( - partial_transcript_events[1][0] == "typing in progress" - ), "Incorrect partial transcript text" + assert partial_transcript_events[0][0] == "typing in prog", ( + "Incorrect partial transcript text" + ) + assert partial_transcript_events[1][0] == "typing in progress", ( + "Incorrect partial transcript text" + ) # Check that we received the final transcript event assert len(transcript_events) == 1, "Expected 1 final transcript event" - assert ( - transcript_events[0][0] == "typing in progress complete" - ), "Incorrect final transcript text" + assert transcript_events[0][0] == "typing in progress complete", ( + "Incorrect final transcript text" + ) # Cleanup await stt.close() @@ -208,9 +212,7 @@ def on_partial_transcript(event): @pytest.mark.asyncio @patch("getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClient) async def test_real_time_error_emission(): - """ - Test that errors are emitted in real-time. - """ + """Test that errors are emitted in real-time.""" # Create the Deepgram STT instance stt = DeepgramSTT(api_key="test-api-key") @@ -243,9 +245,7 @@ def on_error(error): @pytest.mark.asyncio @patch("getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClient) async def test_close_cleanup(): - """ - Test that the STT service is properly closed and cleaned up. - """ + """Test that the STT service is properly closed and cleaned up.""" # Create the Deepgram STT instance stt = DeepgramSTT(api_key="test-api-key") @@ -282,8 +282,7 @@ def on_transcript(event): @pytest.mark.asyncio @patch("getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClient) async def test_asynchronous_mode_behavior(): - """ - Test that Deepgram operates in asynchronous mode: + """Test that Deepgram operates in asynchronous mode: 1. Events are emitted immediately when they arrive 2. _process_audio_impl always returns None (no result collection) """ diff --git a/getstream/plugins/deepgram/tests/test_stt.py b/getstream/plugins/deepgram/tests/test_stt.py index bfceaaec..0795b1ad 100644 --- a/getstream/plugins/deepgram/tests/test_stt.py +++ b/getstream/plugins/deepgram/tests/test_stt.py @@ -1,13 +1,14 @@ -import json -import pytest import asyncio -import numpy as np -from unittest.mock import patch, MagicMock +import json import os +from unittest.mock import MagicMock, patch + +import numpy as np +import pytest from getstream.plugins.deepgram.stt import DeepgramSTT -from getstream.video.rtc.track_util import PcmData from getstream.plugins.test_utils import get_audio_asset, get_json_metadata +from getstream.video.rtc.track_util import PcmData # Mock the Deepgram client to avoid real API calls during tests @@ -62,8 +63,8 @@ def emit_transcript(self, text, is_final=True): } for i, word in enumerate(text.split()) ], - } - ] + }, + ], }, "channel_index": 0, } @@ -73,7 +74,8 @@ def emit_transcript(self, text, is_final=True): # Call the handler with the connection and the result self.event_handlers[LiveTranscriptionEvents.Transcript]( - self, result=transcript_json + self, + result=transcript_json, ) @@ -153,8 +155,8 @@ def emit_transcript(self, text, is_final=True): } for i, word in enumerate(text.split()) ], - } - ] + }, + ], }, "channel_index": 0, } @@ -164,7 +166,8 @@ def emit_transcript(self, text, is_final=True): # Call the handler with the connection and the result self.event_handlers[LiveTranscriptionEvents.Transcript]( - self, result=transcript_json + self, + result=transcript_json, ) @@ -174,7 +177,7 @@ def __init__(self, api_key=None): self.listen = MagicMock() self.listen.websocket = MagicMock() self.listen.websocket.v = MagicMock( - return_value=MockDeepgramConnectionWithKeepAlive() + return_value=MockDeepgramConnectionWithKeepAlive(), ) @@ -199,9 +202,9 @@ def mia_metadata(): @pytest.fixture def audio_data(mia_mp3_path): """Load and prepare the audio data for testing.""" - import torchaudio - import torch import numpy as np + import torch + import torchaudio from scipy import signal # Load the mp3 file @@ -218,7 +221,7 @@ def audio_data(mia_mp3_path): target_sample_rate = 48000 if original_sample_rate != target_sample_rate: number_of_samples = round( - len(data) * float(target_sample_rate) / original_sample_rate + len(data) * float(target_sample_rate) / original_sample_rate, ) data = signal.resample(data, number_of_samples) @@ -248,7 +251,6 @@ async def test_deepgram_stt_initialization(): @patch.dict(os.environ, {"DEEPGRAM_API_KEY": "env-var-api-key"}) async def test_deepgram_stt_initialization_with_env_var(): """Test that the Deepgram STT initializes correctly when DEEPGRAM_API_KEY is set.""" - # Initialize without providing an API key – implementation should fall back to env var stt = DeepgramSTT() assert stt is not None @@ -356,7 +358,11 @@ def on_error(event): @pytest.mark.asyncio async def test_deepgram_with_real_api( - audio_data, mia_metadata, deepgram_api_key, deepgram_model, deepgram_language + audio_data, + mia_metadata, + deepgram_api_key, + deepgram_model, + deepgram_language, ): """Integration test with the real Deepgram API. @@ -382,7 +388,9 @@ async def test_deepgram_with_real_api( ) stt = DeepgramSTT( - api_key=deepgram_api_key, options=options, language=deepgram_language + api_key=deepgram_api_key, + options=options, + language=deepgram_language, ) # Track events @@ -396,7 +404,9 @@ def on_transcript(event): @stt.on("partial_transcript") def on_partial(event): - partial_transcripts.append((event.text, event.user_metadata, {"is_final": False})) + partial_transcripts.append( + (event.text, event.user_metadata, {"is_final": False}), + ) @stt.on("error") def on_error(event): @@ -405,7 +415,7 @@ def on_error(event): # Process audio # Print debug info about the audio print( - f"Processing audio: sample_rate={audio_data.sample_rate}, format={audio_data.format}, samples_length={len(audio_data.samples)}" + f"Processing audio: sample_rate={audio_data.sample_rate}, format={audio_data.format}, samples_length={len(audio_data.samples)}", ) # We'll retry if there are connection issues @@ -486,11 +496,15 @@ def on_error(event): # Re-register event handlers @stt.on("transcript") def on_transcript(event): - transcripts.append((event.text, event.user_metadata, {"is_final": True})) + transcripts.append( + (event.text, event.user_metadata, {"is_final": True}), + ) @stt.on("partial_transcript") def on_partial(event): - partial_transcripts.append((event.text, event.user_metadata, {"is_final": False})) + partial_transcripts.append( + (event.text, event.user_metadata, {"is_final": False}), + ) @stt.on("error") def on_error(event): @@ -498,9 +512,9 @@ def on_error(event): else: # Final attempt failed print(f"All retry attempts failed: {e}") - assert ( - False - ), f"Failed to process audio after {max_retries} attempts: {e}" + assert False, ( + f"Failed to process audio after {max_retries} attempts: {e}" + ) # Cleanup and close the connection try: @@ -510,11 +524,11 @@ def on_error(event): print(f"Error closing STT: {e}") # We should have received transcripts - assert ( - len(transcripts) > 0 or len(partial_transcripts) > 0 - ), "No transcripts received after sending audio" + assert len(transcripts) > 0 or len(partial_transcripts) > 0, ( + "No transcripts received after sending audio" + ) print( - f"Received {len(transcripts)} transcripts and {len(partial_transcripts)} partial transcripts" + f"Received {len(transcripts)} transcripts and {len(partial_transcripts)} partial transcripts", ) # Print the transcripts for debugging @@ -529,7 +543,8 @@ def on_error(event): @pytest.mark.asyncio @patch( - "getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClientWithKeepAlive + "getstream.plugins.deepgram.stt.stt.DeepgramClient", + MockDeepgramClientWithKeepAlive, ) async def test_deepgram_keep_alive_mechanism(): """Test that the keep-alive mechanism works.""" @@ -541,9 +556,9 @@ async def test_deepgram_keep_alive_mechanism(): await asyncio.sleep(0.2) # We should see that keep-alive messages have been sent - assert ( - len(connection.sent_text_messages) > 0 - ), "No keep-alive messages sent after wait" + assert len(connection.sent_text_messages) > 0, ( + "No keep-alive messages sent after wait" + ) # Cleanup await stt.close() @@ -551,7 +566,8 @@ async def test_deepgram_keep_alive_mechanism(): @pytest.mark.asyncio @patch( - "getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClientWithKeepAlive + "getstream.plugins.deepgram.stt.stt.DeepgramClient", + MockDeepgramClientWithKeepAlive, ) async def test_deepgram_keep_alive_after_audio(): """Test that keep-alive messages are sent after audio is processed.""" @@ -569,9 +585,9 @@ async def test_deepgram_keep_alive_after_audio(): await asyncio.sleep(0.2) # We should see that keep-alive messages have been sent - assert ( - len(connection.sent_text_messages) > 0 - ), "No keep-alive messages sent after audio processing" + assert len(connection.sent_text_messages) > 0, ( + "No keep-alive messages sent after audio processing" + ) # Cleanup await stt.close() @@ -579,7 +595,8 @@ async def test_deepgram_keep_alive_after_audio(): @pytest.mark.asyncio @patch( - "getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClientWithKeepAlive + "getstream.plugins.deepgram.stt.stt.DeepgramClient", + MockDeepgramClientWithKeepAlive, ) async def test_deepgram_keep_alive_direct(): """Test that we can directly send keep-alive messages.""" @@ -594,9 +611,9 @@ async def test_deepgram_keep_alive_direct(): assert success, "Failed to send keep-alive message" # The connection should have received the message - assert ( - len(connection.sent_text_messages) > 0 - ), "No keep-alive messages were recorded" + assert len(connection.sent_text_messages) > 0, ( + "No keep-alive messages were recorded" + ) # If the connection has a keep_alive method, then the send_text method should not be used if hasattr(connection, "keep_alive"): @@ -608,7 +625,8 @@ async def test_deepgram_keep_alive_direct(): @pytest.mark.asyncio @patch( - "getstream.plugins.deepgram.stt.stt.DeepgramClient", MockDeepgramClientWithKeepAlive + "getstream.plugins.deepgram.stt.stt.DeepgramClient", + MockDeepgramClientWithKeepAlive, ) async def test_deepgram_close_message(): """Test that the finish message is sent when the connection is closed.""" @@ -652,8 +670,7 @@ def mock_send_text(message): @pytest.mark.asyncio async def test_deepgram_with_real_api_keep_alive(): - """ - Test Deepgram STT with real API and keep-alive functionality. + """Test Deepgram STT with real API and keep-alive functionality. This test uses a real Deepgram API connection to test keep-alive behavior. """ @@ -680,7 +697,9 @@ async def test_deepgram_with_real_api_keep_alive(): from getstream.audio.utils import resample_audio audio_data = resample_audio( - audio_data, original_sample_rate, target_sample_rate + audio_data, + original_sample_rate, + target_sample_rate, ) # Convert to int16 PCM @@ -691,7 +710,9 @@ async def test_deepgram_with_real_api_keep_alive(): # Create PCM data pcm_data = PcmData( - samples=pcm_samples, sample_rate=target_sample_rate, format="s16" + samples=pcm_samples, + sample_rate=target_sample_rate, + format="s16", ) except Exception as e: @@ -710,7 +731,9 @@ def on_transcript(event): @stt.on("partial_transcript") def on_partial_transcript(event): - partial_transcripts.append((event.text, event.user_metadata, {"is_final": False})) + partial_transcripts.append( + (event.text, event.user_metadata, {"is_final": False}), + ) @stt.on("error") def on_error(event): @@ -736,8 +759,7 @@ def on_error(event): @pytest.mark.asyncio async def test_deepgram_real_integration(): - """ - Integration test with the real Deepgram API using the mia.mp3 test file. + """Integration test with the real Deepgram API using the mia.mp3 test file. This test processes the mia.mp3 audio file and validates the transcription results against expected content. This is crucial for ensuring the Deepgram plugin @@ -772,7 +794,9 @@ async def test_deepgram_real_integration(): from getstream.audio.utils import resample_audio audio_data = resample_audio( - audio_data, original_sample_rate, target_sample_rate + audio_data, + original_sample_rate, + target_sample_rate, ) # Convert to int16 PCM @@ -782,7 +806,7 @@ async def test_deepgram_real_integration(): pcm_samples = (audio_data * 32767.0).astype(np.int16) print( - f"Testing with mia.mp3: {len(pcm_samples)} samples at {target_sample_rate}Hz" + f"Testing with mia.mp3: {len(pcm_samples)} samples at {target_sample_rate}Hz", ) print(f"Audio duration: {len(pcm_samples) / target_sample_rate:.2f} seconds") print(f"Audio range: {pcm_samples.min()} to {pcm_samples.max()}") @@ -793,7 +817,7 @@ async def test_deepgram_real_integration(): # Extract expected text from mia.json metadata expected_segments = mia_metadata.get("segments", []) expected_full_text = " ".join( - [segment["text"] for segment in expected_segments] + [segment["text"] for segment in expected_segments], ).strip() expected_words = expected_full_text.lower().split() @@ -801,7 +825,9 @@ async def test_deepgram_real_integration(): print(f"Expected word count: {len(expected_words)}") stt = DeepgramSTT( - api_key=api_key, sample_rate=target_sample_rate, interim_results=True + api_key=api_key, + sample_rate=target_sample_rate, + interim_results=True, ) # Track events @@ -813,12 +839,20 @@ async def test_deepgram_real_integration(): def on_transcript(event): # Extract words from the text for metadata words = event.text.lower().split() if event.text else [] - transcripts.append((event.text, event.user_metadata, {"is_final": True, "confidence": 0.9, "words": words})) + transcripts.append( + ( + event.text, + event.user_metadata, + {"is_final": True, "confidence": 0.9, "words": words}, + ), + ) print(f"Final transcript: {event.text}") @stt.on("partial_transcript") def on_partial_transcript(event): - partial_transcripts.append((event.text, event.user_metadata, {"is_final": False})) + partial_transcripts.append( + (event.text, event.user_metadata, {"is_final": False}), + ) print(f"Partial transcript: {event.text}") @stt.on("error") @@ -838,7 +872,9 @@ def on_error(event): chunk_samples = pcm_samples[i:end_idx] chunk_data = PcmData( - samples=chunk_samples, sample_rate=target_sample_rate, format="s16" + samples=chunk_samples, + sample_rate=target_sample_rate, + format="s16", ) await stt.process_audio(chunk_data) @@ -906,14 +942,14 @@ def on_error(event): print(f"Key words found: {found_key_words}") # We should find at least some key words from the story - assert ( - len(found_key_words) >= 2 - ), f"Expected to find at least 2 key words from {key_words}, but only found {found_key_words}" + assert len(found_key_words) >= 2, ( + f"Expected to find at least 2 key words from {key_words}, but only found {found_key_words}" + ) # Check that we got a reasonable amount of text - assert ( - len(actual_words) >= 10 - ), f"Expected at least 10 words, but got {len(actual_words)}: {combined_text}" + assert len(actual_words) >= 10, ( + f"Expected at least 10 words, but got {len(actual_words)}: {combined_text}" + ) # Verify metadata structure assert "confidence" in metadata diff --git a/getstream/plugins/elevenlabs/tests/test_tts.py b/getstream/plugins/elevenlabs/tests/test_tts.py index 68a51e9f..53c248bd 100644 --- a/getstream/plugins/elevenlabs/tests/test_tts.py +++ b/getstream/plugins/elevenlabs/tests/test_tts.py @@ -1,7 +1,8 @@ +import asyncio import os +from unittest.mock import MagicMock, patch + import pytest -import asyncio -from unittest.mock import patch, MagicMock from getstream.plugins.elevenlabs.tts import ElevenLabsTTS from getstream.video.rtc.audio_track import AudioStreamTrack @@ -31,12 +32,15 @@ def __init__(self, api_key=None): async def mock_stream(*args, **kwargs): for chunk in mock_audio: yield chunk - + self.text_to_speech.stream = mock_stream @pytest.mark.asyncio -@patch("getstream.plugins.elevenlabs.tts.tts.AsyncElevenLabs", MockAsyncElevenLabsClient) +@patch( + "getstream.plugins.elevenlabs.tts.tts.AsyncElevenLabs", + MockAsyncElevenLabsClient, +) async def test_elevenlabs_tts_initialization(): """Test that the ElevenLabs TTS initializes correctly with explicit API key.""" tts = ElevenLabsTTS(api_key="test-api-key") @@ -45,18 +49,23 @@ async def test_elevenlabs_tts_initialization(): @pytest.mark.asyncio -@patch("getstream.plugins.elevenlabs.tts.tts.AsyncElevenLabs", MockAsyncElevenLabsClient) +@patch( + "getstream.plugins.elevenlabs.tts.tts.AsyncElevenLabs", + MockAsyncElevenLabsClient, +) @patch.dict(os.environ, {"ELEVENLABS_API_KEY": "env-var-api-key"}) async def test_elevenlabs_tts_initialization_with_env_var(): """ElevenLabsTTS should use ELEVENLABS_API_KEY when no key argument is given.""" - tts = ElevenLabsTTS() # no explicit key provided assert tts is not None assert tts.client.api_key == "env-var-api-key" @pytest.mark.asyncio -@patch("getstream.plugins.elevenlabs.tts.tts.AsyncElevenLabs", MockAsyncElevenLabsClient) +@patch( + "getstream.plugins.elevenlabs.tts.tts.AsyncElevenLabs", + MockAsyncElevenLabsClient, +) async def test_elevenlabs_tts_synthesize(): """Test that synthesize returns an audio stream.""" tts = ElevenLabsTTS(api_key="test-api-key") @@ -77,7 +86,10 @@ async def test_elevenlabs_tts_synthesize(): @pytest.mark.asyncio -@patch("getstream.plugins.elevenlabs.tts.tts.AsyncElevenLabs", MockAsyncElevenLabsClient) +@patch( + "getstream.plugins.elevenlabs.tts.tts.AsyncElevenLabs", + MockAsyncElevenLabsClient, +) async def test_elevenlabs_tts_send(): """Test that send writes audio to the track and emits events.""" tts = ElevenLabsTTS(api_key="test-api-key") @@ -137,10 +149,10 @@ async def test_elevenlabs_tts_with_custom_client(): """Test that ElevenLabs TTS can be initialized with a custom client.""" # Create a custom mock client custom_client = MockAsyncElevenLabsClient(api_key="custom-api-key") - + # Initialize TTS with the custom client tts = ElevenLabsTTS(client=custom_client) - + # Verify that the custom client is used assert tts.client is custom_client assert tts.client.api_key == "custom-api-key" @@ -151,17 +163,17 @@ async def test_elevenlabs_tts_with_custom_client(): async def test_elevenlabs_tts_stop_method(): """Test that the stop method properly flushes the audio track.""" tts = ElevenLabsTTS(api_key="test-api-key") - + # Create a mock audio track with flush method track = MockAudioTrack() track.flush = MagicMock(return_value=asyncio.Future()) track.flush.return_value.set_result(None) - + tts.set_output_track(track) - + # Call stop method await tts.stop_audio() - + # Verify that flush was called on the track track.flush.assert_called_once() @@ -171,24 +183,23 @@ async def test_elevenlabs_tts_stop_method(): async def test_elevenlabs_tts_stop_method_handles_exceptions(): """Test that the stop method handles flush exceptions gracefully.""" tts = ElevenLabsTTS(api_key="test-api-key") - + # Create a mock audio track with flush method that raises an exception track = MockAudioTrack() track.flush = MagicMock(side_effect=Exception("Flush error")) - + tts.set_output_track(track) - + # Call stop method - should not raise an exception await tts.stop_audio() - + # Verify that flush was called on the track track.flush.assert_called_once() @pytest.mark.asyncio async def test_elevenlabs_with_real_api(): - """ - Integration test with the real ElevenLabs API. + """Integration test with the real ElevenLabs API. This test uses the actual ElevenLabs API with the ELEVENLABS_API_KEY environment variable. @@ -205,8 +216,7 @@ async def test_elevenlabs_with_real_api(): # Skip the test if the ELEVENLABS_API_KEY environment variable is not set if not api_key: pytest.skip( - "ELEVENLABS_API_KEY environment variable not set. " - "Add it to your .env file." + "ELEVENLABS_API_KEY environment variable not set. Add it to your .env file.", ) # Create a real ElevenLabs TTS instance with the API key explicitly set diff --git a/getstream/plugins/elevenlabs/tts/tts.py b/getstream/plugins/elevenlabs/tts/tts.py index c7e459c9..0cd9cfad 100644 --- a/getstream/plugins/elevenlabs/tts/tts.py +++ b/getstream/plugins/elevenlabs/tts/tts.py @@ -1,10 +1,12 @@ import logging +import os +from collections.abc import AsyncIterator +from typing import Optional -from getstream.plugins.common import TTS from elevenlabs.client import AsyncElevenLabs + +from getstream.plugins.common import TTS from getstream.video.rtc.audio_track import AudioStreamTrack -from typing import AsyncIterator, Optional -import os class ElevenLabsTTS(TTS): @@ -15,8 +17,7 @@ def __init__( model_id: str = "eleven_multilingual_v2", client: Optional[AsyncElevenLabs] = None, ): - """ - Initialize the ElevenLabs TTS service. + """Initialize the ElevenLabs TTS service. Args: api_key: ElevenLabs API key. If not provided, the ELEVENLABS_API_KEY @@ -24,6 +25,7 @@ def __init__( voice_id: The voice ID to use for synthesis model_id: The model ID to use for synthesis client: Optionally pass in your own instance of the ElvenLabs Client. + """ super().__init__() @@ -42,16 +44,15 @@ def set_output_track(self, track: AudioStreamTrack) -> None: super().set_output_track(track) async def stream_audio(self, text: str, *args, **kwargs) -> AsyncIterator[bytes]: - """ - Convert text to speech using ElevenLabs API. + """Convert text to speech using ElevenLabs API. Args: text: The text to convert to speech Returns: An async iterator of audio chunks as bytes - """ + """ audio_stream = self.client.text_to_speech.stream( text=text, voice_id=self.voice_id, @@ -65,12 +66,12 @@ async def stream_audio(self, text: str, *args, **kwargs) -> AsyncIterator[bytes] return audio_stream async def stop_audio(self) -> None: - """ - Clears the queue and stops playing audio. + """Clears the queue and stops playing audio. This method can be used manually or under the hood in response to turn events. Returns: None + """ if self.track is not None: try: diff --git a/getstream/plugins/fal/stt/stt.py b/getstream/plugins/fal/stt/stt.py index a160caa8..d6b33adc 100644 --- a/getstream/plugins/fal/stt/stt.py +++ b/getstream/plugins/fal/stt/stt.py @@ -1,5 +1,4 @@ -""" -Fal Wizper STT Plugin for Stream +"""Fal Wizper STT Plugin for Stream Provides real-time audio transcription and translation using fal-ai/wizper (Whisper v3). This plugin integrates with Stream's audio processing pipeline to provide high-quality @@ -24,23 +23,23 @@ async def on_error(error: str): """ import io +import logging import os import tempfile import time -import logging -from typing import Any, Dict, Optional, List, Tuple import wave +from typing import Any, Dict, List, Optional, Tuple import fal_client -from getstream.video.rtc.track_util import PcmData + from getstream.plugins.common import STT +from getstream.video.rtc.track_util import PcmData logger = logging.getLogger(__name__) class FalWizperSTT(STT): - """ - Audio transcription and translation using fal-ai/wizper (Whisper v3). + """Audio transcription and translation using fal-ai/wizper (Whisper v3). This plugin provides real-time speech-to-text capabilities using the fal-ai/wizper service, which is based on OpenAI's Whisper v3 model. It supports both transcription @@ -49,6 +48,7 @@ class FalWizperSTT(STT): Attributes: task: The task type - either "transcribe" or "translate" target_language: Target language code for translation (e.g., "pt" for Portuguese) + """ def __init__( @@ -58,13 +58,13 @@ def __init__( sample_rate: int = 48000, client: Optional[fal_client.AsyncClient] = None, ): - """ - Initialize FalWizperSTT. + """Initialize FalWizperSTT. Args: task: "transcribe" or "translate" target_language: Target language code (e.g., "pt" for Portuguese) sample_rate: Sample rate of the audio in Hz. + """ super().__init__(sample_rate=sample_rate) self.task = task @@ -74,14 +74,14 @@ def __init__( self._fal_client = client if client is not None else fal_client.AsyncClient() def _pcm_to_wav_bytes(self, pcm_data: PcmData) -> bytes: - """ - Convert PCM data to WAV format bytes. + """Convert PCM data to WAV format bytes. Args: pcm_data: PCM audio data from Stream's audio pipeline Returns: WAV format audio data as bytes + """ wav_buffer = io.BytesIO() @@ -95,10 +95,11 @@ def _pcm_to_wav_bytes(self, pcm_data: PcmData) -> bytes: return wav_buffer.read() async def _process_audio_impl( - self, pcm_data: PcmData, user_metadata: Optional[Dict[str, Any]] = None + self, + pcm_data: PcmData, + user_metadata: Optional[Dict[str, Any]] = None, ) -> Optional[List[Tuple[bool, str, Dict[str, Any]]]]: - """ - Process accumulated speech audio through fal-ai/wizper. + """Process accumulated speech audio through fal-ai/wizper. This method is typically called by VAD (Voice Activity Detection) systems when speech segments are detected. @@ -106,6 +107,7 @@ async def _process_audio_impl( Args: speech_audio: Accumulated speech audio as numpy array user: User metadata from the Stream call + """ if self._is_closed: logger.debug("connection is closed, ignoring audio") @@ -145,13 +147,16 @@ async def _process_audio_impl( # Use regular subscribe since streaming isn't supported result = await self._fal_client.subscribe( - "fal-ai/wizper", arguments=input_params + "fal-ai/wizper", + arguments=input_params, ) if "text" in result: text = result["text"].strip() if text: self._emit_transcript_event( - text, user_metadata, {"chunks": result.get("chunks", [])} + text, + user_metadata, + {"chunks": result.get("chunks", [])}, ) finally: # Clean up temporary file diff --git a/getstream/plugins/fal/tests/test_stt.py b/getstream/plugins/fal/tests/test_stt.py index dadb2d39..60008cdd 100644 --- a/getstream/plugins/fal/tests/test_stt.py +++ b/getstream/plugins/fal/tests/test_stt.py @@ -1,9 +1,10 @@ """Tests for the FalWizperSTT plugin.""" import asyncio -import pytest from unittest.mock import AsyncMock, MagicMock, patch + import numpy as np +import pytest from getstream.plugins.fal.stt.stt import FalWizperSTT from getstream.video.rtc.track_util import PcmData @@ -13,7 +14,7 @@ def stt(): """Provides a FalWizperSTT instance with a mocked fal_client.""" with patch( - "getstream.plugins.fal.stt.stt.fal_client.AsyncClient" + "getstream.plugins.fal.stt.stt.fal_client.AsyncClient", ) as mock_fal_client: stt_instance = FalWizperSTT() stt_instance._fal_client = mock_fal_client.return_value @@ -47,10 +48,10 @@ def test_pcm_to_wav_bytes(self, stt): async def test_process_audio_impl_success_transcribe(self, stt): """Test successful transcription with a valid response.""" stt._fal_client.upload_file = AsyncMock( - return_value="http://mock.url/audio.wav" + return_value="http://mock.url/audio.wav", ) stt._fal_client.subscribe = AsyncMock( - return_value={"text": " This is a test. ", "chunks": []} + return_value={"text": " This is a test. ", "chunks": []}, ) transcript_handler = AsyncMock() @@ -65,7 +66,8 @@ async def test_process_audio_impl_success_transcribe(self, stt): with ( patch( - "tempfile.NamedTemporaryFile", new_callable=MagicMock + "tempfile.NamedTemporaryFile", + new_callable=MagicMock, ) as mock_temp_file, patch("os.unlink", new_callable=MagicMock) as mock_unlink, ): @@ -98,14 +100,14 @@ async def test_process_audio_impl_success_translate(self): with patch("getstream.plugins.fal.stt.stt.fal_client.AsyncClient"): stt = FalWizperSTT(task="translate", target_language="pt") stt._fal_client.upload_file = AsyncMock( - return_value="http://mock.url/audio.wav" + return_value="http://mock.url/audio.wav", ) stt._fal_client.subscribe = AsyncMock( - return_value={"text": "This is a test.", "chunks": []} + return_value={"text": "This is a test.", "chunks": []}, ) samples = (np.sin(np.linspace(0, 440 * 2 * np.pi, 480)) * 32767).astype( - np.int16 + np.int16, ) pcm_data = PcmData( samples=samples, @@ -122,17 +124,17 @@ async def test_process_audio_impl_success_translate(self): async def test_process_audio_impl_no_text(self, stt): """Test that no transcript is emitted if the API response lacks 'text'.""" stt._fal_client.upload_file = AsyncMock( - return_value="http://mock.url/audio.wav" + return_value="http://mock.url/audio.wav", ) stt._fal_client.subscribe = AsyncMock( - return_value={"chunks": []} + return_value={"chunks": []}, ) # No 'text' field transcript_handler = AsyncMock() stt.on("transcript", transcript_handler) samples = (np.sin(np.linspace(0, 440 * 2 * np.pi, 480)) * 32767).astype( - np.int16 + np.int16, ) pcm_data = PcmData( samples=samples, @@ -149,17 +151,17 @@ async def test_process_audio_impl_no_text(self, stt): async def test_process_audio_impl_empty_text(self, stt): """Test that no transcript is emitted for empty or whitespace-only text.""" stt._fal_client.upload_file = AsyncMock( - return_value="http://mock.url/audio.wav" + return_value="http://mock.url/audio.wav", ) stt._fal_client.subscribe = AsyncMock( - return_value={"text": " ", "chunks": []} + return_value={"text": " ", "chunks": []}, ) # Empty text transcript_handler = AsyncMock() stt.on("transcript", transcript_handler) samples = (np.sin(np.linspace(0, 440 * 2 * np.pi, 480)) * 32767).astype( - np.int16 + np.int16, ) pcm_data = PcmData( samples=samples, @@ -181,7 +183,7 @@ async def test_process_audio_impl_api_error(self, stt): stt.on("error", error_handler) samples = (np.sin(np.linspace(0, 440 * 2 * np.pi, 480)) * 32767).astype( - np.int16 + np.int16, ) pcm_data = PcmData( samples=samples, @@ -217,7 +219,7 @@ async def test_process_audio_impl_when_closed(self, stt): """Test that audio is ignored if the STT service is closed.""" await stt.close() samples = (np.sin(np.linspace(0, 440 * 2 * np.pi, 480)) * 32767).astype( - np.int16 + np.int16, ) pcm_data = PcmData( samples=samples, diff --git a/getstream/plugins/gemini/live/live.py b/getstream/plugins/gemini/live/live.py index cdbe25d6..425c9d81 100644 --- a/getstream/plugins/gemini/live/live.py +++ b/getstream/plugins/gemini/live/live.py @@ -5,33 +5,33 @@ from __future__ import annotations -import logging import asyncio -import os -from typing import Optional, Any, Dict, List import contextlib +import logging +import os +from typing import Any, Dict, List, Optional + +import numpy as np from google.genai import Client +from google.genai.live import AsyncSession from google.genai.types import ( - LiveConnectConfigDict, + AudioTranscriptionConfigDict, Blob, + ContextWindowCompressionConfigDict, + LiveConnectConfigDict, Modality, - AudioTranscriptionConfigDict, + PrebuiltVoiceConfigDict, RealtimeInputConfigDict, - TurnCoverage, + SlidingWindowDict, SpeechConfigDict, + TurnCoverage, VoiceConfigDict, - PrebuiltVoiceConfigDict, - ContextWindowCompressionConfigDict, - SlidingWindowDict, ) -from google.genai.live import AsyncSession -import numpy as np -from getstream.plugins.common import STS from getstream.audio.utils import resample_audio -from getstream.video.rtc.track_util import PcmData +from getstream.plugins.common import STS from getstream.video.rtc.audio_track import AudioStreamTrack - +from getstream.video.rtc.track_util import PcmData logger = logging.getLogger(__name__) @@ -67,8 +67,8 @@ def __init__( silence_timeout_ms: Time to wait for silence after user stops speaking. provider_config: Provider config to pass through unchanged. Pass a ``LiveConnectConfigDict`` from ``google.genai.types``. - """ + """ super().__init__( model=model, instructions=instructions, @@ -83,17 +83,18 @@ def __init__( ) if not self.api_key: raise ValueError( - "GOOGLE_API_KEY or GEMINI_API_KEY not provided and not found in env" + "GOOGLE_API_KEY or GEMINI_API_KEY not provided and not found in env", ) if Client is None: raise ImportError( - "failed to import google genai SDK, install with: uv add google-genai" + "failed to import google genai SDK, install with: uv add google-genai", ) # Use v1beta for Live APIs as required by current SDK self.client = Client( - api_key=self.api_key, http_options={"api_version": "v1beta"} + api_key=self.api_key, + http_options={"api_version": "v1beta"}, ) self.model = model # Build a default config if none provided, and apply simple overrides @@ -118,7 +119,9 @@ def __init__( # Create an outbound audio track for assistant speech self.output_track = AudioStreamTrack( - framerate=24000, stereo=False, format="s16" + framerate=24000, + stereo=False, + format="s16", ) # Kick off background connect if we are in an event loop loop = asyncio.get_running_loop() @@ -131,11 +134,11 @@ def _default_config(self) -> LiveConnectConfigDict: output_audio_transcription=AudioTranscriptionConfigDict(), speech_config=SpeechConfigDict( voice_config=VoiceConfigDict( - prebuilt_voice_config=PrebuiltVoiceConfigDict(voice_name="Puck") - ) + prebuilt_voice_config=PrebuiltVoiceConfigDict(voice_name="Puck"), + ), ), realtime_input_config=RealtimeInputConfigDict( - turn_coverage=TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY + turn_coverage=TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY, ), context_window_compression=ContextWindowCompressionConfigDict( trigger_tokens=25600, @@ -161,7 +164,7 @@ def _compose_config( speech_cfg: Dict[str, Any] = dict(cfg.get("speech_config") or {}) if voice is not None: speech_cfg["voice_config"] = VoiceConfigDict( - prebuilt_voice_config=PrebuiltVoiceConfigDict(voice_name=voice) + prebuilt_voice_config=PrebuiltVoiceConfigDict(voice_name=voice), ) if speech_cfg: cfg["speech_config"] = speech_cfg @@ -214,6 +217,7 @@ async def send_audio_pcm(self, pcm: PcmData, target_rate: int = 48000): Args: pcm: PcmData from RTC pipeline (int16 numpy array or bytes). target_rate: Target sample rate for Gemini input. + """ if not self._is_connected or not self._session: await self.wait_until_ready() @@ -232,7 +236,9 @@ async def send_audio_pcm(self, pcm: PcmData, target_rate: int = 48000): # Resample if needed if pcm.sample_rate != target_rate: audio_array = resample_audio( - audio_array, pcm.sample_rate, target_rate + audio_array, + pcm.sample_rate, + target_rate, ).astype(np.int16) # Activity detection to avoid constant interruption when streaming continuous audio @@ -256,7 +262,9 @@ async def send_audio_pcm(self, pcm: PcmData, target_rate: int = 48000): mime = f"audio/pcm;rate={target_rate}" blob = Blob(data=audio_bytes, mime_type=mime) logger.debug( - "Sending %d bytes audio to Gemini Live (%s)", len(audio_bytes), mime + "Sending %d bytes audio to Gemini Live (%s)", + len(audio_bytes), + mime, ) await self._session.send_realtime_input(audio=blob) @@ -277,6 +285,7 @@ async def start_response_listener( Args: emit_events: If True, emit an "audio" event with chunk bytes. + """ if not self._is_connected or not self._session: await self.wait_until_ready() @@ -313,7 +322,7 @@ async def _audio_receive_loop(): logger.info("Response listener started") self._audio_receiver_task = asyncio.create_task(_audio_receive_loop()) - return None + return async def interrupt_playback(self) -> None: """Stop current playback immediately and clear queued audio chunks.""" diff --git a/getstream/plugins/gemini/tests/test_live.py b/getstream/plugins/gemini/tests/test_live.py index eb9aac1a..cb554b62 100644 --- a/getstream/plugins/gemini/tests/test_live.py +++ b/getstream/plugins/gemini/tests/test_live.py @@ -1,9 +1,9 @@ +import asyncio +import importlib import sys import types -import asyncio -from unittest.mock import AsyncMock from typing import Any, cast -import importlib +from unittest.mock import AsyncMock import numpy as np import pytest @@ -64,10 +64,9 @@ async def _gen(): for data, text in self._responses: obj = types.SimpleNamespace() obj.data = data - setattr(obj, "text", text) + obj.text = text yield obj self._receive_calls += 1 - return return _gen() diff --git a/getstream/plugins/gemini/tests/test_live_integration.py b/getstream/plugins/gemini/tests/test_live_integration.py index c9dbc5e8..ac8a1082 100644 --- a/getstream/plugins/gemini/tests/test_live_integration.py +++ b/getstream/plugins/gemini/tests/test_live_integration.py @@ -1,5 +1,5 @@ -import os import asyncio +import os import pytest @@ -14,8 +14,7 @@ @pytest.mark.asyncio async def test_gemini_live_with_real_api(): - """ - Optional smoke test: requires GOOGLE_API_KEY and google-genai installed. + """Optional smoke test: requires GOOGLE_API_KEY and google-genai installed. Connects, sends a short text, and asserts we receive audio or text back. """ api_key = os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY") @@ -57,8 +56,8 @@ async def _on_text(text: str): await asyncio.sleep(0.2) try: - assert ( - events["audio"] or events["text"] - ), "No response received from Gemini Live" + assert events["audio"] or events["text"], ( + "No response received from Gemini Live" + ) finally: await sts.close() diff --git a/getstream/plugins/kokoro/tests/test_tts.py b/getstream/plugins/kokoro/tests/test_tts.py index b8637ec0..014b6a98 100644 --- a/getstream/plugins/kokoro/tests/test_tts.py +++ b/getstream/plugins/kokoro/tests/test_tts.py @@ -1,5 +1,5 @@ -from unittest.mock import patch, MagicMock import asyncio +from unittest.mock import MagicMock, patch import numpy as np import pytest @@ -7,7 +7,6 @@ from getstream.plugins.kokoro.tts import KokoroTTS from getstream.video.rtc.audio_track import AudioStreamTrack - ############################ # Test utilities & fixtures ############################ @@ -72,7 +71,7 @@ async def test_kokoro_send_writes_and_emits(): @tts.on("audio") def _on_audio(event): # Extract the audio data from the event - if hasattr(event, 'audio_data') and event.audio_data is not None: + if hasattr(event, "audio_data") and event.audio_data is not None: received.append(event.audio_data) else: received.append(b"") @@ -107,10 +106,10 @@ async def test_kokoro_tts_with_custom_client(): """Test that Kokoro TTS can be initialized with a custom client.""" # Create a custom mock client custom_client = _MockKPipeline() - + # Initialize TTS with the custom client tts = KokoroTTS(client=custom_client) - + # Verify that the custom client is used assert tts.client is custom_client @@ -120,17 +119,17 @@ async def test_kokoro_tts_with_custom_client(): async def test_kokoro_tts_stop_method(): """Test that the stop method properly flushes the audio track.""" tts = KokoroTTS() - + # Create a mock audio track with flush method track = MockAudioTrack() track.flush = MagicMock(return_value=asyncio.Future()) track.flush.return_value.set_result(None) - + tts.set_output_track(track) - + # Call stop method await tts.stop_audio() - + # Verify that flush was called on the track track.flush.assert_called_once() @@ -140,15 +139,15 @@ async def test_kokoro_tts_stop_method(): async def test_kokoro_tts_stop_method_handles_exceptions(): """Test that the stop method handles flush exceptions gracefully.""" tts = KokoroTTS() - + # Create a mock audio track with flush method that raises an exception track = MockAudioTrack() track.flush = MagicMock(side_effect=Exception("Flush error")) - + tts.set_output_track(track) - + # Call stop method - should not raise an exception await tts.stop_audio() - + # Verify that flush was called on the track track.flush.assert_called_once() diff --git a/getstream/plugins/kokoro/tts/tts.py b/getstream/plugins/kokoro/tts/tts.py index 8bc3d60b..8a74d9d8 100644 --- a/getstream/plugins/kokoro/tts/tts.py +++ b/getstream/plugins/kokoro/tts/tts.py @@ -2,9 +2,10 @@ import asyncio import logging +from collections.abc import AsyncIterator +from typing import List, Optional import numpy as np -from typing import AsyncIterator, List, Optional from getstream.plugins.common import TTS from getstream.video.rtc.audio_track import AudioStreamTrack @@ -31,7 +32,7 @@ def __init__( if KPipeline is None: raise ImportError( - "The 'kokoro' package is not installed. ``pip install kokoro`` first." + "The 'kokoro' package is not installed. ``pip install kokoro`` first.", ) self._pipeline = ( @@ -47,32 +48,32 @@ def __init__( def set_output_track(self, track: AudioStreamTrack) -> None: # noqa: D401 if track.framerate != self.sample_rate: raise TypeError( - f"Invalid framerate {track.framerate}, Kokoro requires {self.sample_rate} Hz" + f"Invalid framerate {track.framerate}, Kokoro requires {self.sample_rate} Hz", ) super().set_output_track(track) async def stream_audio(self, text: str, *_, **__) -> AsyncIterator[bytes]: # noqa: D401 loop = asyncio.get_event_loop() chunks: List[bytes] = await loop.run_in_executor( - None, lambda: list(self._generate_chunks(text)) + None, + lambda: list(self._generate_chunks(text)), ) return chunks async def stop_audio(self) -> None: - """ - Clears the queue and stops playing audio. - - """ + """Clears the queue and stops playing audio.""" try: await self.track.flush() return except Exception as e: logging.error(f"Error flushing audio track: {e}") - def _generate_chunks(self, text: str): for _gs, _ps, audio in self._pipeline( - text, voice=self.voice, speed=self.speed, split_pattern=r"\n+" + text, + voice=self.voice, + speed=self.speed, + split_pattern=r"\n+", ): if not isinstance(audio, np.ndarray): audio = np.asarray(audio) diff --git a/getstream/plugins/moonshine/stt/stt.py b/getstream/plugins/moonshine/stt/stt.py index c0bb7078..4b49f8b7 100644 --- a/getstream/plugins/moonshine/stt/stt.py +++ b/getstream/plugins/moonshine/stt/stt.py @@ -1,18 +1,19 @@ -import os import logging +import os import time -from typing import Dict, Any, Optional, Tuple, List +from typing import Any, Dict, List, Optional, Tuple + import numpy as np import soundfile as sf -from getstream.plugins.common import STT -from getstream.video.rtc.track_util import PcmData -from getstream.audio.utils import resample_audio from getstream.audio.pcm_utils import ( + log_audio_processing_info, pcm_to_numpy_array, validate_sample_rate_compatibility, - log_audio_processing_info, ) +from getstream.audio.utils import resample_audio +from getstream.plugins.common import STT +from getstream.video.rtc.track_util import PcmData logger = logging.getLogger(__name__) @@ -35,8 +36,7 @@ class MoonshineSTT(STT): - """ - Moonshine-based Speech-to-Text implementation. + """Moonshine-based Speech-to-Text implementation. Moonshine is a family of speech-to-text models optimized for fast and accurate automatic speech recognition (ASR) on resource-constrained devices. @@ -60,14 +60,14 @@ def __init__( min_audio_length_ms: int = 100, target_dbfs: float = -26.0, ): - """ - Initialize the Moonshine STT service. + """Initialize the Moonshine STT service. Args: model_name: Moonshine model to use ("moonshine/tiny" or "moonshine/base") sample_rate: Sample rate of the audio in Hz (default: 16000, Moonshine's native rate) min_audio_length_ms: Minimum audio length required for transcription target_dbfs: Target RMS level in dBFS for audio normalization (default: -26.0, Moonshine's optimal level) + """ super().__init__(sample_rate=sample_rate) @@ -75,14 +75,14 @@ def __init__( if not MOONSHINE_AVAILABLE: raise ImportError( "moonshine_onnx is not installed. " - "Please install it from GitHub: pip install 'useful-moonshine-onnx@git+https://github.com/usefulsensors/moonshine.git#subdirectory=moonshine-onnx'" + "Please install it from GitHub: pip install 'useful-moonshine-onnx@git+https://github.com/usefulsensors/moonshine.git#subdirectory=moonshine-onnx'", ) # Validate and normalize model name if model_name not in _SUPPORTED_MODELS: raise ValueError( f"Unknown Moonshine model '{model_name}'. " - f"Allowed values: {', '.join(_SUPPORTED_MODELS.keys())}" + f"Allowed values: {', '.join(_SUPPORTED_MODELS.keys())}", ) self.model_name = _SUPPORTED_MODELS[model_name] # Use canonical value self.min_audio_length_ms = min_audio_length_ms @@ -106,27 +106,27 @@ def __init__( logger.info("Moonshine model selected", extra={"checkpoint": self.model_name}) def _normalize_audio(self, audio_data: np.ndarray) -> np.ndarray: - """ - Normalize audio data to float32 in range [-1, 1]. + """Normalize audio data to float32 in range [-1, 1]. Args: audio_data: Input audio data as int16 Returns: Normalized audio data as float32 + """ # Convert int16 to float32 and normalize to [-1, 1] return audio_data.astype(np.float32) / 32768.0 def _rms_normalise(self, audio: np.ndarray) -> np.ndarray: - """ - Boost or attenuate audio to match target RMS in dBFS. + """Boost or attenuate audio to match target RMS in dBFS. Args: audio: Input audio data as int16 Returns: RMS-normalized audio data as int16 + """ # Calculate current RMS in dB rms = np.sqrt(np.mean(audio.astype(np.float32) ** 2)) @@ -153,16 +153,17 @@ def _rms_normalise(self, audio: np.ndarray) -> np.ndarray: return boosted.astype(np.int16) async def _transcribe_audio( - self, audio_data: np.ndarray + self, + audio_data: np.ndarray, ) -> Optional[Tuple[str, float]]: - """ - Transcribe audio data using Moonshine in-memory. + """Transcribe audio data using Moonshine in-memory. Args: audio_data: Audio data as float32 array Returns: Transcribed text and processing time, or None if transcription failed + """ try: # Check minimum audio length @@ -208,7 +209,8 @@ async def _transcribe_audio( import tempfile with tempfile.NamedTemporaryFile( - suffix=".wav", delete=False + suffix=".wav", + delete=False, ) as temp_file: temp_path = temp_file.name @@ -246,22 +248,21 @@ async def _transcribe_audio( }, ) return text, processing_time * 1000 - else: - logger.debug("Empty transcription result") - return None - else: - logger.debug("No transcription result") + logger.debug("Empty transcription result") return None + logger.debug("No transcription result") + return None except Exception as e: logger.error("Error during transcription", exc_info=e) return None async def _process_audio_impl( - self, pcm_data: PcmData, user_metadata: Optional[Dict[str, Any]] = None + self, + pcm_data: PcmData, + user_metadata: Optional[Dict[str, Any]] = None, ) -> Optional[List[Tuple[bool, str, Dict[str, Any]]]]: - """ - Process audio data through Moonshine for transcription. + """Process audio data through Moonshine for transcription. Moonshine operates in synchronous mode - it processes audio immediately and returns results to the base class for event emission. @@ -274,6 +275,7 @@ async def _process_audio_impl( List of tuples (is_final, text, metadata) representing transcription results, or None if no results are available. Moonshine returns final results only since it doesn't support streaming transcription. + """ if self._is_closed: logger.warning("Moonshine STT is closed, ignoring audio") @@ -291,12 +293,16 @@ async def _process_audio_impl( # Validate and resample if needed using shared utility validate_sample_rate_compatibility( - pcm_data.sample_rate, self.sample_rate, "Moonshine" + pcm_data.sample_rate, + self.sample_rate, + "Moonshine", ) if pcm_data.sample_rate != self.sample_rate: audio_array = resample_audio( - audio_array, pcm_data.sample_rate, self.sample_rate + audio_array, + pcm_data.sample_rate, + self.sample_rate, ).astype(np.int16) # Normalize audio for Moonshine @@ -335,8 +341,7 @@ async def _process_audio_impl( return None async def flush(self): - """ - Flush any remaining audio in the buffer and process it. + """Flush any remaining audio in the buffer and process it. Note: This is a no-op since we no longer buffer audio. """ diff --git a/getstream/plugins/moonshine/tests/test_stt.py b/getstream/plugins/moonshine/tests/test_stt.py index 4e429c64..41ebb8cd 100644 --- a/getstream/plugins/moonshine/tests/test_stt.py +++ b/getstream/plugins/moonshine/tests/test_stt.py @@ -1,11 +1,12 @@ -import pytest import asyncio -import numpy as np from unittest.mock import patch +import numpy as np +import pytest + from getstream.plugins.moonshine.stt import MoonshineSTT -from getstream.video.rtc.track_util import PcmData from getstream.plugins.test_utils import get_audio_asset, get_json_metadata +from getstream.video.rtc.track_util import PcmData # Skip all tests in this module if moonshine_onnx is not installed try: @@ -25,8 +26,7 @@ def transcribe(audio_path, model_name): # Simulate different responses based on model name if "base" in model_name: return ["This is a high quality transcription from the base model."] - else: - return ["This is a transcription from the tiny model."] + return ["This is a transcription from the tiny model."] @pytest.fixture @@ -76,7 +76,9 @@ def mia_audio_data(mia_mp3_path): # Return PCM data with the resampled rate return PcmData( - samples=pcm_samples, sample_rate=target_sample_rate, format="s16" + samples=pcm_samples, + sample_rate=target_sample_rate, + format="s16", ) except Exception: # Fall back to synthetic data if file loading fails @@ -223,7 +225,10 @@ async def test_moonshine_audio_resampling(): # Test resampling from 48kHz to 16kHz using the shared utility original_data = np.random.randint( - -1000, 1000, 48000, dtype=np.int16 + -1000, + 1000, + 48000, + dtype=np.int16, ) # 1 second at 48kHz resampled = resample_audio(original_data, 48000, 16000).astype(np.int16) @@ -267,7 +272,10 @@ async def mock_transcribe_audio(audio_data): # Create test audio audio_array = np.random.randint( - -1000, 1000, 8000, dtype=np.int16 + -1000, + 1000, + 8000, + dtype=np.int16, ) # 0.5 seconds pcm_data = PcmData(samples=audio_array, sample_rate=16000, format="s16") @@ -551,7 +559,7 @@ async def test_moonshine_with_mia_audio_mocked(mia_audio_data, mia_metadata): # Extract expected text from mia.json metadata expected_segments = mia_metadata.get("segments", []) expected_full_text = " ".join( - [segment["text"] for segment in expected_segments] + [segment["text"] for segment in expected_segments], ).strip() # Mock the transcribe function to return the expected text @@ -598,7 +606,9 @@ def on_error(error): # Verify metadata structure assert metadata["model_name"] == "moonshine/base" - assert metadata["confidence"] is None # Moonshine doesn't provide confidence scores + assert ( + metadata["confidence"] is None + ) # Moonshine doesn't provide confidence scores assert metadata["target_sample_rate"] == 16000 assert "processing_time_ms" in metadata assert "original_sample_rate" in metadata @@ -616,8 +626,7 @@ def on_error(error): # Integration test with real Moonshine (if available) @pytest.mark.asyncio async def test_moonshine_real_integration(mia_audio_data, mia_metadata): - """ - Integration test with the real Moonshine library using the mia.mp3 test file. + """Integration test with the real Moonshine library using the mia.mp3 test file. This test processes the mia.mp3 audio file and compares the transcription results with the expected content from mia.json metadata. @@ -629,19 +638,19 @@ async def test_moonshine_real_integration(mia_audio_data, mia_metadata): pytest.skip("Audio sample too short for meaningful integration test") print( - f"Testing with mia.mp3: {len(mia_audio_data.samples)} samples at {mia_audio_data.sample_rate}Hz" + f"Testing with mia.mp3: {len(mia_audio_data.samples)} samples at {mia_audio_data.sample_rate}Hz", ) print( - f"Audio duration: {len(mia_audio_data.samples) / mia_audio_data.sample_rate:.2f} seconds" + f"Audio duration: {len(mia_audio_data.samples) / mia_audio_data.sample_rate:.2f} seconds", ) print( - f"Audio range: {mia_audio_data.samples.min()} to {mia_audio_data.samples.max()}" + f"Audio range: {mia_audio_data.samples.min()} to {mia_audio_data.samples.max()}", ) # Extract expected text from mia.json metadata expected_segments = mia_metadata.get("segments", []) expected_full_text = " ".join( - [segment["text"] for segment in expected_segments] + [segment["text"] for segment in expected_segments], ).strip() expected_words = expected_full_text.lower().split() @@ -704,9 +713,9 @@ def on_error(error): print(f"Error {i + 1}: {error}") # We should either get transcripts or errors, but not silence - assert ( - len(transcripts) > 0 or len(errors) > 0 - ), "No transcripts or errors received" + assert len(transcripts) > 0 or len(errors) > 0, ( + "No transcripts or errors received" + ) # If we got transcripts, verify they contain reasonable content if transcripts: @@ -745,14 +754,14 @@ def on_error(error): print(f"Key words found: {found_key_words}") # We should find at least some key words from the story - assert ( - len(found_key_words) >= 2 - ), f"Expected to find at least 2 key words from {key_words}, but only found {found_key_words}" + assert len(found_key_words) >= 2, ( + f"Expected to find at least 2 key words from {key_words}, but only found {found_key_words}" + ) # Check that we got a reasonable amount of text - assert ( - len(actual_words) >= 10 - ), f"Expected at least 10 words, but got {len(actual_words)}: {combined_text}" + assert len(actual_words) >= 10, ( + f"Expected at least 10 words, but got {len(actual_words)}: {combined_text}" + ) # Verify metadata structure assert "processing_time_ms" in metadata diff --git a/getstream/plugins/openai/sts/realtime.py b/getstream/plugins/openai/sts/realtime.py index 53c8ddf8..331c392e 100644 --- a/getstream/plugins/openai/sts/realtime.py +++ b/getstream/plugins/openai/sts/realtime.py @@ -11,9 +11,8 @@ - Support for custom instructions and voice selection - Session management and updates -Example +Example: ------- - ```python from getstream.stream import Stream from getstream.plugins.sts.openai_realtime import OpenAIRealtime @@ -50,6 +49,7 @@ 3. Forwarding all events from OpenAI/Stream 4. Managing the connection lifecycle 5. Providing helper methods for common operations + """ from __future__ import annotations @@ -62,7 +62,6 @@ from getstream.video.call import Call from getstream.video.openai import ConnectionManagerWrapper - logger = logging.getLogger(__name__) @@ -83,8 +82,8 @@ def __init__( model: Model ID to use when connecting. voice: Optional voice selection passed to the session. instructions: Optional system instructions passed to the session. - """ + """ super().__init__() self.api_key = api_key or os.getenv("OPENAI_API_KEY") @@ -109,7 +108,6 @@ async def connect( β€’ every event coming from OpenAI/Stream, verbatim β€’ ``disconnected`` – after normal closure or error """ - if self._is_connected: raise RuntimeError("AI agent already connected") @@ -140,7 +138,7 @@ async def connect( self._emit_connected_event() logger.info( - f"Connected OpenAI agent to call {call.call_type}/{call.id} using model {self.model}" + f"Connected OpenAI agent to call {call.call_type}/{call.id} using model {self.model}", ) return self._connection @@ -161,7 +159,7 @@ async def send_function_call_output(self, tool_call_id: str, output: str): "type": "function_call_output", "call_id": tool_call_id, "output": output, - } + }, ) async def send_user_message(self, text: str): @@ -177,9 +175,9 @@ async def send_user_message(self, text: str): { "type": "input_text", "text": text, - } + }, ], - } + }, ) await self.request_assistant_response() diff --git a/getstream/plugins/openai/tests/test_openai_realtime.py b/getstream/plugins/openai/tests/test_openai_realtime.py index af5b957c..ed020988 100644 --- a/getstream/plugins/openai/tests/test_openai_realtime.py +++ b/getstream/plugins/openai/tests/test_openai_realtime.py @@ -1,7 +1,7 @@ +import asyncio from unittest.mock import AsyncMock, MagicMock import pytest -import asyncio from getstream.plugins.openai.sts import OpenAIRealtime from getstream.video.call import Call @@ -51,7 +51,9 @@ async def _on_connected(event): # Ensure call.connect_openai invoked with the right params mock_call.connect_openai.assert_called_once_with( - "key123", "assistant", model="gpt-4o-realtime-preview" + "key123", + "assistant", + model="gpt-4o-realtime-preview", ) @@ -63,7 +65,7 @@ async def test_update_session_calls_underlying_client(mock_call, mock_connection await sts.update_session(voice="nova", temperature=0.5) mock_connection.session.update.assert_awaited_once_with( - session={"voice": "nova", "temperature": 0.5} + session={"voice": "nova", "temperature": 0.5}, ) @@ -97,7 +99,7 @@ async def test_send_function_call_output(mock_call, mock_connection): "type": "function_call_output", "call_id": "tool-123", "output": '{"ok": true}', - } + }, ) diff --git a/getstream/plugins/silero/tests/test_bench.py b/getstream/plugins/silero/tests/test_bench.py index be753c52..47b23b49 100644 --- a/getstream/plugins/silero/tests/test_bench.py +++ b/getstream/plugins/silero/tests/test_bench.py @@ -1,23 +1,23 @@ -""" -Benchmark tests for Silero VAD performance. +"""Benchmark tests for Silero VAD performance. This module provides tests for benchmarking the Silero VAD implementation, processing 10 seconds of audio and reporting RTF (Real-Time Factor) and other metrics. """ +import logging import time + import numpy as np -import logging import pytest import soundfile as sf + from getstream.plugins.silero.vad import SileroVAD -from getstream.video.rtc.track_util import PcmData from getstream.plugins.test_utils import get_audio_asset +from getstream.video.rtc.track_util import PcmData async def benchmark_vad(use_onnx=False, device="cpu"): - """ - Benchmark the Silero VAD implementation. + """Benchmark the Silero VAD implementation. Args: use_onnx: Whether to use ONNX runtime @@ -25,6 +25,7 @@ async def benchmark_vad(use_onnx=False, device="cpu"): Returns: Dictionary with benchmark results + """ logging.basicConfig(level=logging.ERROR) # Suppress logs during benchmark @@ -56,7 +57,7 @@ async def benchmark_vad(use_onnx=False, device="cpu"): for formant, amplitude in [(600, 1.0), (1200, 0.5), (2400, 0.2)]: audio_data += (amplitude * 32767 * np.sin(2 * np.pi * formant * t)).astype( - np.int16 + np.int16, ) # Ensure we have 10 seconds of audio @@ -87,7 +88,7 @@ def on_audio(event, user=None): # Process audio chunks for chunk in chunks: await vad.process_audio( - PcmData(samples=chunk, sample_rate=sample_rate, format="s16") + PcmData(samples=chunk, sample_rate=sample_rate, format="s16"), ) # Flush any remaining speech @@ -161,6 +162,6 @@ async def test_vad_benchmark_onnx(): print(f" Speech segments detected: {results['speech_segments']}") # Verify that the ONNX implementation is reasonably efficient - assert ( - results["rtf"] < 2.0 - ), f"ONNX VAD performance too slow: RTF = {results['rtf']:.3f}" + assert results["rtf"] < 2.0, ( + f"ONNX VAD performance too slow: RTF = {results['rtf']:.3f}" + ) diff --git a/getstream/plugins/silero/tests/test_performance.py b/getstream/plugins/silero/tests/test_performance.py index f92ebf81..936dfa92 100644 --- a/getstream/plugins/silero/tests/test_performance.py +++ b/getstream/plugins/silero/tests/test_performance.py @@ -1,14 +1,13 @@ -""" -Performance tests for Silero VAD implementation. +"""Performance tests for Silero VAD implementation. This module contains performance tests for the Silero VAD implementation, measuring CPU time to ensure that changes don't significantly impact performance. """ -import time import asyncio -import numpy as np +import time +import numpy as np import pytest from getstream.plugins.silero.vad import SileroVAD @@ -46,7 +45,10 @@ async def test_performance(): end = start + segment_length if end <= total_samples: audio_data[start:end] = np.random.randint( - -10000, 10000, size=(end - start), dtype=np.int16 + -10000, + 10000, + size=(end - start), + dtype=np.int16, ) # Create PCM data @@ -71,7 +73,7 @@ def on_audio(event): elapsed_time = time.time() - start_time print( - f"Processing time: {elapsed_time:.3f} seconds for {duration_sec} seconds of audio" + f"Processing time: {elapsed_time:.3f} seconds for {duration_sec} seconds of audio", ) print(f"Detected {len(audio_events)} audio events") diff --git a/getstream/plugins/silero/tests/test_vad.py b/getstream/plugins/silero/tests/test_vad.py index 599200a6..0a4b2b5e 100644 --- a/getstream/plugins/silero/tests/test_vad.py +++ b/getstream/plugins/silero/tests/test_vad.py @@ -1,16 +1,16 @@ -import os -import pytest import asyncio import logging -import numpy as np -import soundfile as sf +import os import tempfile +import numpy as np +import pytest +import soundfile as sf import torchaudio from getstream.plugins.silero.vad import SileroVAD -from getstream.video.rtc.track_util import PcmData from getstream.plugins.test_utils import get_audio_asset, get_json_metadata +from getstream.video.rtc.track_util import PcmData # Setup logging for the test logging.basicConfig(level=logging.INFO) @@ -84,7 +84,11 @@ def vad_setup(): async def process_audio_file( - vad, data, original_sample_rate, detected_segments, partial_segments=None + vad, + data, + original_sample_rate, + detected_segments, + partial_segments=None, ): """Process audio data with VAD and collect detected speech segments.""" @@ -96,7 +100,7 @@ async def on_audio(event): samples = event.audio_data if event.audio_data is not None else b"" detected_segments.append({"duration": duration, "bytes": len(samples)}) logger.info( - f"Detected speech segment: {duration:.2f} seconds ({len(samples)} bytes)" + f"Detected speech segment: {duration:.2f} seconds ({len(samples)} bytes)", ) # Add handler for partial events if tracking them @@ -107,11 +111,9 @@ async def on_partial(event): # Extract duration and samples from the event duration = event.duration_ms / 1000.0 if event.duration_ms else 0.0 samples = event.audio_data if event.audio_data is not None else b"" - partial_segments.append( - {"duration": duration, "bytes": len(samples)} - ) + partial_segments.append({"duration": duration, "bytes": len(samples)}) logger.info( - f"Partial speech data: {duration:.2f} seconds ({len(samples)} bytes)" + f"Partial speech data: {duration:.2f} seconds ({len(samples)} bytes)", ) # Resample if needed @@ -126,7 +128,7 @@ async def on_partial(event): # Process the audio data await vad.process_audio( - PcmData(samples=pcm_samples, sample_rate=vad.sample_rate, format="s16") + PcmData(samples=pcm_samples, sample_rate=vad.sample_rate, format="s16"), ) # Ensure we flush any remaining speech @@ -154,7 +156,7 @@ async def on_audio(event): samples = event.audio_data if event.audio_data is not None else b"" detected_segments.append({"duration": duration, "bytes": len(samples)}) logger.info( - f"Detected speech segment: {duration:.2f} seconds ({len(samples)} bytes)" + f"Detected speech segment: {duration:.2f} seconds ({len(samples)} bytes)", ) # Add handler for partial events if tracking them @@ -165,11 +167,9 @@ async def on_partial(event): # Extract duration and samples from the event duration = event.duration_ms / 1000.0 if event.duration_ms else 0.0 samples = event.audio_data if event.audio_data is not None else b"" - partial_segments.append( - {"duration": duration, "bytes": len(samples)} - ) + partial_segments.append({"duration": duration, "bytes": len(samples)}) logger.info( - f"Partial speech data: {duration:.2f} seconds ({len(samples)} bytes)" + f"Partial speech data: {duration:.2f} seconds ({len(samples)} bytes)", ) # Resample if needed @@ -182,7 +182,7 @@ async def on_partial(event): # Calculate chunk size in samples for the target sample rate chunk_samples = int(vad.sample_rate * chunk_size_ms / 1000) logger.info( - f"Processing audio in {chunk_size_ms}ms chunks ({chunk_samples} samples per chunk)" + f"Processing audio in {chunk_size_ms}ms chunks ({chunk_samples} samples per chunk)", ) # Process audio in chunks @@ -201,7 +201,7 @@ async def on_partial(event): # Process the chunk await vad.process_audio( - PcmData(samples=chunk_pcm, sample_rate=vad.sample_rate, format="s16") + PcmData(samples=chunk_pcm, sample_rate=vad.sample_rate, format="s16"), ) # Add a small delay to simulate real-time processing @@ -215,25 +215,28 @@ async def on_partial(event): def verify_detected_speech( - detected_segments, expected_duration=None, expected_turns=None, tolerance=0.1 + detected_segments, + expected_duration=None, + expected_turns=None, + tolerance=0.1, ): - """ - Verify that the detected speech matches expectations. + """Verify that the detected speech matches expectations. Args: detected_segments: List of detected speech segments expected_duration: Expected total duration of speech in seconds expected_turns: Expected number of speech turns tolerance: Tolerance for duration validation (e.g., 0.1 = Β±10%) + """ # Verify that speech was detected assert len(detected_segments) > 0, "No speech segments were detected" # Verify number of turns if specified if expected_turns is not None: - assert ( - len(detected_segments) == expected_turns - ), f"Expected {expected_turns} speech turns, got {len(detected_segments)}" + assert len(detected_segments) == expected_turns, ( + f"Expected {expected_turns} speech turns, got {len(detected_segments)}" + ) # Calculate total detected duration total_detected_duration = sum(segment["duration"] for segment in detected_segments) @@ -247,7 +250,7 @@ def verify_detected_speech( logger.info(f"Expected speech duration: {expected_duration:.2f} seconds") logger.info(f"Detected speech duration: {total_detected_duration:.2f} seconds") logger.info( - f"Tolerance: Β±{tolerance * 100:.0f}% ({min_expected:.2f} - {max_expected:.2f}s)" + f"Tolerance: Β±{tolerance * 100:.0f}% ({min_expected:.2f} - {max_expected:.2f}s)", ) if len(detected_segments) > 0: @@ -257,27 +260,27 @@ def verify_detected_speech( logger.info(f"Speech segment {i + 1}: {segment['duration']:.2f}s") # Verify that the duration is within expected range - assert ( - min_expected <= total_detected_duration <= max_expected - ), f"Expected speech duration {expected_duration}s (Β±{tolerance * 100:.0f}%), got {total_detected_duration}s" + assert min_expected <= total_detected_duration <= max_expected, ( + f"Expected speech duration {expected_duration}s (Β±{tolerance * 100:.0f}%), got {total_detected_duration}s" + ) def verify_partial_events(partial_segments, detected_segments): - """ - Verify that partial events were received before final speech events. + """Verify that partial events were received before final speech events. Args: partial_segments: List of partial speech events detected_segments: List of final speech events + """ # Verify that at least one partial event was observed before each final audio event assert len(partial_segments) > 0, "No partial speech events were detected" # Each detected segment should have at least one corresponding partial event # For simplicity, we just check that we have at least one partial event per detected segment - assert ( - len(partial_segments) >= len(detected_segments) - ), f"Expected at least {len(detected_segments)} partial events, got {len(partial_segments)}" + assert len(partial_segments) >= len(detected_segments), ( + f"Expected at least {len(detected_segments)} partial events, got {len(partial_segments)}" + ) @pytest.mark.asyncio @@ -299,7 +302,11 @@ async def test_silero_vad_speech_detection(audio_data, mia_metadata, vad_setup): # Process the entire audio file await process_audio_file( - vad, data, sample_rate, detected_segments, partial_segments + vad, + data, + sample_rate, + detected_segments, + partial_segments, ) # Verify that speech was detected (without duration validation) @@ -314,8 +321,7 @@ async def test_silero_vad_speech_detection(audio_data, mia_metadata, vad_setup): @pytest.mark.asyncio async def test_streaming_chunks_20ms(audio_data, mia_metadata): - """ - Test that streaming chunks gives the same results as processing the entire file. + """Test that streaming chunks gives the same results as processing the entire file. This test verifies that the VAD works in streaming mode. """ # Create a new VAD for this test using the same parameters as vad_setup @@ -334,7 +340,12 @@ async def test_streaming_chunks_20ms(audio_data, mia_metadata): # Process the audio in small chunks to simulate streaming await process_audio_in_chunks( - vad, data, sample_rate, detected_segments, partial_segments, chunk_size_ms=20 + vad, + data, + sample_rate, + detected_segments, + partial_segments, + chunk_size_ms=20, ) # Verify that speech was detected (without duration validation) @@ -373,7 +384,7 @@ async def on_audio(event): samples = event.audio_data if event.audio_data is not None else b"" detected_segments.append({"duration": duration, "bytes": len(samples)}) logger.info( - f"Detected speech segment: {duration:.2f} seconds ({len(samples)} bytes)" + f"Detected speech segment: {duration:.2f} seconds ({len(samples)} bytes)", ) @vad.on("partial") @@ -383,12 +394,12 @@ async def on_partial(event): samples = event.audio_data if event.audio_data is not None else b"" partial_segments.append({"duration": duration, "bytes": len(samples)}) logger.info( - f"Partial speech data: {duration:.2f} seconds ({len(samples)} bytes)" + f"Partial speech data: {duration:.2f} seconds ({len(samples)} bytes)", ) # Process the audio data as bytes await vad.process_audio( - PcmData(samples=pcm_bytes, sample_rate=vad.sample_rate, format="s16") + PcmData(samples=pcm_bytes, sample_rate=vad.sample_rate, format="s16"), ) # Ensure we flush any remaining speech @@ -420,7 +431,8 @@ async def test_silence_no_turns(): # Create 5 seconds of silence (zeros) silence_duration_seconds = 5 silence_samples = np.zeros( - vad.sample_rate * silence_duration_seconds, dtype=np.int16 + vad.sample_rate * silence_duration_seconds, + dtype=np.int16, ) # Flag to track if audio event was fired @@ -432,7 +444,7 @@ async def on_audio(event): nonlocal audio_event_fired audio_event_fired = True logger.info( - f"Audio event detected on silence! Duration: {event.duration_ms/1000.0:.2f}s" + f"Audio event detected on silence! Duration: {event.duration_ms / 1000.0:.2f}s", ) @vad.on("partial") @@ -440,7 +452,7 @@ async def on_partial(event): nonlocal partial_event_fired partial_event_fired = True logger.info( - f"Partial event detected on silence! Duration: {event.duration_ms/1000.0:.2f}s" + f"Partial event detected on silence! Duration: {event.duration_ms / 1000.0:.2f}s", ) # Process the silence in chunks to simulate streaming @@ -448,7 +460,7 @@ async def on_partial(event): for i in range(0, len(silence_samples), chunk_size): chunk = silence_samples[i : i + chunk_size] await vad.process_audio( - PcmData(samples=chunk, sample_rate=vad.sample_rate, format="s16") + PcmData(samples=chunk, sample_rate=vad.sample_rate, format="s16"), ) # Ensure we flush any remaining audio @@ -495,22 +507,18 @@ def on_audio(event, user=None): detected_speech.append(event) duration = event.duration_ms / 1000.0 if event.duration_ms else 0.0 samples = event.audio_data if event.audio_data is not None else b"" - logger.info( - f"Audio event: {duration:.2f}s ({len(samples)} samples)" - ) + logger.info(f"Audio event: {duration:.2f}s ({len(samples)} samples)") @vad.on("partial") def on_partial(event, user=None): partial_events.append(event) duration = event.duration_ms / 1000.0 if event.duration_ms else 0.0 samples = event.audio_data if event.audio_data is not None else b"" - logger.info( - f"Partial event: {duration:.2f}s ({len(samples)} samples)" - ) + logger.info(f"Partial event: {duration:.2f}s ({len(samples)} samples)") # Process the audio data await vad.process_audio( - PcmData(samples=audio_data, sample_rate=vad.sample_rate, format="s16") + PcmData(samples=audio_data, sample_rate=vad.sample_rate, format="s16"), ) # Ensure we flush any remaining speech @@ -525,9 +533,9 @@ def on_partial(event, user=None): # Verify that partial events were received before the final audio event assert len(partial_events) > 0, "No partial events detected" - assert ( - len(partial_events) >= len(detected_speech) - ), f"Expected at least {len(detected_speech)} partial events, got {len(partial_events)}" + assert len(partial_events) >= len(detected_speech), ( + f"Expected at least {len(detected_speech)} partial events, got {len(partial_events)}" + ) logger.info(f"Detected {len(partial_events)} partial events") # Clean up @@ -565,7 +573,7 @@ def on_partial(event, user=None): # Process the silent audio data await vad.process_audio( - PcmData(samples=silence, sample_rate=vad.sample_rate, format="s16") + PcmData(samples=silence, sample_rate=vad.sample_rate, format="s16"), ) # Ensure we flush any remaining buffer @@ -613,13 +621,13 @@ def on_partial_16k(event, user=None): # Load 16 kHz audio file audio_path_16k = get_audio_asset("formant_speech_16k.wav") audio_data_16k, sample_rate_16k = sf.read(audio_path_16k, dtype="int16") - assert ( - sample_rate_16k == 16000 - ), f"Expected sample rate 16000, got {sample_rate_16k}" + assert sample_rate_16k == 16000, ( + f"Expected sample rate 16000, got {sample_rate_16k}" + ) # Process the 16 kHz audio await vad_16k.process_audio( - PcmData(samples=audio_data_16k, sample_rate=16000, format="s16") + PcmData(samples=audio_data_16k, sample_rate=16000, format="s16"), ) await vad_16k.flush() await asyncio.sleep(0.1) @@ -647,29 +655,29 @@ def on_partial_48k(event, user=None): # Load 48 kHz audio file audio_path_48k = get_audio_asset("formant_speech_48k.wav") audio_data_48k, sample_rate_48k = sf.read(audio_path_48k, dtype="int16") - assert ( - sample_rate_48k == 48000 - ), f"Expected sample rate 48000, got {sample_rate_48k}" + assert sample_rate_48k == 48000, ( + f"Expected sample rate 48000, got {sample_rate_48k}" + ) # Process the 48 kHz audio await vad_48k.process_audio( - PcmData(samples=audio_data_48k, sample_rate=48000, format="s16") + PcmData(samples=audio_data_48k, sample_rate=48000, format="s16"), ) await vad_48k.flush() await asyncio.sleep(0.1) # Verify both detected speech segments and partial events - assert ( - len(detected_speech_16k) > 0 - ), "No speech segments detected in 16 kHz audio" - assert ( - len(detected_speech_48k) > 0 - ), "No speech segments detected in 48 kHz audio" + assert len(detected_speech_16k) > 0, ( + "No speech segments detected in 16 kHz audio" + ) + assert len(detected_speech_48k) > 0, ( + "No speech segments detected in 48 kHz audio" + ) logger.info( - f"Detected {len(detected_speech_16k)} speech segments in 16 kHz audio" + f"Detected {len(detected_speech_16k)} speech segments in 16 kHz audio", ) logger.info( - f"Detected {len(detected_speech_48k)} speech segments in 48 kHz audio" + f"Detected {len(detected_speech_48k)} speech segments in 48 kHz audio", ) # Verify partial events @@ -677,10 +685,10 @@ def on_partial_48k(event, user=None): assert len(partial_events_48k) > 0, "No partial events detected in 48 kHz audio" # Don't require specific counts, just verify partials were emitted logger.info( - f"Detected {len(partial_events_16k)} partial events in 16 kHz audio" + f"Detected {len(partial_events_16k)} partial events in 16 kHz audio", ) logger.info( - f"Detected {len(partial_events_48k)} partial events in 48 kHz audio" + f"Detected {len(partial_events_48k)} partial events in 48 kHz audio", ) # Clean up @@ -689,8 +697,7 @@ def on_partial_48k(event, user=None): @pytest.mark.asyncio async def test_bytearray_efficiency(self): - """ - Test that the bytearray implementation is memory efficient. + """Test that the bytearray implementation is memory efficient. This is a basic test to ensure the implementation avoids O(nΒ²) memory growth. """ import tracemalloc @@ -709,7 +716,10 @@ async def test_bytearray_efficiency(self): # Shorter duration to avoid timeout duration_sec = 2 samples = np.random.randint( - -10000, 10000, size=16000 * duration_sec, dtype=np.int16 + -10000, + 10000, + size=16000 * duration_sec, + dtype=np.int16, ) # Start memory tracking @@ -738,7 +748,7 @@ async def mock_is_speech(frame): chunk = samples[i : i + chunk_size] # Process the audio await vad.process_audio( - PcmData(samples=chunk, sample_rate=16000, format="s16") + PcmData(samples=chunk, sample_rate=16000, format="s16"), ) # Take memory snapshot after processing @@ -758,7 +768,7 @@ async def mock_is_speech(frame): ratio = total_memory_mb / audio_size_mb if audio_size_mb > 0 else 0 logger.info( - f"Memory growth: {total_memory_mb:.2f} MB for {audio_size_mb:.2f} MB of audio (ratio: {ratio:.2f})" + f"Memory growth: {total_memory_mb:.2f} MB for {audio_size_mb:.2f} MB of audio (ratio: {ratio:.2f})", ) # The ratio should be close to 1.0 for efficient buffering (no excessive copying) @@ -774,8 +784,7 @@ async def mock_is_speech(frame): @pytest.mark.asyncio async def test_cuda_fallback(self): - """ - Test that the Silero VAD falls back to CPU when CUDA is not available. + """Test that the Silero VAD falls back to CPU when CUDA is not available. This test will be skipped if CUDA is available. """ import torch @@ -792,9 +801,9 @@ async def test_cuda_fallback(self): ) # Check that the device fell back to CPU - assert ( - vad.device_name == "cpu" - ), "Failed to fall back to CPU when CUDA unavailable" + assert vad.device_name == "cpu", ( + "Failed to fall back to CPU when CUDA unavailable" + ) assert vad.device.type == "cpu", "Device is not CPU after fallback" # Create a short silence for inference @@ -808,9 +817,7 @@ async def test_cuda_fallback(self): @pytest.mark.asyncio async def test_flush_api(self): - """ - Test that flush() properly emits a speech turn even if the speech is not complete. - """ + """Test that flush() properly emits a speech turn even if the speech is not complete.""" # Initialize the VAD with longer padding to ensure speech doesn't end naturally vad = SileroVAD( sample_rate=16000, @@ -839,7 +846,7 @@ def on_audio(event, user=None): # Process half the audio to get speech started but not completed await vad.process_audio( - PcmData(samples=half_audio, sample_rate=sample_rate, format="s16") + PcmData(samples=half_audio, sample_rate=sample_rate, format="s16"), ) # Mark that we're about to flush @@ -850,19 +857,16 @@ def on_audio(event, user=None): # Verify that at least one speech segment was emitted due to the flush assert len(detected_speech) > 0, "No speech segments detected after flush" - assert detected_speech[-1][ - "from_flush" - ], "Last speech segment was not triggered by flush" + assert detected_speech[-1]["from_flush"], ( + "Last speech segment was not triggered by flush" + ) # Clean up await vad.close() @pytest.mark.asyncio async def test_onnx_fallback(self): - """ - Test that the Silero VAD falls back to PyTorch when ONNX is not available or fails. - """ - + """Test that the Silero VAD falls back to PyTorch when ONNX is not available or fails.""" # Initialize VAD with ONNX requested vad = SileroVAD( sample_rate=16000, diff --git a/getstream/plugins/silero/vad/__init__.py b/getstream/plugins/silero/vad/__init__.py index 2e73c085..3094e0c4 100644 --- a/getstream/plugins/silero/vad/__init__.py +++ b/getstream/plugins/silero/vad/__init__.py @@ -1,5 +1,4 @@ -""" -Silero Voice Activity Detection (VAD) implementation. +"""Silero Voice Activity Detection (VAD) implementation. This module provides a high-performance speech detection implementation using the Silero VAD model (https://github.com/snakers4/silero-vad). diff --git a/getstream/plugins/silero/vad/vad.py b/getstream/plugins/silero/vad/vad.py index a0707773..4f8bc37f 100644 --- a/getstream/plugins/silero/vad/vad.py +++ b/getstream/plugins/silero/vad/vad.py @@ -1,15 +1,16 @@ import logging -import torch -import numpy as np -import warnings import time -from typing import Dict, Any, Optional -from getstream.plugins.common import VAD -from getstream.video.rtc.track_util import PcmData +import warnings +from typing import Any, Dict, Optional + +import numpy as np +import torch + from getstream.audio.utils import resample_audio -from getstream.plugins.common.events import VADAudioEvent +from getstream.plugins.common import VAD from getstream.plugins.common.event_utils import register_global_event - +from getstream.plugins.common.events import VADAudioEvent +from getstream.video.rtc.track_util import PcmData try: import onnxruntime as ort @@ -23,8 +24,7 @@ class SileroVAD(VAD): - """ - Voice Activity Detection implementation using Silero VAD model. + """Voice Activity Detection implementation using Silero VAD model. This class implements the VAD interface using the Silero VAD model, which is a high-performance speech detection model. @@ -53,8 +53,7 @@ def __init__( partial_frames: int = 10, use_onnx: bool = False, ): - """ - Initialize the Silero VAD. + """Initialize the Silero VAD. Args: sample_rate: Audio sample rate in Hz expected for input @@ -69,6 +68,7 @@ def __init__( device: Device to run the model on ("cpu", "cuda", "cuda:0", etc.) partial_frames: Number of frames to process before emitting a "partial" event use_onnx: Whether to use ONNX runtime for inference instead of PyTorch + """ # Issue deprecation warning for frame_size if frame_size is not None: @@ -101,13 +101,13 @@ def __init__( if self.model_rate == 16000 and self.window_samples != 512: logger.warning( f"Adjusting window_samples from {self.window_samples} to 512, " - "which is required by Silero VAD at 16kHz" + "which is required by Silero VAD at 16kHz", ) self.window_samples = 512 elif self.model_rate == 8000 and self.window_samples != 256: logger.warning( f"Adjusting window_samples from {self.window_samples} to 256, " - "which is required by Silero VAD at 8kHz" + "which is required by Silero VAD at 8kHz", ) self.window_samples = 256 @@ -162,7 +162,7 @@ def _load_torch_model(self) -> None: logger.info(f"Using device: {self.device}") except Exception as e: logger.warning( - f"Failed to use device {self.device_name}: {e}, falling back to CPU" + f"Failed to use device {self.device_name}: {e}, falling back to CPU", ) self.device = torch.device("cpu") self.device_name = "cpu" @@ -202,7 +202,7 @@ def _load_onnx_model(self) -> None: else: if self.device_name.startswith("cuda"): logger.warning( - "CUDA requested but not available for ONNX, falling back to CPU" + "CUDA requested but not available for ONNX, falling back to CPU", ) providers = ["CPUExecutionProvider"] self.device_name = "cpu" @@ -236,7 +236,9 @@ def _load_onnx_model(self) -> None: # Create ONNX session self.onnx_session = ort.InferenceSession( - tmp.name, sess_options=session_options, providers=providers + tmp.name, + sess_options=session_options, + providers=providers, ) # Get input name @@ -246,7 +248,7 @@ def _load_onnx_model(self) -> None: except Exception as e: logger.warning( - f"Failed to load ONNX model: {e}, falling back to PyTorch model" + f"Failed to load ONNX model: {e}, falling back to PyTorch model", ) self.use_onnx = False self._load_torch_model() @@ -258,14 +260,14 @@ def reset_states(self) -> None: self._resampled = np.array([], dtype=np.float32) async def is_speech(self, frame: PcmData) -> float: - """ - Detect speech in an audio frame using the Silero VAD model. + """Detect speech in an audio frame using the Silero VAD model. Args: frame: PcmData object containing audio samples Returns: Probability (0.0 to 1.0) that the frame contains speech + """ try: # Convert PCM data to float32 in range [-1.0, 1.0] @@ -277,7 +279,9 @@ async def is_speech(self, frame: PcmData) -> float: # Resample the accumulated raw buffer to model rate if needed if frame.sample_rate != self.model_rate: resampled_new = resample_audio( - self._raw_buffer, frame.sample_rate, self.model_rate + self._raw_buffer, + frame.sample_rate, + self.model_rate, ) # Reset raw buffer after resampling self._raw_buffer = np.array([], dtype=np.float32) @@ -353,15 +357,15 @@ async def is_speech(self, frame: PcmData) -> float: return 0.0 async def _flush_speech_buffer(self, user: Optional[Dict[str, Any]] = None) -> None: - """ - Flush the accumulated speech buffer if it meets minimum length requirements. + """Flush the accumulated speech buffer if it meets minimum length requirements. Args: user: User metadata to include with emitted audio events + """ # Calculate min speech frames based on ms min_speech_frames = int( - self.min_speech_ms * self.sample_rate / 1000 / self.frame_size + self.min_speech_ms * self.sample_rate / 1000 / self.frame_size, ) # Convert bytearray to numpy array @@ -385,7 +389,7 @@ async def _flush_speech_buffer(self, user: Optional[Dict[str, Any]] = None) -> N duration_ms=duration_ms, speech_probability=0.8, # Default value, could be enhanced frame_count=len(speech_data) // self.frame_size, - user_metadata=user + user_metadata=user, ) register_global_event(audio_event) self.emit("audio", audio_event) # Structured event @@ -403,11 +407,11 @@ async def reset(self) -> None: self.reset_states() async def flush(self, user=None) -> None: - """ - Flush accumulated speech buffer and emit any pending audio events. + """Flush accumulated speech buffer and emit any pending audio events. Args: user: User metadata to include with emitted audio events + """ await super().flush(user) # Reset buffer after flushing diff --git a/getstream/plugins/test_utils.py b/getstream/plugins/test_utils.py index ce4d3bbc..cfcbf602 100644 --- a/getstream/plugins/test_utils.py +++ b/getstream/plugins/test_utils.py @@ -1,5 +1,4 @@ -""" -Test utility functions for GetStream plugins. +"""Test utility functions for GetStream plugins. This module provides utility functions for accessing test assets and other resources from the main test directory. @@ -18,10 +17,10 @@ and get_json_metadata() functions provided in this module. """ -import os import json +import os import sys -from typing import Dict, Any +from typing import Any, Dict # Load environment variables from .env files try: @@ -47,13 +46,13 @@ try: # Add project root to path to ensure imports work project_root = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ) if project_root not in sys.path: sys.path.insert(0, project_root) # Import fixtures from tests module - from tests.fixtures import client, call, get_user, shared_call + from tests.fixtures import call, client, get_user, shared_call # Re-export fixtures __all__ = [ @@ -72,14 +71,14 @@ def get_asset_path(file_name: str) -> str: - """ - Get the path to an asset file in the tests/assets directory. + """Get the path to an asset file in the tests/assets directory. Args: file_name: The name of the asset file Returns: The full path to the asset file + """ # Find the project root (where tests/assets is located) project_root = _find_project_root() @@ -89,39 +88,39 @@ def get_asset_path(file_name: str) -> str: def get_audio_asset(file_name: str) -> str: - """ - Get the path to an audio asset file. + """Get the path to an audio asset file. Args: file_name: The name of the audio file (e.g., "mia.mp3") Returns: The full path to the audio file + """ return get_asset_path(file_name) def get_json_metadata(file_name: str) -> Dict[str, Any]: - """ - Load and return the contents of a JSON metadata file. + """Load and return the contents of a JSON metadata file. Args: file_name: The name of the JSON file (e.g., "mia.json") Returns: The parsed JSON content as a dictionary + """ json_path = get_asset_path(file_name) - with open(json_path, "r") as f: + with open(json_path) as f: return json.load(f) def _find_project_root() -> str: - """ - Find the project root directory by looking for the 'tests/assets' directory. + """Find the project root directory by looking for the 'tests/assets' directory. Returns: The path to the project root directory + """ # Start from the current file's directory current_dir = os.path.dirname(os.path.abspath(__file__)) @@ -141,7 +140,7 @@ def _find_project_root() -> str: # If not found by walking up, try a fixed path from the current file # This assumes the module is at getstream/plugins/test_utils.py possible_root = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ) if os.path.isdir(os.path.join(possible_root, "tests", "assets")): return possible_root @@ -153,5 +152,5 @@ def _find_project_root() -> str: raise FileNotFoundError( "Could not find the project root directory with 'tests/assets'. " - "Make sure you're running tests from the project root or a subdirectory." + "Make sure you're running tests from the project root or a subdirectory.", ) diff --git a/getstream/rate_limit.py b/getstream/rate_limit.py index 0217e8e5..3840005c 100644 --- a/getstream/rate_limit.py +++ b/getstream/rate_limit.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from datetime import datetime, timezone from typing import Any, Optional + import httpx @@ -26,7 +27,8 @@ def get_first_nonempty_value(header: str) -> Any: limit_value = int(get_first_nonempty_value(limit)) remaining_value = int(get_first_nonempty_value(remaining)) reset_value = datetime.fromtimestamp( - float(get_first_nonempty_value(reset)), timezone.utc + float(get_first_nonempty_value(reset)), + timezone.utc, ) return RateLimitInfo( limit=limit_value, diff --git a/getstream/stream.py b/getstream/stream.py index 18db5b1b..3fc082f8 100644 --- a/getstream/stream.py +++ b/getstream/stream.py @@ -1,5 +1,5 @@ -from functools import cached_property import time +from functools import cached_property from typing import List import jwt @@ -13,13 +13,14 @@ from getstream.utils import validate_and_clean_url from getstream.video.client import VideoClient - BASE_URL = "https://chat.stream-io-api.com/" class Settings(BaseSettings): model_config = ConfigDict( - env_file=".env", env_file_encoding="utf-8", extra="ignore" + env_file=".env", + env_file_encoding="utf-8", + extra="ignore", ) STREAM_API_KEY: str = "test_key" @@ -28,8 +29,7 @@ class Settings(BaseSettings): class Stream(CommonClient): - """ - A class used to represent a Stream client. + """A class used to represent a Stream client. Contains methods to interact with Video and Chat modules of Stream API. """ @@ -59,8 +59,7 @@ def __init__( @classmethod def from_env(cls, timeout: float = 6.0) -> "Stream": - """ - Construct a StreamClient by loading its credentials and base_url + """Construct a StreamClient by loading its credentials and base_url from environment variables (via our pydantic Settings). """ settings = Settings() @@ -74,10 +73,7 @@ def from_env(cls, timeout: float = 6.0) -> "Stream": @cached_property def video(self): - """ - Video stream client. - - """ + """Video stream client.""" return VideoClient( api_key=self.api_key, base_url=self.base_url, @@ -88,10 +84,7 @@ def video(self): @cached_property def chat(self): - """ - Chat stream client. - - """ + """Chat stream client.""" return ChatClient( api_key=self.api_key, base_url=self.base_url, @@ -102,10 +95,7 @@ def chat(self): @cached_property def moderation(self): - """ - Moderation stream client. - - """ + """Moderation stream client.""" return ModerationClient( api_key=self.api_key, base_url=self.base_url, @@ -115,8 +105,7 @@ def moderation(self): ) def upsert_users(self, *users: UserRequest): - """ - Creates or updates users. This method performs an "upsert" operation, + """Creates or updates users. This method performs an "upsert" operation, where it checks if each user already exists and updates their information if they do, or creates a new user entry if they do not. """ @@ -128,14 +117,13 @@ def create_token( user_id: str, expiration: int = None, ): - """ - Generates a token for a given user, with an optional expiration time. + """Generates a token for a given user, with an optional expiration time. - Args: + Args: user_id (str): The unique identifier of the user for whom the token is being created. expiration (int, optional): The duration in seconds after which the token should expire. - Returns: + Returns: str: A token generated for the user which may include encoded data about the user and the token's expiration time. @@ -145,10 +133,10 @@ def create_token( >>> token = client.create_token("alice", expiration=3600) # doctest: +ELLIPSIS - Notes: + Notes: - Expiration is handled as UNIX time (seconds since epoch), and it is recommended to provide this in UTC. - """ + """ if user_id is None or user_id == "": raise ValueError("user_id is required") @@ -162,7 +150,10 @@ def create_call_token( expiration: int = None, ): return self._create_token( - user_id=user_id, call_cids=call_cids, role=role, expiration=expiration + user_id=user_id, + call_cids=call_cids, + role=role, + expiration=expiration, ) def _create_token( diff --git a/getstream/stream_response.py b/getstream/stream_response.py index 5c462049..296bc716 100644 --- a/getstream/stream_response.py +++ b/getstream/stream_response.py @@ -1,5 +1,5 @@ -from typing import Any, Optional, Generic import typing +from typing import Any, Generic, Optional import httpx diff --git a/getstream/utils/__init__.py b/getstream/utils/__init__.py index 9d8d7c53..ed070531 100644 --- a/getstream/utils/__init__.py +++ b/getstream/utils/__init__.py @@ -1,11 +1,9 @@ import json -from typing import Dict, List, Optional, Union -from urllib.parse import quote -from datetime import datetime -from datetime import timezone -from urllib.parse import urlparse, urlunparse import logging import sys +from datetime import datetime, timezone +from typing import Dict, List, Optional, Union +from urllib.parse import quote, urlparse, urlunparse from .event_emitter import StreamAsyncIOEventEmitter @@ -13,8 +11,7 @@ def validate_and_clean_url(url): - """ - Validates a given URL and removes any trailing slashes. + """Validates a given URL and removes any trailing slashes. Args: url (str): The URL to validate and clean. @@ -24,8 +21,8 @@ def validate_and_clean_url(url): Raises: ValueError: If the URL is not valid. - """ + """ parsed_url = urlparse(url) if parsed_url.scheme not in ("http", "https"): raise ValueError("Provided URL is not a valid HTTP URL.") @@ -36,14 +33,14 @@ def validate_and_clean_url(url): def encode_datetime(date: Optional[datetime]) -> Optional[str]: - """ - Encodes a datetime object into an ISO 8601 formatted string. + """Encodes a datetime object into an ISO 8601 formatted string. Args: date (Optional[datetime]): The datetime object to encode. Returns: Optional[str]: The ISO 8601 string representation of the datetime, or None if input is None. + """ if date is None: return None @@ -61,8 +58,7 @@ def datetime_from_unix_ns( ] ], ) -> Optional[Union[datetime, Dict[str, datetime], List[datetime]]]: - """ - Converts unix timestamp(s) (nanoseconds since epoch) to datetime object(s). + """Converts unix timestamp(s) (nanoseconds since epoch) to datetime object(s). Can handle single values, dictionaries, or lists of values. Args: @@ -71,6 +67,7 @@ def datetime_from_unix_ns( Returns: Datetime object(s) or None if input is None. + """ if ts is None: return None @@ -91,8 +88,7 @@ def datetime_from_unix_ns( def build_query_param(**kwargs): - """ - Constructs a dictionary of query parameters from keyword arguments. + """Constructs a dictionary of query parameters from keyword arguments. This function handles various data types: - JSON-serializable objects with a `to_json` method will be serialized using that method. @@ -105,6 +101,7 @@ def build_query_param(**kwargs): Returns: dict: A dictionary where keys are parameter names and values are URL-ready strings. + """ params = {} for key, value in kwargs.items(): @@ -126,8 +123,7 @@ def build_query_param(**kwargs): def build_body_dict(**kwargs): - """ - Constructs a dictionary for the body of a request, handling nested structures. + """Constructs a dictionary for the body of a request, handling nested structures. If an object has a `to_dict` method, it calls this method to serialize the object. It handles nested dictionaries and lists recursively. @@ -136,30 +132,30 @@ def build_body_dict(**kwargs): Returns: dict: A dictionary with keys corresponding to kwargs keys and values processed, potentially recursively. + """ def handle_value(value): if hasattr(value, "to_dict") and callable(value.to_dict): return value.to_dict() - elif isinstance(value, dict): + if isinstance(value, dict): return {k: handle_value(v) for k, v in value.items()} - elif isinstance(value, list): + if isinstance(value, list): return [handle_value(v) for v in value] - else: - return value + return value data = {key: handle_value(value) for key, value in kwargs.items()} return data def configure_logging(level=None, handler=None, format=None): - """ - Configure logging for the Stream library. + """Configure logging for the Stream library. Args: level: The logging level to use (default: logging.INFO) handler: A custom handler to use (default: StreamHandler) format: A custom format string (default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + """ # Get the root logger for the library logger = logging.getLogger("getstream") diff --git a/getstream/utils/event_emitter.py b/getstream/utils/event_emitter.py index af398362..556b5a82 100644 --- a/getstream/utils/event_emitter.py +++ b/getstream/utils/event_emitter.py @@ -1,12 +1,12 @@ import asyncio +import inspect import logging + from pyee.asyncio import AsyncIOEventEmitter -import inspect class StreamAsyncIOEventEmitter(AsyncIOEventEmitter): - """ - AsyncIOEventEmitter with wildcard pattern support for event names. + """AsyncIOEventEmitter with wildcard pattern support for event names. Supports patterns like: - '*' - matches all events @@ -27,7 +27,6 @@ def emit(self, event, *args, **kwargs): ``asyncio.create_task`` and errors are routed through the loop's exception handler (falling back to logging if none is available). """ - # First, attempt to emit the event via the parent implementation. We # need to *safely* handle the special ``error`` event which pyee will # raise as a ``PyeeError`` if no explicit ``'error'`` listeners are @@ -45,7 +44,7 @@ def emit(self, event, *args, **kwargs): if isinstance(pyee_exc, PyeeError) and event == "error": # Always swallow; treat it as normal when using wildcard-only handling. logging.getLogger(__name__).debug( - "Suppressed PyeeError for unhandled 'error' event" + "Suppressed PyeeError for unhandled 'error' event", ) result = False else: @@ -120,17 +119,16 @@ def _matches_pattern(self, event, pattern): """Check if an event matches a wildcard pattern""" if pattern == "*": return True - elif pattern.endswith("**"): + if pattern.endswith("**"): # Match multiple levels: 'api.**' matches 'api.user.login' prefix = pattern[:-2] return event.startswith(prefix) - elif pattern.endswith("*"): + if pattern.endswith("*"): # Match single level: 'api.*' matches 'api.user' but not 'api.user.login' prefix = pattern[:-1] remainder = event[len(prefix) :] if event.startswith(prefix) else "" return event.startswith(prefix) and "." not in remainder - else: - return event == pattern + return event == pattern @staticmethod def _report_listener_error(loop, exc, event, args, kwargs): @@ -147,11 +145,13 @@ def _report_listener_error(loop, exc, event, args, kwargs): "event": event, "args": args, "kwargs": kwargs, - } + }, ) else: logging.getLogger(__name__).error( - "Exception in wildcard event handler: %s", exc, exc_info=exc + "Exception in wildcard event handler: %s", + exc, + exc_info=exc, ) @staticmethod @@ -162,5 +162,7 @@ def _handle_task_result(task: asyncio.Task): exc = task.exception() if exc: logging.getLogger(__name__).error( - "Exception in async wildcard listener: %s", exc, exc_info=exc + "Exception in async wildcard listener: %s", + exc, + exc_info=exc, ) diff --git a/getstream/utils/retry.py b/getstream/utils/retry.py index a53189de..f9096506 100644 --- a/getstream/utils/retry.py +++ b/getstream/utils/retry.py @@ -1,5 +1,5 @@ import logging -from typing import Callable, Optional, Any +from typing import Any, Callable, Optional logger = logging.getLogger("getstream.utils.retry") @@ -11,7 +11,7 @@ def __init__(self, original_exception: Exception, attempts: int): self.original_exception = original_exception self.attempts = attempts super().__init__( - f"All {attempts} retry attempts exhausted. Last error: {original_exception}" + f"All {attempts} retry attempts exhausted. Last error: {original_exception}", ) @@ -23,6 +23,7 @@ def default_backoff(attempt: int) -> float: Returns: The number of seconds to sleep + """ raise NotImplementedError("Retry functionality has been removed") @@ -35,6 +36,7 @@ def default_can_retry(exception: Exception) -> bool: Returns: True if the exception can be retried, False otherwise + """ raise NotImplementedError("Retry functionality has been removed") @@ -61,7 +63,10 @@ def __enter__(self) -> "Retry": raise NotImplementedError("Retry functionality has been removed") def __exit__( - self, exc_type: Any, exc_val: Optional[Exception], exc_tb: Any + self, + exc_type: Any, + exc_val: Optional[Exception], + exc_tb: Any, ) -> bool: """Exit the context manager.""" raise NotImplementedError("Retry functionality has been removed") diff --git a/getstream/video/call.py b/getstream/video/call.py index 33bc2fb5..cf744514 100644 --- a/getstream/video/call.py +++ b/getstream/video/call.py @@ -5,7 +5,11 @@ class Call: def __init__( - self, client, call_type: str, call_id: str = None, custom_data: Dict = None + self, + client, + call_type: str, + call_id: str = None, + custom_data: Dict = None, ): self.id = call_id self.call_type = call_type @@ -17,12 +21,15 @@ def _sync_from_response(self, data): self.custom_data = data.call.custom def connect_openai( - self, openai_api_key, agent_user_id, model="gpt-4o-realtime-preview" + self, + openai_api_key, + agent_user_id, + model="gpt-4o-realtime-preview", ): - from .openai import get_openai_realtime_client, ConnectionManagerWrapper + from .openai import ConnectionManagerWrapper, get_openai_realtime_client client = get_openai_realtime_client(openai_api_key, self.client.base_url) - token = self.client.stream.create_token(agent_user_id) + token = self.client.stream_audio.create_token(agent_user_id) connection_manager = client.beta.realtime.connect( extra_query={ "call_type": self.call_type, @@ -96,7 +103,9 @@ def get_or_create( def block_user(self, user_id: str) -> StreamResponse[BlockUserResponse]: response = self.client.block_user( - type=self.call_type, id=self.id, user_id=user_id + type=self.call_type, + id=self.id, + user_id=user_id, ) self._sync_from_response(response.data) return response @@ -113,7 +122,11 @@ def send_call_event( user: Optional[UserRequest] = None, ) -> StreamResponse[SendCallEventResponse]: response = self.client.send_call_event( - type=self.call_type, id=self.id, user_id=user_id, custom=custom, user=user + type=self.call_type, + id=self.id, + user_id=user_id, + custom=custom, + user=user, ) self._sync_from_response(response.data) return response @@ -223,7 +236,10 @@ def query_call_participants( def video_pin(self, session_id: str, user_id: str) -> StreamResponse[PinResponse]: response = self.client.video_pin( - type=self.call_type, id=self.id, session_id=session_id, user_id=user_id + type=self.call_type, + id=self.id, + session_id=session_id, + user_id=user_id, ) self._sync_from_response(response.data) return response @@ -234,19 +250,25 @@ def list_recordings(self) -> StreamResponse[ListRecordingsResponse]: return response def get_call_report( - self, session_id: Optional[str] = None + self, + session_id: Optional[str] = None, ) -> StreamResponse[GetCallReportResponse]: response = self.client.get_call_report( - type=self.call_type, id=self.id, session_id=session_id + type=self.call_type, + id=self.id, + session_id=session_id, ) self._sync_from_response(response.data) return response def start_rtmp_broadcasts( - self, broadcasts: List[RTMPBroadcastRequest] + self, + broadcasts: List[RTMPBroadcastRequest], ) -> StreamResponse[StartRTMPBroadcastsResponse]: response = self.client.start_rtmp_broadcasts( - type=self.call_type, id=self.id, broadcasts=broadcasts + type=self.call_type, + id=self.id, + broadcasts=broadcasts, ) self._sync_from_response(response.data) return response @@ -290,7 +312,8 @@ def start_closed_captions( return response def start_frame_recording( - self, recording_external_storage: Optional[str] = None + self, + recording_external_storage: Optional[str] = None, ) -> StreamResponse[StartFrameRecordingResponse]: response = self.client.start_frame_recording( type=self.call_type, @@ -301,7 +324,8 @@ def start_frame_recording( return response def start_recording( - self, recording_external_storage: Optional[str] = None + self, + recording_external_storage: Optional[str] = None, ) -> StreamResponse[StartRecordingResponse]: response = self.client.start_recording( type=self.call_type, @@ -333,10 +357,13 @@ def stop_hls_broadcasting(self) -> StreamResponse[StopHLSBroadcastingResponse]: return response def stop_closed_captions( - self, stop_transcription: Optional[bool] = None + self, + stop_transcription: Optional[bool] = None, ) -> StreamResponse[StopClosedCaptionsResponse]: response = self.client.stop_closed_captions( - type=self.call_type, id=self.id, stop_transcription=stop_transcription + type=self.call_type, + id=self.id, + stop_transcription=stop_transcription, ) self._sync_from_response(response.data) return response @@ -372,10 +399,13 @@ def stop_recording(self) -> StreamResponse[StopRecordingResponse]: return response def stop_transcription( - self, stop_closed_captions: Optional[bool] = None + self, + stop_closed_captions: Optional[bool] = None, ) -> StreamResponse[StopTranscriptionResponse]: response = self.client.stop_transcription( - type=self.call_type, id=self.id, stop_closed_captions=stop_closed_captions + type=self.call_type, + id=self.id, + stop_closed_captions=stop_closed_captions, ) self._sync_from_response(response.data) return response @@ -387,16 +417,23 @@ def list_transcriptions(self) -> StreamResponse[ListTranscriptionsResponse]: def unblock_user(self, user_id: str) -> StreamResponse[UnblockUserResponse]: response = self.client.unblock_user( - type=self.call_type, id=self.id, user_id=user_id + type=self.call_type, + id=self.id, + user_id=user_id, ) self._sync_from_response(response.data) return response def video_unpin( - self, session_id: str, user_id: str + self, + session_id: str, + user_id: str, ) -> StreamResponse[UnpinResponse]: response = self.client.video_unpin( - type=self.call_type, id=self.id, session_id=session_id, user_id=user_id + type=self.call_type, + id=self.id, + session_id=session_id, + user_id=user_id, ) self._sync_from_response(response.data) return response @@ -418,19 +455,29 @@ def update_user_permissions( return response def delete_recording( - self, session: str, filename: str + self, + session: str, + filename: str, ) -> StreamResponse[DeleteRecordingResponse]: response = self.client.delete_recording( - type=self.call_type, id=self.id, session=session, filename=filename + type=self.call_type, + id=self.id, + session=session, + filename=filename, ) self._sync_from_response(response.data) return response def delete_transcription( - self, session: str, filename: str + self, + session: str, + filename: str, ) -> StreamResponse[DeleteTranscriptionResponse]: response = self.client.delete_transcription( - type=self.call_type, id=self.id, session=session, filename=filename + type=self.call_type, + id=self.id, + session=session, + filename=filename, ) self._sync_from_response(response.data) return response diff --git a/getstream/video/client.py b/getstream/video/client.py index 8abff784..16225787 100644 --- a/getstream/video/client.py +++ b/getstream/video/client.py @@ -1,11 +1,10 @@ -from getstream.video.rest_client import VideoRestClient from getstream.video.call import Call +from getstream.video.rest_client import VideoRestClient class VideoClient(VideoRestClient): def __init__(self, api_key: str, base_url, token, timeout, stream): - """ - Initializes VideoClient with BaseClient instance + """Initializes VideoClient with BaseClient instance :param api_key: A string representing the client's API key :param base_url: A string representing the base uniform resource locator :param token: A string instance representing the client's token diff --git a/getstream/video/openai.py b/getstream/video/openai.py index 10f7bffd..50146d5c 100644 --- a/getstream/video/openai.py +++ b/getstream/video/openai.py @@ -4,9 +4,7 @@ class ConnectionManagerWrapper: - """ - Wrapper for OpenAI's connection manager that adds error checking and better error messages. - """ + """Wrapper for OpenAI's connection manager that adds error checking and better error messages.""" def __init__(self, connection_manager, call_type, call_id): self.connection_manager = connection_manager @@ -28,7 +26,7 @@ def __aiter__(self): async def __anext__(self): if not hasattr(self, "connection"): raise RuntimeError( - "Connection not established. Use 'async with' to establish the connection first." + "Connection not established. Use 'async with' to establish the connection first.", ) try: @@ -40,7 +38,9 @@ async def __anext__(self): if hasattr(event, "type") and event.type == "error": try: error_json = json.dumps( - event.__dict__ if hasattr(event, "__dict__") else str(event) + event.__dict__ + if hasattr(event, "__dict__") + else str(event), ) raise Exception(error_json) except TypeError: @@ -74,7 +74,7 @@ def import_openai(): except ImportError: raise ImportError( "failed to import openai, make sure to install this package like this: " - "getstream[openai-realtime]" + "getstream[openai-realtime]", ) return openai @@ -112,7 +112,7 @@ def patched_connection_manager_prepare_url(self): if not client_attr: raise RuntimeError( "Failed to patch OpenAI client: could not find client attribute on connection manager. " - "The OpenAI SDK structure may have changed." + "The OpenAI SDK structure may have changed.", ) client = getattr(self, client_attr) @@ -120,15 +120,14 @@ def patched_connection_manager_prepare_url(self): if not hasattr(client, "websocket_base_url"): raise RuntimeError( "Failed to patch OpenAI client: client does not have websocket_base_url attribute. " - "The OpenAI SDK structure may have changed." + "The OpenAI SDK structure may have changed.", ) return httpx.URL(client.websocket_base_url) async def patched_recv(self): - """ - Receive the next message from the connection and parses it into a `RealtimeServerEvent` object. + """Receive the next message from the connection and parses it into a `RealtimeServerEvent` object. Canceling this method is safe. There's no risk of losing data. """ @@ -136,7 +135,7 @@ async def patched_recv(self): if not hasattr(self, "recv_bytes") or not hasattr(self, "parse_event"): raise RuntimeError( "Failed to patch OpenAI client: connection object does not have expected methods. " - "The OpenAI SDK structure may have changed." + "The OpenAI SDK structure may have changed.", ) try: @@ -146,7 +145,7 @@ async def patched_recv(self): # Create a simple class for type checking, but warn that the SDK structure might have changed warnings.warn( "Could not import ErrorEvent from openai.types.beta.realtime.error_event. " - "The OpenAI SDK structure may have changed." + "The OpenAI SDK structure may have changed.", ) class ErrorEvent: @@ -164,7 +163,7 @@ def patch_realtime_connect(client): if not hasattr(client, "beta") or not hasattr(client.beta, "realtime"): raise RuntimeError( "Failed to patch OpenAI client: client does not have beta.realtime. " - "The OpenAI SDK structure may have changed." + "The OpenAI SDK structure may have changed.", ) # Try to patch the AsyncRealtimeConnection.recv method directly @@ -186,7 +185,7 @@ def patch_realtime_connect(client): except ImportError as e: warnings.warn( f"Could not directly patch AsyncRealtimeConnection.recv: {str(e)}. " - "Will attempt to patch at runtime." + "Will attempt to patch at runtime.", ) # Try to patch the connection manager's _prepare_url method @@ -205,7 +204,7 @@ def patch_realtime_connect(client): if not prepare_url_candidates: raise ImportError( - "AsyncRealtimeConnectionManager does not have a method that prepares URLs" + "AsyncRealtimeConnectionManager does not have a method that prepares URLs", ) # Use the first candidate @@ -215,7 +214,8 @@ def patch_realtime_connect(client): # Save the original method AsyncRealtimeConnectionManager._original_prepare_url = getattr( - AsyncRealtimeConnectionManager, prepare_url_method + AsyncRealtimeConnectionManager, + prepare_url_method, ) # Replace with our patched version @@ -227,7 +227,7 @@ def patch_realtime_connect(client): except ImportError as e: warnings.warn( f"Could not directly patch AsyncRealtimeConnectionManager._prepare_url: {str(e)}. " - "Will attempt to patch at runtime." + "Will attempt to patch at runtime.", ) # Monkey patch the __aenter__ method of AsyncRealtimeConnectionManager to patch the connection's recv method @@ -247,7 +247,8 @@ async def patched_aenter(self): # Patch the connection's recv method if it hasn't been patched already if hasattr(connection, "recv") and not hasattr( - connection, "_original_recv" + connection, + "_original_recv", ): connection._original_recv = connection.recv connection.recv = types.MethodType(patched_recv, connection) @@ -259,19 +260,19 @@ async def patched_aenter(self): except (ImportError, AttributeError) as e: warnings.warn( f"Could not patch AsyncRealtimeConnectionManager.__aenter__: {str(e)}. " - "The recv method may not be patched correctly." + "The recv method may not be patched correctly.", ) def dict_to_class(dictionary): - """ - Convert a dictionary to a StreamEvent object with a nice string representation. + """Convert a dictionary to a StreamEvent object with a nice string representation. Args: dictionary: The dictionary to convert. Returns: A StreamEvent object with properties from the dictionary. + """ class StreamEvent: @@ -294,7 +295,7 @@ def __repr__(self): # Convert snake_case to CamelCase subparts = part.split("_") camel_parts.append( - "".join(subpart.capitalize() for subpart in subparts) + "".join(subpart.capitalize() for subpart in subparts), ) else: camel_parts.append(part.capitalize()) diff --git a/getstream/video/rest_client.py b/getstream/video/rest_client.py index 47684459..b22e2ee2 100644 --- a/getstream/video/rest_client.py +++ b/getstream/video/rest_client.py @@ -2,13 +2,12 @@ from getstream.base import BaseClient from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.utils import build_query_param, build_body_dict +from getstream.utils import build_body_dict, build_query_param class VideoRestClient(BaseClient): def __init__(self, api_key: str, base_url: str, timeout: float, token: str): - """ - Initializes VideoClient with BaseClient instance + """Initializes VideoClient with BaseClient instance :param api_key: A string representing the client's API key :param base_url: A string representing the base uniform resource locator :param timeout: A number representing the time limit for a request @@ -67,7 +66,9 @@ def query_call_members( ) return self.post( - "/api/v2/video/call/members", QueryCallMembersResponse, json=json + "/api/v2/video/call/members", + QueryCallMembersResponse, + json=json, ) def query_call_stats( @@ -98,7 +99,10 @@ def get_call( video: Optional[bool] = None, ) -> StreamResponse[GetCallResponse]: query_params = build_query_param( - members_limit=members_limit, ring=ring, notify=notify, video=video + members_limit=members_limit, + ring=ring, + notify=notify, + video=video, ) path_params = { "type": type, @@ -125,7 +129,9 @@ def update_call( "id": id, } json = build_body_dict( - starts_at=starts_at, custom=custom, settings_override=settings_override + starts_at=starts_at, + custom=custom, + settings_override=settings_override, ) return self.patch( @@ -165,7 +171,10 @@ def get_or_create_call( ) def block_user( - self, type: str, id: str, user_id: str + self, + type: str, + id: str, + user_id: str, ) -> StreamResponse[BlockUserResponse]: path_params = { "type": type, @@ -181,7 +190,10 @@ def block_user( ) def delete_call( - self, type: str, id: str, hard: Optional[bool] = None + self, + type: str, + id: str, + hard: Optional[bool] = None, ) -> StreamResponse[DeleteCallResponse]: path_params = { "type": type, @@ -303,7 +315,8 @@ def update_call_members( "id": id, } json = build_body_dict( - remove_members=remove_members, update_members=update_members + remove_members=remove_members, + update_members=update_members, ) return self.post( @@ -371,7 +384,11 @@ def query_call_participants( ) def video_pin( - self, type: str, id: str, session_id: str, user_id: str + self, + type: str, + id: str, + session_id: str, + user_id: str, ) -> StreamResponse[PinResponse]: path_params = { "type": type, @@ -387,7 +404,9 @@ def video_pin( ) def list_recordings( - self, type: str, id: str + self, + type: str, + id: str, ) -> StreamResponse[ListRecordingsResponse]: path_params = { "type": type, @@ -401,7 +420,10 @@ def list_recordings( ) def get_call_report( - self, type: str, id: str, session_id: Optional[str] = None + self, + type: str, + id: str, + session_id: Optional[str] = None, ) -> StreamResponse[GetCallReportResponse]: query_params = build_query_param(session_id=session_id) path_params = { @@ -417,7 +439,10 @@ def get_call_report( ) def start_rtmp_broadcasts( - self, type: str, id: str, broadcasts: List[RTMPBroadcastRequest] + self, + type: str, + id: str, + broadcasts: List[RTMPBroadcastRequest], ) -> StreamResponse[StartRTMPBroadcastsResponse]: path_params = { "type": type, @@ -433,7 +458,9 @@ def start_rtmp_broadcasts( ) def stop_all_rtmp_broadcasts( - self, type: str, id: str + self, + type: str, + id: str, ) -> StreamResponse[StopAllRTMPBroadcastsResponse]: path_params = { "type": type, @@ -467,7 +494,9 @@ def stop_rtmp_broadcast( ) def start_hls_broadcasting( - self, type: str, id: str + self, + type: str, + id: str, ) -> StreamResponse[StartHLSBroadcastingResponse]: path_params = { "type": type, @@ -506,7 +535,10 @@ def start_closed_captions( ) def start_frame_recording( - self, type: str, id: str, recording_external_storage: Optional[str] = None + self, + type: str, + id: str, + recording_external_storage: Optional[str] = None, ) -> StreamResponse[StartFrameRecordingResponse]: path_params = { "type": type, @@ -522,7 +554,10 @@ def start_frame_recording( ) def start_recording( - self, type: str, id: str, recording_external_storage: Optional[str] = None + self, + type: str, + id: str, + recording_external_storage: Optional[str] = None, ) -> StreamResponse[StartRecordingResponse]: path_params = { "type": type, @@ -563,7 +598,9 @@ def start_transcription( ) def stop_hls_broadcasting( - self, type: str, id: str + self, + type: str, + id: str, ) -> StreamResponse[StopHLSBroadcastingResponse]: path_params = { "type": type, @@ -577,7 +614,10 @@ def stop_hls_broadcasting( ) def stop_closed_captions( - self, type: str, id: str, stop_transcription: Optional[bool] = None + self, + type: str, + id: str, + stop_transcription: Optional[bool] = None, ) -> StreamResponse[StopClosedCaptionsResponse]: path_params = { "type": type, @@ -593,7 +633,9 @@ def stop_closed_captions( ) def stop_frame_recording( - self, type: str, id: str + self, + type: str, + id: str, ) -> StreamResponse[StopFrameRecordingResponse]: path_params = { "type": type, @@ -636,7 +678,9 @@ def stop_live( ) def stop_recording( - self, type: str, id: str + self, + type: str, + id: str, ) -> StreamResponse[StopRecordingResponse]: path_params = { "type": type, @@ -650,7 +694,10 @@ def stop_recording( ) def stop_transcription( - self, type: str, id: str, stop_closed_captions: Optional[bool] = None + self, + type: str, + id: str, + stop_closed_captions: Optional[bool] = None, ) -> StreamResponse[StopTranscriptionResponse]: path_params = { "type": type, @@ -666,7 +713,9 @@ def stop_transcription( ) def list_transcriptions( - self, type: str, id: str + self, + type: str, + id: str, ) -> StreamResponse[ListTranscriptionsResponse]: path_params = { "type": type, @@ -680,7 +729,10 @@ def list_transcriptions( ) def unblock_user( - self, type: str, id: str, user_id: str + self, + type: str, + id: str, + user_id: str, ) -> StreamResponse[UnblockUserResponse]: path_params = { "type": type, @@ -696,7 +748,11 @@ def unblock_user( ) def video_unpin( - self, type: str, id: str, session_id: str, user_id: str + self, + type: str, + id: str, + session_id: str, + user_id: str, ) -> StreamResponse[UnpinResponse]: path_params = { "type": type, @@ -737,7 +793,11 @@ def update_user_permissions( ) def delete_recording( - self, type: str, id: str, session: str, filename: str + self, + type: str, + id: str, + session: str, + filename: str, ) -> StreamResponse[DeleteRecordingResponse]: path_params = { "type": type, @@ -753,7 +813,11 @@ def delete_recording( ) def delete_transcription( - self, type: str, id: str, session: str, filename: str + self, + type: str, + id: str, + session: str, + filename: str, ) -> StreamResponse[DeleteTranscriptionResponse]: path_params = { "type": type, @@ -813,7 +877,9 @@ def delete_call_type(self, name: str) -> StreamResponse[Response]: } return self.delete( - "/api/v2/video/calltypes/{name}", Response, path_params=path_params + "/api/v2/video/calltypes/{name}", + Response, + path_params=path_params, ) def get_call_type(self, name: str) -> StreamResponse[GetCallTypeResponse]: @@ -864,5 +930,7 @@ def query_aggregate_call_stats( json = build_body_dict(_from=_from, to=to, report_types=report_types) return self.post( - "/api/v2/video/stats", QueryAggregateCallStatsResponse, json=json + "/api/v2/video/stats", + QueryAggregateCallStatsResponse, + json=json, ) diff --git a/getstream/video/rtc/__init__.py b/getstream/video/rtc/__init__.py index e07e0bdf..1fb8ba3a 100644 --- a/getstream/video/rtc/__init__.py +++ b/getstream/video/rtc/__init__.py @@ -1,21 +1,20 @@ import logging -from typing import Optional from getstream.video.call import Call +from getstream.video.rtc.connection_manager import ConnectionManager +from getstream.video.rtc.connection_utils import join_call_coordinator_request from getstream.video.rtc.location_discovery import ( - HTTPHintLocationDiscovery, - HEADER_CLOUDFRONT_POP, FALLBACK_LOCATION_NAME, + HEADER_CLOUDFRONT_POP, STREAM_PROD_URL, + HTTPHintLocationDiscovery, ) from getstream.video.rtc.models import ( + Credentials, JoinCallRequest, JoinCallResponse, ServerCredentials, - Credentials, ) -from getstream.video.rtc.connection_utils import join_call_coordinator_request -from getstream.video.rtc.connection_manager import ConnectionManager logger = logging.getLogger(__name__) @@ -25,18 +24,18 @@ # before throwing, suggest the user to install the `webrtc` optional dependency raise ImportError( "The `webrtc` optional dependency is required to use the `getstream.video.rtc` module. " - "Please install it using the following command: `pip install getstream[webrtc]`" + "Please install it using the following command: `pip install getstream[webrtc]`", ) logger.debug(f"loaded aiortc {aiortc.__version__} correctly") async def discover_location(): - """ - Discover the closest location based on CloudFront pop headers. + """Discover the closest location based on CloudFront pop headers. Returns: str: The 3-character location code (e.g. "IAD") + """ logger.info("Discovering location") discovery = HTTPHintLocationDiscovery(logger=logger) @@ -45,10 +44,12 @@ async def discover_location(): async def join( - call: Call, user_id: Optional[str] = None, create=True, **kwargs + call: Call, + user_id: str = None, + create=True, + **kwargs, ) -> ConnectionManager: - """ - Join a call. This method will: + """Join a call. This method will: - discover the best location - join the call (or create it if needed) - setup the peer connection @@ -62,6 +63,7 @@ async def join( Returns: A ConnectionManager object that can be used as a context manager + """ # Return ConnectionManager instance that handles everything internally # when used as an async context manager and async iterator diff --git a/getstream/video/rtc/audio_track.py b/getstream/video/rtc/audio_track.py index efd633c2..b54fb21f 100644 --- a/getstream/video/rtc/audio_track.py +++ b/getstream/video/rtc/audio_track.py @@ -1,11 +1,11 @@ -import time import asyncio +import fractions import logging +import time import aiortc from av import AudioFrame from av.frame import Frame -import fractions logger = logging.getLogger(__name__) @@ -14,16 +14,20 @@ class AudioStreamTrack(aiortc.mediastreams.MediaStreamTrack): kind = "audio" def __init__( - self, framerate=8000, stereo=False, format="s16", max_queue_size=10000 + self, + framerate=8000, + stereo=False, + format="s16", + max_queue_size=10000, ): - """ - Initialize an AudioStreamTrack that reads data from a queue. + """Initialize an AudioStreamTrack that reads data from a queue. Args: framerate: Sample rate in Hz (default: 8000) stereo: Whether to use stereo output (default: False) format: Audio format (default: "s16") max_queue_size: Maximum number of frames to keep in queue (default: 100) + """ super().__init__() self.framerate = framerate @@ -51,11 +55,11 @@ def __init__( self._pending_data = bytearray() async def write(self, data): - """ - Add audio data to the queue. + """Add audio data to the queue. Args: data: Audio data bytes to be played + """ # Check if queue is getting too large and trim if necessary if self._queue.qsize() >= self.max_queue_size: @@ -82,9 +86,7 @@ async def write(self, data): ) async def flush(self) -> None: - """ - Clear any pending audio from the internal queue and buffer so playback stops immediately. - """ + """Clear any pending audio from the internal queue and buffer so playback stops immediately.""" # Drain queue cleared = 0 while not self._queue.empty(): @@ -99,8 +101,7 @@ async def flush(self) -> None: logger.debug("Flushed audio queue", extra={"cleared_items": cleared}) async def recv(self) -> Frame: - """ - Receive the next audio frame. + """Receive the next audio frame. If queue has data, use that; otherwise return silence. """ if self.readyState != "live": @@ -174,19 +175,25 @@ async def recv(self) -> Frame: # Use a variable size frame to handle more than 20ms actual_samples = len(data_to_play) // bytes_per_sample frame = AudioFrame( - format=self.format, layout=self.layout, samples=actual_samples + format=self.format, + layout=self.layout, + samples=actual_samples, ) else: # For real data, use fixed size and store excess frame = AudioFrame( - format=self.format, layout=self.layout, samples=samples + format=self.format, + layout=self.layout, + samples=samples, ) self._pending_data = data_to_play[bytes_per_frame:] data_to_play = data_to_play[:bytes_per_frame] else: # Standard 20ms frame frame = AudioFrame( - format=self.format, layout=self.layout, samples=samples + format=self.format, + layout=self.layout, + samples=samples, ) # Update the frame with the data we have diff --git a/getstream/video/rtc/connection_manager.py b/getstream/video/rtc/connection_manager.py index a78c6eb1..527e9b72 100644 --- a/getstream/video/rtc/connection_manager.py +++ b/getstream/video/rtc/connection_manager.py @@ -2,39 +2,38 @@ import json import logging import uuid -from typing import Optional, Dict, Any +from typing import Any, Dict, Optional import aioice import aiortc from twirp.context import Context from getstream.utils import StreamAsyncIOEventEmitter -from getstream.video.rtc.coordinator.ws import StreamAPIWS -from getstream.video.rtc.pb.stream.video.sfu.event import events_pb2 -from getstream.video.rtc.pb.stream.video.sfu.models import models_pb2 -from getstream.video.rtc.pb.stream.video.sfu.signal_rpc import signal_pb2 -from getstream.video.rtc.twirp_client_wrapper import SfuRpcError, SignalClient - from getstream.video.call import Call from getstream.video.rtc.connection_utils import ( + ConnectionOptions, ConnectionState, SfuConnectionError, - ConnectionOptions, connect_websocket, join_call, ) +from getstream.video.rtc.coordinator.ws import StreamAPIWS +from getstream.video.rtc.location_discovery import HTTPHintLocationDiscovery +from getstream.video.rtc.models import JoinCallResponse +from getstream.video.rtc.network_monitor import NetworkMonitor +from getstream.video.rtc.participants import ParticipantsState +from getstream.video.rtc.pb.stream.video.sfu.event import events_pb2 +from getstream.video.rtc.pb.stream.video.sfu.models import models_pb2 +from getstream.video.rtc.pb.stream.video.sfu.signal_rpc import signal_pb2 +from getstream.video.rtc.peer_connection import PeerConnectionManager +from getstream.video.rtc.reconnection import ReconnectionManager +from getstream.video.rtc.recording import RecordingManager from getstream.video.rtc.track_util import ( fix_sdp_msid_semantic, parse_track_stream_mapping, ) -from getstream.video.rtc.network_monitor import NetworkMonitor -from getstream.video.rtc.recording import RecordingManager -from getstream.video.rtc.participants import ParticipantsState from getstream.video.rtc.tracks import SubscriptionConfig, SubscriptionManager -from getstream.video.rtc.reconnection import ReconnectionManager -from getstream.video.rtc.peer_connection import PeerConnectionManager -from getstream.video.rtc.location_discovery import HTTPHintLocationDiscovery -from getstream.video.rtc.models import JoinCallResponse +from getstream.video.rtc.twirp_client_wrapper import SfuRpcError, SignalClient logger = logging.getLogger(__name__) @@ -79,7 +78,8 @@ def __init__( self._network_monitor: NetworkMonitor = NetworkMonitor(self) self._reconnector: ReconnectionManager = ReconnectionManager(self) self._subscription_manager: SubscriptionManager = SubscriptionManager( - self, subscription_config + self, + subscription_config, ) self._peer_manager: PeerConnectionManager = PeerConnectionManager(self) @@ -115,7 +115,7 @@ async def _on_ice_trickle(self, event): return candidate = aiortc.rtcicetransport.candidate_from_aioice( - aioice.Candidate.from_sdp(candidate_sdp) + aioice.Candidate.from_sdp(candidate_sdp), ) candidate.sdpMid = ice_candidate.get("sdpMid") candidate.sdpMLineIndex = ice_candidate.get("sdpMLineIndex") @@ -140,13 +140,14 @@ async def _on_subscriber_offer(self, event: events_pb2.SubscriberOffer): fixed_sdp = fix_sdp_msid_semantic(event.sdp) # Parse SDP to create track_id to stream_id mapping self.participants_state.set_track_stream_mapping( - parse_track_stream_mapping(fixed_sdp) + parse_track_stream_mapping(fixed_sdp), ) # The SDP offer from the SFU might already contain candidates (trickled) # or have a different structure. We set it as the remote description. # The aiortc library handles merging and interpretation. remote_description = aiortc.RTCSessionDescription( - type="offer", sdp=fixed_sdp + type="offer", + sdp=fixed_sdp, ) logger.debug(f"""Setting remote description with SDP: {remote_description.sdp}""") @@ -159,7 +160,7 @@ async def _on_subscriber_offer(self, event: events_pb2.SubscriberOffer): logger.info( f"""Sending answer with local description: - {self.subscriber_pc.localDescription.sdp}""" + {self.subscriber_pc.localDescription.sdp}""", ) try: @@ -215,8 +216,7 @@ async def _connect_internal( token: Optional[str] = None, session_id: Optional[str] = None, ) -> None: - """ - Internal connection method that handles the core connection logic. + """Internal connection method that handles the core connection logic. Args: region: Optional region to connect to @@ -226,6 +226,7 @@ async def _connect_internal( Raises: SfuConnectionError: If connection fails + """ self.connection_state = ConnectionState.JOINING @@ -273,10 +274,12 @@ async def _connect_internal( # Connect track subscription events to subscription manager self._ws_client.on_event( - "track_published", self._subscription_manager.handle_track_published + "track_published", + self._subscription_manager.handle_track_published, ) self._ws_client.on_event( - "track_unpublished", self._subscription_manager.handle_track_unpublished + "track_unpublished", + self._subscription_manager.handle_track_unpublished, ) # Connect subscriber offer event to handle SDP negotiation @@ -307,9 +310,9 @@ async def _connect_internal( self.twirp_context = Context(headers={"authorization": token}) # Step 5: Create coordinator websocket (temporarily disabled to test) - user_token = self.call.client.stream.create_token(user_id=self.user_id) + user_token = self.call.client.stream_audio.create_token(user_id=self.user_id) self._coordinator_ws_client = StreamAPIWS( - api_key=self.call.client.stream.api_key, + api_key=self.call.client.stream_audio.api_key, token=user_token, user_details={"id": self.user_id}, ) @@ -324,8 +327,7 @@ async def _connect_internal( logger.info("Successfully connected to SFU") async def connect(self): - """ - Connect to SFU. + """Connect to SFU. This method automatically handles retry logic for transient errors like "server is full" and network issues. @@ -334,8 +336,7 @@ async def connect(self): await self._connect_internal() async def wait(self): - """ - Wait until the connection is over. + """Wait until the connection is over. This is useful for tests and examples where you want to wait for the connection to end rather than just sleeping for a fixed time. @@ -355,7 +356,7 @@ async def leave(self): await self._network_monitor.stop_monitoring() await self._peer_manager.close() if self._ws_client: - self._ws_client.close() + await self._ws_client.close() self._ws_client = None if self._coordinator_ws_client: await self._coordinator_ws_client.disconnect() @@ -385,13 +386,25 @@ async def add_tracks(self, audio=None, video=None): """Add multiple audio and video tracks in a single negotiation.""" await self._peer_manager.add_tracks(audio, video) + async def addTrack(self, track, track_info=None): + """Add a single track (backward compatibility).""" + if track.kind == "video": + await self.add_tracks(video=track) + else: + await self.add_tracks(audio=track) + async def start_recording( - self, recording_types, user_ids=None, output_dir="recordings" + self, + recording_types, + user_ids=None, + output_dir="recordings", ): """Start recording.""" logger.info("Starting recording") await self._recording_manager.start_recording( - recording_types, user_ids, output_dir + recording_types, + user_ids, + output_dir, ) async def stop_recording(self, recording_types=None, user_ids=None): @@ -445,7 +458,10 @@ def subscriber_negotiation_lock(self): return self._peer_manager.subscriber_negotiation_lock async def _cleanup_connections( - self, ws_client=None, publisher_pc=None, subscriber_pc=None + self, + ws_client=None, + publisher_pc=None, + subscriber_pc=None, ): """Close provided connections safely; used by ReconnectionManager.""" try: diff --git a/getstream/video/rtc/connection_utils.py b/getstream/video/rtc/connection_utils.py index 59b5c3bc..5add6384 100644 --- a/getstream/video/rtc/connection_utils.py +++ b/getstream/video/rtc/connection_utils.py @@ -1,5 +1,4 @@ -""" -Connection utilities for video streaming. +"""Connection utilities for video streaming. This module provides core connection-related functionality including: - Connection state management @@ -11,11 +10,12 @@ import asyncio import logging -from enum import Enum from dataclasses import dataclass -from typing import Any, Tuple, Optional +from enum import Enum +from typing import Any, Optional, Tuple import aiortc + from getstream.base import StreamResponse from getstream.models import CallRequest from getstream.utils import build_body_dict @@ -23,14 +23,14 @@ from getstream.video.rtc.models import JoinCallResponse from getstream.video.rtc.pb.stream.video.sfu.event import events_pb2 from getstream.video.rtc.pb.stream.video.sfu.models.models_pb2 import ( - TrackInfo, - TRACK_TYPE_VIDEO, TRACK_TYPE_AUDIO, - VideoLayer, + TRACK_TYPE_VIDEO, + TrackInfo, VideoDimension, + VideoLayer, ) +from getstream.video.rtc.signaling import SignalingError, WebSocketClient from getstream.video.rtc.track_util import BufferedMediaTrack, detect_video_properties -from getstream.video.rtc.signaling import WebSocketClient, SignalingError logger = logging.getLogger(__name__) @@ -106,14 +106,18 @@ async def join_call( """Join call via coordinator API.""" try: join_response = await join_call_coordinator_request( - call, user_id, location=location, create=create, **kwargs + call, + user_id, + location=location, + create=create, + **kwargs, ) if local_sfu: join_response.data.credentials.server.url = "http://127.0.0.1:3031/twirp" join_response.data.credentials.server.ws_endpoint = "ws://127.0.0.1:3031/ws" logger.debug( - f"Received SFU credentials: {join_response.data.credentials.server}" + f"Received SFU credentials: {join_response.data.credentials.server}", ) return join_response @@ -147,15 +151,16 @@ async def join_call_coordinator_request( Returns: A response containing the call information and credentials + """ # Create a token for this user - token = call.client.stream.create_token(user_id=user_id) + token = call.client.stream_audio.create_token(user_id=user_id) # Create a new client with this token - client = call.client.stream.__class__( - api_key=call.client.stream.api_key, - api_secret=call.client.stream.api_secret, - base_url=call.client.stream.base_url, + client = call.client.stream_audio.__class__( + api_key=call.client.stream_audio.api_key, + api_secret=call.client.stream_audio.api_secret, + base_url=call.client.stream_audio.base_url, ) # Set up authentication @@ -197,6 +202,7 @@ def create_join_request(join_response, session_id: str) -> events_pb2.JoinReques Returns: A JoinRequest protobuf message configured with data from the coordinator response + """ # Get credentials from the coordinator response credentials = join_response.data.credentials @@ -221,6 +227,7 @@ async def prepare_video_track_info( Raises: Exception: If video property detection fails + """ buffered_video = None @@ -230,7 +237,8 @@ async def prepare_video_track_info( # Detect video properties - with timeout to avoid hanging video_props = await asyncio.wait_for( - detect_video_properties(buffered_video), timeout=3.0 + detect_video_properties(buffered_video), + timeout=3.0, ) video_info = TrackInfo( @@ -293,6 +301,7 @@ def create_audio_track_info(audio: aiortc.mediastreams.MediaStreamTrack) -> Trac Returns: A TrackInfo object for the audio track + """ return TrackInfo( track_id=audio.id, @@ -309,6 +318,7 @@ def get_websocket_url(join_response, local_sfu: bool = False) -> str: Returns: The WebSocket URL to connect to + """ if local_sfu: return "ws://127.0.0.1:3031/ws" @@ -321,8 +331,7 @@ async def connect_websocket( session_id: str, options: ConnectionOptions, ) -> Tuple[WebSocketClient, events_pb2.SfuEvent]: - """ - Connect to the WebSocket server. + """Connect to the WebSocket server. Args: token: Authentication token @@ -335,6 +344,7 @@ async def connect_websocket( Raises: SignalingError: If connection fails + """ logger.info(f"Connecting to WebSocket at {ws_url}") @@ -375,6 +385,7 @@ def _is_retryable(retry_state: Any) -> bool: Returns: True if the error should be retried, False otherwise + """ # Extract the actual exception from the retry state if hasattr(retry_state, "outcome") and retry_state.outcome.failed: diff --git a/getstream/video/rtc/coordinator/__init__.py b/getstream/video/rtc/coordinator/__init__.py index 60cb4858..a73aaeb1 100644 --- a/getstream/video/rtc/coordinator/__init__.py +++ b/getstream/video/rtc/coordinator/__init__.py @@ -1,5 +1,4 @@ -""" -WebSocket client for Stream Video Coordinator. +"""WebSocket client for Stream Video Coordinator. This module provides the StreamAPIWS class for connecting to Stream's Video Coordinator via WebSocket for real-time communication. diff --git a/getstream/video/rtc/coordinator/backoff.py b/getstream/video/rtc/coordinator/backoff.py index 9d990c6b..a38fa120 100644 --- a/getstream/video/rtc/coordinator/backoff.py +++ b/getstream/video/rtc/coordinator/backoff.py @@ -1,21 +1,21 @@ -""" -Exponential backoff implementation for WebSocket reconnection. +"""Exponential backoff implementation for WebSocket reconnection. This module provides utilities for implementing exponential backoff strategies when reconnecting to failed WebSocket connections. """ import logging -from typing import AsyncIterator +from collections.abc import AsyncIterator logger = logging.getLogger(__name__) async def exp_backoff( - max_retries: int, base: float = 1.0, factor: float = 2.0 + max_retries: int, + base: float = 1.0, + factor: float = 2.0, ) -> AsyncIterator[float]: - """ - Generate exponential backoff delays for retry attempts. + """Generate exponential backoff delays for retry attempts. Args: max_retries: Maximum number of retry attempts @@ -35,6 +35,7 @@ async def exp_backoff( >>> delays = asyncio.run(example()) >>> delays [1.0, 2.0, 4.0] + """ for attempt in range(max_retries): delay = base * (factor**attempt) diff --git a/getstream/video/rtc/coordinator/errors.py b/getstream/video/rtc/coordinator/errors.py index 29294691..ecb3db65 100644 --- a/getstream/video/rtc/coordinator/errors.py +++ b/getstream/video/rtc/coordinator/errors.py @@ -1,5 +1,4 @@ -""" -Exception classes for WebSocket coordinator operations. +"""Exception classes for WebSocket coordinator operations. This module defines the exception hierarchy for WebSocket-related errors in the Stream Video Coordinator client. @@ -11,8 +10,7 @@ class StreamWSException(Exception): - """ - Base exception class for all WebSocket coordinator-related errors. + """Base exception class for all WebSocket coordinator-related errors. This is the root exception from which all other WebSocket coordinator exceptions inherit. @@ -22,8 +20,7 @@ class StreamWSException(Exception): class StreamWSAuthError(StreamWSException): - """ - Exception raised when authentication with the WebSocket server fails. + """Exception raised when authentication with the WebSocket server fails. This error is raised when the first frame received from the server has type == "error", indicating authentication failure. This error @@ -34,8 +31,7 @@ class StreamWSAuthError(StreamWSException): class StreamWSConnectionError(StreamWSException): - """ - Exception raised when there are transient connection issues. + """Exception raised when there are transient connection issues. This error is raised for network-related failures, socket errors, and other transient connection problems that may be resolved by retrying. @@ -45,8 +41,7 @@ class StreamWSConnectionError(StreamWSException): class StreamWSMaxRetriesExceeded(StreamWSException): - """ - Exception raised when the maximum number of retry attempts is exceeded. + """Exception raised when the maximum number of retry attempts is exceeded. This error is raised when all retry attempts have been exhausted and the connection still cannot be established. diff --git a/getstream/video/rtc/coordinator/ws.py b/getstream/video/rtc/coordinator/ws.py index a7ea7d56..8d2191dd 100644 --- a/getstream/video/rtc/coordinator/ws.py +++ b/getstream/video/rtc/coordinator/ws.py @@ -1,5 +1,4 @@ -""" -WebSocket client implementation for Stream Video Coordinator. +"""WebSocket client implementation for Stream Video Coordinator. This module contains the StreamAPIWS class that provides an asynchronous WebSocket client for communicating with Stream's Video Coordinator. @@ -14,12 +13,13 @@ import websockets from getstream.utils import StreamAsyncIOEventEmitter + +from .backoff import exp_backoff from .errors import ( StreamWSAuthError, StreamWSConnectionError, StreamWSMaxRetriesExceeded, ) -from .backoff import exp_backoff logger = logging.getLogger(__name__) @@ -27,8 +27,7 @@ class StreamAPIWS(StreamAsyncIOEventEmitter): - """ - Asynchronous WebSocket client for Stream Video Coordinator. + """Asynchronous WebSocket client for Stream Video Coordinator. This client handles authentication, event dispatching, and connection management for communicating with Stream's Video Coordinator WebSocket API. @@ -48,8 +47,7 @@ def __init__( backoff_factor: float = 2.0, logger: Optional[logging.Logger] = None, ): - """ - Initialize the WebSocket client. + """Initialize the WebSocket client. Args: api_key: Stream API key @@ -62,6 +60,7 @@ def __init__( backoff_base: Base delay for exponential backoff backoff_factor: Factor for exponential backoff logger: Optional logger instance + """ super().__init__() @@ -91,11 +90,11 @@ def __init__( self._initial_connection = True # Track if this is the first connection def _build_auth_payload(self) -> dict: - """ - Build the authentication payload to send after connection. + """Build the authentication payload to send after connection. Returns: Authentication payload as a dictionary + """ payload = { "token": self.token, @@ -109,8 +108,7 @@ def _build_auth_payload(self) -> dict: return payload async def _open_socket(self) -> dict: - """ - Open WebSocket connection and perform authentication. + """Open WebSocket connection and perform authentication. Returns: The first message received from server @@ -118,6 +116,7 @@ async def _open_socket(self) -> dict: Raises: StreamWSAuthError: If authentication fails StreamWSConnectionError: If connection fails + """ self._logger.debug("Opening WebSocket connection", extra={"uri": self.uri}) @@ -168,9 +167,7 @@ async def _open_socket(self) -> dict: return message async def _reader_task_func(self) -> None: - """ - Background task that reads messages from the WebSocket and emits events. - """ + """Background task that reads messages from the WebSocket and emits events.""" self._logger.debug("Starting reader task") try: @@ -187,12 +184,14 @@ async def _reader_task_func(self) -> None: message = json.loads(raw_message) event_type = message.get("type", "unknown") self._logger.debug( - "Received message", extra={"type": event_type} + "Received message", + extra={"type": event_type}, ) self.emit(event_type, message) except json.JSONDecodeError as e: self._logger.warning( - "Failed to parse message as JSON", exc_info=e + "Failed to parse message as JSON", + exc_info=e, ) except websockets.exceptions.ConnectionClosed as e: @@ -218,7 +217,8 @@ async def _reader_task_func(self) -> None: break except websockets.exceptions.WebSocketException as e: self._logger.error( - "WebSocket protocol error in reader task", exc_info=e + "WebSocket protocol error in reader task", + exc_info=e, ) if self._connected and not self._reconnect_in_progress: await self._trigger_reconnect() @@ -236,9 +236,7 @@ async def _reader_task_func(self) -> None: self._logger.debug("Reader task ended") async def _heartbeat_task_func(self) -> None: - """ - Background task that sends heartbeat messages and monitors connection health. - """ + """Background task that sends heartbeat messages and monitors connection health.""" self._logger.debug("Starting heartbeat task") try: @@ -303,7 +301,8 @@ async def _heartbeat_task_func(self) -> None: break except Exception as e: self._logger.error( - "Unexpected error while sending heartbeat", exc_info=e + "Unexpected error while sending heartbeat", + exc_info=e, ) if not self._reconnect_in_progress: await self._trigger_reconnect() @@ -316,9 +315,7 @@ async def _heartbeat_task_func(self) -> None: self._logger.debug("Heartbeat task ended") async def _trigger_reconnect(self) -> None: - """ - Trigger reconnection process. - """ + """Trigger reconnection process.""" if self._reconnect_in_progress: return @@ -334,11 +331,11 @@ async def _trigger_reconnect(self) -> None: self._reconnect_in_progress = False async def _reconnect(self) -> None: - """ - Attempt to reconnect using exponential backoff strategy. + """Attempt to reconnect using exponential backoff strategy. Raises: StreamWSMaxRetriesExceeded: If all retry attempts fail + """ self._logger.info("Starting reconnection process") @@ -377,7 +374,7 @@ async def _reconnect(self) -> None: if not self._connected: self._logger.debug( - "Connection was closed during backoff, aborting reconnection" + "Connection was closed during backoff, aborting reconnection", ) return @@ -399,7 +396,8 @@ async def _reconnect(self) -> None: except StreamWSAuthError as e: # Authentication errors should not trigger retry self._logger.error( - "Authentication failed during reconnection", exc_info=e + "Authentication failed during reconnection", + exc_info=e, ) self._connected = False raise @@ -411,7 +409,7 @@ async def _reconnect(self) -> None: self._logger.error("All reconnection attempts failed") self._connected = False raise StreamWSMaxRetriesExceeded( - "Maximum number of reconnection attempts exceeded" + "Maximum number of reconnection attempts exceeded", ) async def _start_background_tasks(self) -> None: @@ -442,8 +440,7 @@ async def _cancel_background_tasks(self) -> None: self._heartbeat_task = None async def connect(self) -> dict: - """ - Open the socket, authenticate, and wait for the first server frame. + """Open the socket, authenticate, and wait for the first server frame. Returns: The first event payload (usually {"type": "connection.ok"}) @@ -451,6 +448,7 @@ async def connect(self) -> dict: Raises: StreamWSAuthError: If the first frame has type == "error" or authentication times out StreamWSConnectionError: If there are connection issues + """ self._logger.info("Connecting to coordinator", extra={"uri": self.uri}) @@ -497,9 +495,7 @@ async def connect(self) -> dict: raise StreamWSConnectionError(f"Unexpected connection error: {e}") from e async def disconnect(self) -> None: - """ - Cancel internal tasks and close the WebSocket cleanly. - """ + """Cancel internal tasks and close the WebSocket cleanly.""" self._logger.info("Disconnecting from coordinator") self._connected = False @@ -524,10 +520,10 @@ async def disconnect(self) -> None: @property def connected(self) -> bool: - """ - Check if the WebSocket is currently connected. + """Check if the WebSocket is currently connected. Returns: True if connected, False otherwise + """ return self._connected and self._websocket is not None diff --git a/getstream/video/rtc/coordinator_api.py b/getstream/video/rtc/coordinator_api.py index f9fdfbac..145e5127 100644 --- a/getstream/video/rtc/coordinator_api.py +++ b/getstream/video/rtc/coordinator_api.py @@ -1,14 +1,12 @@ -""" -Coordinator API functions for Stream Video RTC. -""" +"""Coordinator API functions for Stream Video RTC.""" import logging from typing import Optional from getstream.base import StreamResponse from getstream.models import CallRequest -from getstream.video.call import Call from getstream.utils import build_body_dict +from getstream.video.call import Call # Import the types we need from __init__ without creating circular imports from getstream.video.rtc.models import JoinCallResponse @@ -40,15 +38,16 @@ async def join_call_coordinator_request( Returns: A response containing the call information and credentials + """ # Create a token for this user - token = call.client.stream.create_token(user_id=user_id) + token = call.client.stream_audio.create_token(user_id=user_id) # Create a new client with this token - client = call.client.stream.__class__( - api_key=call.client.stream.api_key, - api_secret=call.client.stream.api_secret, - base_url=call.client.stream.base_url, + client = call.client.stream_audio.__class__( + api_key=call.client.stream_audio.api_key, + api_secret=call.client.stream_audio.api_secret, + base_url=call.client.stream_audio.base_url, ) # Set up authentication diff --git a/getstream/video/rtc/location_discovery.py b/getstream/video/rtc/location_discovery.py index bd268dd1..fcd84755 100644 --- a/getstream/video/rtc/location_discovery.py +++ b/getstream/video/rtc/location_discovery.py @@ -1,5 +1,4 @@ -""" -Location discovery for video streaming. +"""Location discovery for video streaming. This module provides functionality to discover the optimal location for video streaming connections based on CloudFront POP headers. @@ -9,7 +8,7 @@ import http.client import logging from contextlib import contextmanager -from typing import ContextManager, Optional, Protocol +from typing import Optional, Protocol # Constants matching the Go implementation HEADER_CLOUDFRONT_POP = "X-Amz-Cf-Pop" @@ -23,11 +22,12 @@ class HTTPClient(Protocol): """Protocol defining the HTTP client interface.""" - def request(self, method: str, url: str, body=None, headers=None, **kwargs) -> None: + def request(self, method: str, url: str, body=None, headers=None, **kwargs): """Make an HTTP request.""" ... - def response(self) -> ContextManager[http.client.HTTPResponse]: + @contextmanager + def response(self): """Get the HTTP response.""" ... @@ -42,14 +42,14 @@ def __init__( client: Optional[HTTPClient] = None, logger: Optional[logging.Logger] = None, ): - """ - Initialize the location discovery service. + """Initialize the location discovery service. Args: url: The URL to use for discovery max_retries: Maximum number of retries for discovery client: HTTP client to use logger: Logger instance + """ self.url = url self.max_retries = max_retries @@ -59,34 +59,42 @@ def __init__( @functools.lru_cache(maxsize=1) def discover(self, context=None) -> str: - """ - Discover the closest location based on CloudFront pop. + """Discover the closest location based on CloudFront pop. Args: context: Optional context (for compatibility with Go implementation) Returns: The 3-character location code (e.g. "IAD", "FRA") + """ - # Basic validation to match previous behavior and provide fast-fail parsed_url = self.url.split("://", 1) if len(parsed_url) != 2: self.logger.warning("Invalid URL format: %s", self.url) return FALLBACK_LOCATION_NAME + protocol, host_path = parsed_url + host = host_path.split("/", 1)[0] + path = "/" + host_path.split("/", 1)[1] if "/" in host_path else "/" + for i in range(self.max_retries): self.logger.info("Discovering location, attempt %d", i + 1) try: - # Use injected HTTP client (or default) for requests - self.client.request("HEAD", self.url) - with self.client.response() as response: - if response.status != 200: - self.logger.warning( - "Unexpected status code: %d", response.status - ) - continue + if protocol.lower() == "https": + conn = http.client.HTTPSConnection(host, timeout=1) + else: + conn = http.client.HTTPConnection(host, timeout=1) + + conn.request("HEAD", path) + response = conn.getresponse() + + if response.status != 200: + self.logger.warning("Unexpected status code: %d", response.status) + continue - pop_name = response.getheader(HEADER_CLOUDFRONT_POP, "") + pop_name = response.getheader(HEADER_CLOUDFRONT_POP, "") + response.read() # Read and discard the response body + conn.close() if len(pop_name) < 3: self.logger.warning("Invalid pop name: %s", pop_name) @@ -109,11 +117,11 @@ def discover(self, context=None) -> str: def create_default_http_client(): - """ - Create a default HTTP client with appropriate timeouts. + """Create a default HTTP client with appropriate timeouts. Returns: A simple HTTP client + """ class SimpleHTTPClient: diff --git a/getstream/video/rtc/models.py b/getstream/video/rtc/models.py index 98885f30..aa969439 100644 --- a/getstream/video/rtc/models.py +++ b/getstream/video/rtc/models.py @@ -1,10 +1,11 @@ -""" -Data models for the RTC module -""" +"""Data models for the RTC module""" -from dataclasses import field as dc_field, dataclass -from typing import List, Optional, Dict, Any -from dataclasses_json import config as dc_config, DataClassJsonMixin +from dataclasses import dataclass +from dataclasses import field as dc_field +from typing import Any, Dict, List, Optional + +from dataclasses_json import DataClassJsonMixin +from dataclasses_json import config as dc_config from getstream.models import CallRequest, CallResponse, MemberResponse @@ -12,17 +13,21 @@ @dataclass class JoinCallRequest(DataClassJsonMixin): create: Optional[bool] = dc_field( - default=False, metadata=dc_config(field_name="create") + default=False, + metadata=dc_config(field_name="create"), ) data: "Optional[CallRequest]" = dc_field( - default=None, metadata=dc_config(field_name="data") + default=None, + metadata=dc_config(field_name="data"), ) ring: Optional[bool] = dc_field(default=None, metadata=dc_config(field_name="ring")) notify: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="notify") + default=None, + metadata=dc_config(field_name="notify"), ) video: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="video") + default=None, + metadata=dc_config(field_name="video"), ) location: str = dc_field(default="", metadata=dc_config(field_name="location")) @@ -39,7 +44,7 @@ class Credentials(DataClassJsonMixin): server: ServerCredentials = dc_field(metadata=dc_config(field_name="server")) token: str = dc_field(metadata=dc_config(field_name="token")) ice_servers: List[Dict[str, Any]] = dc_field( - metadata=dc_config(field_name="ice_servers") + metadata=dc_config(field_name="ice_servers"), ) diff --git a/getstream/video/rtc/network_monitor.py b/getstream/video/rtc/network_monitor.py index 725f6c04..48c14288 100644 --- a/getstream/video/rtc/network_monitor.py +++ b/getstream/video/rtc/network_monitor.py @@ -1,6 +1,4 @@ -""" -Monitors network connectivity and manages network state. -""" +"""Monitors network connectivity and manages network state.""" import asyncio import logging @@ -26,8 +24,7 @@ def __init__( check_interval: float = 1.0, required_successful_pings: int = 1, ): - """ - Initialize network monitor. + """Initialize network monitor. Args: connection_manager: The ConnectionManager instance @@ -35,6 +32,7 @@ def __init__( connectivity_timeout: Timeout for ping checks check_interval: Seconds between connectivity checks required_successful_pings: Number of successful pings needed to consider network online + """ super().__init__() @@ -97,11 +95,11 @@ async def _monitor_loop(self): await asyncio.sleep(self.check_interval) async def _check_connectivity(self) -> bool: - """ - Check connectivity using ping3 package. + """Check connectivity using ping3 package. Returns: True if required number of pings succeed, False otherwise + """ successful_pings = 0 @@ -109,13 +107,14 @@ async def _check_connectivity(self) -> bool: try: # Run ping in executor to avoid blocking response_time = await asyncio.get_event_loop().run_in_executor( - None, lambda: ping3.ping(host, timeout=self.connectivity_timeout) + None, + lambda: ping3.ping(host, timeout=self.connectivity_timeout), ) if response_time is not None: successful_pings += 1 self.logger.debug( - f"Ping to {host} successful: {response_time:.2f}s" + f"Ping to {host} successful: {response_time:.2f}s", ) # If we've reached the required number of successful pings, we're online @@ -130,11 +129,11 @@ async def _check_connectivity(self) -> bool: return False async def _handle_network_change(self, online: bool): - """ - Handle network status changes and emit events. + """Handle network status changes and emit events. Args: online: True if network is now available, False if unavailable + """ status = "online" if online else "offline" self.logger.debug(f"Network status changed to {status}") @@ -187,8 +186,9 @@ async def handle_network_change(event_data): asyncio.create_task( self.connection_manager.reconnector.reconnect( - strategy, "Going online" - ) + strategy, + "Going online", + ), ) @self.on("network_online") diff --git a/getstream/video/rtc/participants.py b/getstream/video/rtc/participants.py index 6a57c0ec..543bee20 100644 --- a/getstream/video/rtc/participants.py +++ b/getstream/video/rtc/participants.py @@ -1,3 +1,4 @@ +import logging from typing import Optional from pyee.asyncio import AsyncIOEventEmitter @@ -5,8 +6,6 @@ from getstream.video.rtc.pb.stream.video.sfu.event import events_pb2 from getstream.video.rtc.pb.stream.video.sfu.models import models_pb2 -import logging - logger = logging.getLogger(__name__) diff --git a/getstream/video/rtc/pc.py b/getstream/video/rtc/pc.py index c1801d5e..444a83e5 100644 --- a/getstream/video/rtc/pc.py +++ b/getstream/video/rtc/pc.py @@ -1,20 +1,18 @@ import asyncio import logging -from typing import Optional, Any +from typing import Any, Optional import aiortc from aiortc.contrib.media import MediaRelay +from pyee.asyncio import AsyncIOEventEmitter from getstream.video.rtc.track_util import AudioTrackHandler -from pyee.asyncio import AsyncIOEventEmitter logger = logging.getLogger(__name__) def parse_track_id(id: str) -> tuple[str, str]: - """ - Parse the webRTC media track and returns a tuple including: the id of the participant, the type of track - """ + """Parse the webRTC media track and returns a tuple including: the id of the participant, the type of track""" participant_id, track_type, _ = id.split(":") return participant_id, track_type @@ -27,10 +25,10 @@ def __init__( ) -> None: if configuration is None: configuration = aiortc.RTCConfiguration( - iceServers=[aiortc.RTCIceServer(urls="stun:stun.l.google.com:19302")] + iceServers=[aiortc.RTCIceServer(urls="stun:stun.l.google.com:19302")], ) logger.info( - f"Creating publisher peer connection with configuration: {configuration}" + f"Creating publisher peer connection with configuration: {configuration}", ) super().__init__(configuration) self.manager = manager @@ -39,7 +37,7 @@ def __init__( @self.on("icegatheringstatechange") def on_icegatheringstatechange(): logger.info( - f"Publisher ICE gathering state changed to {self.iceGatheringState}" + f"Publisher ICE gathering state changed to {self.iceGatheringState}", ) if self.iceGatheringState == "complete": logger.info("Publisher: All ICE candidates have been gathered.") @@ -47,7 +45,7 @@ def on_icegatheringstatechange(): @self.on("iceconnectionstatechange") def on_iceconnectionstatechange(): logger.info( - f"Publisher ICE connection state changed to {self.iceConnectionState}" + f"Publisher ICE connection state changed to {self.iceConnectionState}", ) @self.on("connectionstatechange") @@ -58,16 +56,16 @@ def on_connectionstatechange(): async def handle_answer(self, response): """Handles the SDP answer received from the SFU for the publisher connection.""" - logger.info(f"Publisher received answer {response.sdp}") remote_description = aiortc.RTCSessionDescription( - type="answer", sdp=response.sdp + type="answer", + sdp=response.sdp, ) await self.setRemoteDescription(remote_description) logger.info( - f"Publisher remote description set successfully. {self.localDescription}" + f"Publisher remote description set successfully. {self.localDescription}", ) async def wait_for_connected(self, timeout: float = 15.0): @@ -106,10 +104,10 @@ def __init__( ) -> None: if configuration is None: configuration = aiortc.RTCConfiguration( - iceServers=[aiortc.RTCIceServer(urls="stun:stun.l.google.com:19302")] + iceServers=[aiortc.RTCIceServer(urls="stun:stun.l.google.com:19302")], ) logger.info( - f"creating subscriber peer connection with configuration: {configuration}" + f"creating subscriber peer connection with configuration: {configuration}", ) super().__init__(configuration) self.connection = connection @@ -135,9 +133,10 @@ async def on_track(track: aiortc.mediastreams.MediaStreamTrack): if track.kind == "audio": # Add a new subscriber for AudioTrackHandler handler = AudioTrackHandler( - relay.subscribe(track), lambda pcm: self.emit("audio", pcm, user) + relay.subscribe(track), + lambda pcm: self.emit("audio", pcm, user), ) - asyncio.create_task(handler.start()) + asyncio.ensure_future(handler.start()) self.emit("track_added", relay.subscribe(track), user) @@ -148,7 +147,8 @@ def on_icegatheringstatechange(): logger.info("All ICE candidates have been gathered.") def add_track_subscriber( - self, track_id: str + self, + track_id: str, ) -> Optional[aiortc.mediastreams.MediaStreamTrack]: """Add a new subscriber to an existing track's MediaRelay.""" track_data = self.track_map.get(track_id) diff --git a/getstream/video/rtc/peer_connection.py b/getstream/video/rtc/peer_connection.py index 8a3b963f..c9e07780 100644 --- a/getstream/video/rtc/peer_connection.py +++ b/getstream/video/rtc/peer_connection.py @@ -1,6 +1,4 @@ -""" -Manages WebRTC peer connections for video streaming. -""" +"""Manages WebRTC peer connections for video streaming.""" import asyncio import logging @@ -14,9 +12,9 @@ prepare_video_track_info, ) from getstream.video.rtc.pb.stream.video.sfu.signal_rpc import signal_pb2 +from getstream.video.rtc.pc import PublisherPeerConnection, SubscriberPeerConnection from getstream.video.rtc.track_util import patch_sdp_offer from getstream.video.rtc.twirp_client_wrapper import SfuRpcError -from getstream.video.rtc.pc import PublisherPeerConnection, SubscriberPeerConnection logger = logging.getLogger(__name__) @@ -38,7 +36,7 @@ async def setup_subscriber(self): "failed", ]: self.subscriber_pc = SubscriberPeerConnection( - connection=self.connection_manager + connection=self.connection_manager, ) @self.subscriber_pc.on("audio") @@ -49,10 +47,14 @@ async def on_audio(pcm_data, user): async def on_track_added(track, user): """Handle track events from MediaRelay subscribers""" await self.connection_manager.recording_manager.on_track_received( - track, user + track, + user, ) self.connection_manager.emit( - "track_added", track._source.id, track.kind, user + "track_added", + track._source.id, + track.kind, + user, ) logger.debug("Created new subscriber peer connection") @@ -94,7 +96,7 @@ async def add_tracks( if self.publisher_pc is None: self.publisher_pc = PublisherPeerConnection( - manager=self.connection_manager + manager=self.connection_manager, ) if audio: @@ -170,10 +172,10 @@ async def add_tracks( async def restore_published_tracks(self): """Restore published tracks using their stored MediaRelay instances.""" track_ids = list( - self.connection_manager.reconnector.reconnection_info.published_tracks.keys() + self.connection_manager.reconnector.reconnection_info.published_tracks.keys(), ) logger.info( - f"Restoring {len(track_ids)} published tracks with MediaRelay - Track IDs: {track_ids}" + f"Restoring {len(track_ids)} published tracks with MediaRelay - Track IDs: {track_ids}", ) # Collect all tracks to restore @@ -214,7 +216,7 @@ async def restore_published_tracks(self): logger.info(f"Restored additional video track {i}: {track.id}") logger.info( - f"Successfully restored all {len(track_ids)} tracks using stored MediaRelay instances" + f"Successfully restored all {len(track_ids)} tracks using stored MediaRelay instances", ) except Exception as e: diff --git a/getstream/video/rtc/reconnection.py b/getstream/video/rtc/reconnection.py index 28835625..e432dd90 100644 --- a/getstream/video/rtc/reconnection.py +++ b/getstream/video/rtc/reconnection.py @@ -1,6 +1,4 @@ -""" -Handles reconnection logic for the video connection. -""" +"""Handles reconnection logic for the video connection.""" import asyncio import logging @@ -67,12 +65,12 @@ def __init__(self, connection_manager): self._fast_reconnect_deadline_seconds = 10 async def reconnect(self, strategy: str, reason: str): - """ - Main reconnection orchestrator. + """Main reconnection orchestrator. Args: strategy: The reconnection strategy to use reason: Human-readable reason for the reconnection + """ async with self._reconnect_lock: # Check if already in a reconnection state @@ -84,7 +82,7 @@ async def reconnect(self, strategy: str, reason: str): logger.debug( "Reconnection already in progress", extra={ - "current_state": self.connection_manager.connection_state.value + "current_state": self.connection_manager.connection_state.value, }, ) return @@ -94,7 +92,8 @@ async def reconnect(self, strategy: str, reason: str): self.reconnection_info.reason = reason logger.info( - "Starting reconnection", extra={"strategy": strategy, "reason": reason} + "Starting reconnection", + extra={"strategy": strategy, "reason": reason}, ) try: @@ -113,13 +112,14 @@ async def _execute_reconnection_loop(self, reconnect_start_time: float): timeout = _DISCONNECTION_TIMEOUT_SECONDS if 0 < timeout < (time.time() - reconnect_start_time): logger.warning( - "Stopping reconnection attempts after reaching disconnection timeout" + "Stopping reconnection attempts after reaching disconnection timeout", ) self.connection_manager.connection_state = ( ConnectionState.RECONNECTING_FAILED ) self.connection_manager.emit( - "reconnection_failed", {"reason": "Disconnection timeout exceeded"} + "reconnection_failed", + {"reason": "Disconnection timeout exceeded"}, ) return @@ -134,7 +134,7 @@ async def _execute_reconnection_loop(self, reconnect_start_time: float): await self.set_network_event.wait() logger.info( - f"Executing reconnection with strategy {self.reconnection_info.strategy}" + f"Executing reconnection with strategy {self.reconnection_info.strategy}", ) # Execute strategy-specific reconnection @@ -252,7 +252,9 @@ async def _reconnect_rejoin(self): try: # Close old connections efficiently await self.connection_manager._cleanup_connections( - old_ws_client, old_publisher, old_subscriber + old_ws_client, + old_publisher, + old_subscriber, ) # Use _connect_internal for fresh connection @@ -312,5 +314,7 @@ async def _reconnect_migrate(self): finally: # Clean up old connections await self.connection_manager._cleanup_connections( - current_ws_client, current_publisher, current_subscriber + current_ws_client, + current_publisher, + current_subscriber, ) diff --git a/getstream/video/rtc/recording.py b/getstream/video/rtc/recording.py index 4bae2a02..7ef4d3c1 100644 --- a/getstream/video/rtc/recording.py +++ b/getstream/video/rtc/recording.py @@ -144,17 +144,17 @@ async def start_recording(self): # Create MediaRecorder self._recorder = MediaRecorder(str(filepath)) logger.info( - f"TrackRecorder {self.recorder_id}: Created MediaRecorder for {filepath}" + f"TrackRecorder {self.recorder_id}: Created MediaRecorder for {filepath}", ) self._recorder.addTrack(self._track) logger.info( - f"TrackRecorder {self.recorder_id}: Added track {type(self._track).__name__} to MediaRecorder" + f"TrackRecorder {self.recorder_id}: Added track {type(self._track).__name__} to MediaRecorder", ) # Start recording await self._recorder.start() logger.info( - f"TrackRecorder {self.recorder_id}: MediaRecorder started successfully" + f"TrackRecorder {self.recorder_id}: MediaRecorder started successfully", ) self._is_recording = True self._start_time = time.time() @@ -171,7 +171,7 @@ async def start_recording(self): ) logger.info( - f"Started {self.track_type.value} recording {self.recorder_id}: {self._filename}" + f"Started {self.track_type.value} recording {self.recorder_id}: {self._filename}", ) except Exception as e: logger.error(f"Error starting recording for {self.recorder_id}: {e}") @@ -216,7 +216,7 @@ async def stop_recording(self): ) logger.info( - f"Stopped {self.track_type.value} recording {self.recorder_id} after {duration:.2f}s" + f"Stopped {self.track_type.value} recording {self.recorder_id} after {duration:.2f}s", ) @@ -272,7 +272,8 @@ async def _mixing_loop(self): try: # Try to receive frame with short timeout aiortc_frame = await asyncio.wait_for( - track.recv(), timeout=0.001 + track.recv(), + timeout=0.001, ) # Convert aiortc frame to our AudioFrame format @@ -344,7 +345,8 @@ async def recv(self) -> AiortcAudioFrame: try: # Wait for mixed frame with timeout mixed_frame = await asyncio.wait_for( - self._mixed_frame_buffer.get(), timeout=timeout + self._mixed_frame_buffer.get(), + timeout=timeout, ) return mixed_frame except asyncio.TimeoutError: @@ -357,10 +359,11 @@ async def recv(self) -> AiortcAudioFrame: def _create_silent_frame(self) -> AiortcAudioFrame: """Create a silent audio frame.""" samples_per_frame = int( - self.config.audio_sample_rate * self.config.frame_duration + self.config.audio_sample_rate * self.config.frame_duration, ) silent_samples = np.zeros( - (samples_per_frame, self.config.audio_channels), dtype=np.int16 + (samples_per_frame, self.config.audio_channels), + dtype=np.int16, ) return AiortcAudioFrame( @@ -384,9 +387,8 @@ def _aiortc_frame_to_bytes(self, aiortc_frame: AiortcAudioFrame) -> Optional[byt # Preserve full frame to maintain audio quality return samples.tobytes() - else: - # Fallback: use planes - return aiortc_frame.planes[0].to_bytes() + # Fallback: use planes + return aiortc_frame.planes[0].to_bytes() except Exception as e: logger.warning(f"Error converting aiortc frame to bytes: {e}") return None @@ -397,7 +399,7 @@ def _bytes_to_aiortc_frame(self, audio_bytes: bytes) -> Optional[AiortcAudioFram # Calculate expected frame size for consistent timing (20ms) bytes_per_sample = 2 # 16-bit = 2 bytes per sample expected_samples = int( - self.config.audio_sample_rate * self.config.frame_duration + self.config.audio_sample_rate * self.config.frame_duration, ) expected_bytes = ( expected_samples * self.config.audio_channels * bytes_per_sample @@ -406,12 +408,15 @@ def _bytes_to_aiortc_frame(self, audio_bytes: bytes) -> Optional[AiortcAudioFram # Normalize audio data to expected frame size to prevent stretched audio normalized_bytes = self._normalize_audio_frame_size( - audio_bytes, expected_bytes + audio_bytes, + expected_bytes, ) # Create AudioFrame with consistent frame size frame = AiortcAudioFrame( - format="s16", layout=layout, samples=expected_samples + format="s16", + layout=layout, + samples=expected_samples, ) # Update the frame planes with our normalized audio data @@ -429,12 +434,14 @@ def _bytes_to_aiortc_frame(self, audio_bytes: bytes) -> Optional[AiortcAudioFram return None def _normalize_audio_frame_size( - self, audio_bytes: bytes, target_bytes: int + self, + audio_bytes: bytes, + target_bytes: int, ) -> bytes: """Normalize audio frame size to target duration while preserving quality.""" if len(audio_bytes) == target_bytes: return audio_bytes - elif len(audio_bytes) > target_bytes: + if len(audio_bytes) > target_bytes: # Input frame is longer than target - use resampling samples = np.frombuffer(audio_bytes, dtype=np.int16) target_samples = target_bytes // 2 # 2 bytes per sample @@ -446,23 +453,23 @@ def _normalize_audio_frame_size( # Interpolate samples to new length resampled = np.interp( - target_indices, original_indices, samples.astype(np.float32) + target_indices, + original_indices, + samples.astype(np.float32), ) resampled_int16 = np.clip(resampled, -32767, 32767).astype(np.int16) return resampled_int16.tobytes() - else: - return bytes(target_bytes) - else: - # Input frame is shorter than target - pad with silence - padding_bytes = target_bytes - len(audio_bytes) - return audio_bytes + bytes(padding_bytes) + return bytes(target_bytes) + # Input frame is shorter than target - pad with silence + padding_bytes = target_bytes - len(audio_bytes) + return audio_bytes + bytes(padding_bytes) def add_track(self, user_id: str, track: MediaStreamTrack) -> None: """Add a user track to the mix.""" self._user_tracks[user_id] = track logger.info( - f"Added user {user_id} track. Total tracks: {len(self._user_tracks)}" + f"Added user {user_id} track. Total tracks: {len(self._user_tracks)}", ) # Signal that tracks are now available for mixing @@ -645,8 +652,9 @@ def _extract_timestamp(self, frame: AudioFrame) -> float: and frame.metadata.pts_seconds is not None ): return float(frame.metadata.pts_seconds) - elif hasattr(frame.metadata, "pts") and hasattr( - frame.metadata, "time_base" + if hasattr(frame.metadata, "pts") and hasattr( + frame.metadata, + "time_base", ): if ( frame.metadata.pts is not None @@ -736,8 +744,7 @@ def has_tracks(self) -> bool: class RecordingManager(AsyncIOEventEmitter): - """ - Manages all audio and video recording operations. + """Manages all audio and video recording operations. Events emitted: - recording_started: {recording_types, user_ids, output_dir} @@ -888,7 +895,7 @@ async def on_track_removed(self, user_id: str, track_type: str): recorder_key = f"{user_id}_{track_type}" await self._stop_user_recording_by_key(recorder_key, track_type) logger.info( - f"Stopped {track_type} recording for user {user_id} whose {track_type} track was removed" + f"Stopped {track_type} recording for user {user_id} whose {track_type} track was removed", ) # Remove from pending @@ -903,7 +910,9 @@ async def on_track_received(self, track, user): return user_id = getattr( - user, "user_id", getattr(user, "id", str(user) if user else "unknown_user") + user, + "user_id", + getattr(user, "id", str(user) if user else "unknown_user"), ) # Apply user filtering @@ -946,7 +955,11 @@ async def _start_user_track_recording(self, user_id: str, track: MediaStreamTrac # Create track recorder with track and filename track_recorder = TrackRecorder( - track_type, track, filename, f"user_{user_id}_{track.kind}", self.config + track_type, + track, + filename, + f"user_{user_id}_{track.kind}", + self.config, ) # Forward events @@ -993,7 +1006,7 @@ def on_recording_error(data): except Exception as e: logger.error( - f"Failed to start {track.kind} recording for user {user_id}: {e}" + f"Failed to start {track.kind} recording for user {user_id}: {e}", ) self.emit( "recording_error", @@ -1025,7 +1038,9 @@ async def _start_recording_type(self, recording_type: RecordingType) -> None: # Create composite recorder with filename self._composite_audio_recorder = CompositeAudioRecorder( - self._target_user_ids, filename, self.config + self._target_user_ids, + filename, + self.config, ) self._setup_composite_recorder_events() @@ -1033,7 +1048,9 @@ async def _start_recording_type(self, recording_type: RecordingType) -> None: await self._composite_audio_recorder.start_recording() async def _stop_recording_type( - self, recording_type: RecordingType, user_ids: Optional[List[str]] = None + self, + recording_type: RecordingType, + user_ids: Optional[List[str]] = None, ): """Stop a specific recording type.""" if recording_type == RecordingType.TRACK: @@ -1087,7 +1104,10 @@ def _setup_recorder_events(self): self._setup_composite_recorder_events() def _create_event_forwarder( - self, source_event: str, target_event: str, transform_data=None + self, + source_event: str, + target_event: str, + transform_data=None, ): """Create a generic event forwarder function.""" @@ -1128,19 +1148,25 @@ def add_user_id_error(data): recorder.on( "recording_started", self._create_event_forwarder( - "recording_started", "user_recording_started", add_user_id + "recording_started", + "user_recording_started", + add_user_id, ), ) recorder.on( "recording_stopped", self._create_event_forwarder( - "recording_stopped", "user_recording_stopped", add_user_id + "recording_stopped", + "user_recording_stopped", + add_user_id, ), ) recorder.on( "recording_error", self._create_event_forwarder( - "recording_error", "recording_error", add_user_id_error + "recording_error", + "recording_error", + add_user_id_error, ), ) @@ -1191,7 +1217,9 @@ def add_composite_error_context(data): self._composite_audio_recorder.on( "recording_error", self._create_event_forwarder( - "recording_error", "recording_error", add_composite_error_context + "recording_error", + "recording_error", + add_composite_error_context, ), ) @@ -1204,10 +1232,9 @@ def _to_bytes(self, pcm_data) -> bytes: samples = pcm_data.samples if isinstance(samples, bytes): return samples - elif isinstance(samples, np.ndarray): + if isinstance(samples, np.ndarray): return samples.astype(np.int16).tobytes() - else: - raise ValueError(f"Unsupported PcmData.samples type: {type(samples)}") + raise ValueError(f"Unsupported PcmData.samples type: {type(samples)}") if isinstance(pcm_data, np.ndarray): return pcm_data.astype(np.int16).tobytes() @@ -1224,7 +1251,7 @@ def get_recording_status(self) -> dict: """Get current recording status.""" # Combine audio and video recorder keys all_active_recordings = list(self._audio_recorders.keys()) + list( - self._video_recorders.keys() + self._video_recorders.keys(), ) return { @@ -1280,12 +1307,11 @@ def get_recording_status(self) -> dict: } def record_audio_data(self, pcm_data, user_id: str): - """ - Legacy method for recording audio data directly. + """Legacy method for recording audio data directly. This is a no-op since the current implementation uses track-based recording. """ logger.debug( - f"record_audio_data called for user {user_id} - this is a legacy method and will be ignored" + f"record_audio_data called for user {user_id} - this is a legacy method and will be ignored", ) pass diff --git a/getstream/video/rtc/signaling.py b/getstream/video/rtc/signaling.py index 9165048e..05524214 100644 --- a/getstream/video/rtc/signaling.py +++ b/getstream/video/rtc/signaling.py @@ -1,11 +1,14 @@ import asyncio -import threading -import websocket import logging +import threading import time -from typing import Any, Callable, Awaitable +from collections.abc import Awaitable +from typing import Any, Callable + +import websocket from getstream.utils import StreamAsyncIOEventEmitter + from .pb.stream.video.sfu.event import events_pb2 logger = logging.getLogger(__name__) @@ -18,8 +21,7 @@ class SignalingError(Exception): class WebSocketClient(StreamAsyncIOEventEmitter): - """ - WebSocket client for Stream Video signaling. + """WebSocket client for Stream Video signaling. Handles WebSocket connection, message serialization/deserialization, and event handling. """ @@ -29,13 +31,13 @@ def __init__( join_request: events_pb2.JoinRequest, main_loop: asyncio.AbstractEventLoop, ): - """ - Initialize a new WebSocket client. + """Initialize a new WebSocket client. Args: url: The WebSocket server URL join_request: The JoinRequest protobuf message to send for authentication main_loop: The main asyncio event loop to run callbacks on + """ super().__init__() self.url = url @@ -54,14 +56,14 @@ def __init__( self.ping_interval = 10 # seconds async def connect(self): - """ - Establish WebSocket connection and authenticate. + """Establish WebSocket connection and authenticate. Returns: The first SfuEvent received (JoinResponse if successful) Raises: SignalingError: If the connection fails or the first message is an error + """ if self.closed: raise SignalingError("Cannot reconnect a closed WebSocket client") @@ -84,7 +86,8 @@ async def connect(self): # Wait for first message await asyncio.get_event_loop().run_in_executor( - None, self.first_message_event.wait + None, + self.first_message_event.wait, ) # Check if the first message is an error @@ -114,7 +117,6 @@ def _on_open(self, ws): def _on_message(self, ws, message): """Handle incoming WebSocket messages.""" - event = events_pb2.SfuEvent() event.ParseFromString(message) logger.debug(f"WebSocket message received {event.WhichOneof('event_payload')}") @@ -179,7 +181,7 @@ def _ping_loop(self): # Create and send health check request health_check_req = events_pb2.HealthCheckRequest() sfu_request = events_pb2.SfuRequest( - health_check_request=health_check_req + health_check_request=health_check_req, ) # Send the serialized request @@ -192,11 +194,11 @@ def _ping_loop(self): time.sleep(self.ping_interval) async def _dispatch_event(self, event): - """ - Dispatch an event to the appropriate handlers using emit. + """Dispatch an event to the appropriate handlers using emit. Args: event: The SfuEvent to dispatch + """ # Get the oneof event type event_type = event.WhichOneof("event_payload") @@ -206,12 +208,12 @@ async def _dispatch_event(self, event): self.emit(event_type, payload) def on_event(self, event_type: str, callback: Callable[[Any], Awaitable[None]]): - """ - Register an event handler for a specific event type. + """Register an event handler for a specific event type. Args: event_type: The event type to listen for, or "*" for all events callback: An async function to call when the event occurs + """ # Use the parent class's on method for regular events # For wildcard events, use on_wildcard method diff --git a/getstream/video/rtc/track_util.py b/getstream/video/rtc/track_util.py index 3130d9ab..04696cf0 100644 --- a/getstream/video/rtc/track_util.py +++ b/getstream/video/rtc/track_util.py @@ -1,12 +1,11 @@ import asyncio - -import av -import numpy as np +import logging import re -from typing import Dict, Any, NamedTuple, Callable, Optional +from typing import Any, Callable, Dict, NamedTuple, Optional -import logging import aiortc +import av +import numpy as np from aiortc import MediaStreamTrack from aiortc.mediastreams import MediaStreamError from numpy.typing import NDArray @@ -15,8 +14,7 @@ class PcmData(NamedTuple): - """ - A named tuple representing PCM audio data. + """A named tuple representing PCM audio data. Attributes: format: The format of the audio data. @@ -25,6 +23,7 @@ class PcmData(NamedTuple): pts: The presentation timestamp of the audio data. dts: The decode timestamp of the audio data. time_base: The time base for converting timestamps to seconds. + """ format: str @@ -36,11 +35,11 @@ class PcmData(NamedTuple): @property def duration(self) -> float: - """ - Calculate the duration of the audio data in seconds. + """Calculate the duration of the audio data in seconds. Returns: float: Duration in seconds. + """ # The samples field contains a numpy array of audio samples # For s16 format, each element in the array is one sample (int16) @@ -66,7 +65,7 @@ def duration(self) -> float: num_samples = len(self.samples) except TypeError: logger.warning( - f"Cannot determine sample count for type {type(self.samples)}" + f"Cannot determine sample count for type {type(self.samples)}", ) return 0.0 @@ -87,8 +86,7 @@ def dts_seconds(self) -> Optional[float]: def patch_sdp_offer(sdp: str) -> str: - """ - Patches an SDP offer to ensure consistent ICE and DTLS parameters across all media sections. + """Patches an SDP offer to ensure consistent ICE and DTLS parameters across all media sections. This function: 1. Ensures all media descriptions have the same ice-ufrag, ice-pwd, and fingerprint values @@ -101,6 +99,7 @@ def patch_sdp_offer(sdp: str) -> str: Returns: The modified SDP string with consistent parameters across all media sections. + """ # Parse the SDP session = aiortc.sdp.SessionDescription.parse(sdp) @@ -141,8 +140,7 @@ def patch_sdp_offer(sdp: str) -> str: def fix_sdp_msid_semantic(sdp: str) -> str: - """ - Fix SDP msid-semantic format by ensuring there is a space after "WMS". + """Fix SDP msid-semantic format by ensuring there is a space after "WMS". The WebRTC spec requires a space between "WMS" and any identifiers. Some SDPs may incorrectly have "WMS*" instead of "WMS *" which can @@ -153,6 +151,7 @@ def fix_sdp_msid_semantic(sdp: str) -> str: Returns: The fixed SDP string + """ return re.sub(r"a=msid-semantic:WMS\*", r"a=msid-semantic:WMS *", sdp) @@ -246,14 +245,14 @@ def stop(self): async def detect_video_properties( video_track: aiortc.mediastreams.MediaStreamTrack, ) -> Dict[str, Any]: - """ - Detect video track properties by peeking at frames. + """Detect video track properties by peeking at frames. Args: video_track: A video MediaStreamTrack Returns: Dict containing width (int), height (int), fps (int), and bitrate (int) in kbps + """ logger.info("Detecting video track properties") @@ -316,7 +315,7 @@ async def detect_video_properties( logger.warning("Cannot calculate FPS: zero or negative time delta") else: logger.warning( - "Cannot calculate FPS: missing PTS or time_base information" + "Cannot calculate FPS: missing PTS or time_base information", ) except Exception as e: logger.warning(f"Error calculating FPS: {e}, using default 30 fps") @@ -351,7 +350,7 @@ async def detect_video_properties( logger.info(f"Detected video properties: {width}x{height} at {fps}fps") logger.info( - f"Calculated bitrate: {bitrate} kbps (based on {bits_per_pixel} bits/pixel)" + f"Calculated bitrate: {bitrate} kbps (based on {bits_per_pixel} bits/pixel)", ) return {"width": width, "height": height, "fps": fps, "bitrate": bitrate} @@ -373,16 +372,16 @@ async def detect_video_properties( class AudioTrackHandler: - """ - A helper to receive raw PCM data from an aiortc AudioStreamTrack + """A helper to receive raw PCM data from an aiortc AudioStreamTrack and feed it into the provided callback. """ def __init__( - self, track: MediaStreamTrack, on_audio_frame: Callable[[PcmData], Any] + self, + track: MediaStreamTrack, + on_audio_frame: Callable[[PcmData], Any], ): - """ - :param track: The incoming audio track (from `pc.on("track")`). + """:param track: The incoming audio track (from `pc.on("track")`). :param on_audio_frame: A callback function that will receive the PCM data as a NumPy array (int16). """ @@ -392,16 +391,12 @@ def __init__( self._stopped = False async def start(self): - """ - Start reading frames from the track in a background task. - """ + """Start reading frames from the track in a background task.""" if self._task is None: self._task = asyncio.create_task(self._run_track()) async def stop(self): - """ - Stop reading frames and clean up. - """ + """Stop reading frames and clean up.""" self._stopped = True if self._task: self._task.cancel() @@ -412,10 +407,7 @@ async def stop(self): self._task = None async def _run_track(self): - """ - Internal coroutine that continuously pulls frames from the track. - """ - + """Internal coroutine that continuously pulls frames from the track.""" while not self._stopped: try: frame = await self.track.recv() @@ -439,7 +431,7 @@ async def _run_track(self): pcm_ndarray = frame.to_ndarray() except Exception as plane_error: logger.error( - f"Error converting audio frame to ndarray: {plane_error}, dropping frame" + f"Error converting audio frame to ndarray: {plane_error}, dropping frame", ) break @@ -454,7 +446,7 @@ async def _run_track(self): except ValueError as e: logger.error( f"Error reshaping stereo audio: {e}. " - f"Original shape: {pcm_ndarray.shape}, channels: {len(frame.layout.channels)}" + f"Original shape: {pcm_ndarray.shape}, channels: {len(frame.layout.channels)}", ) break @@ -470,7 +462,7 @@ async def _run_track(self): time_base = float(frame.time_base) except (TypeError, ValueError): logger.warning( - f"Could not convert time_base to float: {frame.time_base}" + f"Could not convert time_base to float: {frame.time_base}", ) time_base = None @@ -482,5 +474,5 @@ async def _run_track(self): pts=pts, dts=dts, time_base=time_base, - ) + ), ) diff --git a/getstream/video/rtc/tracks.py b/getstream/video/rtc/tracks.py index 3ca661e4..f7622f54 100644 --- a/getstream/video/rtc/tracks.py +++ b/getstream/video/rtc/tracks.py @@ -1,20 +1,19 @@ -""" -Consolidated track management module for video streaming. +"""Consolidated track management module for video streaming. Handles track publishing, subscription configuration, and subscription management. """ import asyncio import logging from dataclasses import dataclass, field -from typing import Dict, List, Optional, Any +from typing import Any, Dict, List, Optional from pyee.asyncio import AsyncIOEventEmitter -from getstream.video.rtc.pb.stream.video.sfu.models.models_pb2 import VideoDimension +from getstream.video.rtc.pb.stream.video.sfu.models import models_pb2 from getstream.video.rtc.pb.stream.video.sfu.models.models_pb2 import ( TrackType, + VideoDimension, ) -from getstream.video.rtc.pb.stream.video.sfu.models import models_pb2 from getstream.video.rtc.pb.stream.video.sfu.signal_rpc import signal_pb2 from getstream.video.rtc.twirp_client_wrapper import SfuRpcError @@ -30,10 +29,10 @@ class TrackSubscriptionConfig: # Preferred dimensions video_dimension: VideoDimension = field( - default_factory=lambda: VideoDimension(width=1920, height=1080) + default_factory=lambda: VideoDimension(width=1920, height=1080), ) screenshare_dimension: VideoDimension = field( - default_factory=lambda: VideoDimension(width=1920, height=1080) + default_factory=lambda: VideoDimension(width=1920, height=1080), ) @@ -49,10 +48,11 @@ class SubscriptionConfig: Mapping of role β†’ rule. max_subscriptions : Optional[int] Global cap on active subscriptions. + """ default: TrackSubscriptionConfig = field( - default_factory=lambda: TrackSubscriptionConfig() + default_factory=lambda: TrackSubscriptionConfig(), ) role_filters: Dict[str, TrackSubscriptionConfig] = field(default_factory=dict) max_subscriptions: Optional[int] = None @@ -71,7 +71,8 @@ def __init__( self._subscription_config = subscription_config or SubscriptionConfig() self._subscribed_track_details: List[signal_pb2.TrackSubscriptionDetails] = [] self._expected_tracks: Dict[ - str, models_pb2.Participant + str, + models_pb2.Participant, ] = {} # track_type:user_id:session_id -> participant self._received_track_order: List[ str @@ -79,7 +80,8 @@ def __init__( self._lock = asyncio.Lock() def _get_role_config( - self, participant: Optional[models_pb2.Participant] + self, + participant: Optional[models_pb2.Participant], ) -> Optional[TrackSubscriptionConfig]: """Determine which TrackSubscriptionConfig applies to the participant.""" if not self._subscription_config: @@ -94,7 +96,9 @@ def _get_role_config( return self._subscription_config.default def _should_subscribe( - self, participant: Optional[models_pb2.Participant], track_type: int + self, + participant: Optional[models_pb2.Participant], + track_type: int, ) -> bool: """Check if a given track should be subscribed according to role config.""" cfg = self._get_role_config(participant) @@ -199,13 +203,13 @@ async def _update_subscriptions(self): async def handle_track_published(self, event): """Handle new remote track publications from the SFU.""" logger.error( - f"Handling track published: {event.user_id} - {event.session_id} - {event.type}" + f"Handling track published: {event.user_id} - {event.session_id} - {event.type}", ) try: # Keep participants state up-to-date if hasattr(event, "participant"): self.connection_manager.participants_state.add_participant( - event.participant + event.participant, ) # Register expected track for this user @@ -220,7 +224,8 @@ async def handle_track_published(self, event): return if not self._should_subscribe( - getattr(event, "participant", None), track_type + getattr(event, "participant", None), + track_type, ): logger.error(f"Not subscribing to track: {event}") return @@ -243,7 +248,7 @@ async def handle_track_published(self, event): >= self._subscription_config.max_subscriptions ): logger.error( - "Max subscription limit reached, skipping new track subscription" + "Max subscription limit reached, skipping new track subscription", ) return diff --git a/getstream/video/rtc/twirp_async_client_embed.py b/getstream/video/rtc/twirp_async_client_embed.py index ce8780b3..9cb8b4d4 100644 --- a/getstream/video/rtc/twirp_async_client_embed.py +++ b/getstream/video/rtc/twirp_async_client_embed.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Code copied directly from: # https://github.com/verloop/twirpy/blob/main/twirp/async_client.py # Embedded here as the library doesn't seem to ship it correctly. @@ -13,13 +12,14 @@ # Assuming these modules exist within the installed twirp package # or need to be embedded as well if they are also problematic. # For now, assume they are available from the installed 'twirp' base. -from twirp import exceptions -from twirp import errors +from twirp import errors, exceptions class AsyncTwirpClient: def __init__( - self, address: str, session: Optional[aiohttp.ClientSession] = None + self, + address: str, + session: Optional[aiohttp.ClientSession] = None, ) -> None: # Ensure address ends with a slash for urljoin to work correctly if not address.endswith("/"): @@ -28,7 +28,14 @@ def __init__( self._session = session async def _make_request( - self, *, url, ctx, request, response_obj, session=None, **kwargs + self, + *, + url, + ctx, + request, + response_obj, + session=None, + **kwargs, ): headers = ctx.get_headers() if "headers" in kwargs: @@ -51,7 +58,9 @@ async def _make_request( try: # Use the correctly constructed full_url async with await session.post( - url=full_url, data=request.SerializeToString(), **kwargs + url=full_url, + data=request.SerializeToString(), + **kwargs, ) as resp: if resp.status == 200: response = response_obj() @@ -61,7 +70,10 @@ async def _make_request( raise exceptions.TwirpServerException.from_json(await resp.json()) except (aiohttp.ContentTypeError, json.JSONDecodeError): raise exceptions.twirp_error_from_intermediary( - resp.status, resp.reason, resp.headers, await resp.text() + resp.status, + resp.reason, + resp.headers, + await resp.text(), ) from None except asyncio.TimeoutError as e: raise exceptions.TwirpServerException( diff --git a/getstream/video/rtc/twirp_client_wrapper.py b/getstream/video/rtc/twirp_client_wrapper.py index 433b5b88..6557a376 100644 --- a/getstream/video/rtc/twirp_client_wrapper.py +++ b/getstream/video/rtc/twirp_client_wrapper.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- -import logging -import functools import asyncio # Import asyncio if not already present +import functools +import logging + from getstream.video.rtc.pb.stream.video.sfu.models import models_pb2 from getstream.video.rtc.pb.stream.video.sfu.signal_rpc.signal_twirp import ( AsyncSignalServerClient, @@ -20,7 +20,7 @@ def __init__(self, code, message, method_name): self.message = message self.method_name = method_name super().__init__( - f"SFU RPC Error in {method_name} - Code: {code}, Message: {message}" + f"SFU RPC Error in {method_name} - Code: {code}, Message: {message}", ) @@ -74,10 +74,9 @@ async def wrapped_method(*args, **kwargs): # Return the dynamic wrapper return wrapped_method - else: - # For non-callable attributes, or non-async methods, or private methods, - # return the original attribute directly. - return original_attr + # For non-callable attributes, or non-async methods, or private methods, + # return the original attribute directly. + return original_attr # No need to explicitly override each method like SetPublisher, SendAnswer etc. # __getattribute__ handles them dynamically. diff --git a/getstream/video/rtc/utils.py b/getstream/video/rtc/utils.py index e2c7d4ee..9c6ccc18 100644 --- a/getstream/video/rtc/utils.py +++ b/getstream/video/rtc/utils.py @@ -3,8 +3,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: - """ - Helper function to open browser with Stream call link. + """Helper function to open browser with Stream call link. This utility function is useful for example projects and demos that need to provide a quick way for users to join a call via browser. @@ -16,6 +15,7 @@ def open_browser(api_key: str, token: str, call_id: str) -> str: Returns: The URL that was opened + """ base_url = "https://pronto.getstream.io/bare/join/" params = {"api_key": api_key, "token": token, "skip_lobby": "true"} diff --git a/pyproject.toml b/pyproject.toml index a67f0773..b2791baa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,5 +127,4 @@ exclude = [ ] [tool.ruff.lint] -extend-select = ["I"] ignore = ["F405", "F403"] diff --git a/scripts/create_test_assets.py b/scripts/create_test_assets.py index f08fcc49..76b4a54f 100644 --- a/scripts/create_test_assets.py +++ b/scripts/create_test_assets.py @@ -1,6 +1,7 @@ import os -import torchaudio + import torch +import torchaudio def create_test_assets(): @@ -35,7 +36,8 @@ def create_test_assets(): # Create 48 kHz version print(f"Creating 48 kHz version: {output_48k}") resampler_48k = torchaudio.transforms.Resample( - orig_freq=sample_rate, new_freq=48000 + orig_freq=sample_rate, + new_freq=48000, ) resampled_waveform_48k = resampler_48k(waveform) torchaudio.save(output_48k, resampled_waveform_48k, sample_rate=48000) @@ -45,7 +47,8 @@ def create_test_assets(): # Create 16 kHz version print(f"Creating 16 kHz version: {output_16k}") resampler_16k = torchaudio.transforms.Resample( - orig_freq=sample_rate, new_freq=16000 + orig_freq=sample_rate, + new_freq=16000, ) resampled_waveform_16k = resampler_16k(waveform) torchaudio.save(output_16k, resampled_waveform_16k, sample_rate=16000) diff --git a/tests/base.py b/tests/base.py index ce858b2a..8ff899af 100644 --- a/tests/base.py +++ b/tests/base.py @@ -6,17 +6,14 @@ class VideoTestClass(ABC): - """ - Abstract base class for video tests that need to share the same call and client objects using pytest fixture - """ + """Abstract base class for video tests that need to share the same call and client objects using pytest fixture""" client: Stream call: Call def wait_for_task(client, task_id, timeout_ms=10000, poll_interval_ms=1000): - """ - Wait until the task is completed or timeout is reached. + """Wait until the task is completed or timeout is reached. Args: client: The client used to make the API call. @@ -29,6 +26,7 @@ def wait_for_task(client, task_id, timeout_ms=10000, poll_interval_ms=1000): Raises: TimeoutError: If the task is not completed within the timeout period. + """ start_time = time.time() * 1000 # Convert to milliseconds while True: @@ -37,6 +35,6 @@ def wait_for_task(client, task_id, timeout_ms=10000, poll_interval_ms=1000): return response if (time.time() * 1000) - start_time > timeout_ms: raise TimeoutError( - f"Task {task_id} did not complete within {timeout_ms} seconds" + f"Task {task_id} did not complete within {timeout_ms} seconds", ) time.sleep(poll_interval_ms / 1000.0) diff --git a/tests/conftest.py b/tests/conftest.py index 685c8ea6..5813ae9c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,9 @@ -import pytest import os + +import pytest from dotenv import load_dotenv -from tests.fixtures import client, call, get_user, shared_call + +from tests.fixtures import call, client, get_user, shared_call __all__ = ["client", "call", "get_user", "shared_call"] @@ -14,7 +16,8 @@ def load_env(): def pytest_configure(config): """Register custom markers.""" config.addinivalue_line( - "markers", "skip_in_ci: mark test to skip when running in CI" + "markers", + "skip_in_ci: mark test to skip when running in CI", ) diff --git a/tests/fixtures.py b/tests/fixtures.py index e68ac2a8..46f6ad3c 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -4,7 +4,7 @@ import pytest from getstream import Stream -from getstream.models import UserRequest, FullUserResponse +from getstream.models import FullUserResponse, UserRequest def _client(): @@ -23,10 +23,7 @@ def call(client: Stream): @pytest.fixture(scope="class") def shared_call(request): - """ - Use this fixture to decorate test classes subclassing base.VideoTestClass - - """ + """Use this fixture to decorate test classes subclassing base.VideoTestClass""" request.cls.client = _client() request.cls.call = request.cls.client.video.call("default", str(uuid.uuid4())) @@ -34,7 +31,9 @@ def shared_call(request): @pytest.fixture def get_user(client: Stream): def inner( - name: str = None, image: str = None, custom: Dict[str, object] = None + name: str = None, + image: str = None, + custom: Dict[str, object] = None, ) -> FullUserResponse: id = str(uuid.uuid4()) return client.upsert_users( diff --git a/tests/rtc/coordinator/test_backoff.py b/tests/rtc/coordinator/test_backoff.py index dae9ccfe..9f883aca 100644 --- a/tests/rtc/coordinator/test_backoff.py +++ b/tests/rtc/coordinator/test_backoff.py @@ -1,8 +1,7 @@ -""" -Tests for exponential backoff utilities. -""" +"""Tests for exponential backoff utilities.""" import pytest + from getstream.video.rtc.coordinator.backoff import exp_backoff @@ -16,9 +15,9 @@ async def test_exp_backoff_default_parameters(): async for delay in exp_backoff(max_retries=max_retries): actual_delays.append(delay) - assert ( - actual_delays == expected_delays - ), f"Expected delays {expected_delays}, but got {actual_delays}" + assert actual_delays == expected_delays, ( + f"Expected delays {expected_delays}, but got {actual_delays}" + ) @pytest.mark.asyncio @@ -33,9 +32,9 @@ async def test_exp_backoff_custom_parameters(): async for delay in exp_backoff(max_retries=max_retries, base=base, factor=factor): actual_delays.append(delay) - assert ( - actual_delays == expected_delays - ), f"Expected delays {expected_delays}, but got {actual_delays}" + assert actual_delays == expected_delays, ( + f"Expected delays {expected_delays}, but got {actual_delays}" + ) @pytest.mark.asyncio @@ -48,9 +47,9 @@ async def test_exp_backoff_zero_retries(): async for delay in exp_backoff(max_retries=max_retries): actual_delays.append(delay) - assert ( - actual_delays == expected_delays - ), f"Expected no delays for zero retries, but got {actual_delays}" + assert actual_delays == expected_delays, ( + f"Expected no delays for zero retries, but got {actual_delays}" + ) @pytest.mark.asyncio @@ -64,9 +63,9 @@ async def test_exp_backoff_single_retry(): async for delay in exp_backoff(max_retries=max_retries, base=base): actual_delays.append(delay) - assert ( - actual_delays == expected_delays - ), f"Expected delays {expected_delays}, but got {actual_delays}" + assert actual_delays == expected_delays, ( + f"Expected delays {expected_delays}, but got {actual_delays}" + ) @pytest.mark.asyncio @@ -81,6 +80,6 @@ async def test_exp_backoff_fractional_factor(): async for delay in exp_backoff(max_retries=max_retries, base=base, factor=factor): actual_delays.append(delay) - assert ( - actual_delays == expected_delays - ), f"Expected delays {expected_delays}, but got {actual_delays}" + assert actual_delays == expected_delays, ( + f"Expected delays {expected_delays}, but got {actual_delays}" + ) diff --git a/tests/rtc/coordinator/test_connect.py b/tests/rtc/coordinator/test_connect.py index 78f60cf4..dfde3810 100644 --- a/tests/rtc/coordinator/test_connect.py +++ b/tests/rtc/coordinator/test_connect.py @@ -1,6 +1,4 @@ -""" -Tests for StreamAPIWS connect and disconnect functionality. -""" +"""Tests for StreamAPIWS connect and disconnect functionality.""" import asyncio import json @@ -8,12 +6,12 @@ import pytest import websockets -from getstream.video.rtc.coordinator.ws import StreamAPIWS +from getstream.stream import Stream from getstream.video.rtc.coordinator.errors import ( StreamWSAuthError, StreamWSConnectionError, ) -from getstream.stream import Stream +from getstream.video.rtc.coordinator.ws import StreamAPIWS @pytest.mark.asyncio @@ -266,7 +264,6 @@ def on_connection_ok(data): @pytest.mark.asyncio async def test_auth_payload_structure(): """Test that authentication payload has correct structure.""" - # Mock server that captures and validates auth payload captured_auth = None @@ -319,7 +316,9 @@ async def mock_server(websocket): async def test_disconnect_without_connect(): """Test that disconnect works even if never connected.""" client = StreamAPIWS( - api_key="test_key", token="test_token", user_details={"id": "test_user"} + api_key="test_key", + token="test_token", + user_details={"id": "test_user"}, ) # Should not raise an exception @@ -331,7 +330,9 @@ async def test_disconnect_without_connect(): async def test_integration_test_simple(client: Stream): token = client.create_token("user_id") ws_client = StreamAPIWS( - api_key=client.api_key, token=token, user_details={"id": "user_id"} + api_key=client.api_key, + token=token, + user_details={"id": "user_id"}, ) response = await ws_client.connect() assert response["type"] == "connection.ok" @@ -341,7 +342,9 @@ async def test_integration_test_simple(client: Stream): @pytest.mark.asyncio async def test_integration_test_bad_auth_raises(client: Stream): ws_client = StreamAPIWS( - api_key=client.api_key, token="tok", user_details={"id": "xxx"} + api_key=client.api_key, + token="tok", + user_details={"id": "xxx"}, ) # Test that authentication error is raised with invalid token diff --git a/tests/rtc/coordinator/test_errors.py b/tests/rtc/coordinator/test_errors.py index 4554c521..47204b0c 100644 --- a/tests/rtc/coordinator/test_errors.py +++ b/tests/rtc/coordinator/test_errors.py @@ -1,12 +1,11 @@ -""" -Tests for WebSocket coordinator exception hierarchy. -""" +"""Tests for WebSocket coordinator exception hierarchy.""" import pytest + from getstream.video.rtc.coordinator.errors import ( - StreamWSException, StreamWSAuthError, StreamWSConnectionError, + StreamWSException, StreamWSMaxRetriesExceeded, ) diff --git a/tests/rtc/coordinator/test_heartbeat.py b/tests/rtc/coordinator/test_heartbeat.py index b453c7e1..6dbf4c37 100644 --- a/tests/rtc/coordinator/test_heartbeat.py +++ b/tests/rtc/coordinator/test_heartbeat.py @@ -1,19 +1,18 @@ -""" -Tests for StreamAPIWS heartbeat and reconnection functionality. -""" +"""Tests for StreamAPIWS heartbeat and reconnection functionality.""" import asyncio import json -import pytest import time from unittest.mock import patch + +import pytest import websockets -from getstream.video.rtc.coordinator.ws import StreamAPIWS from getstream.video.rtc.coordinator.errors import ( StreamWSConnectionError, StreamWSMaxRetriesExceeded, ) +from getstream.video.rtc.coordinator.ws import StreamAPIWS @pytest.mark.asyncio @@ -60,16 +59,16 @@ async def mock_server(websocket): await client.disconnect() # Verify heartbeats were sent - assert ( - len(heartbeats_received) >= 2 - ), f"Expected at least 2 heartbeats, got {len(heartbeats_received)}" + assert len(heartbeats_received) >= 2, ( + f"Expected at least 2 heartbeats, got {len(heartbeats_received)}" + ) # Verify timing between heartbeats if len(heartbeats_received) >= 2: interval = heartbeats_received[1] - heartbeats_received[0] - assert ( - 0.08 <= interval <= 0.15 - ), f"Heartbeat interval should be ~0.1s, got {interval}" + assert 0.08 <= interval <= 0.15, ( + f"Heartbeat interval should be ~0.1s, got {interval}" + ) finally: server.close() @@ -127,9 +126,9 @@ async def mock_server(websocket): await client.disconnect() # Verify reconnection occurred - assert ( - len(connection_attempts) >= 2 - ), f"Expected at least 2 connection attempts, got {len(connection_attempts)}" + assert len(connection_attempts) >= 2, ( + f"Expected at least 2 connection attempts, got {len(connection_attempts)}" + ) finally: server.close() @@ -184,9 +183,9 @@ async def mock_server(websocket): await client.disconnect() # Verify reconnection occurred - assert ( - len(connection_attempts) >= 2 - ), f"Expected at least 2 connection attempts, got {len(connection_attempts)}" + assert len(connection_attempts) >= 2, ( + f"Expected at least 2 connection attempts, got {len(connection_attempts)}" + ) finally: server.close() @@ -251,7 +250,7 @@ async def mock_server(websocket): await asyncio.sleep(0.1) await websocket.send( - json.dumps({"type": "another.message", "data": "test2"}) + json.dumps({"type": "another.message", "data": "test2"}), ) # Keep server alive @@ -290,9 +289,9 @@ def on_another_message(data): await asyncio.sleep(0.5) # Wait for messages to arrive # Verify messages were received - assert ( - len(messages_received) >= 2 - ), f"Expected at least 2 messages, got {len(messages_received)}" + assert len(messages_received) >= 2, ( + f"Expected at least 2 messages, got {len(messages_received)}" + ) # Verify last_received was updated (should be more recent than initial connection) assert client._last_received > initial_time @@ -352,7 +351,6 @@ async def mock_server(websocket): @pytest.mark.asyncio async def test_heartbeat_includes_client_id(): """Test that heartbeats include client_id when available.""" - # Track sent messages sent_messages = [] @@ -423,7 +421,6 @@ async def mock_server(websocket): @pytest.mark.asyncio async def test_heartbeat_without_client_id(): """Test that heartbeats work when no client_id is available.""" - # Track sent messages sent_messages = [] diff --git a/tests/rtc/test_audio_track.py b/tests/rtc/test_audio_track.py index 5eb98add..3579d2ed 100644 --- a/tests/rtc/test_audio_track.py +++ b/tests/rtc/test_audio_track.py @@ -1,5 +1,5 @@ -import pytest import numpy as np +import pytest from getstream.video.rtc.audio_track import AudioStreamTrack diff --git a/tests/rtc/test_join.py b/tests/rtc/test_join.py index ac13873c..24b7f217 100644 --- a/tests/rtc/test_join.py +++ b/tests/rtc/test_join.py @@ -1,16 +1,17 @@ -import pytest -from getstream.stream import Stream -from getstream.video import rtc -from getstream.video.rtc import audio_track -import os # Import os for path manipulation import asyncio +import logging +import os # Import os for path manipulation +import time import uuid from multiprocessing import Process, Value -import time +from typing import Any, Callable + +import pytest from dotenv import load_dotenv -import logging -from typing import Callable, Any +from getstream.stream import Stream +from getstream.video import rtc +from getstream.video.rtc import audio_track from getstream.video.rtc.track_util import PcmData pytest.skip("Skipping RTC join tests during regular test runs", allow_module_level=True) @@ -28,8 +29,7 @@ def run_process_with_stream_client( task_func: Callable[[Stream, Any, str, int, logging.Logger], Any], *args, ): - """ - Common function to handle process setup for Stream client processes. + """Common function to handle process setup for Stream client processes. Args: process_type: String identifier for the process ('SENDER' or 'RECEIVER') @@ -38,11 +38,13 @@ def run_process_with_stream_client( test_timeout: Timeout in seconds task_func: The specific task function to run in the process *args: Additional arguments to pass to the task function + """ async def _async_runner(): import logging import sys + from getstream.stream import Stream # Configure logging in this process @@ -74,7 +76,7 @@ async def _async_runner(): flush=True, ) raise ValueError( - "API credentials not found. Make sure STREAM_API_KEY and STREAM_API_SECRET are set." + "API credentials not found. Make sure STREAM_API_KEY and STREAM_API_SECRET are set.", ) client = Stream(api_key=api_key, api_secret=api_secret) @@ -110,7 +112,7 @@ async def receiver_task( @connection.on("audio") async def audio_handler(pcm: PcmData, participant): logger.info( - f"Audio event received: {len(pcm.samples)} bytes of PCM data from {participant}" + f"Audio event received: {len(pcm.samples)} bytes of PCM data from {participant}", ) # Set the shared flag to indicate audio was received received_flag.value = 1 @@ -160,14 +162,23 @@ async def sender_task( # Function to run the receiver in a separate process - defined at module level def run_receiver(call_id, received_flag, file_path, test_timeout): run_process_with_stream_client( - "RECEIVER", call_id, file_path, test_timeout, receiver_task, received_flag + "RECEIVER", + call_id, + file_path, + test_timeout, + receiver_task, + received_flag, ) # Function to run the sender in a separate process - defined at module level def run_sender(call_id, file_path, test_timeout): run_process_with_stream_client( - "SENDER", call_id, file_path, test_timeout, sender_task + "SENDER", + call_id, + file_path, + test_timeout, + sender_task, ) @@ -187,7 +198,6 @@ async def test_join_two_users(client: Stream): Uses separate processes for sender and receiver to ensure complete isolation. """ - # Configure logging in the main process logger = logging.getLogger("test_join_two_users") @@ -258,9 +268,10 @@ async def test_join_two_users(client: Stream): @pytest.mark.asyncio async def test_detect_video_properties(client: Stream): from aiortc.contrib.media import MediaPlayer + from getstream.video.rtc.track_util import ( - detect_video_properties, BufferedMediaTrack, + detect_video_properties, ) file_path = "/Users/tommaso/src/data-samples/video/SampleVideo_1280x720_30mb.mp4" @@ -286,16 +297,16 @@ async def test_detect_video_properties(client: Stream): # Verify detected properties match expectations assert "width" in video_props, "Width not detected" assert "height" in video_props, "Height not detected" - assert ( - video_props["width"] == 1280 - ), f"Incorrect width detected: {video_props['width']}" - assert ( - video_props["height"] == 720 - ), f"Incorrect height detected: {video_props['height']}" + assert video_props["width"] == 1280, ( + f"Incorrect width detected: {video_props['width']}" + ) + assert video_props["height"] == 720, ( + f"Incorrect height detected: {video_props['height']}" + ) assert video_props["fps"] == 25, "Invalid FPS value" - assert ( - 1000 <= video_props["bitrate"] <= 2000 - ), f"Unexpected bitrate: {video_props['bitrate']}" + assert 1000 <= video_props["bitrate"] <= 2000, ( + f"Unexpected bitrate: {video_props['bitrate']}" + ) finally: # Ensure player is properly closed @@ -356,9 +367,9 @@ async def test_play_audio_track_from_text(client: Stream): @pytest.mark.asyncio async def test_full_echo(client: Stream): - from getstream.plugins.silero import SileroVAD from getstream.plugins.deepgram import DeepgramSTT from getstream.plugins.gs_elevenlabs import ElevenLabsTTS + from getstream.plugins.silero import SileroVAD audio = audio_track.AudioStreamTrack(framerate=16000) vad = SileroVAD() @@ -381,14 +392,14 @@ async def on_audio(pcm: PcmData, user): @vad.on("audio") async def on_speech_detected(pcm: PcmData, user): print( - f"{time.time()} Speech detected from user: {user} duration {pcm.duration}" + f"{time.time()} Speech detected from user: {user} duration {pcm.duration}", ) await stt.process_audio(pcm, None) @stt.on("transcript") async def on_transcript(text: str, user: Any, metadata: dict[str, Any]): print( - f"{time.time()} got text from audio, will echo back the transcript {text}" + f"{time.time()} got text from audio, will echo back the transcript {text}", ) await tts_instance.send(text) diff --git a/tests/rtc/test_location_discovery.py b/tests/rtc/test_location_discovery.py index bcfa69d0..449658cc 100644 --- a/tests/rtc/test_location_discovery.py +++ b/tests/rtc/test_location_discovery.py @@ -4,10 +4,10 @@ import pytest from getstream.video.rtc.location_discovery import ( - HTTPHintLocationDiscovery, - HEADER_CLOUDFRONT_POP, FALLBACK_LOCATION_NAME, + HEADER_CLOUDFRONT_POP, STREAM_PROD_URL, + HTTPHintLocationDiscovery, ) @@ -20,6 +20,7 @@ def setUp(self): self.discovery = HTTPHintLocationDiscovery( url=STREAM_PROD_URL, max_retries=3, + client=self.client_mock, logger=self.logger_mock, ) @@ -66,7 +67,7 @@ def test_discover_success(self, mock_https_connection): self.assertEqual(location, "IAD") # Verify that the correct HTTP request was made - mock_conn.request.assert_called_once_with("HEAD", "/", None, {}) + mock_conn.request.assert_called_once_with("HEAD", "/") mock_response.getheader.assert_called_once_with(HEADER_CLOUDFRONT_POP, "") mock_response.read.assert_called_once() mock_conn.close.assert_called_once() @@ -130,7 +131,8 @@ def test_discover_exception(self, mock_https_connection): # Verify that the warning was logged self.logger_mock.warning.assert_called_with( - "HEAD request failed: %s", "Connection error" + "HEAD request failed: %s", + "Connection error", ) @patch("http.client.HTTPSConnection") @@ -166,7 +168,8 @@ def test_discover_invalid_url(self): # Verify that the warning was logged self.logger_mock.warning.assert_called_with( - "Invalid URL format: %s", "invalid-url" + "Invalid URL format: %s", + "invalid-url", ) def test_discover_caching(self): diff --git a/tests/rtc/test_location_discovery_integration.py b/tests/rtc/test_location_discovery_integration.py index b641002c..40e262f3 100644 --- a/tests/rtc/test_location_discovery_integration.py +++ b/tests/rtc/test_location_discovery_integration.py @@ -1,16 +1,16 @@ -import pytest import logging +import pytest + from getstream.video.rtc.location_discovery import ( - HTTPHintLocationDiscovery, FALLBACK_LOCATION_NAME, + HTTPHintLocationDiscovery, ) @pytest.mark.integration def test_real_discovery(): - """ - Integration test that connects to the real Stream hint URL. + """Integration test that connects to the real Stream hint URL. This test requires network access and will make a real HTTP request. It's marked with integration to make it easy to exclude from regular test runs. @@ -29,8 +29,7 @@ def test_real_discovery(): @pytest.mark.integration def test_cached_discovery(): - """ - Test that the discovery is cached and only makes one HTTP request. + """Test that the discovery is cached and only makes one HTTP request. This test requires network access and will make a real HTTP request. """ @@ -57,8 +56,7 @@ def test_cached_discovery(): @pytest.mark.integration def test_fallback_with_invalid_url(): - """ - Test that discovery falls back to the default location with an invalid URL. + """Test that discovery falls back to the default location with an invalid URL. This test doesn't require network access since it should fail immediately. """ diff --git a/tests/rtc/test_logging.py b/tests/rtc/test_logging.py index 89c2a54c..a3aa5a0f 100644 --- a/tests/rtc/test_logging.py +++ b/tests/rtc/test_logging.py @@ -1,5 +1,5 @@ -import logging import io +import logging from getstream.utils import configure_logging from getstream.video.rtc import logger as rtc_logger diff --git a/tests/rtc/test_track_util.py b/tests/rtc/test_track_util.py index 081e2943..36cebfd1 100644 --- a/tests/rtc/test_track_util.py +++ b/tests/rtc/test_track_util.py @@ -1,12 +1,12 @@ import asyncio -import pytest -import numpy as np -from unittest.mock import Mock import logging +from unittest.mock import Mock -from getstream.video.rtc.track_util import AudioTrackHandler, PcmData -import getstream.video.rtc.track_util as track_util +import numpy as np +import pytest +from getstream.video.rtc import track_util +from getstream.video.rtc.track_util import AudioTrackHandler, PcmData logger = logging.getLogger(__name__) @@ -29,7 +29,10 @@ def __init__(self, sample_rate=48000, samples=None, channels=1): samples = np.random.randint(-1000, 1000, num_samples, dtype=np.int16) else: samples = np.random.randint( - -1000, 1000, (num_samples, channels), dtype=np.int16 + -1000, + 1000, + (num_samples, channels), + dtype=np.int16, ) self._samples = samples @@ -38,8 +41,7 @@ def to_ndarray(self, format="s16"): """Mock the to_ndarray method that's causing issues in newer PyAV.""" if format == "s16": return self._samples - else: - raise ValueError(f"Unsupported format: {format}") + raise ValueError(f"Unsupported format: {format}") class MockAudioTrack: @@ -56,7 +58,7 @@ async def recv(self): if self.should_raise: if isinstance(self.should_raise, Exception): raise self.should_raise - elif self.should_raise == "cancelled": + if self.should_raise == "cancelled": raise asyncio.CancelledError() if self.frame_index >= len(self.frames): @@ -76,7 +78,6 @@ def _monkeypatch_audio_frame(monkeypatch): and ``to_ndarray``). This covers both ``MockAudioFrame`` and ad-hoc classes like ``BrokenAudioFrame`` defined inside individual tests. """ - real_audio_frame_cls = track_util.av.AudioFrame class _AudioFrameMeta(type): @@ -171,14 +172,16 @@ def callback(pcm_data: PcmData): assert received_data[0].sample_rate == 48000 assert received_data[0].format == "s16" np.testing.assert_array_equal( - received_data[0].samples, np.array([1, 2, 3, 4], dtype=np.int16) + received_data[0].samples, + np.array([1, 2, 3, 4], dtype=np.int16), ) # Check second frame assert received_data[1].sample_rate == 48000 assert received_data[1].format == "s16" np.testing.assert_array_equal( - received_data[1].samples, np.array([5, 6, 7, 8], dtype=np.int16) + received_data[1].samples, + np.array([5, 6, 7, 8], dtype=np.int16), ) @@ -187,7 +190,8 @@ async def test_run_track_stereo_audio(): """Test _run_track with stereo audio frames (should convert to mono).""" # Create mock frames with stereo audio stereo_samples = np.array( - [[1, 2], [3, 4], [5, 6], [7, 8]], dtype=np.int16 + [[1, 2], [3, 4], [5, 6], [7, 8]], + dtype=np.int16, ) # 4 samples, 2 channels frames = [ MockAudioFrame(sample_rate=48000, samples=stereo_samples, channels=2), @@ -215,7 +219,8 @@ def callback(pcm_data: PcmData): # Check that stereo was converted to mono (average of channels) expected_mono = np.array( - [1.5, 3.5, 5.5, 7.5], dtype=np.int16 + [1.5, 3.5, 5.5, 7.5], + dtype=np.int16, ) # Average of each stereo pair assert received_data[0].sample_rate == 48000 assert received_data[0].format == "s16" diff --git a/tests/rtc/test_video_properties.py b/tests/rtc/test_video_properties.py index 7b4f1a3e..015ed7f6 100644 --- a/tests/rtc/test_video_properties.py +++ b/tests/rtc/test_video_properties.py @@ -1,14 +1,16 @@ -import pytest -import os import asyncio +import os + +import pytest from aiortc.contrib.media import MediaPlayer + from getstream.stream import Stream from getstream.video import rtc +from getstream.video.rtc.pb.stream.video.sfu.models.models_pb2 import TRACK_TYPE_VIDEO from getstream.video.rtc.track_util import ( - detect_video_properties, BufferedMediaTrack, + detect_video_properties, ) -from getstream.video.rtc.pb.stream.video.sfu.models.models_pb2 import TRACK_TYPE_VIDEO @pytest.mark.asyncio @@ -43,12 +45,12 @@ async def test_detect_video_properties(): assert "bitrate" in properties, "Bitrate not estimated" # Since we know this specific file is 1280x720, verify those dimensions - assert ( - properties["width"] == 1280 - ), f"Incorrect width detected: {properties['width']}" - assert ( - properties["height"] == 720 - ), f"Incorrect height detected: {properties['height']}" + assert properties["width"] == 1280, ( + f"Incorrect width detected: {properties['width']}" + ) + assert properties["height"] == 720, ( + f"Incorrect height detected: {properties['height']}" + ) # FPS should be a reasonable value (typically around 25-30 for this test video) assert 20 <= properties["fps"] <= 60, f"Unexpected FPS: {properties['fps']}" @@ -66,7 +68,9 @@ async def test_detect_video_properties(): expected_bitrate - margin <= properties["bitrate"] <= expected_bitrate + margin - ), f"Unexpected bitrate: {properties['bitrate']}, expected around {expected_bitrate}" + ), ( + f"Unexpected bitrate: {properties['bitrate']}, expected around {expected_bitrate}" + ) print(f"Detected video properties: {properties}") print(f"Expected bitrate calculation: {expected_bitrate} kbps") @@ -115,9 +119,9 @@ async def test_buffered_media_track(): # Receiving again should get a new frame since buffer is now empty frame4 = await buffered_track.recv() - assert ( - frame3 is not frame4 - ), "Receiving after consuming the buffer should get a new frame" + assert frame3 is not frame4, ( + "Receiving after consuming the buffer should get a new frame" + ) # Now let's get multiple frames in sequence to see if PTS values work as expected frame5 = await buffered_track.recv() @@ -139,9 +143,9 @@ async def test_buffered_media_track(): estimated_fps = int(1 / delta_seconds) if delta_seconds > 0 else 0 # For a normal video, this should be a reasonable FPS value - assert ( - 20 <= estimated_fps <= 60 - ), f"Calculated FPS ({estimated_fps}) outside expected range" + assert 20 <= estimated_fps <= 60, ( + f"Calculated FPS ({estimated_fps}) outside expected range" + ) print(f"Calculated FPS from frames: {estimated_fps}") finally: @@ -179,7 +183,7 @@ async def test_prepare_video_track_info(client: Stream): # Test the prepare_video_track_info method track_info, buffered_track = await connection.prepare_video_track_info( - player.video + player.video, ) # Verify the returned values @@ -191,12 +195,12 @@ async def test_prepare_video_track_info(client: Stream): assert len(track_info.layers) == 1, "Expected one video layer" layer = track_info.layers[0] - assert ( - layer.video_dimension.width == 1280 - ), f"Incorrect width: {layer.video_dimension.width}" - assert ( - layer.video_dimension.height == 720 - ), f"Incorrect height: {layer.video_dimension.height}" + assert layer.video_dimension.width == 1280, ( + f"Incorrect width: {layer.video_dimension.width}" + ) + assert layer.video_dimension.height == 720, ( + f"Incorrect height: {layer.video_dimension.height}" + ) assert 20 <= layer.fps <= 60, f"Unexpected FPS: {layer.fps}" # Verify that the buffered track wraps the original track @@ -205,7 +209,7 @@ async def test_prepare_video_track_info(client: Stream): print(f"Track info: {track_info}") print( - f"Video layer properties: width={layer.video_dimension.width}, height={layer.video_dimension.height}, fps={layer.fps}, bitrate={layer.bitrate}" + f"Video layer properties: width={layer.video_dimension.width}, height={layer.video_dimension.height}, fps={layer.fps}, bitrate={layer.bitrate}", ) finally: diff --git a/tests/test_chat_integration.py b/tests/test_chat_integration.py index a7876556..85c2e5ac 100644 --- a/tests/test_chat_integration.py +++ b/tests/test_chat_integration.py @@ -1,18 +1,21 @@ import uuid +from getstream import Stream from getstream.models import ( - UpdateUserPartialRequest, QueryUsersPayload, + UpdateUserPartialRequest, + UserRequest, ) -from getstream.models import UserRequest -from getstream import Stream def test_upsert_users(client: Stream): users = {} user_id = str(uuid.uuid4()) users[user_id] = UserRequest( - id=user_id, role="admin", custom={"premium": True}, name=user_id + id=user_id, + role="admin", + custom={"premium": True}, + name=user_id, ) client.update_users(users=users) @@ -27,8 +30,11 @@ def test_update_users_partial(client: Stream): user_id = str(uuid.uuid4()) users = { user_id: UserRequest( - id=user_id, role="admin", custom={"premium": True}, name=user_id - ) + id=user_id, + role="admin", + custom={"premium": True}, + name=user_id, + ), } client.update_users(users=users) response = client.update_users_partial( @@ -37,8 +43,8 @@ def test_update_users_partial(client: Stream): id=user_id, set={"role": "admin", "color": "blue"}, unset=["name"], - ) - ] + ), + ], ) assert user_id in response.data.users assert not response.data.users[user_id].name @@ -50,8 +56,11 @@ def test_deactivate_and_reactivate_users(client: Stream): user_id = str(uuid.uuid4()) users = { user_id: UserRequest( - id=user_id, role="admin", custom={"premium": True}, name=user_id - ) + id=user_id, + role="admin", + custom={"premium": True}, + name=user_id, + ), } client.update_users(users=users) @@ -66,8 +75,11 @@ def test_delete_user(client: Stream): user_id = str(uuid.uuid4()) users = { user_id: UserRequest( - id=user_id, role="admin", custom={"premium": True}, name=user_id - ) + id=user_id, + role="admin", + custom={"premium": True}, + name=user_id, + ), } client.update_users(users=users) client.delete_users(user_ids=[user_id]) diff --git a/tests/test_client.py b/tests/test_client.py index 7ba4378b..90623400 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,6 +1,7 @@ -from getstream import Stream import pytest +from getstream import Stream + def test_incorrect_client_throws_exception(): with pytest.raises(TypeError): diff --git a/tests/test_decoding.py b/tests/test_decoding.py index 7f25e8b3..3afda51a 100644 --- a/tests/test_decoding.py +++ b/tests/test_decoding.py @@ -1,19 +1,19 @@ +import json +import os from dataclasses import dataclass, field from datetime import datetime, timezone -import json from typing import Dict, List, Optional -import os import pytest +from dataclasses_json import DataClassJsonMixin, config from getstream.models import GetCallResponse, OwnCapability, OwnCapabilityType from getstream.utils import ( + build_body_dict, + build_query_param, datetime_from_unix_ns, encode_datetime, - build_query_param, - build_body_dict, ) -from dataclasses_json import DataClassJsonMixin, config class MockJsonSerializable: @@ -147,13 +147,13 @@ def test_with_to_dict_method(): def test_with_nested_dictionaries(): result = build_body_dict( - info={"name": "John", "address": {"street": "123 Elm St", "city": "Somewhere"}} + info={"name": "John", "address": {"street": "123 Elm St", "city": "Somewhere"}}, ) expected = { "info": { "name": "John", "address": {"street": "123 Elm St", "city": "Somewhere"}, - } + }, } assert result == expected, "Failed to handle nested dictionaries" @@ -161,9 +161,9 @@ def test_with_nested_dictionaries(): def test_with_lists_and_dicts(): result = build_body_dict(data=[1, 2, {"num": 3}, [4, 5]]) expected = {"data": [1, 2, {"num": 3}, [4, 5]]} - assert ( - result == expected - ), "Failed to handle lists containing dictionaries and other lists" + assert result == expected, ( + "Failed to handle lists containing dictionaries and other lists" + ) def test_empty_input(): @@ -178,14 +178,14 @@ def test_complex_nested_structures(): "id": 1, "profile": MockObjectWithToDict(), "history": [2010, 2012, {"year": 2014}], - } + }, ) expected = { "user": { "id": 1, "profile": {"type": "Mock", "valid": True}, "history": [2010, 2012, {"year": 2014}], - } + }, } assert result == expected, "Failed to handle complex nested structures" @@ -197,30 +197,30 @@ def test_encode_datetime_with_none(): def test_encode_datetime_with_valid_datetime(): date = datetime(2022, 1, 1, 15, 30, 45) expected = "2022-01-01T15:30:45" - assert ( - encode_datetime(date) == expected - ), f"Expected {expected}, got {encode_datetime(date)}" + assert encode_datetime(date) == expected, ( + f"Expected {expected}, got {encode_datetime(date)}" + ) def test_encode_datetime_with_timezone_aware_datetime(): date = datetime(2022, 1, 1, 15, 30, 45, tzinfo=timezone.utc) expected = "2022-01-01T15:30:45+00:00" - assert ( - encode_datetime(date) == expected - ), f"Expected {expected}, got {encode_datetime(date)}" + assert encode_datetime(date) == expected, ( + f"Expected {expected}, got {encode_datetime(date)}" + ) @dataclass class MockRequest(DataClassJsonMixin): own_capabilities: "List[OwnCapabilityType]" = field( - metadata=config(field_name="own_capabilities") + metadata=config(field_name="own_capabilities"), ) def test_encode_own_capability(): obj = MockRequest(own_capabilities=[OwnCapability.BLOCK_USERS, "custom-value"]) assert build_body_dict(obj=obj) == { - "obj": {"own_capabilities": ["block-users", "custom-value"]} + "obj": {"own_capabilities": ["block-users", "custom-value"]}, } @@ -231,19 +231,19 @@ class CallSessionResponse(DataClassJsonMixin): metadata=config( encoder=str, decoder=datetime_from_unix_ns, - ) + ), ) accepted_by: Dict[str, datetime] = field( metadata=config( encoder=str, decoder=datetime_from_unix_ns, - ) + ), ) rejected_by: Dict[str, datetime] = field( metadata=config( encoder=str, decoder=datetime_from_unix_ns, - ) + ), ) ended_at: Optional[datetime] = field( default=None, @@ -274,24 +274,47 @@ def test_call_session_response_from_dict(): assert isinstance(call_session.missed_by, dict) assert len(call_session.missed_by) == 2 assert call_session.missed_by["user1"] == datetime( - 2020, 1, 1, 0, 0, tzinfo=timezone.utc + 2020, + 1, + 1, + 0, + 0, + tzinfo=timezone.utc, ) assert call_session.missed_by["user2"] == datetime( - 2020, 1, 1, 0, 0, 1, tzinfo=timezone.utc + 2020, + 1, + 1, + 0, + 0, + 1, + tzinfo=timezone.utc, ) # Check accepted_by assert isinstance(call_session.accepted_by, dict) assert len(call_session.accepted_by) == 1 assert call_session.accepted_by["user3"] == datetime( - 2020, 1, 1, 0, 0, 2, tzinfo=timezone.utc + 2020, + 1, + 1, + 0, + 0, + 2, + tzinfo=timezone.utc, ) # Check rejected_by assert isinstance(call_session.rejected_by, dict) assert len(call_session.rejected_by) == 1 assert call_session.rejected_by["user4"] == datetime( - 2020, 1, 1, 0, 0, 3, tzinfo=timezone.utc + 2020, + 1, + 1, + 0, + 0, + 3, + tzinfo=timezone.utc, ) # Check ended_at @@ -319,7 +342,13 @@ def test_call_session_response_from_dict_with_none(): assert len(call_session.missed_by) == 2 assert call_session.missed_by["user1"] is None assert call_session.missed_by["user2"] == datetime( - 2020, 1, 1, 0, 0, 1, tzinfo=timezone.utc + 2020, + 1, + 1, + 0, + 0, + 1, + tzinfo=timezone.utc, ) # Check accepted_by @@ -330,7 +359,13 @@ def test_call_session_response_from_dict_with_none(): assert isinstance(call_session.rejected_by, dict) assert len(call_session.rejected_by) == 1 assert call_session.rejected_by["user3"] == datetime( - 2020, 1, 1, 0, 0, 3, tzinfo=timezone.utc + 2020, + 1, + 1, + 0, + 0, + 3, + tzinfo=timezone.utc, ) # Check ended_at @@ -348,5 +383,12 @@ def test_get_call_response_from_dict(): call_response = GetCallResponse.from_dict(call_data) missed_by = call_response.call.session.missed_by assert missed_by["789012"] == datetime( - 2024, 8, 22, 11, 34, 19, 330435, tzinfo=timezone.utc + 2024, + 8, + 22, + 11, + 34, + 19, + 330435, + tzinfo=timezone.utc, ) diff --git a/tests/test_pcm_utils.py b/tests/test_pcm_utils.py index 282b608b..a497cecb 100644 --- a/tests/test_pcm_utils.py +++ b/tests/test_pcm_utils.py @@ -1,22 +1,22 @@ -""" -Tests for getstream.audio.pcm_utils module. +"""Tests for getstream.audio.pcm_utils module. This module tests all PCM audio utility functions including conversion, validation, and logging functionality. """ import logging -import pytest + import numpy as np +import pytest from getstream.audio.pcm_utils import ( - pcm_to_numpy_array, + log_audio_processing_info, numpy_array_to_bytes, + pcm_to_numpy_array, validate_sample_rate_compatibility, - log_audio_processing_info, ) -from getstream.video.rtc.track_util import PcmData from getstream.plugins.test_utils import get_audio_asset +from getstream.video.rtc.track_util import PcmData class TestPcmToNumpyArray: @@ -324,7 +324,9 @@ def test_round_trip_conversion_with_real_audio(self): # Create PcmData with bytes pcm_data_bytes = PcmData( - format="s16", sample_rate=sample_rate, samples=bytes_result + format="s16", + sample_rate=sample_rate, + samples=bytes_result, ) # Convert back to numpy array @@ -341,7 +343,9 @@ def test_validate_common_sample_rates(self): for target_rate in common_rates: # Should not raise any exceptions validate_sample_rate_compatibility( - input_rate, target_rate, "test_plugin" + input_rate, + target_rate, + "test_plugin", ) def test_processing_info_with_real_audio(self, caplog): diff --git a/tests/test_signaling.py b/tests/test_signaling.py index dbdafe68..ccdfc7a6 100644 --- a/tests/test_signaling.py +++ b/tests/test_signaling.py @@ -1,10 +1,11 @@ import asyncio -import pytest import threading from unittest.mock import patch -from getstream.video.rtc.signaling import WebSocketClient, SignalingError +import pytest + from getstream.video.rtc.pb.stream.video.sfu.event import events_pb2 +from getstream.video.rtc.signaling import SignalingError, WebSocketClient class TestWebSocketClient: @@ -43,7 +44,9 @@ async def test_connect_success(self, join_request, mock_websocket): """Test successful connection flow.""" # Create client client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Prepare a successful response @@ -99,7 +102,9 @@ async def test_connect_error(self, join_request, mock_websocket): """Test connection with error response.""" # Create client client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Prepare an error response @@ -134,7 +139,9 @@ async def test_websocket_error_during_connect(self, join_request, mock_websocket """Test WebSocket error during connection.""" # Create client client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Start connection @@ -149,7 +156,8 @@ async def test_websocket_error_during_connect(self, join_request, mock_websocket # Verify connect throws an exception with pytest.raises( - SignalingError, match="Connection failed: Connection failed" + SignalingError, + match="Connection failed: Connection failed", ): await connect_task @@ -161,7 +169,9 @@ async def test_event_callbacks(self, join_request, mock_websocket): """Test event callbacks are executed correctly.""" # Create client client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Prepare a successful join response first @@ -221,7 +231,9 @@ async def test_thread_usage(self, join_request, mock_websocket): """Test that thread is properly used and managed.""" # Create client client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Mock threading.Thread to capture usage @@ -246,7 +258,8 @@ async def test_thread_usage(self, join_request, mock_websocket): join_response.join_response.reconnected = False on_message_callback = mock_websocket.call_args[1]["on_message"] on_message_callback( - mock_websocket.return_value, join_response.SerializeToString() + mock_websocket.return_value, + join_response.SerializeToString(), ) # Complete the connect diff --git a/tests/test_signaling_better.py b/tests/test_signaling_better.py index 268ed944..51e5260c 100644 --- a/tests/test_signaling_better.py +++ b/tests/test_signaling_better.py @@ -1,7 +1,8 @@ -import pytest -from unittest.mock import MagicMock, patch import asyncio import sys +from unittest.mock import MagicMock, patch + +import pytest # Mock websocket module mock_websocket = MagicMock() @@ -92,7 +93,9 @@ async def test_websocket_client_initialization(): # Create a client join_request = MockJoinRequest() client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Verify initial state @@ -129,7 +132,9 @@ async def test_websocket_client_direct_methods(): # Create a client with mocked dependencies join_request = MockJoinRequest() client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Create mocks for testing callbacks @@ -187,7 +192,9 @@ async def test_websocket_client_close(): # Create a client join_request = MockJoinRequest() client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Create mocks for the resources diff --git a/tests/test_signaling_mock.py b/tests/test_signaling_mock.py index fc4bdf65..c9e0df87 100644 --- a/tests/test_signaling_mock.py +++ b/tests/test_signaling_mock.py @@ -1,9 +1,9 @@ import asyncio -import pytest +import sys import threading from unittest.mock import MagicMock, patch -import sys +import pytest # Mock required dependencies mock_websocket = MagicMock() @@ -12,7 +12,13 @@ # Create a proper WebSocketApp mock class class MockWebSocketApp: def __init__( - self, url, on_open=None, on_message=None, on_error=None, on_close=None, **kwargs + self, + url, + on_open=None, + on_message=None, + on_error=None, + on_close=None, + **kwargs, ): self.url = url self.on_open = on_open @@ -70,7 +76,7 @@ def __init__(self): def HasField(self, field_name): if field_name == "join_response": return self._which_oneof == "join_response" - elif field_name == "error": + if field_name == "error": return self._which_oneof == "error" return False @@ -143,7 +149,7 @@ def __init__(self, *args, **kwargs): patcher2 = patch.dict( "sys.modules", { - "getstream.video.rtc.pb.stream.video.sfu.event.events_pb2": self.mock_events_pb2 + "getstream.video.rtc.pb.stream.video.sfu.event.events_pb2": self.mock_events_pb2, }, ) patcher3 = patch("threading.Thread", self.mock_thread_class) @@ -172,7 +178,8 @@ def define_client_class(self): import asyncio import concurrent.futures import logging - from typing import Any, Callable, Dict, Awaitable, List + from collections.abc import Awaitable + from typing import Any, Callable, Dict, List logger = logging.getLogger(__name__) @@ -182,28 +189,31 @@ class SignalingError(Exception): pass class WebSocketClient: - """ - WebSocket client for Stream Video signaling. + """WebSocket client for Stream Video signaling. Handles WebSocket connection, message serialization/deserialization, and event handling. """ def __init__( - self, url: str, join_request, main_loop: asyncio.AbstractEventLoop + self, + url: str, + join_request, + main_loop: asyncio.AbstractEventLoop, ): - """ - Initialize a new WebSocket client. + """Initialize a new WebSocket client. Args: url: The WebSocket server URL join_request: The JoinRequest protobuf message to send for authentication main_loop: The main asyncio event loop to run callbacks on + """ self.url = url self.join_request = join_request self.main_loop = main_loop self.ws = None self.event_handlers: Dict[ - str, List[Callable[[Any], Awaitable[None]]] + str, + List[Callable[[Any], Awaitable[None]]], ] = {} self.first_message_event = asyncio.Event() self.first_message = None @@ -214,18 +224,19 @@ def __init__( # Thread pool for executing callbacks self.executor = concurrent.futures.ThreadPoolExecutor( - max_workers=4, thread_name_prefix="ws-callback-" + max_workers=4, + thread_name_prefix="ws-callback-", ) async def connect(self): - """ - Establish WebSocket connection and authenticate. + """Establish WebSocket connection and authenticate. Returns: The first SfuEvent received (JoinResponse if successful) Raises: SignalingError: If the connection fails or the first message is an error + """ if self.closed: raise SignalingError("Cannot reconnect a closed WebSocket client") @@ -299,7 +310,7 @@ def _on_message(self, ws, message): if not self.first_message_event.is_set(): self.first_message = event self.main_loop.call_soon_threadsafe( - self.first_message_event.set + self.first_message_event.set, ) # Dispatch to event handlers @@ -328,16 +339,16 @@ def _on_error(self, ws, error): def _on_close(self, ws, close_status_code, close_msg): """Handle WebSocket close event.""" logger.debug( - f"WebSocket connection closed: {close_status_code} {close_msg}" + f"WebSocket connection closed: {close_status_code} {close_msg}", ) self.running = False def _dispatch_event(self, event): - """ - Dispatch an event to the appropriate handlers. + """Dispatch an event to the appropriate handlers. Args: event: The SfuEvent to dispatch + """ # Get the oneof event type event_type = event.WhichOneof("event_payload") @@ -355,14 +366,13 @@ def _dispatch_event(self, event): self._run_handler_async(handler, event) def _run_handler_async(self, handler, payload): - """ - Run an event handler asynchronously in a separate thread. + """Run an event handler asynchronously in a separate thread. Args: handler: The async handler function payload: The event payload to pass to the handler - """ + """ # In test environment, run handlers directly in the main loop try: # Schedule the handler to run in the main loop @@ -372,14 +382,16 @@ def _run_handler_async(self, handler, payload): logger.error(f"Error in event handler: {str(e)}") def on_event( - self, event_type: str, callback: Callable[[Any], Awaitable[None]] + self, + event_type: str, + callback: Callable[[Any], Awaitable[None]], ): - """ - Register an event handler for a specific event type. + """Register an event handler for a specific event type. Args: event_type: The event type to listen for, or "*" for all events callback: An async function to call when the event occurs + """ if event_type not in self.event_handlers: self.event_handlers[event_type] = [] @@ -433,7 +445,9 @@ def join_request(self): async def test_connect_success(self, join_request): """Test successful connection flow.""" client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) try: @@ -470,7 +484,9 @@ async def test_connect_success(self, join_request): async def test_connect_error(self, join_request): """Test connection with error response.""" client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) try: @@ -491,7 +507,8 @@ async def test_connect_error(self, join_request): # Verify connect throws an exception with pytest.raises( - SignalingError, match="Connection failed: Error message" + SignalingError, + match="Connection failed: Error message", ): await connect_task finally: @@ -502,7 +519,9 @@ async def test_connect_error(self, join_request): async def test_websocket_error_during_connect(self, join_request): """Test WebSocket error during connection.""" client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) try: @@ -529,7 +548,9 @@ async def test_websocket_error_during_connect(self, join_request): async def test_event_callbacks(self, join_request): """Test event callbacks are executed correctly.""" client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) # Create a callback tracking variable @@ -584,7 +605,9 @@ async def on_any_event(event): async def test_thread_usage(self, join_request): """Test that thread is properly used and managed.""" client = WebSocketClient( - "wss://test.url", join_request, asyncio.get_running_loop() + "wss://test.url", + join_request, + asyncio.get_running_loop(), ) try: diff --git a/tests/test_signaling_simple.py b/tests/test_signaling_simple.py index 34f7955e..fe10f1f0 100644 --- a/tests/test_signaling_simple.py +++ b/tests/test_signaling_simple.py @@ -1,7 +1,8 @@ import asyncio -import pytest from unittest.mock import MagicMock +import pytest + # Create a simple mock version of the WebSocketClient that doesn't rely on protobuf class MockWebSocketClient: diff --git a/tests/test_stream_response.py b/tests/test_stream_response.py index 9da81784..9cec8f4c 100644 --- a/tests/test_stream_response.py +++ b/tests/test_stream_response.py @@ -1,15 +1,16 @@ -from dataclasses_json import DataClassJsonMixin, config -from getstream.models import OwnCapability -from httpx import Response +from dataclasses import dataclass, field from datetime import timezone +from typing import Any, List +from unittest import TestCase + +from dataclasses_json import DataClassJsonMixin, config from dateutil.parser import parse as dt_parse +from httpx import Response + +from getstream.models import OwnCapability from getstream.rate_limit import RateLimitInfo from getstream.stream_response import StreamResponse -from unittest import TestCase -from dataclasses import dataclass, field -from typing import Any, List - @dataclass class Dummy: @@ -19,7 +20,7 @@ class Dummy: @dataclass class MockRequest(DataClassJsonMixin): own_capabilities: List[OwnCapability] = field( - metadata=config(field_name="own_capabilities") + metadata=config(field_name="own_capabilities"), ) @@ -29,7 +30,7 @@ def setUp(self): "x-ratelimit-limit": "100", "x-ratelimit-remaining": "80", "x-ratelimit-reset": str( - int(dt_parse("2022-01-01").replace(tzinfo=timezone.utc).timestamp()) + int(dt_parse("2022-01-01").replace(tzinfo=timezone.utc).timestamp()), ), } self.response = Response(200, headers=headers) @@ -52,10 +53,12 @@ def test_headers(self): def test_status_code(self): self.assertEqual( - self.stream_response_dummy.status_code(), self.response.status_code + self.stream_response_dummy.status_code(), + self.response.status_code, ) self.assertEqual( - self.stream_response_dict.status_code(), self.response.status_code + self.stream_response_dict.status_code(), + self.response.status_code, ) def test_rate_limit(self): @@ -66,20 +69,22 @@ def test_rate_limit(self): self.assertEqual(rate_limit_dummy.limit, 100) self.assertEqual(rate_limit_dummy.remaining, 80) self.assertEqual( - rate_limit_dummy.reset, dt_parse("2022-01-01").replace(tzinfo=timezone.utc) + rate_limit_dummy.reset, + dt_parse("2022-01-01").replace(tzinfo=timezone.utc), ) self.assertIsInstance(rate_limit_dict, RateLimitInfo) self.assertEqual(rate_limit_dict.limit, 100) self.assertEqual(rate_limit_dict.remaining, 80) self.assertEqual( - rate_limit_dict.reset, dt_parse("2022-01-01").replace(tzinfo=timezone.utc) + rate_limit_dict.reset, + dt_parse("2022-01-01").replace(tzinfo=timezone.utc), ) def test_response_with_enum(self): obj = MockRequest.from_dict( { "own_capabilities": ["block-users", "asd"], - } + }, ) self.assertEqual(obj.own_capabilities, [OwnCapability.BLOCK_USERS, "asd"]) diff --git a/tests/test_twirp_client_wrapper.py b/tests/test_twirp_client_wrapper.py index be7fb614..0b605312 100644 --- a/tests/test_twirp_client_wrapper.py +++ b/tests/test_twirp_client_wrapper.py @@ -1,17 +1,17 @@ -# -*- coding: utf-8 -*- import unittest -from unittest.mock import AsyncMock, patch, MagicMock +from unittest.mock import AsyncMock, MagicMock, patch -# Try importing the problematic module directly -# import twirp.async_client # <-- REMOVE THIS DEBUG IMPORT +from twirp.context import Context -# Modules to test -from getstream.video.rtc.twirp_client_wrapper import SignalClient, SfuRpcError +from getstream.video.rtc.pb.stream.video.sfu.models import models_pb2 # Protobufs needed for requests/responses and error codes from getstream.video.rtc.pb.stream.video.sfu.signal_rpc import signal_pb2 -from getstream.video.rtc.pb.stream.video.sfu.models import models_pb2 -from twirp.context import Context + +# Try importing the problematic module directly +# import twirp.async_client # <-- REMOVE THIS DEBUG IMPORT +# Modules to test +from getstream.video.rtc.twirp_client_wrapper import SfuRpcError, SignalClient # No longer need to patch the base class path # ASYNC_CLIENT_PATH = "getstream.video.rtc.twirp_client_wrapper.AsyncSignalServerClient" @@ -22,7 +22,7 @@ async def test_rpc_success_no_error_field(self): """Test RPC call succeeds when the response has no error field.""" # Create a real instance of the wrapper wrapper_client = SignalClient( - address="http://mock_address" + address="http://mock_address", ) # Address needed for init # Create a MagicMock to simulate the response object @@ -120,7 +120,9 @@ async def test_rpc_error_raises_sfu_rpc_error(self): MAKE_REQUEST_PATH = "getstream.video.rtc.twirp_async_client_embed.AsyncTwirpClient._make_request" with patch( - MAKE_REQUEST_PATH, new_callable=AsyncMock, return_value=mock_response + MAKE_REQUEST_PATH, + new_callable=AsyncMock, + return_value=mock_response, ) as mock_make_request: ctx = Context() request = signal_pb2.UpdateSubscriptionsRequest(session_id="sid") diff --git a/tests/test_video_examples.py b/tests/test_video_examples.py index 2bf10142..bf6ffc51 100644 --- a/tests/test_video_examples.py +++ b/tests/test_video_examples.py @@ -1,21 +1,22 @@ -import pytest -import uuid import asyncio +import uuid +from datetime import datetime, timedelta, timezone + +import pytest from getstream import Stream from getstream.base import StreamAPIException from getstream.models import ( + BackstageSettingsRequest, CallRequest, CallSettingsRequest, - ScreensharingSettingsRequest, - OwnCapability, + FrameRecordingSettingsRequest, LimitsSettingsRequest, - BackstageSettingsRequest, + OwnCapability, + ScreensharingSettingsRequest, SessionSettingsRequest, - FrameRecordingSettingsRequest, ) from getstream.video.call import Call -from datetime import datetime, timezone, timedelta from tests.test_video_integration import get_openai_api_key_or_skip @@ -34,10 +35,16 @@ def test_create_user(client: Stream): client.upsert_users( UserRequest( - id="tommaso-id", name="tommaso", role="admin", custom={"country": "NL"} + id="tommaso-id", + name="tommaso", + role="admin", + custom={"country": "NL"}, ), UserRequest( - id="thierry-id", name="thierry", role="admin", custom={"country": "US"} + id="thierry-id", + name="thierry", + role="admin", + custom={"country": "US"}, ), ) @@ -47,6 +54,7 @@ def test_create_user(client: Stream): def test_create_call_with_members(client: Stream): import uuid + from getstream.models import ( CallRequest, MemberRequest, @@ -69,7 +77,7 @@ def test_block_unblock_user_from_calls(client: Stream, call: Call, get_user): call.get_or_create( data=CallRequest( created_by_id="tommaso-id", - ) + ), ) call.block_user(bad_user.id) response = call.get() @@ -85,7 +93,7 @@ def test_send_custom_event(client: Stream, call: Call, get_user): call.get_or_create( data=CallRequest( created_by_id="tommaso-id", - ) + ), ) call.send_call_event(user_id=user.id, custom={"bananas": "good"}) @@ -96,13 +104,14 @@ def test_update_settings(call: Call): call.get_or_create( data=CallRequest( created_by_id=user_id, - ) + ), ) call.update( settings_override=CallSettingsRequest( screensharing=ScreensharingSettingsRequest( - enabled=True, access_request_enabled=True + enabled=True, + access_request_enabled=True, ), ), ) @@ -113,7 +122,7 @@ def test_mute_all(call: Call): call.get_or_create( data=CallRequest( created_by_id=user_id, - ) + ), ) call.mute_users( @@ -131,7 +140,7 @@ def test_mute_some_users(call: Call, get_user): call.get_or_create( data=CallRequest( created_by_id=user_id, - ) + ), ) call.mute_users( @@ -149,7 +158,7 @@ def test_update_user_permissions(call: Call, get_user): call.get_or_create( data=CallRequest( created_by_id=user_id, - ) + ), ) alice = get_user() @@ -196,7 +205,7 @@ def test_create_call_with_session_timer(call: Call): max_duration_seconds=3600, ), ), - ) + ), ) assert response.data.call.settings.limits.max_duration_seconds == 3600 @@ -207,7 +216,7 @@ def test_create_call_with_session_timer(call: Call): limits=LimitsSettingsRequest( max_duration_seconds=7200, ), - ) + ), ) assert response.data.call.settings.limits.max_duration_seconds == 7200 @@ -217,7 +226,7 @@ def test_create_call_with_session_timer(call: Call): limits=LimitsSettingsRequest( max_duration_seconds=0, ), - ) + ), ) assert response.data.call.settings.limits.max_duration_seconds == 0 @@ -253,7 +262,7 @@ def test_create_call_with_backstage_and_join_ahead_set(client: Stream, call: Cal join_ahead_time_seconds=300, ), ), - ) + ), ) assert response.data.call.join_ahead_time_seconds == 300 @@ -264,7 +273,7 @@ def test_create_call_with_backstage_and_join_ahead_set(client: Stream, call: Cal backstage=BackstageSettingsRequest( join_ahead_time_seconds=600, ), - ) + ), ) assert response.data.call.join_ahead_time_seconds == 600 @@ -274,7 +283,7 @@ def test_create_call_with_backstage_and_join_ahead_set(client: Stream, call: Cal backstage=BackstageSettingsRequest( join_ahead_time_seconds=0, ), - ) + ), ) assert response.data.call.join_ahead_time_seconds == 0 @@ -291,7 +300,7 @@ def test_create_call_with_custom_session_inactivity_timeout(call: Call): inactivity_timeout_seconds=5, ), ), - ) + ), ) assert response.data.call.settings.session.inactivity_timeout_seconds == 5 @@ -343,18 +352,19 @@ def test_query_call_participants(client): call.get_or_create( data=CallRequest( created_by_id=str(uuid.uuid4()), - ) + ), ) call.query_call_participants(filter_conditions={"user_id": {"$eq": "user-id"}}) # filter by published track call.query_call_participants( - filter_conditions={"published_tracks": {"$eq": "video"}} + filter_conditions={"published_tracks": {"$eq": "video"}}, ) # filter multiple users call.query_call_participants( - filter_conditions={"user_id": {"$in": ["user-id-1", "user-id-2"]}}, limit=100 + filter_conditions={"user_id": {"$in": ["user-id-1", "user-id-2"]}}, + limit=100, ) @@ -368,10 +378,12 @@ def test_create_call_with_custom_frame_recording_settings(client: Stream): created_by_id=user_id, settings_override=CallSettingsRequest( frame_recording=FrameRecordingSettingsRequest( - capture_interval_in_seconds=3, mode="auto-on", quality="1080p" + capture_interval_in_seconds=3, + mode="auto-on", + quality="1080p", ), ), - ) + ), ) assert response.data.call.settings.frame_recording.capture_interval_in_seconds == 3 @@ -386,7 +398,9 @@ def test_create_call_type_with_custom_frame_recording_settings(client: Stream): name="frame_recording_" + str(uuid.uuid4()), settings=CallSettingsRequest( frame_recording=FrameRecordingSettingsRequest( - capture_interval_in_seconds=5, mode="auto-on", quality="720p" + capture_interval_in_seconds=5, + mode="auto-on", + quality="720p", ), ), ) @@ -411,7 +425,7 @@ async def test_connect_openai(client: Stream, capsys): call.connect_openai(openai_api_key, "lucy") as connection, ): await connection.session.update( - session={"instructions": "help the user with history questions"} + session={"instructions": "help the user with history questions"}, ) await connection.session.update(session={"voice": "ballad"}) async for event in connection: @@ -428,9 +442,9 @@ async def test_connect_openai(client: Stream, capsys): { "type": "input_text", "text": "Say hello to Kazuki in Japanese", - } + }, ], - } + }, ) await connection.response.create() except asyncio.TimeoutError: diff --git a/tests/test_video_integration.py b/tests/test_video_integration.py index 62e78f72..1bf7d2da 100644 --- a/tests/test_video_integration.py +++ b/tests/test_video_integration.py @@ -1,46 +1,45 @@ +import os import time - -import pytest import uuid + import jwt -import os +import pytest + +from getstream.base import StreamAPIException from getstream.models import ( - S3Request, - CallSettingsRequest, - OwnCapability, + APNS, AudioSettingsRequest, - ScreensharingSettingsRequest, - RecordSettingsRequest, BackstageSettingsRequest, - GeofenceSettingsRequest, CallRequest, + CallSettingsRequest, + EventNotificationSettings, + GeofenceSettingsRequest, LayoutSettingsRequest, NotificationSettings, - EventNotificationSettings, - APNS, - UserRequest, + OwnCapability, QueryUsersPayload, + RecordSettingsRequest, + S3Request, + ScreensharingSettingsRequest, + UserRequest, ) - -from getstream.base import StreamAPIException from getstream.stream import Stream from getstream.video.call import Call -from tests.base import VideoTestClass -from tests.base import wait_for_task +from tests.base import VideoTestClass, wait_for_task CALL_TYPE_NAME = f"calltype{uuid.uuid4()}" EXTERNAL_STORAGE_NAME = f"storage{uuid.uuid4()}" def get_openai_api_key_or_skip(): - """ - Get the OpenAI API key from environment variables or skip the test. + """Get the OpenAI API key from environment variables or skip the test. Returns: str: The OpenAI API key. Raises: pytest.skip: If the OpenAI API key is not set. + """ openai_api_key = os.environ.get("OPENAI_API_KEY") if not openai_api_key: @@ -75,14 +74,14 @@ def test_teams(client: Stream): data=CallRequest( created_by_id=user_id, team="blue", - ) + ), ) assert response.data.call.team == "blue" response = client.query_users( QueryUsersPayload( - filter_conditions={"id": user_id, "teams": {"$in": ["red", "blue"]}} - ) + filter_conditions={"id": user_id, "teams": {"$in": ["red", "blue"]}}, + ), ) assert len(response.data.users) > 0 assert user_id in [u.id for u in response.data.users] @@ -91,13 +90,13 @@ def test_teams(client: Stream): QueryUsersPayload( filter_conditions={ "teams": None, - } - ) + }, + ), ) assert len([u for u in response.data.users if len(u.teams) > 0]) == 0 response = client.video.query_calls( - filter_conditions={"id": call_id, "team": {"$eq": "blue"}} + filter_conditions={"id": call_id, "team": {"$eq": "blue"}}, ) assert len(response.data.calls) > 0 @@ -140,7 +139,8 @@ def test_create_call_type(self, client: Stream): enabled=True, call_notification=EventNotificationSettings( apns=APNS( - title="{{ user.display_name }} invites you to a call", body="" + title="{{ user.display_name }} invites you to a call", + body="", ), enabled=True, ), @@ -167,7 +167,8 @@ def test_create_call_type(self, client: Stream): ), call_missed=EventNotificationSettings( apns=APNS( - title="{{ user.display_name }} invites you to a call", body="" + title="{{ user.display_name }} invites you to a call", + body="", ), enabled=True, ), @@ -311,7 +312,7 @@ def test_create_call(self): geofencing=GeofenceSettingsRequest( names=[ "canada", - ] + ], ), screensharing=ScreensharingSettingsRequest( enabled=False, @@ -352,7 +353,7 @@ def test_enable_call_recording(self): recording=RecordSettingsRequest( mode="available", audio_only=True, - ) + ), ), ) assert response.data.call.settings.recording.mode == "available" @@ -382,7 +383,9 @@ def test_get_call_report_for_latest_session(self): def test_get_call_report_for_specified_session(self): with pytest.raises(StreamAPIException): self.client.video.get_call_report( - self.call.call_type, self.call.id, session_id="non_existent" + self.call.call_type, + self.call.id, + session_id="non_existent", ) diff --git a/tests/test_video_openai.py b/tests/test_video_openai.py index 4dc2281f..3c37f27d 100644 --- a/tests/test_video_openai.py +++ b/tests/test_video_openai.py @@ -1,5 +1,4 @@ -""" -Tests for the OpenAI patching functionality in getstream.video.openai. +"""Tests for the OpenAI patching functionality in getstream.video.openai. This test file is designed to verify that the patching of the OpenAI client works correctly and to detect regressions when upgrading to newer versions of the OpenAI SDK. @@ -16,16 +15,17 @@ """ import json -import pytest -from unittest.mock import MagicMock, patch, AsyncMock import os +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest from getstream.video.openai import ( - import_openai, + dict_to_class, get_openai_realtime_client, - patched_connection_manager_prepare_url, + import_openai, patch_realtime_connect, - dict_to_class, + patched_connection_manager_prepare_url, ) @@ -46,7 +46,7 @@ def test_get_openai_realtime_client_http(self): with ( patch("getstream.video.openai.import_openai") as mock_import_openai, patch( - "getstream.video.openai.patch_realtime_connect" + "getstream.video.openai.patch_realtime_connect", ) as mock_patch_realtime_connect, ): mock_openai = MagicMock() @@ -65,7 +65,7 @@ def test_get_openai_realtime_client_https(self): with ( patch("getstream.video.openai.import_openai") as mock_import_openai, patch( - "getstream.video.openai.patch_realtime_connect" + "getstream.video.openai.patch_realtime_connect", ) as mock_patch_realtime_connect, ): mock_openai = MagicMock() @@ -89,11 +89,7 @@ def test_patched_connection_manager_prepare_url(self): # Set up the client attribute with a different name to test attribute discovery mock_client = MagicMock() mock_client.websocket_base_url = "wss://example.com/video/connect_agent" - setattr( - mock_connection_manager, - "_AsyncRealtimeConnectionManager__client", - mock_client, - ) + mock_connection_manager._AsyncRealtimeConnectionManager__client = mock_client # Call the function result = patched_connection_manager_prepare_url(mock_connection_manager) @@ -109,7 +105,7 @@ async def test_patched_recv(self): mock_connection = MagicMock() mock_connection.recv_bytes = AsyncMock() mock_connection.recv_bytes.return_value = json.dumps( - {"type": "error", "message": "test error"} + {"type": "error", "message": "test error"}, ).encode() # Define a custom ErrorEvent class for testing @@ -215,7 +211,7 @@ async def test_patched_aenter(self): original_recv = AsyncMock(return_value="original") mock_connection.recv = original_recv mock_connection.recv_bytes = AsyncMock( - return_value=json.dumps({"type": "message"}).encode() + return_value=json.dumps({"type": "message"}).encode(), ) # Create a mock connection manager @@ -264,8 +260,8 @@ async def test_integration_with_mocks(self): mock_connection = MagicMock() mock_connection.recv_bytes = AsyncMock( return_value=json.dumps( - {"type": "message", "content": "Hello, world!"} - ).encode() + {"type": "message", "content": "Hello, world!"}, + ).encode(), ) mock_connection.parse_event.return_value = MagicMock() @@ -310,7 +306,7 @@ def test_get_openai_realtime_client_from_env(self): with ( patch("getstream.video.openai.import_openai") as mock_import_openai, patch( - "getstream.video.openai.patch_realtime_connect" + "getstream.video.openai.patch_realtime_connect", ) as mock_patch_realtime_connect, ): mock_openai = MagicMock() diff --git a/tests/test_webrtc_generation.py b/tests/test_webrtc_generation.py index fc1b5936..3f204aca 100644 --- a/tests/test_webrtc_generation.py +++ b/tests/test_webrtc_generation.py @@ -7,8 +7,8 @@ SetPublisherResponse, ) from getstream.video.rtc.pb.stream.video.sfu.signal_rpc.signal_twirp import ( - SignalServerServer, SignalServerClient, + SignalServerServer, ) diff --git a/uv.lock b/uv.lock index 84ab28e3..9064bd9c 100644 --- a/uv.lock +++ b/uv.lock @@ -11,6 +11,7 @@ resolution-markers = [ [manifest] members = [ "getstream", + "getstream-plugins-assemblyai", "getstream-plugins-cartesia", "getstream-plugins-common", "getstream-plugins-deepgram", @@ -219,6 +220,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] +[[package]] +name = "assemblyai" +version = "0.43.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/a6/51324e434a250f44f5d6d041e5acb8a98451604dbe8aaa9e5f91bf1dc902/assemblyai-0.43.1.tar.gz", hash = "sha256:c39e8ddb4434af316ade3259e51f86be8134c24be821d8cd84502850ea551951", size = 53970, upload-time = "2025-08-12T21:22:14.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c4/3df745c07cc66be2d1134629b7007e3c8f3b9931caa5a62e023dcc7984cd/assemblyai-0.43.1-py3-none-any.whl", hash = "sha256:4e6e11eb07130c5c87aea84cc63d80933a6588e9b3aeee80026c2419756a3513", size = 50053, upload-time = "2025-08-12T21:22:13.255Z" }, +] + [[package]] name = "async-timeout" version = "5.0.1" @@ -1152,6 +1168,43 @@ dev = [ { name = "ruff", specifier = ">=0.12.1" }, ] +[[package]] +name = "getstream-plugins-assemblyai" +version = "0.1.0" +source = { editable = "getstream/plugins/assemblyai" } +dependencies = [ + { name = "assemblyai" }, + { name = "getstream", extra = ["webrtc"] }, + { name = "getstream-plugins-common" }, + { name = "numpy" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "scipy" }, + { name = "soundfile" }, + { name = "torchaudio" }, +] + +[package.metadata] +requires-dist = [ + { name = "assemblyai", specifier = ">=0.43.1" }, + { name = "getstream", extras = ["webrtc"], editable = "." }, + { name = "getstream-plugins-common", editable = "getstream/plugins/common" }, + { name = "numpy", specifier = ">=2.2.6,<2.3" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-asyncio", specifier = ">=1.0.0" }, + { name = "scipy", specifier = ">=1.15.3,<1.16" }, + { name = "soundfile", specifier = ">=0.13.1" }, + { name = "torchaudio", specifier = ">=2.7.1" }, +] + [[package]] name = "getstream-plugins-cartesia" version = "0.1.0"