From 75f7388fb382e9f4c412b1a5ed4b97e74033a8a7 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 23 Sep 2026 11:08:24 +0100 Subject: [PATCH 1/8] chore: initialize SDK regeneration branch From ea0a5c2e69058f5ee42f687ef0bbc75392e1988d Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 23 Sep 2026 11:10:17 +0100 Subject: [PATCH 2/8] chore: unfreeze files pending regen --- .fernignore | 66 +-- .gitignore.bak | 8 + .../agent/v1/requests/agent_v1settings.py.bak | 31 ++ .../v1/requests/agent_v1settings_agent.py.bak | 27 ++ .../agent_v1settings_agent_context.py.bak | 35 ++ src/deepgram/agent/v1/socket_client.py.bak | 387 ++++++++++++++++++ .../agent/v1/types/agent_v1settings.py.bak | 42 ++ .../v1/types/agent_v1settings_agent.py.bak | 62 +++ .../agent_v1settings_agent_context.py.bak | 65 +++ ...tings_agent_context_listen_provider.py.bak | 79 ++++ ...nt_v1settings_agent_listen_provider.py.bak | 79 ++++ .../types/agent_v1update_listen_listen.py.bak | 64 +++ src/deepgram/core/api_error.py.bak | 25 ++ src/deepgram/core/parse_error.py.bak | 38 ++ src/deepgram/core/query_encoder.py.bak | 65 +++ src/deepgram/listen/v1/socket_client.py.bak | 234 +++++++++++ src/deepgram/listen/v2/socket_client.py.bak | 247 +++++++++++ .../types/listen_v2configure_failure.py.bak | 35 ++ .../types/listen_v2configure_success.py.bak | 49 +++ ...sten_v2configure_success_thresholds.py.bak | 30 ++ .../listen/v2/types/listen_v2connected.py.bak | 35 ++ .../v2/types/listen_v2fatal_error.py.bak | 40 ++ .../listen/v2/types/listen_v2turn_info.py.bak | 102 +++++ .../listen_v2turn_info_words_item.py.bak | 38 ++ .../deepgram_listen_provider_v2.py.bak | 47 +++ src/deepgram/speak/v1/socket_client.py.bak | 235 +++++++++++ src/deepgram/speak/v2/socket_client.py.bak | 268 ++++++++++++ src/deepgram/types/deepgram.py.bak | 44 ++ .../types/deepgram_listen_provider_v2.py.bak | 88 ++++ .../types/listen_v2eot_threshold.py.bak | 11 + .../types/speak_settings_v1provider.py.bak | 117 ++++++ tests/wire/test_listen_v1_media.py.bak | 102 +++++ .../wire/test_manage_v1_projects_keys.py.bak | 60 +++ .../test_manage_v1_projects_requests.py.bak | 67 +++ 34 files changed, 2889 insertions(+), 33 deletions(-) create mode 100644 .gitignore.bak create mode 100644 src/deepgram/agent/v1/requests/agent_v1settings.py.bak create mode 100644 src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak create mode 100644 src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak create mode 100644 src/deepgram/agent/v1/socket_client.py.bak create mode 100644 src/deepgram/agent/v1/types/agent_v1settings.py.bak create mode 100644 src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak create mode 100644 src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak create mode 100644 src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak create mode 100644 src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak create mode 100644 src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak create mode 100644 src/deepgram/core/api_error.py.bak create mode 100644 src/deepgram/core/parse_error.py.bak create mode 100644 src/deepgram/core/query_encoder.py.bak create mode 100644 src/deepgram/listen/v1/socket_client.py.bak create mode 100644 src/deepgram/listen/v2/socket_client.py.bak create mode 100644 src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak create mode 100644 src/deepgram/listen/v2/types/listen_v2configure_success.py.bak create mode 100644 src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak create mode 100644 src/deepgram/listen/v2/types/listen_v2connected.py.bak create mode 100644 src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak create mode 100644 src/deepgram/listen/v2/types/listen_v2turn_info.py.bak create mode 100644 src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak create mode 100644 src/deepgram/requests/deepgram_listen_provider_v2.py.bak create mode 100644 src/deepgram/speak/v1/socket_client.py.bak create mode 100644 src/deepgram/speak/v2/socket_client.py.bak create mode 100644 src/deepgram/types/deepgram.py.bak create mode 100644 src/deepgram/types/deepgram_listen_provider_v2.py.bak create mode 100644 src/deepgram/types/listen_v2eot_threshold.py.bak create mode 100644 src/deepgram/types/speak_settings_v1provider.py.bak create mode 100644 tests/wire/test_listen_v1_media.py.bak create mode 100644 tests/wire/test_manage_v1_projects_keys.py.bak create mode 100644 tests/wire/test_manage_v1_projects_requests.py.bak diff --git a/.fernignore b/.fernignore index 8334454b..3f8fe93d 100644 --- a/.fernignore +++ b/.fernignore @@ -27,25 +27,25 @@ src/deepgram/listen/v2/types/_dict_compat.py # - listen/v2 send_configure: runtime tolerance for a raw dict alongside the # generated ListenV2Configure model # [temporarily frozen — manual patches listed above] -src/deepgram/agent/v1/socket_client.py -src/deepgram/listen/v1/socket_client.py -src/deepgram/listen/v2/socket_client.py -src/deepgram/speak/v1/socket_client.py -src/deepgram/speak/v2/socket_client.py +src/deepgram/agent/v1/socket_client.py.bak +src/deepgram/listen/v1/socket_client.py.bak +src/deepgram/listen/v2/socket_client.py.bak +src/deepgram/speak/v1/socket_client.py.bak +src/deepgram/speak/v2/socket_client.py.bak # The generated eot_threshold descriptions omit that eot_timeout_ms still ends # idle turns. The DeepgramListenProviderV2 model below shares this patch with # its language_hint compatibility shim. Remove these patches after the API # definition includes the timeout requirement and Fern regenerates them. # [temporarily frozen] -src/deepgram/types/listen_v2eot_threshold.py -src/deepgram/requests/deepgram_listen_provider_v2.py +src/deepgram/types/listen_v2eot_threshold.py.bak +src/deepgram/requests/deepgram_listen_provider_v2.py.bak # Agent TTS provider expressivity must reject fractional values rather than # allowing Pydantic v1 to truncate them before the API sees the request. # [temporarily frozen — remove when Fern emits a strict integer] -src/deepgram/types/speak_settings_v1provider.py -src/deepgram/types/deepgram.py +src/deepgram/types/speak_settings_v1provider.py.bak +src/deepgram/types/deepgram.py.bak # Backward-compat patch: AgentV1SettingsAgentContext schema restructure as of # 2026-05-05. The new schema nests messages under .context.messages; this file @@ -53,12 +53,12 @@ src/deepgram/types/deepgram.py # kwarg and remaps it to `context={"messages": ...}` so existing callers keep # producing the correct wire payload, plus a read-side `.messages` property so # existing attribute access keeps working. -src/deepgram/agent/v1/types/agent_v1settings_agent_context.py -src/deepgram/agent/v1/types/agent_v1settings_agent.py -src/deepgram/agent/v1/types/agent_v1settings.py -src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py -src/deepgram/agent/v1/requests/agent_v1settings_agent.py -src/deepgram/agent/v1/requests/agent_v1settings.py +src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak +src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak +src/deepgram/agent/v1/types/agent_v1settings.py.bak +src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak +src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak +src/deepgram/agent/v1/requests/agent_v1settings.py.bak # Backward-compatibility alias shims for renamed generated public types/params. # These are hand-written wrappers around the current generated names and must not be regenerated. @@ -136,9 +136,9 @@ src/deepgram/requests/deepgram_listen_provider_v2language_hint.py # idle turns and must be increased for manual ForceEndTurn control. Remove that correction # when the API definition provides it; remove the shim and unfreeze when the singular alias # is intentionally retired in a future major release. -src/deepgram/types/deepgram_listen_provider_v2.py -src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py -src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py +src/deepgram/types/deepgram_listen_provider_v2.py.bak +src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak +src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak # Backward-compat patch for the 2026-07-31 AgentV1UpdateListen provider retype. The # generated `provider` field is now the required discriminated union @@ -147,7 +147,7 @@ src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py # that coerces a legacy DeepgramListenProviderV1/V2 (or a dict lacking the version # discriminant) into the new shape so existing callers keep working. Remove and unfreeze # when the old provider payloads are retired in a future major. -src/deepgram/agent/v1/types/agent_v1update_listen_listen.py +src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak # Hand-written compat shim recreating ListenV2CloseStreamType, which Fern removed in the # 2026-06-15 regen (docs #946). The original generated type wrongly allowed @@ -188,23 +188,23 @@ src/deepgram/listen/v2/types/__init__.py # the websockets DEBUG handshake logs; this is the other path to it. Unfreeze if the # generator starts redacting credentials itself. -src/deepgram/core/api_error.py -src/deepgram/core/parse_error.py +src/deepgram/core/api_error.py.bak +src/deepgram/core/parse_error.py.bak -src/deepgram/core/query_encoder.py +src/deepgram/core/query_encoder.py.bak # Read-side compatibility for the SDK 7.7 Listen V2 response retype. These # generated response models inherit the hand-written base above so callers can # temporarily use both attribute and read-only subscript access. Remove the custom # inheritance and unfreeze these files in the next major release. # [temporarily frozen -- manual patch described above] -src/deepgram/listen/v2/types/listen_v2connected.py -src/deepgram/listen/v2/types/listen_v2turn_info.py -src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py -src/deepgram/listen/v2/types/listen_v2configure_success.py -src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py -src/deepgram/listen/v2/types/listen_v2configure_failure.py -src/deepgram/listen/v2/types/listen_v2fatal_error.py +src/deepgram/listen/v2/types/listen_v2connected.py.bak +src/deepgram/listen/v2/types/listen_v2turn_info.py.bak +src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak +src/deepgram/listen/v2/types/listen_v2configure_success.py.bak +src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak +src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak +src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak # Hand-written custom tests tests/custom/test_api_error_redaction.py @@ -240,14 +240,14 @@ tests/custom/test_websocket_streaming_coverage.py tests/typecheck/compat_aliases.py # Wire test with restored compatibility coverage for legacy create-key request alias -tests/wire/test_manage_v1_projects_keys.py +tests/wire/test_manage_v1_projects_keys.py.bak # Wire test with restored query-parameter coverage for manage.v1.projects.requests.list. # The 2026-08-11 regen simplified the upstream spec *example* for this endpoint, and Fern # derives the wire test from the example, so the generated test dropped all ten optional # query params (including the datetime -> ISO-8601 Z encoding) while the client signature # still forwards them. Frozen so future regens do not silently drop that check again. -tests/wire/test_manage_v1_projects_requests.py +tests/wire/test_manage_v1_projects_requests.py.bak # Wire test with restored query-parameter coverage for listen.v1.media.transcribe_url. # Third instance of the same mechanism as the two entries above: the 2026-08-18 regen @@ -256,7 +256,7 @@ tests/wire/test_manage_v1_projects_requests.py # dropped all 37 optional query params -- including the bool -> lowercase "true"/"false" # assertions -- while the client signature still forwards every one of them. Frozen so # future regens cannot silently drop that check again. -tests/wire/test_listen_v1_media.py +tests/wire/test_listen_v1_media.py.bak # Manual standalone tests tests/manual @@ -306,7 +306,7 @@ AGENTS.md # file before (fdcce88, ad93815, 4bee463) and would strip them. # [temporarily frozen — unfreeze + re-diff before each regen, then re-apply the # three coverage lines] -.gitignore +.gitignore.bak # Folders to ignore .github diff --git a/.gitignore.bak b/.gitignore.bak new file mode 100644 index 00000000..765eb109 --- /dev/null +++ b/.gitignore.bak @@ -0,0 +1,8 @@ +.mypy_cache/ +.ruff_cache/ +__pycache__/ +dist/ +poetry.toml +.coverage +htmlcov/ +coverage.xml diff --git a/src/deepgram/agent/v1/requests/agent_v1settings.py.bak b/src/deepgram/agent/v1/requests/agent_v1settings.py.bak new file mode 100644 index 00000000..5ada0975 --- /dev/null +++ b/src/deepgram/agent/v1/requests/agent_v1settings.py.bak @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions +from .agent_v1settings_agent import AgentV1SettingsAgentParams +from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams +from .agent_v1settings_audio import AgentV1SettingsAudioParams +from .agent_v1settings_flags import AgentV1SettingsFlagsParams + + +class AgentV1SettingsParams(typing_extensions.TypedDict): + type: typing.Literal["Settings"] + tags: typing_extensions.NotRequired[typing.Sequence[str]] + """ + Tags to associate with the request + """ + + experimental: typing_extensions.NotRequired[bool] + """ + To enable experimental features + """ + + flags: typing_extensions.NotRequired[AgentV1SettingsFlagsParams] + mip_opt_out: typing_extensions.NotRequired[bool] + """ + To opt out of Deepgram Model Improvement Program + """ + + audio: AgentV1SettingsAudioParams + agent: typing.Union[AgentV1SettingsAgentParams, AgentV1SettingsAgentContextParams, str] diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak b/src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak new file mode 100644 index 00000000..01bffc21 --- /dev/null +++ b/src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams +from .agent_v1settings_agent_listen import AgentV1SettingsAgentListenParams +from .agent_v1settings_agent_speak import AgentV1SettingsAgentSpeakParams +from .agent_v1settings_agent_think import AgentV1SettingsAgentThinkParams + + +class AgentV1SettingsAgentParams(typing_extensions.TypedDict): + language: typing_extensions.NotRequired[str] + """ + Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. + """ + + context: typing_extensions.NotRequired[AgentV1SettingsAgentContextParams] + """ + Conversation context including the history of messages and function calls + """ + + listen: typing_extensions.NotRequired[AgentV1SettingsAgentListenParams] + think: typing_extensions.NotRequired[AgentV1SettingsAgentThinkParams] + speak: typing_extensions.NotRequired[AgentV1SettingsAgentSpeakParams] + greeting: typing_extensions.NotRequired[str] + """ + Optional message that agent will speak at the start + """ diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak new file mode 100644 index 00000000..b399ef99 --- /dev/null +++ b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions +from .agent_v1settings_agent_context_context import AgentV1SettingsAgentContextContextParams +from .agent_v1settings_agent_context_listen import AgentV1SettingsAgentContextListenParams +from .agent_v1settings_agent_context_messages_item import AgentV1SettingsAgentContextMessagesItemParams +from .agent_v1settings_agent_context_speak import AgentV1SettingsAgentContextSpeakParams +from .agent_v1settings_agent_context_think import AgentV1SettingsAgentContextThinkParams + + +class AgentV1SettingsAgentContextParams(typing_extensions.TypedDict): + language: typing_extensions.NotRequired[str] + """ + Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. + """ + + messages: typing_extensions.NotRequired[typing.Sequence[AgentV1SettingsAgentContextMessagesItemParams]] + """ + Deprecated. Conversation history as a list of messages and function calls. + """ + + context: typing_extensions.NotRequired[AgentV1SettingsAgentContextContextParams] + """ + Conversation context including the history of messages and function calls + """ + + listen: typing_extensions.NotRequired[AgentV1SettingsAgentContextListenParams] + think: typing_extensions.NotRequired[AgentV1SettingsAgentContextThinkParams] + speak: typing_extensions.NotRequired[AgentV1SettingsAgentContextSpeakParams] + greeting: typing_extensions.NotRequired[str] + """ + Optional message that agent will speak at the start + """ diff --git a/src/deepgram/agent/v1/socket_client.py.bak b/src/deepgram/agent/v1/socket_client.py.bak new file mode 100644 index 00000000..6d504d3e --- /dev/null +++ b/src/deepgram/agent/v1/socket_client.py.bak @@ -0,0 +1,387 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +import logging +import typing + +import websockets.sync.connection as websockets_sync_connection +from ...core.events import EventEmitterMixin, EventType +from ...core.unchecked_base_model import construct_type +from .types.agent_v1agent_audio_done import AgentV1AgentAudioDone +from .types.agent_v1agent_started_speaking import AgentV1AgentStartedSpeaking +from .types.agent_v1agent_thinking import AgentV1AgentThinking +from .types.agent_v1conversation_text import AgentV1ConversationText +from .types.agent_v1error import AgentV1Error +from .types.agent_v1force_end_turn import AgentV1ForceEndTurn +from .types.agent_v1function_call_cancelled import AgentV1FunctionCallCancelled +from .types.agent_v1function_call_request import AgentV1FunctionCallRequest +from .types.agent_v1history import AgentV1History +from .types.agent_v1inject_agent_message import AgentV1InjectAgentMessage +from .types.agent_v1inject_user_message import AgentV1InjectUserMessage +from .types.agent_v1injection_refused import AgentV1InjectionRefused +from .types.agent_v1keep_alive import AgentV1KeepAlive +from .types.agent_v1latency_report import AgentV1LatencyReport +from .types.agent_v1listen_updated import AgentV1ListenUpdated +from .types.agent_v1prompt_updated import AgentV1PromptUpdated +from .types.agent_v1receive_function_call_response import AgentV1ReceiveFunctionCallResponse +from .types.agent_v1send_function_call_response import AgentV1SendFunctionCallResponse +from .types.agent_v1settings import AgentV1Settings +from .types.agent_v1settings_applied import AgentV1SettingsApplied +from .types.agent_v1speak_updated import AgentV1SpeakUpdated +from .types.agent_v1think_updated import AgentV1ThinkUpdated +from .types.agent_v1update_listen import AgentV1UpdateListen +from .types.agent_v1update_prompt import AgentV1UpdatePrompt +from .types.agent_v1update_speak import AgentV1UpdateSpeak +from .types.agent_v1update_think import AgentV1UpdateThink +from .types.agent_v1user_started_speaking import AgentV1UserStartedSpeaking +from .types.agent_v1warning import AgentV1Warning +from .types.agent_v1welcome import AgentV1Welcome + +try: + from websockets.legacy.client import WebSocketClientProtocol # type: ignore +except ImportError: + from websockets import WebSocketClientProtocol # type: ignore + + +def _sanitize_numeric_types(obj: typing.Any) -> typing.Any: + """Convert whole-number floats to integers for wire-compatible JSON. See: internal-api-specs/issues/205.""" + if isinstance(obj, dict): + return {key: _sanitize_numeric_types(value) for key, value in obj.items()} + if isinstance(obj, list): + return [_sanitize_numeric_types(item) for item in obj] + if isinstance(obj, float) and obj.is_integer(): + return int(obj) + return obj + + +_logger = logging.getLogger(__name__) +V1SocketClientResponse = typing.Union[ + AgentV1ListenUpdated, + AgentV1ThinkUpdated, + AgentV1ReceiveFunctionCallResponse, + AgentV1PromptUpdated, + AgentV1SpeakUpdated, + AgentV1InjectionRefused, + AgentV1Welcome, + AgentV1SettingsApplied, + AgentV1ConversationText, + AgentV1UserStartedSpeaking, + AgentV1AgentThinking, + AgentV1LatencyReport, + AgentV1FunctionCallRequest, + AgentV1FunctionCallCancelled, + AgentV1AgentStartedSpeaking, + AgentV1AgentAudioDone, + AgentV1Error, + AgentV1Warning, + AgentV1History, + bytes, +] + + +class AsyncV1SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: WebSocketClientProtocol): + super().__init__() + self._websocket = websocket + + async def __aiter__(self): + async for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + async def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + await self._emit_async(EventType.OPEN, None) + try: + async for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + await self._emit_async(EventType.MESSAGE, parsed) + except Exception as exc: + await self._emit_async(EventType.ERROR, exc) + finally: + await self._emit_async(EventType.CLOSE, None) + + async def send_settings(self, message: AgentV1Settings) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1Settings. + """ + await self._send_model(message) + + async def send_update_listen(self, message: AgentV1UpdateListen) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateListen. + """ + await self._send_model(message) + + async def send_update_think(self, message: AgentV1UpdateThink) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateThink. + """ + await self._send_model(message) + + async def send_update_speak(self, message: AgentV1UpdateSpeak) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateSpeak. + """ + await self._send_model(message) + + async def send_inject_user_message(self, message: AgentV1InjectUserMessage) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1InjectUserMessage. + """ + await self._send_model(message) + + async def send_inject_agent_message(self, message: AgentV1InjectAgentMessage) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1InjectAgentMessage. + """ + await self._send_model(message) + + async def send_function_call_response(self, message: AgentV1SendFunctionCallResponse) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1SendFunctionCallResponse. + """ + await self._send_model(message) + + async def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1KeepAlive. + """ + await self._send_model(message or AgentV1KeepAlive(type="KeepAlive")) + + async def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdatePrompt. + """ + await self._send_model(message) + + async def send_force_end_turn(self, message: typing.Optional[AgentV1ForceEndTurn] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1ForceEndTurn. + """ + await self._send_model(message or AgentV1ForceEndTurn(type="ForceEndTurn")) + + async def send_media(self, message: bytes) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a bytes. + """ + await self._send(message) + + async def recv(self) -> V1SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = await self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + async def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + await self._websocket.send(data) + + async def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + await self._send(_sanitize_numeric_types(data.dict())) + + +class V1SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: websockets_sync_connection.Connection): + super().__init__() + self._websocket = websocket + + def __iter__(self): + for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + self._emit(EventType.OPEN, None) + try: + for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + self._emit(EventType.MESSAGE, parsed) + except Exception as exc: + self._emit(EventType.ERROR, exc) + finally: + self._emit(EventType.CLOSE, None) + + def send_settings(self, message: AgentV1Settings) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1Settings. + """ + self._send_model(message) + + def send_update_listen(self, message: AgentV1UpdateListen) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateListen. + """ + self._send_model(message) + + def send_update_think(self, message: AgentV1UpdateThink) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateThink. + """ + self._send_model(message) + + def send_update_speak(self, message: AgentV1UpdateSpeak) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateSpeak. + """ + self._send_model(message) + + def send_inject_user_message(self, message: AgentV1InjectUserMessage) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1InjectUserMessage. + """ + self._send_model(message) + + def send_inject_agent_message(self, message: AgentV1InjectAgentMessage) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1InjectAgentMessage. + """ + self._send_model(message) + + def send_function_call_response(self, message: AgentV1SendFunctionCallResponse) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1SendFunctionCallResponse. + """ + self._send_model(message) + + def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1KeepAlive. + """ + self._send_model(message or AgentV1KeepAlive(type="KeepAlive")) + + def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdatePrompt. + """ + self._send_model(message) + + def send_force_end_turn(self, message: typing.Optional[AgentV1ForceEndTurn] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1ForceEndTurn. + """ + self._send_model(message or AgentV1ForceEndTurn(type="ForceEndTurn")) + + def send_media(self, message: bytes) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a bytes. + """ + self._send(message) + + def recv(self) -> V1SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + self._websocket.send(data) + + def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + self._send(_sanitize_numeric_types(data.dict())) diff --git a/src/deepgram/agent/v1/types/agent_v1settings.py.bak b/src/deepgram/agent/v1/types/agent_v1settings.py.bak new file mode 100644 index 00000000..ff58da13 --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1settings.py.bak @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel +from .agent_v1settings_agent import AgentV1SettingsAgent +from .agent_v1settings_agent_context import AgentV1SettingsAgentContext +from .agent_v1settings_audio import AgentV1SettingsAudio +from .agent_v1settings_flags import AgentV1SettingsFlags + + +class AgentV1Settings(UncheckedBaseModel): + type: typing.Literal["Settings"] = "Settings" + tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Tags to associate with the request + """ + + experimental: typing.Optional[bool] = pydantic.Field(default=None) + """ + To enable experimental features + """ + + flags: typing.Optional[AgentV1SettingsFlags] = None + mip_opt_out: typing.Optional[bool] = pydantic.Field(default=None) + """ + To opt out of Deepgram Model Improvement Program + """ + + audio: AgentV1SettingsAudio + agent: typing.Union[AgentV1SettingsAgent, AgentV1SettingsAgentContext, str] + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak b/src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak new file mode 100644 index 00000000..fae21e75 --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from .agent_v1settings_agent_context import AgentV1SettingsAgentContext +from .agent_v1settings_agent_listen import AgentV1SettingsAgentListen + + +class AgentV1SettingsAgent(AgentV1SettingsAgentContext): + # Keep the formerly callable public model name working with the schema's + # current nested context format. + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_legacy_nested_context(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, dict): + return values + + context = values.get("context") + if isinstance(context, AgentV1SettingsAgentContext) and ( + context.context is not None + and context.language is None + and context.listen is None + and context.think is None + and context.speak is None + and context.greeting is None + ): + values = dict(values) + values["context"] = context.context + + listen = values.get("listen") + if isinstance(listen, AgentV1SettingsAgentListen): + values = dict(values) + values["listen"] = listen.dict() + return values + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_legacy_nested_context(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if not isinstance(values, dict): + return values + + context = values.get("context") + if isinstance(context, AgentV1SettingsAgentContext) and ( + context.context is not None + and context.language is None + and context.listen is None + and context.think is None + and context.speak is None + and context.greeting is None + ): + values = dict(values) + values["context"] = context.context + + listen = values.get("listen") + if isinstance(listen, AgentV1SettingsAgentListen): + values = dict(values) + values["listen"] = listen.dict() + return values diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak b/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak new file mode 100644 index 00000000..827aad63 --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak @@ -0,0 +1,65 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel +from .agent_v1settings_agent_context_context import AgentV1SettingsAgentContextContext +from .agent_v1settings_agent_context_listen import AgentV1SettingsAgentContextListen +from .agent_v1settings_agent_context_messages_item import AgentV1SettingsAgentContextMessagesItem +from .agent_v1settings_agent_context_speak import AgentV1SettingsAgentContextSpeak +from .agent_v1settings_agent_context_think import AgentV1SettingsAgentContextThink + + +class AgentV1SettingsAgentContext(UncheckedBaseModel): + language: typing.Optional[str] = pydantic.Field(default=None) + """ + Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. + """ + + context: typing.Optional[AgentV1SettingsAgentContextContext] = pydantic.Field(default=None) + """ + Conversation context including the history of messages and function calls + """ + + listen: typing.Optional[AgentV1SettingsAgentContextListen] = None + think: typing.Optional[AgentV1SettingsAgentContextThink] = None + speak: typing.Optional[AgentV1SettingsAgentContextSpeak] = None + greeting: typing.Optional[str] = pydantic.Field(default=None) + """ + Optional message that agent will speak at the start + """ + + # Translate the pre-restructure top-level messages field to the current + # nested context.messages payload while retaining read-side access. + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_legacy_messages_to_context(cls, values: typing.Any) -> typing.Any: + if isinstance(values, dict) and "messages" in values and "context" not in values: + values = dict(values) + values["context"] = {"messages": values.pop("messages")} + return values + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_legacy_messages_to_context(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if isinstance(values, dict) and "messages" in values and "context" not in values: + values = dict(values) + values["context"] = {"messages": values.pop("messages")} + return values + + @property + def messages(self) -> typing.Optional[typing.List[AgentV1SettingsAgentContextMessagesItem]]: + return None if self.context is None else self.context.messages + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak new file mode 100644 index 00000000..a13d2a63 --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak @@ -0,0 +1,79 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel, UnionMetadata + + +class AgentV1SettingsAgentContextListenProvider_V1(UncheckedBaseModel): + version: typing.Literal["v1"] = "v1" + type: typing.Literal["deepgram"] = "deepgram" + model: typing.Optional[str] = None + language: typing.Optional[str] = None + keyterms: typing.Optional[typing.List[str]] = None + smart_format: typing.Optional[bool] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +class AgentV1SettingsAgentContextListenProvider_V2(UncheckedBaseModel): + version: typing.Literal["v2"] = "v2" + type: typing.Literal["deepgram"] = "deepgram" + model: str + language_hints: typing.Optional[typing.List[str]] = None + language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) + eot_threshold: typing.Optional[float] = None + eager_eot_threshold: typing.Optional[float] = None + eot_timeout_ms: typing.Optional[int] = None + keyterms: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +AgentV1SettingsAgentContextListenProvider = typing_extensions.Annotated[ + typing.Union[AgentV1SettingsAgentContextListenProvider_V1, AgentV1SettingsAgentContextListenProvider_V2], + UnionMetadata(discriminant="version"), +] diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak new file mode 100644 index 00000000..40706b47 --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak @@ -0,0 +1,79 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel, UnionMetadata + + +class AgentV1SettingsAgentListenProvider_V1(UncheckedBaseModel): + version: typing.Literal["v1"] = "v1" + type: typing.Literal["deepgram"] = "deepgram" + model: typing.Optional[str] = None + language: typing.Optional[str] = None + keyterms: typing.Optional[typing.List[str]] = None + smart_format: typing.Optional[bool] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +class AgentV1SettingsAgentListenProvider_V2(UncheckedBaseModel): + version: typing.Literal["v2"] = "v2" + type: typing.Literal["deepgram"] = "deepgram" + model: str + language_hints: typing.Optional[typing.List[str]] = None + language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) + eot_threshold: typing.Optional[float] = None + eager_eot_threshold: typing.Optional[float] = None + eot_timeout_ms: typing.Optional[int] = None + keyterms: typing.Optional[typing.List[str]] = None + + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +AgentV1SettingsAgentListenProvider = typing_extensions.Annotated[ + typing.Union[AgentV1SettingsAgentListenProvider_V1, AgentV1SettingsAgentListenProvider_V2], + UnionMetadata(discriminant="version"), +] diff --git a/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak new file mode 100644 index 00000000..d6ad435a --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak @@ -0,0 +1,64 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel +from ....types.deepgram_listen_provider_v1 import DeepgramListenProviderV1 +from ....types.deepgram_listen_provider_v2 import DeepgramListenProviderV2 +from .agent_v1update_listen_listen_provider import AgentV1UpdateListenListenProvider + +_V1_ONLY_KEYS = frozenset({"language", "smart_format"}) + + +def _coerce_legacy_update_listen_provider(values: typing.Any) -> typing.Any: + """Add the discriminator required by the versioned provider union.""" + if not isinstance(values, dict): + return values + provider = values.get("provider") + if isinstance(provider, DeepgramListenProviderV2): + values = dict(values) + values["provider"] = {**provider.dict(), "version": "v2"} + elif isinstance(provider, DeepgramListenProviderV1): + values = dict(values) + values["provider"] = {**provider.dict(), "version": "v1"} + elif isinstance(provider, dict) and "version" not in provider: + values = dict(values) + version = "v1" if _V1_ONLY_KEYS & provider.keys() else "v2" + provider_cls = DeepgramListenProviderV1 if version == "v1" else DeepgramListenProviderV2 + try: + coerced = provider_cls(**provider).dict() + except Exception: + coerced = dict(provider) + values["provider"] = {**coerced, "version": version} + return values + + +class AgentV1UpdateListenListen(UncheckedBaseModel): + """ + Listen configuration to update. Contains a provider object with the same schema as Settings. The model and language can be changed mid-session. Keyterms can only be updated mid-session for Flux models. + """ + + provider: AgentV1UpdateListenListenProvider + + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_legacy_provider(cls, values: typing.Any) -> typing.Any: + return _coerce_legacy_update_listen_provider(values) + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_legacy_provider(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + return _coerce_legacy_update_listen_provider(values) + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/core/api_error.py.bak b/src/deepgram/core/api_error.py.bak new file mode 100644 index 00000000..2aa93914 --- /dev/null +++ b/src/deepgram/core/api_error.py.bak @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + +from .._secure_logging import redact_sensitive_headers + + +class ApiError(Exception): + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + ) -> None: + self.headers = redact_sensitive_headers(headers) + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/src/deepgram/core/parse_error.py.bak b/src/deepgram/core/parse_error.py.bak new file mode 100644 index 00000000..c510c0e8 --- /dev/null +++ b/src/deepgram/core/parse_error.py.bak @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + +from .._secure_logging import redact_sensitive_headers + + +class ParsingError(Exception): + """ + Raised when the SDK fails to parse/validate a response from the server. + This typically indicates that the server returned a response whose shape + does not match the expected schema. + """ + + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + cause: Optional[Exception] + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + cause: Optional[Exception] = None, + ) -> None: + self.headers = redact_sensitive_headers(headers) + self.status_code = status_code + self.body = body + self.cause = cause + super().__init__() + if cause is not None: + self.__cause__ = cause + + def __str__(self) -> str: + cause_str = f", cause: {self.cause}" if self.cause is not None else "" + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}" diff --git a/src/deepgram/core/query_encoder.py.bak b/src/deepgram/core/query_encoder.py.bak new file mode 100644 index 00000000..58088258 --- /dev/null +++ b/src/deepgram/core/query_encoder.py.bak @@ -0,0 +1,65 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, List, Optional, Tuple + +import pydantic + + +def _coerce_query_value(value: Any) -> Any: + # urllib.parse.urlencode would otherwise turn bools into "True"/"False". + if isinstance(value, bool): + return "true" if value else "false" + return value + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: + result = [] + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.extend(traverse_query_dict(v, key)) + elif isinstance(v, list): + for arr_v in v: + if isinstance(arr_v, dict): + result.extend(traverse_query_dict(arr_v, key)) + else: + result.append((key, _coerce_query_value(arr_v))) + else: + result.append((key, _coerce_query_value(v))) + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + elif isinstance(query_value, list): + encoded_values: List[Tuple[str, Any]] = [] + for value in query_value: + if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): + if isinstance(value, pydantic.BaseModel): + obj_dict = value.dict(by_alias=True) + elif isinstance(value, dict): + obj_dict = value + + encoded_values.extend(single_query_encoder(query_key, obj_dict)) + else: + encoded_values.append((query_key, _coerce_query_value(value))) + + return encoded_values + + return [(query_key, _coerce_query_value(query_value))] + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: + if query is None: + return None + + encoded_query = [] + for k, v in query.items(): + encoded_query.extend(single_query_encoder(k, v)) + return encoded_query diff --git a/src/deepgram/listen/v1/socket_client.py.bak b/src/deepgram/listen/v1/socket_client.py.bak new file mode 100644 index 00000000..5954d116 --- /dev/null +++ b/src/deepgram/listen/v1/socket_client.py.bak @@ -0,0 +1,234 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +import logging +import typing + +import websockets.sync.connection as websockets_sync_connection +from ...core.events import EventEmitterMixin, EventType +from ...core.unchecked_base_model import construct_type +from .types.listen_v1close_stream import ListenV1CloseStream +from .types.listen_v1finalize import ListenV1Finalize +from .types.listen_v1keep_alive import ListenV1KeepAlive +from .types.listen_v1metadata import ListenV1Metadata +from .types.listen_v1results import ListenV1Results +from .types.listen_v1speech_started import ListenV1SpeechStarted +from .types.listen_v1utterance_end import ListenV1UtteranceEnd + +try: + from websockets.legacy.client import WebSocketClientProtocol # type: ignore +except ImportError: + from websockets import WebSocketClientProtocol # type: ignore + +_logger = logging.getLogger(__name__) +V1SocketClientResponse = typing.Union[ListenV1Results, ListenV1Metadata, ListenV1UtteranceEnd, ListenV1SpeechStarted] + + +class AsyncV1SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: WebSocketClientProtocol): + super().__init__() + self._websocket = websocket + + async def __aiter__(self): + async for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + async def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + await self._emit_async(EventType.OPEN, None) + try: + async for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + await self._emit_async(EventType.MESSAGE, parsed) + except Exception as exc: + await self._emit_async(EventType.ERROR, exc) + finally: + await self._emit_async(EventType.CLOSE, None) + + async def send_media(self, message: bytes) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a bytes. + """ + await self._send(message) + + async def send_finalize(self, message: typing.Optional[ListenV1Finalize] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV1Finalize. + """ + await self._send_model(message or ListenV1Finalize(type="Finalize")) + + async def send_close_stream(self, message: typing.Optional[ListenV1CloseStream] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV1CloseStream. + """ + await self._send_model(message or ListenV1CloseStream(type="CloseStream")) + + async def send_keep_alive(self, message: typing.Optional[ListenV1KeepAlive] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV1KeepAlive. + """ + await self._send_model(message or ListenV1KeepAlive(type="KeepAlive")) + + async def recv(self) -> V1SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = await self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + async def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + await self._websocket.send(data) + + async def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + await self._send(data.dict()) + + +class V1SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: websockets_sync_connection.Connection): + super().__init__() + self._websocket = websocket + + def __iter__(self): + for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + self._emit(EventType.OPEN, None) + try: + for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + self._emit(EventType.MESSAGE, parsed) + except Exception as exc: + self._emit(EventType.ERROR, exc) + finally: + self._emit(EventType.CLOSE, None) + + def send_media(self, message: bytes) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a bytes. + """ + self._send(message) + + def send_finalize(self, message: typing.Optional[ListenV1Finalize] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV1Finalize. + """ + self._send_model(message or ListenV1Finalize(type="Finalize")) + + def send_close_stream(self, message: typing.Optional[ListenV1CloseStream] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV1CloseStream. + """ + self._send_model(message or ListenV1CloseStream(type="CloseStream")) + + def send_keep_alive(self, message: typing.Optional[ListenV1KeepAlive] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV1KeepAlive. + """ + self._send_model(message or ListenV1KeepAlive(type="KeepAlive")) + + def recv(self) -> V1SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + self._websocket.send(data) + + def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + self._send(data.dict()) diff --git a/src/deepgram/listen/v2/socket_client.py.bak b/src/deepgram/listen/v2/socket_client.py.bak new file mode 100644 index 00000000..e4e09468 --- /dev/null +++ b/src/deepgram/listen/v2/socket_client.py.bak @@ -0,0 +1,247 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +import logging +import typing + +import websockets.sync.connection as websockets_sync_connection +from ...core.events import EventEmitterMixin, EventType +from ...core.unchecked_base_model import construct_type +from .types.listen_v2close_stream import ListenV2CloseStream +from .types.listen_v2configure import ListenV2Configure +from .types.listen_v2configure_failure import ListenV2ConfigureFailure +from .types.listen_v2configure_success import ListenV2ConfigureSuccess +from .types.listen_v2connected import ListenV2Connected +from .types.listen_v2fatal_error import ListenV2FatalError +from .types.listen_v2force_end_turn import ListenV2ForceEndTurn +from .types.listen_v2turn_info import ListenV2TurnInfo + +try: + from websockets.legacy.client import WebSocketClientProtocol # type: ignore +except ImportError: + from websockets import WebSocketClientProtocol # type: ignore + +_logger = logging.getLogger(__name__) +V2SocketClientResponse = typing.Union[ + ListenV2Connected, ListenV2TurnInfo, ListenV2ConfigureSuccess, ListenV2ConfigureFailure, ListenV2FatalError +] + + +class AsyncV2SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: WebSocketClientProtocol): + super().__init__() + self._websocket = websocket + + async def __aiter__(self): + async for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + async def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + await self._emit_async(EventType.OPEN, None) + try: + async for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + await self._emit_async(EventType.MESSAGE, parsed) + except Exception as exc: + await self._emit_async(EventType.ERROR, exc) + finally: + await self._emit_async(EventType.CLOSE, None) + + async def send_media(self, message: bytes) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a bytes. + """ + await self._send(message) + + async def send_close_stream(self, message: typing.Optional[ListenV2CloseStream] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV2CloseStream. + """ + await self._send_model(message or ListenV2CloseStream(type="CloseStream")) + + async def send_force_end_turn(self, message: typing.Optional[ListenV2ForceEndTurn] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV2ForceEndTurn. + This requires server-side enablement. On deployments without the feature, the server returns + UNPARSABLE_CLIENT_MESSAGE and closes the connection. + """ + await self._send_model(message or ListenV2ForceEndTurn(type="ForceEndTurn")) + + async def send_configure(self, message: typing.Union[ListenV2Configure, typing.Dict[str, typing.Any]]) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV2Configure. + """ + if isinstance(message, dict): + await self._send(message) + else: + await self._send_model(message) + + async def recv(self) -> V2SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = await self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + async def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + await self._websocket.send(data) + + async def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + await self._send(data.dict()) + + +class V2SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: websockets_sync_connection.Connection): + super().__init__() + self._websocket = websocket + + def __iter__(self): + for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + self._emit(EventType.OPEN, None) + try: + for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + self._emit(EventType.MESSAGE, parsed) + except Exception as exc: + self._emit(EventType.ERROR, exc) + finally: + self._emit(EventType.CLOSE, None) + + def send_media(self, message: bytes) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a bytes. + """ + self._send(message) + + def send_close_stream(self, message: typing.Optional[ListenV2CloseStream] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV2CloseStream. + """ + self._send_model(message or ListenV2CloseStream(type="CloseStream")) + + def send_force_end_turn(self, message: typing.Optional[ListenV2ForceEndTurn] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV2ForceEndTurn. + This requires server-side enablement. On deployments without the feature, the server returns + UNPARSABLE_CLIENT_MESSAGE and closes the connection. + """ + self._send_model(message or ListenV2ForceEndTurn(type="ForceEndTurn")) + + def send_configure(self, message: typing.Union[ListenV2Configure, typing.Dict[str, typing.Any]]) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a ListenV2Configure. + """ + if isinstance(message, dict): + self._send(message) + else: + self._send_model(message) + + def recv(self) -> V2SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + self._websocket.send(data) + + def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + self._send(data.dict()) diff --git a/src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak b/src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak new file mode 100644 index 00000000..bf30e353 --- /dev/null +++ b/src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ._dict_compat import ListenV2ResponseDictCompatModel + + +class ListenV2ConfigureFailure(ListenV2ResponseDictCompatModel): + type: typing.Literal["ConfigureFailure"] = pydantic.Field(default="ConfigureFailure") + """ + Message type identifier + """ + + request_id: str = pydantic.Field() + """ + The unique identifier of the request + """ + + sequence_id: int = pydantic.Field() + """ + Starts at `0` and increments for each message the server sends + to the client. This includes messages of other types, like + `TurnInfo` messages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2configure_success.py.bak b/src/deepgram/listen/v2/types/listen_v2configure_success.py.bak new file mode 100644 index 00000000..bde84d6f --- /dev/null +++ b/src/deepgram/listen/v2/types/listen_v2configure_success.py.bak @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....types.listen_v2keyterm import ListenV2Keyterm +from ._dict_compat import ListenV2ResponseDictCompatModel +from .listen_v2configure_success_thresholds import ListenV2ConfigureSuccessThresholds + + +class ListenV2ConfigureSuccess(ListenV2ResponseDictCompatModel): + type: typing.Literal["ConfigureSuccess"] = pydantic.Field(default="ConfigureSuccess") + """ + Message type identifier + """ + + request_id: str = pydantic.Field() + """ + The unique identifier of the request + """ + + thresholds: ListenV2ConfigureSuccessThresholds = pydantic.Field() + """ + Updates each parameter, if it is supplied. If a particular threshold parameter + is not supplied, the configuration continues using the currently configured value. + """ + + keyterms: ListenV2Keyterm + language_hints: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + The currently active language hints. Only applicable to the flux-general-multi model. + """ + + sequence_id: int = pydantic.Field() + """ + Starts at `0` and increments for each message the server sends + to the client. This includes messages of other types, like + `TurnInfo` messages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak b/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak new file mode 100644 index 00000000..e0353ec9 --- /dev/null +++ b/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....types.listen_v2eager_eot_threshold import ListenV2EagerEotThreshold +from ....types.listen_v2eot_threshold import ListenV2EotThreshold +from ....types.listen_v2eot_timeout_ms import ListenV2EotTimeoutMs +from ._dict_compat import ListenV2ResponseDictCompatModel + + +class ListenV2ConfigureSuccessThresholds(ListenV2ResponseDictCompatModel): + """ + Updates each parameter, if it is supplied. If a particular threshold parameter + is not supplied, the configuration continues using the currently configured value. + """ + + eager_eot_threshold: typing.Optional[ListenV2EagerEotThreshold] = None + eot_threshold: typing.Optional[ListenV2EotThreshold] = None + eot_timeout_ms: typing.Optional[ListenV2EotTimeoutMs] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2connected.py.bak b/src/deepgram/listen/v2/types/listen_v2connected.py.bak new file mode 100644 index 00000000..b253d86c --- /dev/null +++ b/src/deepgram/listen/v2/types/listen_v2connected.py.bak @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ._dict_compat import ListenV2ResponseDictCompatModel + + +class ListenV2Connected(ListenV2ResponseDictCompatModel): + type: typing.Literal["Connected"] = pydantic.Field(default="Connected") + """ + Message type identifier + """ + + request_id: str = pydantic.Field() + """ + The unique identifier of the request + """ + + sequence_id: int = pydantic.Field() + """ + Starts at `0` and increments for each message the server sends + to the client. This includes messages of other types, like + `TurnInfo` messages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak b/src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak new file mode 100644 index 00000000..2202c82e --- /dev/null +++ b/src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ._dict_compat import ListenV2ResponseDictCompatModel + + +class ListenV2FatalError(ListenV2ResponseDictCompatModel): + type: typing.Literal["Error"] = pydantic.Field(default="Error") + """ + Message type identifier + """ + + sequence_id: int = pydantic.Field() + """ + Starts at `0` and increments for each message the server sends + to the client. This includes messages of other types, like + `Connected` messages. + """ + + code: str = pydantic.Field() + """ + A string code describing the error, e.g. `INTERNAL_SERVER_ERROR` + """ + + description: str = pydantic.Field() + """ + Prose description of the error + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2turn_info.py.bak b/src/deepgram/listen/v2/types/listen_v2turn_info.py.bak new file mode 100644 index 00000000..969d6d0c --- /dev/null +++ b/src/deepgram/listen/v2/types/listen_v2turn_info.py.bak @@ -0,0 +1,102 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ._dict_compat import ListenV2ResponseDictCompatModel +from .listen_v2turn_info_event import ListenV2TurnInfoEvent +from .listen_v2turn_info_words_item import ListenV2TurnInfoWordsItem + + +class ListenV2TurnInfo(ListenV2ResponseDictCompatModel): + """ + Describes the current turn and latest state of the turn + """ + + type: typing.Literal["TurnInfo"] = "TurnInfo" + request_id: str = pydantic.Field() + """ + The unique identifier of the request + """ + + sequence_id: int = pydantic.Field() + """ + Starts at `0` and increments for each message the server sends to the client. This includes messages of other types, like `Connected` messages. + """ + + event: ListenV2TurnInfoEvent = pydantic.Field() + """ + The type of event being reported. + + - **Update** - Additional audio has been transcribed, but the turn state hasn't changed + - **StartOfTurn** - The user has begun speaking for the first time in the turn + - **EagerEndOfTurn** - The system has moderate confidence that the user has finished speaking for the turn. This is an opportunity to begin preparing an agent reply + - **TurnResumed** - The system detected that speech had ended and therefore sent an **EagerEndOfTurn** event, but speech is actually continuing for this turn + - **EndOfTurn** - The user has finished speaking for the turn + """ + + turn_index: int = pydantic.Field() + """ + The index of the current turn + """ + + audio_window_start: float = pydantic.Field() + """ + Start time in seconds of the audio range that was transcribed + """ + + audio_window_end: float = pydantic.Field() + """ + End time in seconds of the audio range that was transcribed + """ + + transcript: str = pydantic.Field() + """ + Text that was said over the course of the current turn + """ + + words: typing.List[ListenV2TurnInfoWordsItem] = pydantic.Field() + """ + The words in the `transcript` + """ + + end_of_turn_confidence: float = pydantic.Field() + """ + Confidence that no more speech is coming in this turn + """ + + trigger: typing.Optional[str] = pydantic.Field(default=None) + """ + The cause of the turn ending. Present on every `EndOfTurn` event and only there. + + - **model** - the turn ended by Flux's native end-of-turn detection + + - **manual** - the turn ended because a `ForceEndTurn` message was sent + + - **timeout** - the turn ended because `eot_timeout_ms` elapsed + + This is an open enum. New values may be added over time, so clients must tolerate values they do not recognize. + """ + + languages: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Detected languages sorted by descending frequency in the + transcript. Only present when the flux-general-multi model + detects languages in the audio. + """ + + languages_hinted: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + The language hints that were supplied for this turn. Only + present when language hints are configured. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak b/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak new file mode 100644 index 00000000..88240cd9 --- /dev/null +++ b/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ._dict_compat import ListenV2ResponseDictCompatModel + + +class ListenV2TurnInfoWordsItem(ListenV2ResponseDictCompatModel): + word: str = pydantic.Field() + """ + The individual punctuated, properly-cased word from the transcript + """ + + confidence: float = pydantic.Field() + """ + Confidence that this word was transcribed correctly + """ + + start: typing.Optional[float] = pydantic.Field(default=None) + """ + The start time of the word + """ + + end: typing.Optional[float] = pydantic.Field(default=None) + """ + The end time of the word + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/requests/deepgram_listen_provider_v2.py.bak b/src/deepgram/requests/deepgram_listen_provider_v2.py.bak new file mode 100644 index 00000000..02fdf65a --- /dev/null +++ b/src/deepgram/requests/deepgram_listen_provider_v2.py.bak @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class DeepgramListenProviderV2Params(typing_extensions.TypedDict): + type: typing.Literal["deepgram"] + """ + Provider type for speech-to-text + """ + + version: typing_extensions.NotRequired[typing.Literal["v2"]] + """ + Specifies usage of the V2 Deepgram speech-to-text API (e.g. Flux) + """ + + model: str + """ + Model to use for speech to text using the V2 API (e.g. flux-general-en, flux-general-multi) + """ + + language_hints: typing_extensions.NotRequired[typing.Sequence[str]] + """ + An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. + """ + + eot_threshold: typing_extensions.NotRequired[float] + """ + End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it for full manual turn control with the ForceEndTurn message. + """ + + eager_eot_threshold: typing_extensions.NotRequired[float] + """ + End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid range: 0.3 - 0.9. + """ + + eot_timeout_ms: typing_extensions.NotRequired[int] + """ + A turn will be finished when this much time in milliseconds has passed after speech, regardless of EOT confidence. Defaults to 5000. + """ + + keyterms: typing_extensions.NotRequired[typing.Sequence[str]] + """ + Prompt keyterm recognition to improve Keyword Recall Rate + """ diff --git a/src/deepgram/speak/v1/socket_client.py.bak b/src/deepgram/speak/v1/socket_client.py.bak new file mode 100644 index 00000000..e3c28bb5 --- /dev/null +++ b/src/deepgram/speak/v1/socket_client.py.bak @@ -0,0 +1,235 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +import logging +import typing + +import websockets.sync.connection as websockets_sync_connection +from ...core.events import EventEmitterMixin, EventType +from ...core.unchecked_base_model import construct_type +from .types.speak_v1clear import SpeakV1Clear +from .types.speak_v1cleared import SpeakV1Cleared +from .types.speak_v1close import SpeakV1Close +from .types.speak_v1flush import SpeakV1Flush +from .types.speak_v1flushed import SpeakV1Flushed +from .types.speak_v1metadata import SpeakV1Metadata +from .types.speak_v1text import SpeakV1Text +from .types.speak_v1warning import SpeakV1Warning + +try: + from websockets.legacy.client import WebSocketClientProtocol # type: ignore +except ImportError: + from websockets import WebSocketClientProtocol # type: ignore + +_logger = logging.getLogger(__name__) +V1SocketClientResponse = typing.Union[bytes, SpeakV1Metadata, SpeakV1Flushed, SpeakV1Cleared, SpeakV1Warning] + + +class AsyncV1SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: WebSocketClientProtocol): + super().__init__() + self._websocket = websocket + + async def __aiter__(self): + async for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + async def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + await self._emit_async(EventType.OPEN, None) + try: + async for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + await self._emit_async(EventType.MESSAGE, parsed) + except Exception as exc: + await self._emit_async(EventType.ERROR, exc) + finally: + await self._emit_async(EventType.CLOSE, None) + + async def send_text(self, message: SpeakV1Text) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV1Text. + """ + await self._send_model(message) + + async def send_flush(self, message: typing.Optional[SpeakV1Flush] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV1Flush. + """ + await self._send_model(message or SpeakV1Flush(type="Flush")) + + async def send_clear(self, message: typing.Optional[SpeakV1Clear] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV1Clear. + """ + await self._send_model(message or SpeakV1Clear(type="Clear")) + + async def send_close(self, message: typing.Optional[SpeakV1Close] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV1Close. + """ + await self._send_model(message or SpeakV1Close(type="Close")) + + async def recv(self) -> V1SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = await self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + async def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + await self._websocket.send(data) + + async def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + await self._send(data.dict()) + + +class V1SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: websockets_sync_connection.Connection): + super().__init__() + self._websocket = websocket + + def __iter__(self): + for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + self._emit(EventType.OPEN, None) + try: + for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + self._emit(EventType.MESSAGE, parsed) + except Exception as exc: + self._emit(EventType.ERROR, exc) + finally: + self._emit(EventType.CLOSE, None) + + def send_text(self, message: SpeakV1Text) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV1Text. + """ + self._send_model(message) + + def send_flush(self, message: typing.Optional[SpeakV1Flush] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV1Flush. + """ + self._send_model(message or SpeakV1Flush(type="Flush")) + + def send_clear(self, message: typing.Optional[SpeakV1Clear] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV1Clear. + """ + self._send_model(message or SpeakV1Clear(type="Clear")) + + def send_close(self, message: typing.Optional[SpeakV1Close] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV1Close. + """ + self._send_model(message or SpeakV1Close(type="Close")) + + def recv(self) -> V1SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + self._websocket.send(data) + + def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + self._send(data.dict()) diff --git a/src/deepgram/speak/v2/socket_client.py.bak b/src/deepgram/speak/v2/socket_client.py.bak new file mode 100644 index 00000000..c6729c32 --- /dev/null +++ b/src/deepgram/speak/v2/socket_client.py.bak @@ -0,0 +1,268 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +import logging +import typing + +import websockets.sync.connection as websockets_sync_connection +from ...core.events import EventEmitterMixin, EventType +from ...core.unchecked_base_model import construct_type +from .types.speak_v2close import SpeakV2Close +from .types.speak_v2configure import SpeakV2Configure +from .types.speak_v2configure_failure import SpeakV2ConfigureFailure +from .types.speak_v2configure_success import SpeakV2ConfigureSuccess +from .types.speak_v2connected import SpeakV2Connected +from .types.speak_v2error import SpeakV2Error +from .types.speak_v2flush import SpeakV2Flush +from .types.speak_v2flushed import SpeakV2Flushed +from .types.speak_v2interrupt import SpeakV2Interrupt +from .types.speak_v2session_metadata import SpeakV2SessionMetadata +from .types.speak_v2speak import SpeakV2Speak +from .types.speak_v2speech_interrupted import SpeakV2SpeechInterrupted +from .types.speak_v2speech_metadata import SpeakV2SpeechMetadata +from .types.speak_v2speech_started import SpeakV2SpeechStarted +from .types.speak_v2warning import SpeakV2Warning + +try: + from websockets.legacy.client import WebSocketClientProtocol # type: ignore +except ImportError: + from websockets import WebSocketClientProtocol # type: ignore + +_logger = logging.getLogger(__name__) +V2SocketClientResponse = typing.Union[ + bytes, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2SpeechInterrupted, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2ConfigureSuccess, + SpeakV2ConfigureFailure, + SpeakV2Warning, + SpeakV2Error, +] + + +class AsyncV2SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: WebSocketClientProtocol): + super().__init__() + self._websocket = websocket + + async def __aiter__(self): + async for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + async def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + await self._emit_async(EventType.OPEN, None) + try: + async for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + await self._emit_async(EventType.MESSAGE, parsed) + except Exception as exc: + await self._emit_async(EventType.ERROR, exc) + finally: + await self._emit_async(EventType.CLOSE, None) + + async def send_speak(self, message: SpeakV2Speak) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Speak. + """ + await self._send_model(message) + + async def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Flush. + """ + await self._send_model(message or SpeakV2Flush(type="Flush")) + + async def send_interrupt(self, message: typing.Optional[SpeakV2Interrupt] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Interrupt. + """ + await self._send_model(message or SpeakV2Interrupt(type="Interrupt")) + + async def send_configure(self, message: SpeakV2Configure) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Configure. + """ + await self._send_model(message) + + async def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Close. + """ + await self._send_model(message or SpeakV2Close(type="Close")) + + async def recv(self) -> V2SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = await self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + async def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + await self._websocket.send(data) + + async def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + await self._send(data.dict()) + + +class V2SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: websockets_sync_connection.Connection): + super().__init__() + self._websocket = websocket + + def __iter__(self): + for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + self._emit(EventType.OPEN, None) + try: + for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + self._emit(EventType.MESSAGE, parsed) + except Exception as exc: + self._emit(EventType.ERROR, exc) + finally: + self._emit(EventType.CLOSE, None) + + def send_speak(self, message: SpeakV2Speak) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Speak. + """ + self._send_model(message) + + def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Flush. + """ + self._send_model(message or SpeakV2Flush(type="Flush")) + + def send_interrupt(self, message: typing.Optional[SpeakV2Interrupt] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Interrupt. + """ + self._send_model(message or SpeakV2Interrupt(type="Interrupt")) + + def send_configure(self, message: SpeakV2Configure) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Configure. + """ + self._send_model(message) + + def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Close. + """ + self._send_model(message or SpeakV2Close(type="Close")) + + def recv(self) -> V2SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + self._websocket.send(data) + + def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + self._send(data.dict()) diff --git a/src/deepgram/types/deepgram.py.bak b/src/deepgram/types/deepgram.py.bak new file mode 100644 index 00000000..994f7a2c --- /dev/null +++ b/src/deepgram/types/deepgram.py.bak @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ..core.unchecked_base_model import UncheckedBaseModel +from .deepgram_model import DeepgramModel + + +class Deepgram(UncheckedBaseModel): + """ + Deepgram text-to-speech provider. Aura models use version v1 (default); Flux TTS uses version v2 and a flux-* model. Flux TTS is the default when agent.speak is omitted, using the flux-kit-en voice. + """ + + type: typing.Literal["deepgram"] = "deepgram" + version: typing.Optional[str] = pydantic.Field(default=None) + """ + The Deepgram text-to-speech model family. Accepted values: `v1` (Aura, the default) and `v2` (Flux TTS). Use `v1` with an aura-* model and `v2` with a flux-* model. Defaults to `v1` when omitted. + """ + + model: DeepgramModel = pydantic.Field() + """ + Deepgram TTS model. Aura models (version v1) use the aura-* voices; Flux TTS (version v2) uses the flux-{voice}-{language} voices (e.g. flux-alexis-en). Defaults to flux-kit-en when agent.speak is omitted. + """ + + speed: typing.Optional[float] = pydantic.Field(default=None) + """ + Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Aura (version v1) accepts any value from 0.7 to 1.5. Flux TTS (version v2) accepts values from 0.5 to 1.5 in 0.05 increments; a value the family does not accept ends the session with FAILED_TO_SPEAK. Not yet supported in all languages. + """ + + expressivity: typing.Optional[pydantic.StrictInt] = pydantic.Field(default=None) + """ + Delivery register of the generated speech, on a calm-to-animated axis. Flux TTS (version v2) only, on every Flux voice. Accepts the whole numbers -2 to 2, where 0 (the default) is the voice's tuned delivery and the only value validated for production, -2 the calm end of the range and 2 the animated end. Fixed for the session. Beta: behavior may change in future model versions, and non-default values increase the risk of hallucinations and pronunciation errors. See [Expressivity](/docs/tts-expressivity). + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/types/deepgram_listen_provider_v2.py.bak b/src/deepgram/types/deepgram_listen_provider_v2.py.bak new file mode 100644 index 00000000..c166bfac --- /dev/null +++ b/src/deepgram/types/deepgram_listen_provider_v2.py.bak @@ -0,0 +1,88 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ..core.unchecked_base_model import UncheckedBaseModel + + +class DeepgramListenProviderV2(UncheckedBaseModel): + type: typing.Literal["deepgram"] = pydantic.Field(default="deepgram") + """ + Provider type for speech-to-text + """ + + version: typing.Optional[typing.Literal["v2"]] = pydantic.Field(default=None) + """ + Specifies usage of the V2 Deepgram speech-to-text API (e.g. Flux) + """ + + model: str = pydantic.Field() + """ + Model to use for speech to text using the V2 API (e.g. flux-general-en, flux-general-multi) + """ + + language_hints: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. + """ + + language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) + """ + Deprecated. Use `language_hints`. Accepted for backward compatibility and + remapped before serialization so the singular field never reaches the API. + """ + + eot_threshold: typing.Optional[float] = pydantic.Field(default=None) + """ + End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it for full manual turn control with the ForceEndTurn message. + """ + + eager_eot_threshold: typing.Optional[float] = pydantic.Field(default=None) + """ + End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid range: 0.3 - 0.9. + """ + + eot_timeout_ms: typing.Optional[int] = pydantic.Field(default=None) + """ + A turn will be finished when this much time in milliseconds has passed after speech, regardless of EOT confidence. Defaults to 5000. + """ + + keyterms: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Prompt keyterm recognition to improve Keyword Recall Rate + """ + + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/types/listen_v2eot_threshold.py.bak b/src/deepgram/types/listen_v2eot_threshold.py.bak new file mode 100644 index 00000000..9f6e231c --- /dev/null +++ b/src/deepgram/types/listen_v2eot_threshold.py.bak @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListenV2EotThreshold = typing.Any +""" +End-of-turn confidence required to finish a turn. Valid Values 0.5 - +1.0. Set to 1.0 to fully suppress confidence-based end-of-turn detection. +`eot_timeout_ms` still ends idle turns; increase it for full manual +turn control with the `ForceEndTurn` message. +""" diff --git a/src/deepgram/types/speak_settings_v1provider.py.bak b/src/deepgram/types/speak_settings_v1provider.py.bak new file mode 100644 index 00000000..3fbef168 --- /dev/null +++ b/src/deepgram/types/speak_settings_v1provider.py.bak @@ -0,0 +1,117 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ..core.unchecked_base_model import UncheckedBaseModel, UnionMetadata +from .aws_polly_speak_provider_credentials import AwsPollySpeakProviderCredentials +from .aws_polly_speak_provider_engine import AwsPollySpeakProviderEngine +from .aws_polly_speak_provider_voice import AwsPollySpeakProviderVoice +from .cartesia_model_id import CartesiaModelId +from .cartesia_voice import CartesiaVoice +from .deepgram_model import DeepgramModel +from .eleven_labs_speak_provider_model_id import ElevenLabsSpeakProviderModelId +from .open_ai_speak_provider_model import OpenAiSpeakProviderModel +from .open_ai_speak_provider_voice import OpenAiSpeakProviderVoice + + +class SpeakSettingsV1Provider_Deepgram(UncheckedBaseModel): + type: typing.Literal["deepgram"] = "deepgram" + version: typing.Optional[str] = None + model: DeepgramModel + speed: typing.Optional[float] = None + expressivity: typing.Optional[pydantic.StrictInt] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +class SpeakSettingsV1Provider_ElevenLabs(UncheckedBaseModel): + type: typing.Literal["eleven_labs"] = "eleven_labs" + version: typing.Optional[typing.Literal["v1"]] = None + model_id: ElevenLabsSpeakProviderModelId + language: typing.Optional[str] = None + language_code: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +class SpeakSettingsV1Provider_Cartesia(UncheckedBaseModel): + type: typing.Literal["cartesia"] = "cartesia" + version: typing.Optional[typing.Literal["2025-03-17"]] = None + model_id: CartesiaModelId + voice: CartesiaVoice + language: typing.Optional[str] = None + volume: typing.Optional[float] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +class SpeakSettingsV1Provider_OpenAi(UncheckedBaseModel): + type: typing.Literal["open_ai"] = "open_ai" + version: typing.Optional[typing.Literal["v1"]] = None + model: OpenAiSpeakProviderModel + voice: OpenAiSpeakProviderVoice + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +class SpeakSettingsV1Provider_AwsPolly(UncheckedBaseModel): + type: typing.Literal["aws_polly"] = "aws_polly" + voice: AwsPollySpeakProviderVoice + language: str + language_code: typing.Optional[str] = None + engine: AwsPollySpeakProviderEngine + credentials: AwsPollySpeakProviderCredentials + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + +SpeakSettingsV1Provider = typing_extensions.Annotated[ + typing.Union[ + SpeakSettingsV1Provider_Deepgram, + SpeakSettingsV1Provider_ElevenLabs, + SpeakSettingsV1Provider_Cartesia, + SpeakSettingsV1Provider_OpenAi, + SpeakSettingsV1Provider_AwsPolly, + ], + UnionMetadata(discriminant="type"), +] diff --git a/tests/wire/test_listen_v1_media.py.bak b/tests/wire/test_listen_v1_media.py.bak new file mode 100644 index 00000000..d46dbc2a --- /dev/null +++ b/tests/wire/test_listen_v1_media.py.bak @@ -0,0 +1,102 @@ +from .conftest import get_client, verify_request_count + + +def test_listen_v1_media_transcribe_url() -> None: + """Test transcribeUrl endpoint with WireMock""" + test_id = "listen.v1.media.transcribe_url.0" + client = get_client(test_id) + client.listen.v1.media.transcribe_url( + url="https://dpgr.am/spacewalk.wav", + ) + verify_request_count(test_id, "POST", "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/v1/listen", None, 1) + + +def test_listen_v1_media_transcribe_url_serializes_all_query_params() -> None: + """All optional transcribe-url query parameters reach the wire.""" + test_id = "listen.v1.media.transcribe_url.query_params" + client = get_client(test_id) + client.listen.v1.media.transcribe_url( + callback="callback", + callback_method="POST", + extra=["extra"], + sentiment=True, + summarize="v2", + tag=["tag"], + topics=True, + custom_topic=["custom_topic"], + custom_topic_mode="extended", + intents=True, + custom_intent=["custom_intent"], + custom_intent_mode="extended", + detect_entities=True, + detect_language=True, + diarize=True, + diarize_model="latest", + dictation=True, + encoding="linear16", + filler_words=True, + keyterm=["keyterm"], + keywords=["keywords"], + language="language", + measurements=True, + model="nova-3", + multichannel=True, + numerals=True, + paragraphs=True, + profanity_filter=True, + punctuate=True, + redact="redact", + replace=["replace"], + search=["search"], + smart_format=True, + utterances=True, + utt_split=1.1, + version="latest", + mip_opt_out=True, + url="https://dpgr.am/spacewalk.wav", + ) + verify_request_count( + test_id, + "POST", + "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/v1/listen", + { + "callback": "callback", + "callback_method": "POST", + "extra": "extra", + "sentiment": "true", + "summarize": "v2", + "tag": "tag", + "topics": "true", + "custom_topic": "custom_topic", + "custom_topic_mode": "extended", + "intents": "true", + "custom_intent": "custom_intent", + "custom_intent_mode": "extended", + "detect_entities": "true", + "detect_language": "true", + "diarize": "true", + "diarize_model": "latest", + "dictation": "true", + "encoding": "linear16", + "filler_words": "true", + "keyterm": "keyterm", + "keywords": "keywords", + "language": "language", + "measurements": "true", + "model": "nova-3", + "multichannel": "true", + "numerals": "true", + "paragraphs": "true", + "profanity_filter": "true", + "punctuate": "true", + "redact": "redact", + "replace": "replace", + "search": "search", + "smart_format": "true", + "utterances": "true", + "utt_split": "1.1", + "version": "latest", + "mip_opt_out": "true", + }, + 1, + ) diff --git a/tests/wire/test_manage_v1_projects_keys.py.bak b/tests/wire/test_manage_v1_projects_keys.py.bak new file mode 100644 index 00000000..db30e0e0 --- /dev/null +++ b/tests/wire/test_manage_v1_projects_keys.py.bak @@ -0,0 +1,60 @@ +from .conftest import get_client, verify_request_count + +from deepgram.requests import CreateKeyV1RequestOneParams + + +def test_manage_v1_projects_keys_list_() -> None: + """Test list endpoint with WireMock""" + test_id = "manage.v1.projects.keys.list_.0" + client = get_client(test_id) + client.manage.v1.projects.keys.list( + project_id="123456-7890-1234-5678-901234", + status="active", + ) + verify_request_count(test_id, "GET", "/v1/projects/123456-7890-1234-5678-901234/keys", {"status": "active"}, 1) + + +def test_manage_v1_projects_keys_create() -> None: + """Test create endpoint with WireMock""" + test_id = "manage.v1.projects.keys.create.0" + client = get_client(test_id) + client.manage.v1.projects.keys.create( + project_id="project_id", + request={"key": "value"}, + ) + verify_request_count(test_id, "POST", "/v1/projects/project_id/keys", None, 1) + + +def test_manage_v1_projects_keys_create_with_old_request_alias() -> None: + """Test create endpoint with the legacy request alias.""" + test_id = "manage.v1.projects.keys.create.compat" + client = get_client(test_id) + request: CreateKeyV1RequestOneParams = {"key": "value"} + client.manage.v1.projects.keys.create(project_id="project_id", request=request) + verify_request_count(test_id, "POST", "/v1/projects/project_id/keys", None, 1) + + +def test_manage_v1_projects_keys_get() -> None: + """Test get endpoint with WireMock""" + test_id = "manage.v1.projects.keys.get.0" + client = get_client(test_id) + client.manage.v1.projects.keys.get( + project_id="123456-7890-1234-5678-901234", + key_id="123456789012345678901234", + ) + verify_request_count( + test_id, "GET", "/v1/projects/123456-7890-1234-5678-901234/keys/123456789012345678901234", None, 1 + ) + + +def test_manage_v1_projects_keys_delete() -> None: + """Test delete endpoint with WireMock""" + test_id = "manage.v1.projects.keys.delete.0" + client = get_client(test_id) + client.manage.v1.projects.keys.delete( + project_id="123456-7890-1234-5678-901234", + key_id="123456789012345678901234", + ) + verify_request_count( + test_id, "DELETE", "/v1/projects/123456-7890-1234-5678-901234/keys/123456789012345678901234", None, 1 + ) diff --git a/tests/wire/test_manage_v1_projects_requests.py.bak b/tests/wire/test_manage_v1_projects_requests.py.bak new file mode 100644 index 00000000..f95ac71c --- /dev/null +++ b/tests/wire/test_manage_v1_projects_requests.py.bak @@ -0,0 +1,67 @@ +import datetime + +from .conftest import get_client, verify_request_count + + +def test_manage_v1_projects_requests_list_() -> None: + """Test list endpoint with WireMock""" + test_id = "manage.v1.projects.requests.list_.0" + client = get_client(test_id) + client.manage.v1.projects.requests.list( + project_id="12345678-90ab-cdef-1234-567890abcdef", + ) + verify_request_count(test_id, "GET", "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests", None, 1) + + +def test_manage_v1_projects_requests_list_serializes_all_query_params() -> None: + """All optional request-list query parameters reach the wire.""" + test_id = "manage.v1.projects.requests.list_.query_params" + client = get_client(test_id) + client.manage.v1.projects.requests.list( + project_id="12345678-90ab-cdef-1234-567890abcdef", + start=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"), + end=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"), + limit=1.1, + page=1.1, + accessor="12345678-1234-1234-1234-123456789012", + request_id="12345678-1234-1234-1234-123456789012", + deployment="hosted", + endpoint="listen", + method="sync", + status="succeeded", + ) + verify_request_count( + test_id, + "GET", + "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests", + { + "start": "2024-01-15T09:30:00Z", + "end": "2024-01-15T09:30:00Z", + "limit": "1.1", + "page": "1.1", + "accessor": "12345678-1234-1234-1234-123456789012", + "request_id": "12345678-1234-1234-1234-123456789012", + "deployment": "hosted", + "endpoint": "listen", + "method": "sync", + "status": "succeeded", + }, + 1, + ) + + +def test_manage_v1_projects_requests_get() -> None: + """Test get endpoint with WireMock""" + test_id = "manage.v1.projects.requests.get.0" + client = get_client(test_id) + client.manage.v1.projects.requests.get( + project_id="12345678-90ab-cdef-1234-567890abcdef", + request_id="a3f1c9d2-4b7e-4f9a-8c3d-2e5f7b9a1c0d", + ) + verify_request_count( + test_id, + "GET", + "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests/a3f1c9d2-4b7e-4f9a-8c3d-2e5f7b9a1c0d", + None, + 1, + ) From d77c3ee98557f0bc638bfc4e681c6625fb10788c Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 10:14:17 +0000 Subject: [PATCH 3/8] SDK regeneration --- .fern/metadata.json | 4 +- .gitignore | 3 - poetry.lock | 350 +++++++++--------- pyproject.toml | 2 +- .../agent/v1/requests/agent_v1settings.py | 3 +- .../v1/requests/agent_v1settings_agent.py | 26 +- .../agent_v1settings_agent_context.py | 8 - src/deepgram/agent/v1/socket_client.py | 38 +- .../agent/v1/types/agent_v1settings.py | 3 +- .../agent/v1/types/agent_v1settings_agent.py | 57 +-- .../types/agent_v1settings_agent_context.py | 25 -- ...1settings_agent_context_listen_provider.py | 25 -- .../agent_v1settings_agent_listen_provider.py | 25 -- .../v1/types/agent_v1update_listen_listen.py | 39 -- src/deepgram/core/api_error.py | 4 +- src/deepgram/core/client_wrapper.py | 2 +- src/deepgram/core/parse_error.py | 4 +- src/deepgram/core/query_encoder.py | 15 +- src/deepgram/listen/v1/socket_client.py | 30 +- .../listen/v2/requests/listen_v2configure.py | 3 + src/deepgram/listen/v2/socket_client.py | 40 +- .../listen/v2/types/listen_v2configure.py | 3 + .../v2/types/listen_v2configure_failure.py | 4 +- .../v2/types/listen_v2configure_success.py | 4 +- .../listen_v2configure_success_thresholds.py | 4 +- .../listen/v2/types/listen_v2connected.py | 4 +- .../listen/v2/types/listen_v2fatal_error.py | 4 +- .../listen/v2/types/listen_v2turn_info.py | 4 +- .../v2/types/listen_v2turn_info_words_item.py | 4 +- .../requests/deepgram_listen_provider_v2.py | 2 +- src/deepgram/speak/v1/socket_client.py | 30 +- src/deepgram/speak/v2/socket_client.py | 30 +- src/deepgram/types/deepgram.py | 2 +- .../types/deepgram_listen_provider_v2.py | 32 +- .../types/listen_v2configure_numerals.py | 6 + src/deepgram/types/listen_v2eot_threshold.py | 6 +- .../types/speak_settings_v1provider.py | 2 +- tests/wire/test_listen_v1_media.py | 91 ----- tests/wire/test_manage_v1_projects_keys.py | 11 - .../wire/test_manage_v1_projects_requests.py | 39 -- 40 files changed, 302 insertions(+), 686 deletions(-) create mode 100644 src/deepgram/types/listen_v2configure_numerals.py diff --git a/.fern/metadata.json b/.fern/metadata.json index 75a7772d..76ba541c 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -21,8 +21,8 @@ "skip_validation": true } }, - "originGitCommit": "11cbef1122366ca086b021f14a892414ffcc035d", + "originGitCommit": "460af9f2b10e5a374517a3a9e40e3045aaa59a08", "originGitCommitIsDirty": true, "invokedBy": "manual", - "sdkVersion": "7.10.0" + "sdkVersion": "7.10.1" } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 765eb109..d2e4ca80 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,3 @@ __pycache__/ dist/ poetry.toml -.coverage -htmlcov/ -coverage.xml diff --git a/poetry.lock b/poetry.lock index 0e7ec85e..f1d83e65 100644 --- a/poetry.lock +++ b/poetry.lock @@ -842,14 +842,14 @@ httpx = ">=0.27.0" [[package]] name = "idna" -version = "3.19" +version = "3.20" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4"}, - {file = "idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15"}, + {file = "idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c"}, + {file = "idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44"}, ] [package.extras] @@ -869,184 +869,184 @@ files = [ [[package]] name = "multidict" -version = "6.8.0" +version = "6.9.1" description = "multidict implementation" optional = true python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "multidict-6.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c0fe437a6d2f36aac2b49517057776575b5bf359df314cca20d230a6e139c089"}, - {file = "multidict-6.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3a0a31189acf6703307397c6139ddabd734c20c5ef92649fc93e473df6615a3"}, - {file = "multidict-6.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c9c98d2f0126ba84cb45601eed97ff67ff767e19ae6eb3c31b02827b54d700e5"}, - {file = "multidict-6.8.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bc7003991ebd368a20d05228137a37b3d3066751f3ea1e4f7b8efe8e752f2f5"}, - {file = "multidict-6.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2c5fd0fd39574ccd58e1a52565b341aff522c5c836f1b3eb7605c371e61f52c"}, - {file = "multidict-6.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1cd4d66ce894a45482e1ac2837c31d0bd447df35065e542b60055aa2d00404b"}, - {file = "multidict-6.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a2573d0fd34f361a4a14e54d8cda3a91ac4e55fbf0d719698024f3b09c5b147"}, - {file = "multidict-6.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edda19aff836ec515caafc09ea53d2ab144a041f09ee9a7cefcbd3ae4e976256"}, - {file = "multidict-6.8.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e418ec99574ca24365ca96546af285c2b021a1a072478a79f0e3cc3b08837154"}, - {file = "multidict-6.8.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11d71490bf4bbff1141b14b93af419ad68c56b60bea9277fcb3f94dcca4796eb"}, - {file = "multidict-6.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e8e1e895e23818d343e4ae7dd95a0a556fdeaf8b471acf1c0a39b93c6f54d478"}, - {file = "multidict-6.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:15db8e6cab5f4cc9241bc56e69fdf3452cf49c10ee3c7977c742e68a275b3786"}, - {file = "multidict-6.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8dc2d9c3a924ed14166e63650b2cf9f59e7821743bdd50b23802bd97ca09bde5"}, - {file = "multidict-6.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f2fa3d3b1c933d4bcb8fd2018700d5e7235c52f2ab8c88d22286965c5c0f00f8"}, - {file = "multidict-6.8.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d6dc7804c50fabd28644d4d18a4b20aad3681b3e64f3acd3182b330ca73f7a32"}, - {file = "multidict-6.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0eca15d627e942ce186a935061f1568cc46c02e97c419c8da802df2be9f917d8"}, - {file = "multidict-6.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d7e5ba0a0153e35fbce9c51df530c8b4cb0c3012b46a04ff9a048441a269c2ed"}, - {file = "multidict-6.8.0-cp310-cp310-win32.whl", hash = "sha256:b7e62b8fc7bd6cad007b9f2e0ad9c8d4854c06350d5f51e1a439dd18b510ecac"}, - {file = "multidict-6.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:dc911ae6152e455b16a2a1a626aa6cd612fa01efb9d0a4ab3f5cf328b911483d"}, - {file = "multidict-6.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:6300d5176647145ba1e22991c924fb29743e54b4d7b8bc85a0d3ec0e55e189cb"}, - {file = "multidict-6.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b8986d4313dcee7c932837d16a535f1840b827bac1ea7c5c4c80751d0423794"}, - {file = "multidict-6.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2622fe114c0bd66ca5c461859357587f5a5e35ee5ff49fc5643d1bc78dbb41c6"}, - {file = "multidict-6.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26a7aafc992e78872e2c8c1f7248c0e01139cf9020a7781b0c064fa566832712"}, - {file = "multidict-6.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:05c2e90c5289c5f7436ba2c25812a5fbdaa1c1bc11c8d8d3bbf64f5cd7c633dd"}, - {file = "multidict-6.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7941ef106ca1f2c62314a13c7ed913bcf49641f3efdc12864d588e17870920ac"}, - {file = "multidict-6.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a5a7ee1217949ddd43c6b7bcf70d5c22193bb50e8c695386de5905325e93ce9f"}, - {file = "multidict-6.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b62b7e0025aa48dec11e125e655d1157985a5fdcec04b1ad500101ad072b891"}, - {file = "multidict-6.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea880d441be7c510106bc56064be39266d948aef94ad4955e8784690019a5d9f"}, - {file = "multidict-6.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab83fdd8cf307353edba9c427c17a3a021c2522d690f5633dd9f72d28b48ccca"}, - {file = "multidict-6.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3d1f48582686a0a3b81e9b43234766cc96697df72081af3f48107bd3f34d34e5"}, - {file = "multidict-6.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:563661919f603374c40cf45ffcd25535c12b8954203569a2ab1cee5265871cf4"}, - {file = "multidict-6.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2ba9933e8f35fe4a70f540b837254c4055da82dc3a9e500a8f95e61498083a15"}, - {file = "multidict-6.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:68d40b2bace413f3231f5729d3fcfb1837fd31c4907e241b5d43211bfd76f3c2"}, - {file = "multidict-6.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9e246f67ac038568b854ed7c5578e4c6af1f742359901a8fcc3603ff1358df6"}, - {file = "multidict-6.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:29b6e7bc4442a56cf8e0dc1cabf3fdc77cd533568d6829fc76a1effd2ce332ec"}, - {file = "multidict-6.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:82780eb8bf59e8fb25dd081fde6e058805045d6374a7f2f877effc826ca4434b"}, - {file = "multidict-6.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2196ba6df392c3574acadd14ef87550f3611349c8618564de324b806a7a31cee"}, - {file = "multidict-6.8.0-cp311-cp311-win32.whl", hash = "sha256:b8b7aa75146266fd3e2a2437cf69ae188688c04ab8665b163d4257b46c1e0c83"}, - {file = "multidict-6.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b03ca066b47b18b205cc080dca6f76cbd159f8cdd33a02a0700164c13b37e463"}, - {file = "multidict-6.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:54af1266710cb0f305127ae0b970aff8d208057f8a29cd6e1db99b0114947035"}, - {file = "multidict-6.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:22a310ad37672a261e55a8b5e28d0ae08cfb68abb1f46418ccd19835c3b8e836"}, - {file = "multidict-6.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bb8c7da8c861391f7ae48e3593762be2dabe405109e01aec520fbe1a6d15d14b"}, - {file = "multidict-6.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10456943903744ae1249728161c96bd9d2f7eb5ee17fcc2ffda2dc32e1bb36c7"}, - {file = "multidict-6.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:658f5a1895b804423d97b22d06fc0d0b171c7c01dcc3aa9c8faf0c0e26a249a5"}, - {file = "multidict-6.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c10b22860dbd09982d0b8993b66231a861bea2993d4a817ff35273f6ea285a"}, - {file = "multidict-6.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:088b04a66b3c1fce6fe4d771ec184a0426262d0b86709c908477b4ac7965df40"}, - {file = "multidict-6.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9caef53b20a105c0d66518a34be2f71b2783de8d091767575ef86f6ea422236d"}, - {file = "multidict-6.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a5e1583c14775580da05641240ce0d93f36ce3ddef3d5083a827468b0bcfe874"}, - {file = "multidict-6.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003a3bddb32915c3f67096ea41d24e53edf710edb65a1f5d0c70ab40b0e4d20b"}, - {file = "multidict-6.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:35977263d9bf506dbc65349f63b3b8c91606d4abc110990945e3b94bc671319c"}, - {file = "multidict-6.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9dc7b4ff6ef184504b49ef9a4113d49a646653b2ce89f5f48c1f57cdf6ba081"}, - {file = "multidict-6.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:887f9a975996032c686719eb7b3e1e7942fab5079c2b778bbd9afe9a9d78244f"}, - {file = "multidict-6.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f3071e6515cc63714d014da8f738ae9fa3997c476203f3cd46de380c2376ed7b"}, - {file = "multidict-6.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5f3a2af441670d80ce5fdf13b6c1b421fc1fc7fc5182d58ac7486738bb2b742"}, - {file = "multidict-6.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:31e8901637e20ccb3cf8f8848b5d0f7a00462bf5b34f7cf3dcbb2753b18e8b39"}, - {file = "multidict-6.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:13967dca8b2f33230a1427b52438326bb1c9101a1df22a3309ed3fcbbb3c96f0"}, - {file = "multidict-6.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad474c11d851b6fc97cb625e4822bc0cbd567fc07dc2602e28faec5a36b42bbb"}, - {file = "multidict-6.8.0-cp312-cp312-win32.whl", hash = "sha256:7bb0dad75068fee80fcb60f88569722c199d8656a16706702dc6e3b786819c90"}, - {file = "multidict-6.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:7d26dc8f070c0ec5579e987fa615ffd6883086106eefdff9e10d160fc5630630"}, - {file = "multidict-6.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:e6ec7d37841609a691b96a10b4fde386c7cd93ebbb939f59c9f23325ee788395"}, - {file = "multidict-6.8.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:ec0a4d066356054d569a66e0a94691a2058b680be5e710298f61db11a3c4609f"}, - {file = "multidict-6.8.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:714597cb5d5e15a8a449d2ae23c45b486a9e8fa33c462c7a33d7f35b65d92943"}, - {file = "multidict-6.8.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e0db3a4d1e264e225037a6023888972c25206a96e016021a5bea41c9a939f2a9"}, - {file = "multidict-6.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:27747162712e85c84598d364425dbf1714ff335bdb6ba3171c4e5081196e8916"}, - {file = "multidict-6.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:847d6082ae694dc95e548acb201bc100e1cfa96513bc71fdcb86f709dad6c435"}, - {file = "multidict-6.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:88a6df88567680504ae28bfa7a1f2f64243d91e79a40b2c92ef42efc531e23da"}, - {file = "multidict-6.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:560b211fc3bd4a1e1c6de44f6d38113bf5b410dfc89a4c0d2a3c0edbf1a0dfb8"}, - {file = "multidict-6.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202436df907c15adbb94360296c425ea53cf8968a5d2cff9b5b9790ae1972b33"}, - {file = "multidict-6.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c46a08bf070d6849fed483e9d9833f9d06aecb8382ed985be0b38508b3ae958e"}, - {file = "multidict-6.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cd560498ae8e1bcc955643c1d78eb8e338226d07a983c656ea8c4443d3eec0f"}, - {file = "multidict-6.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:758233648ac47b07c575224c4eadd73c8929c3b4c31e2afcfea935fde1cda735"}, - {file = "multidict-6.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:122adc7c46ac1e31ecfc7f81b2530533dccafdba70f5d741649f87e336c63384"}, - {file = "multidict-6.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8125e60f3c70e323ac07dd8b3635f7b3bbc5c3a9ac04ae5988f668ff7ae28a18"}, - {file = "multidict-6.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:83ff054b04915be5c15680da6c6012474a2cc2bf534129a0e8c6a99f17ba7238"}, - {file = "multidict-6.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:930c6058047410e3edff445f5a6e4457f2e089042dede00e2d18ce06f3ceae2e"}, - {file = "multidict-6.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13e26f59f0eecfc5f67c663ad550ffdaf62c0f657547cde387f6c86af1c9449e"}, - {file = "multidict-6.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd789a294d8e098528be29b2669b83005ce569339f8cef167fc0274c3115c34c"}, - {file = "multidict-6.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:3126f2a96704505aa4e92a72d6e8a5d7f29d40a987ced8bf69e29d71dfc71fbc"}, - {file = "multidict-6.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:23c9ee89967b6a9b4048acb3b93b660ed714ce9c8bf3bbe652959bc120dc02dc"}, - {file = "multidict-6.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a62e302fc8cd6aa8972207e7e951d1fdee7c1dda18568305041d19f0e2c00f5"}, - {file = "multidict-6.8.0-cp313-cp313-win32.whl", hash = "sha256:093167d22a8c95af30f597b8a5686f20a14512989942d4be804d119899caca20"}, - {file = "multidict-6.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:f25b61a708bd276e8cbb6afcbbf1b8e793a3be70ba0a842d0b8692020f83b706"}, - {file = "multidict-6.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb36381e1f9f9d06eba2f10bdd438e5d20c07d5b55e1a3eee30b9f44cbf52316"}, - {file = "multidict-6.8.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:f8b09b25e0f4dc2ea9e2adbb1cc3ba11a94d6fa3dd978ae659c8743052e1afbc"}, - {file = "multidict-6.8.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1f57c414be82490bc0e0305fdb834186229b2d9b6a35fa0afd1eb1a772d125ab"}, - {file = "multidict-6.8.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:00be37bde741bf60871082cd347a093218c44886e99231b7516671c70f2c280d"}, - {file = "multidict-6.8.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e37b744849fb631bb52e3dadde35ffeee365a6c41cf71257b5b7acc9cd83fd38"}, - {file = "multidict-6.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c2b2a96cf1dd99fe7867be4c013314225f4d5786e6685906e29932d42aca6f11"}, - {file = "multidict-6.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bea7df027015856ba5d0a88e3b4777ff8cb5c66b58fc108050fe79d4dd9d4d2d"}, - {file = "multidict-6.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d3da668e903c934ed0b587ecacfed6901f6ae6384a6e975887592b61845e78bc"}, - {file = "multidict-6.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64eaeda36ee8d88f9e8616a587a8c66a663283cf6e0dcf013c1ddd8c758e4aef"}, - {file = "multidict-6.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ac746cb365bac1c462da9e3e6ab8904a8efe2217a56b0b2e3d9480f41d2b2602"}, - {file = "multidict-6.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:18f0e06360c3e451a3ab800355773c8d125a758238d780c800b0ee5e90ee903c"}, - {file = "multidict-6.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69708fecaa88bcb2341397b49fc95057a835b02a3670c551b37f95dd79e64e3a"}, - {file = "multidict-6.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9606f583e7acaf61e7b3f56074e14037b9af7cb194590edfc0114b3ae5931ff7"}, - {file = "multidict-6.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1f66fe6a021173d0d47968491791966b9f3e6d61115f2491744aa0c07a6e67af"}, - {file = "multidict-6.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:be007d1aee2cbd530347dcafedb400891a3b5f1bd7135f95cf5d5b330b5219ee"}, - {file = "multidict-6.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8457aff3c12a89a8e1c4674de5c777857fbc429f40fe117a3d29538547cbc364"}, - {file = "multidict-6.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:95c27b4f3f04320fc44e338573f40c5c956b504a7fcf081a157fd0b02579311c"}, - {file = "multidict-6.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d244cf6b52b5ba1c34c3832f4652a668ebb36d95949b96eed9a1c54d916a90dd"}, - {file = "multidict-6.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5bbbb696c8024475b1877d14ce20d5f1cc05b8f6d786cea0fe3aa7fedc02e891"}, - {file = "multidict-6.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:cbd86f9787c5e2f5fd27d8b21458222f107347c6731c4e93dde68f554b466a2d"}, - {file = "multidict-6.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2f8a4b0b4d639d525928c7f30de527bfdf9ead6e44a5e8cb9c50aced5e4590cb"}, - {file = "multidict-6.8.0-cp314-cp314-win32.whl", hash = "sha256:8890c89d662560e51c55ac1304d6f919b23942abe9ae1127cb1de9aa6132fa52"}, - {file = "multidict-6.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:45cc39ba50fb0754a4359b90f8229ae08598fe2266abe3521b4e5a9ba916534a"}, - {file = "multidict-6.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d0264f8d5cb0a803f650a6a8572dfa0cd1e099a2234c588dc8fb220b415b865f"}, - {file = "multidict-6.8.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1969971900b0871530f9b62280dcc2d75688e74d2a69262bc01faf2b96c78f04"}, - {file = "multidict-6.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8180b635290a75af8478f1b3e9810135381ae24833293fe77b85c1c21ff842ab"}, - {file = "multidict-6.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4261863fc8b5ab1b815ede94e592e94c6af5b04616014929057e61859e7382a9"}, - {file = "multidict-6.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0b143d53590e89f43153d81d505a8448d4d57354354385aef8a51d67ffefa27e"}, - {file = "multidict-6.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da1c112c5784ccd9d32cd90be6739fee32644e874eff6ae8f0497cba3e352e58"}, - {file = "multidict-6.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f7eefd0233a7c33ca980a5cfef26f1e9b5e2137839e752a99963696729f12d91"}, - {file = "multidict-6.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:348bb85e2038b40c007383616d73f734869063772372519549ebd7da1723d1a4"}, - {file = "multidict-6.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:095f62ea4e7a3be2f6c567ab695ce10e950f2adb905c1bec82281593e0b2d2ad"}, - {file = "multidict-6.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be569fff1d85cd29391c431c5641c8772acb75bbdc61e60a8e82fceb9023d385"}, - {file = "multidict-6.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3533a03e4e789baf6a286e7b0b1b6da3f3d7c3eab569686ee29ee1d8b52e2cb4"}, - {file = "multidict-6.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1bdb9b8fba5a9aef673ec90db3f55b1ce743f2fbdea4d37dc04d14ccdfc153ff"}, - {file = "multidict-6.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8d7b66c9e09c0bb0add2b5895e646b62a0849e71155066f215523de6b95cbe6"}, - {file = "multidict-6.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:563d6500ca80dac7bba6f48a78e0ffd87e21a7d4d24642c6503a2ddccd70c110"}, - {file = "multidict-6.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:346ac52e56bcda320c0dcdfdd081947ed7cada33afea4e2284bef7b0733bff9b"}, - {file = "multidict-6.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4ee953a5ebaeed38dc21cc032ed17a9d9782802e00042200497ab4b01b0bf7c0"}, - {file = "multidict-6.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2f79cc3e8039a8cf5c77e0811b0807953fd52d0863b9b76970b20d696dc64a78"}, - {file = "multidict-6.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43a4b56555bbcf8af161e7c7682bd93eec10f068c95844511864c018c8e5e13b"}, - {file = "multidict-6.8.0-cp314-cp314t-win32.whl", hash = "sha256:48ea524a25a1cd5972cf293bc95713918cba0bcd6fa9b992d906c857c546abe2"}, - {file = "multidict-6.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d0be2b832435001bc623ca7f1499ca1a853d4f082fb61221a80ce71132f50b26"}, - {file = "multidict-6.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:62b8e291a4f7edbf7cde7a43d831d893ba443a1b627498b53581943b0e348feb"}, - {file = "multidict-6.8.0-cp315-cp315-android_24_x86_64.whl", hash = "sha256:e192018b732f7b168e6604cbdf40fa8e05c996693b9eb445a0d8a73f4b77c5d3"}, - {file = "multidict-6.8.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b25426f9f6ed402835617c8f23609a47045f91ecff365eb6734817e039a8ed25"}, - {file = "multidict-6.8.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:fa6c2880709c84457de104385b704fc28860f27e442ad13966fc4af8e714fe9c"}, - {file = "multidict-6.8.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:eabb03dc3e4ed6333ecd1cc9826ec80e7a98b5506deeb832d7260c8e44166d23"}, - {file = "multidict-6.8.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:59e539c4eb4d3a53b0e630a6ba2b2f2824732b5e73f90e30a280f12fde157b15"}, - {file = "multidict-6.8.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:835d5a90b11d1f5f8200ff3cc8316bded76eebebc92436398947a27657e645e7"}, - {file = "multidict-6.8.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d2d236b8a44ae91536a12ebcb996bdb31cf27425f36b4d05c87f2ba2716050ba"}, - {file = "multidict-6.8.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bb9a60b7faa5d37c426fa91cf4d6738182a1f2755b9fab7c9c64cd466c4ce51e"}, - {file = "multidict-6.8.0-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0ef606c15cac6c90279acf34120784b6f36662cbf382defd3955cd8f1115336b"}, - {file = "multidict-6.8.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:68186a2d4051c8ffd17be33553bea2ec9bbc8ef860fe2980a221d96126296f31"}, - {file = "multidict-6.8.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2cc66abb85e2108c9ff8a1c0d20fa260bf690bbb33caef4ff3ecb2c2cbdfff5d"}, - {file = "multidict-6.8.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69b3e519a132bb943b0daae15fc8c2168706b17f826481d32a32a5e784b129e3"}, - {file = "multidict-6.8.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e41226ecf607f062fe34a2f4cf64ad3a89e3a0180dc800b463b6b14c06dd10dc"}, - {file = "multidict-6.8.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:610c7637bc36b90f39e6c66f710f93d57018f83d53e1e187caaa218c6892b95f"}, - {file = "multidict-6.8.0-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:65c85c79f5a2c04fbbc18f006c014674dc5fdf270cb978d8862c82c6f694e60c"}, - {file = "multidict-6.8.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:628ff11e6720f90acd0c305dfa3339f04a783a20de8cda6ac333ba46447261e8"}, - {file = "multidict-6.8.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:0935971bffd0b479fc90c4811ca787703e93fcb6afea939a375dfc80285ab368"}, - {file = "multidict-6.8.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:9442b14eec262a1f74369bbd07e75bc5155105164649a4b9fbc1ebc7b8fb0b14"}, - {file = "multidict-6.8.0-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:397599503b718f0137f26d3f6532d6955069cd2e5917c47ef581495bc2529ff8"}, - {file = "multidict-6.8.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9e37024b41d7a7e7e9cce14b248d54707c21c2a2ea30a47b71bdcefcafec00f2"}, - {file = "multidict-6.8.0-cp315-cp315-win32.whl", hash = "sha256:071da134651b04a8507dfb331ac0988f376337c2aea59486bf20989fb5b5a64e"}, - {file = "multidict-6.8.0-cp315-cp315-win_amd64.whl", hash = "sha256:3bafff8598f0528017ddc74194e5451d5c22d046c98935f8f86247b0f286e4f8"}, - {file = "multidict-6.8.0-cp315-cp315-win_arm64.whl", hash = "sha256:e886ef8c9879105fe4fc99417447b3a5f35d1131412ce839470bd2089fe2043f"}, - {file = "multidict-6.8.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:883284137e25318ed9735b742ae46341a864888fae28e8b6314c4f84da080f08"}, - {file = "multidict-6.8.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:ca52b9ec80851366197577154c862c4c4c7036ca76ae94cef5cb59c5cfeab944"}, - {file = "multidict-6.8.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:91fa75d0a693832106d98f66c849f034f21c828d14437f1fb97d3784aab89e84"}, - {file = "multidict-6.8.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:307c1acd812fe897e7fbe10c6758822e8c04be4e7c60a9f54901cdf8b5ab8bc3"}, - {file = "multidict-6.8.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf684986a2490628f059a99dd107b566a2d34cf947f8eb8387e0500a1f90c5"}, - {file = "multidict-6.8.0-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:77745725125d01fd613b6db043362aa7c6bfbfdb23d45dbfc3d92bf58160af62"}, - {file = "multidict-6.8.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8daafaa0b2eb43f76898ced78b1e0fb91b38c4fa50da516c18067f2a2d578c20"}, - {file = "multidict-6.8.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c68e0c0649d17c2d0339e3674e86a4aeba4a7e6b21c1e394cf947a95433b31d0"}, - {file = "multidict-6.8.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8a5ac357ac283490a8d1899b0383355fd1f8634b14ba0d59e4c0dd97db85556"}, - {file = "multidict-6.8.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:46029e6e27a3ec0dc55b53f58df82d10f04c5e111f78248279b530bedad2c30a"}, - {file = "multidict-6.8.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:8d1046b5427dcafe6e8a0e07527dd74f1ee694006160162f53f3a17f15aad3b4"}, - {file = "multidict-6.8.0-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:f1f4a220db6ed7c8fd16b6d644ffd1f082651693204daf3275e049fadc849e39"}, - {file = "multidict-6.8.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:029897732a9c798737457e382bf84e8c64237eff224a90aea2639f4413c45e4e"}, - {file = "multidict-6.8.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:29be9fd289e9ab8f480996ea2f686e1654b80242033843cb11691688329423f1"}, - {file = "multidict-6.8.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:29631224698de1e42abc8fa7658d830e0aed0029785144b5832b695da5adef2f"}, - {file = "multidict-6.8.0-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:962f18c59a000f30b084ea2e6b8001521bb315efd4e5f10acf9fb36f366b7882"}, - {file = "multidict-6.8.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c60e50bc5b07faac92fd3a20fa21cc8cf3e3f7204d2867b206c73293ebc19101"}, - {file = "multidict-6.8.0-cp315-cp315t-win32.whl", hash = "sha256:fc5460940f50dff00731b4132366840ba9685286ea88ea104b661899084f3fea"}, - {file = "multidict-6.8.0-cp315-cp315t-win_amd64.whl", hash = "sha256:b367c342327717d644db4c0ddb37ceb655c84822215ea0773a3a36911b74b71d"}, - {file = "multidict-6.8.0-cp315-cp315t-win_arm64.whl", hash = "sha256:0c1c4debad7337627b86837abdf0237ca3cb3d7e17de7eab0177c263878546d4"}, - {file = "multidict-6.8.0-py3-none-any.whl", hash = "sha256:75daa15ca16d6285eb2e104b2f05ee6f8d9836c68da3ce5c85f615a0450eed0e"}, - {file = "multidict-6.8.0.tar.gz", hash = "sha256:5cd4637ce76312ba1e05eb9c5193fec231f64fee0944e135fa1e951242355b37"}, + {file = "multidict-6.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aef74e9beabbd6c4aafc091dabff86d046ccf013ce1e4396c0fbb01b4cad9de8"}, + {file = "multidict-6.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1ca5ebe6d454f1e5496cf052386a559f1080bf2de75bf327ebca0a6003b79f19"}, + {file = "multidict-6.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f6d0c4b70f783305e73f9944d8efe6be1022550f0974ba0ae9d8893c0350fa"}, + {file = "multidict-6.9.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8050e75af7e4c6e2d5260b84eeedb618f3e452e66473432e085b5d1b81429299"}, + {file = "multidict-6.9.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4736d350371337825cac1793c9f7c40701c32a03912548c2a7608e51679cbb96"}, + {file = "multidict-6.9.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:02fe09dc197b8ae7e355371e51e5dce2f39060cd5a8badf94904527e23a3f188"}, + {file = "multidict-6.9.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ad4528cdce058b684f75fad1faf4a6a6c992fe2f08376370ca66e4ce5916a84a"}, + {file = "multidict-6.9.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8885e3808aedbd6725b921fb67dacaae0678933561ddd47c2b01315198c70e2e"}, + {file = "multidict-6.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3b6e6840421c83ccb60398e333b44f910b0907bb409597685ab2eedd1e22eab"}, + {file = "multidict-6.9.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c9648ed33dc8179e4ec04bbc73bd7f0038e1e81217a69467f61f02a78bf07e88"}, + {file = "multidict-6.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d9d6544790ba50438a9c1a903c3c4afb1ec8a7832db5518549275b40ef0dd4b1"}, + {file = "multidict-6.9.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1c9f3c25df6c9d3bbae4f6fd3f514b1c5c740a2110f56ccf36069c85417e289c"}, + {file = "multidict-6.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cf22b43f35b7dbb9f71e8ee2041b5c00029cdfc28ede3a2f31cf8906f9a6c126"}, + {file = "multidict-6.9.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed78ccad4c7b421804f5d524b7740946a7ef75d89dc0dac52e9d7c24c472410"}, + {file = "multidict-6.9.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e3b1aa25f01238886a6baed9e13b9a9240ed344346c79ae280ae65e8603b21d5"}, + {file = "multidict-6.9.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bc94a68ea5e18f8e85dc6b522bcb53093f692c8eb62b4837ac047da73956cbe4"}, + {file = "multidict-6.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73533644e1f69ea1164d56cc6505f564c6c44072b646b66d67df2d31fed0348c"}, + {file = "multidict-6.9.1-cp310-cp310-win32.whl", hash = "sha256:66987aa68b0f7c2a1cc5f388ca962b8ed92b10f79de38d6d0d8c716154f519d9"}, + {file = "multidict-6.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a32b78c1e52ebd8e247bb68300b90b233300d8816faa008ed0713bc539fb6af0"}, + {file = "multidict-6.9.1-cp310-cp310-win_arm64.whl", hash = "sha256:ab64ace1a68682d191d9bedd9d4c939406ad86b1d9f628180410644249ad46c2"}, + {file = "multidict-6.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:910d4260512660484c0dc1588a316fbb35a40c081c36fc51d1225351af17cfe4"}, + {file = "multidict-6.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33fa55b990f81c2927e01399ace0d18926c69d69baa8cdaa819424132fb97987"}, + {file = "multidict-6.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:369b5aa01b241cd3fea6890bdbb11a1425d87bf1515831500d518f4223e9d72c"}, + {file = "multidict-6.9.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:803f8b575a71b1b299d677c28db0653459c79b5308874efec813f17b7457c7f1"}, + {file = "multidict-6.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8a65621b98984a62e59403009591b8a5a7736273aefe1cab64cfb85b365cc07"}, + {file = "multidict-6.9.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:13849a1d4f54c3809ae721e9e83ab28f5ea602f33660cb84eb6ef261eac706c1"}, + {file = "multidict-6.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd9a137a4a9becda3094f3831cd026380f75f6855e051eefe4c73ade524f1cc3"}, + {file = "multidict-6.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9ae9614c317c50836689ce2dfde07c05fe0b16378562e2221746c3913ede3c80"}, + {file = "multidict-6.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8f1e362c9352b50ed120f001046fdbb80810c9d56580f4c3fc13bbe30823387"}, + {file = "multidict-6.9.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dd655518f136febd96c05131a76a863e32fc2a1d7acd4e3c959e3ceb77d8345"}, + {file = "multidict-6.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c5d675da8f1cb5650271c8ad5e95c0a3e5a183c105e72d953b12877b1c8d0fd"}, + {file = "multidict-6.9.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:658f90f49cf5af2441cad0a2b801c3ef520471989a1ec55bcb25b255b2ca8d2f"}, + {file = "multidict-6.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:43124fe172ada86d03ac3c8dc8179091341f6724d5e5d5b160e1587e4cd3761b"}, + {file = "multidict-6.9.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f0adc22a4eb31e545221d93fc73a0f6a8cc2379d0f4309f71d1d17ba938b82"}, + {file = "multidict-6.9.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7fc59e9ba821b220944ccfe0f89c9dc4745f6d097569992356eb03869f21e953"}, + {file = "multidict-6.9.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:87cc632c88ee5dc80e12681047839304d98ee5c9a708d686505767001c9b8b9a"}, + {file = "multidict-6.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b828cf64d62dc09ac183f03c1aeedd164ade96a2ce4934109452edf29de1dd13"}, + {file = "multidict-6.9.1-cp311-cp311-win32.whl", hash = "sha256:2c1aeb92eea59d824f004341b26d5e4b47a8a441cf9726769b9a90abf9d0e08f"}, + {file = "multidict-6.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:5f89dad732280e7a10b74d40b91364f88e13c3f2c08c2ef83a8cd42f7a61af2e"}, + {file = "multidict-6.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:5800368526647146978389dfaa46da3356291e9f0fff9a4ef12e8c2bef964a0d"}, + {file = "multidict-6.9.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:29138fef49828542e859828107e42e50d0e587c513b7eb4b2d92bade2b0860fe"}, + {file = "multidict-6.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:19e31815d41cefc365489e591d105d2baceb2f65aa75d29471fbdbda8651e006"}, + {file = "multidict-6.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ed30be8918e18c8bed0a2e8b70639ecf02feb61ed00ca2e41cfcb2a50fa3f42"}, + {file = "multidict-6.9.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:637f4ae36264bd7b8d9a60193acddc1d735ad52e8ed53a19931ea6d921fea8e5"}, + {file = "multidict-6.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35fc236507fb1b3138f0af5ecd5f94ed752d4d6d826248eae425f86204013eea"}, + {file = "multidict-6.9.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e58952f04772f59f11c6e007471449809a30165188669bca8fdb19dde40a8f24"}, + {file = "multidict-6.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d35a4f1c63f07fbb8c8f9946dea98b21eddf6c57421585f71d91864be3ba2a24"}, + {file = "multidict-6.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b5f8771aaaed7f80e84a4e471d2f29ab6721e4595075e54d03ae1ed951b2000a"}, + {file = "multidict-6.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:976fd7689d69ec78d67d31d38d396d8adb562f7e8368279f76aed4aa451fa06d"}, + {file = "multidict-6.9.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:95052e8777a86bae87c0bd0b5ab22d809e3d1d02bf69e3e66ddda5ba75a05805"}, + {file = "multidict-6.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1a8adfcaf96f587ab138476eaddef95f29b8a2a8a9afbfea8d2fd62180995d02"}, + {file = "multidict-6.9.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1a53de2772cfb74559df2eb4456ec4eeb908435ec55a84b69370d9d745d62aa8"}, + {file = "multidict-6.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f2ce963299d42fa3f22a90adc0fdf174792ffef5ff4c7ffb68260548fb05580"}, + {file = "multidict-6.9.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5b30ddf7234e611ca877575b62840e6af5977f92f1f9d532eedbb05a44ff8004"}, + {file = "multidict-6.9.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63ada7ee2e9345695f9e9bc4c65d72222253f07b1ac94fd0e37555cc6f3c7f60"}, + {file = "multidict-6.9.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c95601ed98fad3f6e2f8fe809c3b526b0fab31ef525e00a155e227f3d17f58a"}, + {file = "multidict-6.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c148e8b596000dd3e4bfe206e70f3e666be18d72032e0012555f2373c52e35d6"}, + {file = "multidict-6.9.1-cp312-cp312-win32.whl", hash = "sha256:f9dad513626a33670f17cddc6078e30e311f444c957e8dbfc5b2b4603c8b4edb"}, + {file = "multidict-6.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:a16a1dc8529f9e734a41c3b856f3eae7ebacdc061dde3f8a844e0c7889c97203"}, + {file = "multidict-6.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:361f7206cf341ba94fb015688f5c8b480f8e63bd58a4c14a48aeca7851a241cc"}, + {file = "multidict-6.9.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:d7bf9e43282d69561618e8a0ea33368d532ebef42f15c096f427090521dd74f3"}, + {file = "multidict-6.9.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:03d47df72f084f757c1cb771188d5f4e3a805e4abc4d67e32509272343ae9382"}, + {file = "multidict-6.9.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6bc94fe17c3c56e5418f79515b786b101845f70609b0d19d0c1ba13448e5633a"}, + {file = "multidict-6.9.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8f2973bbd2bebd9d2e0cd6394c1292a1a19ccd56bdcbe1e174059f1a39be5b40"}, + {file = "multidict-6.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de7738b8c0bb74c4cc16bbd7fb49fc2bcf6430dba11b3432cd52768ae40933e8"}, + {file = "multidict-6.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e5ccad4b7bac125722f48d6f862bed3b514d8526deea06316bb72f152cd30a7c"}, + {file = "multidict-6.9.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a49ff5cdb33654cb7d6a3c377aa2a83ddefaa1db31eb10bcf3c180aa84f9af8a"}, + {file = "multidict-6.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b22ff30006a2f28f8bff878fb93413cbe3a4d1fd517c28081d848c90e9cfd2c8"}, + {file = "multidict-6.9.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3adf06c66041aa21eeb8a71e82379b74773298c8e6d3d839b151aae441a99b94"}, + {file = "multidict-6.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:963a8d8f97057082679523d0fd4c53a38f86bc58cabe4556faef682ae53fa2fa"}, + {file = "multidict-6.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b66ccc5c2cdd26e74fa5d4c29ffae424cc6148bf93ce574821783fb3b6d452c5"}, + {file = "multidict-6.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fd79c521f6290c69125fa2b85fa65d9e657e6a8ffaf722dc881b926bef4aa5c"}, + {file = "multidict-6.9.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d804e4caf5d5da37d6dac1325da5629ebef1e27a294c2b568b295814aa36c7b"}, + {file = "multidict-6.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb0d664505f4b112f384cffeee82e91e3f6448d8e574989479db4439b68cba05"}, + {file = "multidict-6.9.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9e14d17773b1b3c758ff153659a1824608a0cb562c45f484b5ed8a433428444a"}, + {file = "multidict-6.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:083735b7f395894e43adb278d5dae901448883a835ff8f1977e285fefdb10418"}, + {file = "multidict-6.9.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b65121091567847a8cb520d364ab22ba90e00d3cc55fa9eb34bb439f0684bcd1"}, + {file = "multidict-6.9.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ea999ae6e80e66ad5eea287860951b033d0104ca34d6d87c7b5125ebe0e12721"}, + {file = "multidict-6.9.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:095d900c242e00fbe5f321ee072e7278b4153e78c5ce9c1efde167d62c1e4771"}, + {file = "multidict-6.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6441cc837aea58be7d9baef1b2383eb8311ab9303f500f99ac90b584cd78bb14"}, + {file = "multidict-6.9.1-cp313-cp313-win32.whl", hash = "sha256:9c4880d017555d70dea367dd49271830842d48e3891c2da97da7ce8c4abcee40"}, + {file = "multidict-6.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:ac51cd64bae51c462ea58ad2492c9b8209667a4ef60c45c4a304518b67598d5d"}, + {file = "multidict-6.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:37a9ebe00c698279213d56e6c64e1962ab1e092918270649b397cac3dc196ca4"}, + {file = "multidict-6.9.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:fc0dcb22fa9aeabfe3fa4e0430099acff985ec5d77a851382f76cc6146e780e5"}, + {file = "multidict-6.9.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:024123f0ab402ab33828e24eb80fa8f25167d0d3783ba5f287e39ed741e6abf9"}, + {file = "multidict-6.9.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:af1b5a92315048c3e36bbebfa7d4760a9c3e910bc4166f11b20d77d20d6bcfca"}, + {file = "multidict-6.9.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b2483da477932ad1983d1d33c18bc3771c6fb00cfbaaed70a875fd547ef8e840"}, + {file = "multidict-6.9.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:db4d697b18b6ef5528b1f36bfa25072cd2a421869f5963bc0e92c8a34b9e2800"}, + {file = "multidict-6.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:854fd2f1bc6e8a56b89910b5cd7261a8b40f13ebb31572da985ce59c7da0886d"}, + {file = "multidict-6.9.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:03731f6fc036180700c9dc2308205a48e5ca6f3ff03087739ab746f294022201"}, + {file = "multidict-6.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989261c5f1735a165f2e4e87cf6d5f17ab734fa18f9ad0383d5adfcdaefce701"}, + {file = "multidict-6.9.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b4c9e5d05b126b267ac048a89a0e2d9b48b1b648add62d4872906304a8590610"}, + {file = "multidict-6.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9d0a21cf76153de8f2d96a877991d6bc59b9ab5180b949e50db73cc193b4a694"}, + {file = "multidict-6.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7386aa18d98d6b8af44b92173654ec469237fda35f8e8523e43b581a86f476a"}, + {file = "multidict-6.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b69651732c64afb691e50cdc3387cae305e0eeff8804fe3e3ce203876494932a"}, + {file = "multidict-6.9.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1fee9a16d88a1c4865610de31ef5c666671020d7a81d1f510eaf3c97d00ebeba"}, + {file = "multidict-6.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7814bbee202acd3bd240204c17d8b87a4c48c81064fd8674dbe527d94d5a4290"}, + {file = "multidict-6.9.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:95f273bea318a194f656527ee2ed19494327bc500b8e87d9358e3222579aa28d"}, + {file = "multidict-6.9.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:73bcafa21a78d0776b3ee7cd2a63c66f968eb7db8e8d594e32d7329950f6e828"}, + {file = "multidict-6.9.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1a938761c77e0e6edb0c93d02f4e988d44a69e5195e5a3b893e5553311347132"}, + {file = "multidict-6.9.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:37245ca4105386194dd1d292a6f2aae09bfe1bd7ac6f9ec25093e3cf8e9b143b"}, + {file = "multidict-6.9.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f0700527dd5bfa8b7204b08330542f4f388899d3c14d885d8a368992e0eb562d"}, + {file = "multidict-6.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6b7e54fd883671d1a8810704851c044b7173287c552b9f5c3d9e0eb9f00ae194"}, + {file = "multidict-6.9.1-cp314-cp314-win32.whl", hash = "sha256:e81ae656b9935ac4528a71f96bb7a14d949778ed1897c573d3e7ebb9187f8841"}, + {file = "multidict-6.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:acddcac38adc8342ba48aba98896faa7928854bebb62542362138655b5367ee3"}, + {file = "multidict-6.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:32217133dddc58c927805cf6c0731d8144584176b768042ee886d51e71860bc9"}, + {file = "multidict-6.9.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0b2fb8c349d1103863750b5d8cb5ace766917f4b35f3d883c8f778853eaa9f76"}, + {file = "multidict-6.9.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:56d834b74c993a7d7cb2b8ab33a0d55c3e0d4a3d2f2da2808a4ad3d79189711b"}, + {file = "multidict-6.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:783ba7d845d79ce976afd9c1e91a4e5714671defa198ee789e8b23316083a485"}, + {file = "multidict-6.9.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d718fa1b5f0d0dd75e86fbbc5b0c93ea3a5d65216c85c61cd5d7cdddfe08455"}, + {file = "multidict-6.9.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a313bad717dde740959d50850c315b75fd4eb0c5e6b4dc8535db0f1c369be125"}, + {file = "multidict-6.9.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ef01d29fca550ab871fd99154f82c6472aacf8e7def272dfb07b460123850390"}, + {file = "multidict-6.9.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f3bd290711c6e9486173a6ee7cd4e7f00c3971c7908c1ec1b6e5437c5e4c6f9"}, + {file = "multidict-6.9.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:81cdc537e0a42e3c0170752fcadbe450246d5c3b4b7231d6eb9672456605ac94"}, + {file = "multidict-6.9.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b193bf7a443c97d81c47052f60c486071a4bdef4a573fa2514f920089414d45"}, + {file = "multidict-6.9.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7a0c98f6a636adf0d7edd60c61589eaf52d149239af754d0bfeb0effedba53a8"}, + {file = "multidict-6.9.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6e6b7e3a1520c39a2772f414cd9ddf5995f77e4e823dfa1522af383addd465a3"}, + {file = "multidict-6.9.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ca65cced0d67a9039e93bcd98a369920e499bf98296844ff56ead01c9085321f"}, + {file = "multidict-6.9.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:372c063f37480f62c1ae32dc3a1a0a5942b180c883f99789075a8a8ec3c4e709"}, + {file = "multidict-6.9.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:827c92145b3b976b39430129c89d213b250dacbe5fce678e9a03940e6e848983"}, + {file = "multidict-6.9.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9b0124b9c17e9890f0819b2e7a5f65ec9a2f5aabd6c8e7ad090c1675cf70dee6"}, + {file = "multidict-6.9.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:23136f5a564654eb61061ec6d5620a4c1ea32c8f552b65e9982a12b72bff601b"}, + {file = "multidict-6.9.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f2524ec55b3e65cbe235a8b3c36e2af3b635be02ed05e20c94e70c5c943c009e"}, + {file = "multidict-6.9.1-cp314-cp314t-win32.whl", hash = "sha256:35ba0263bae5dd3ad5aad767cc9afc01a8598c7dae30f1b3b2de98b1b32c28bd"}, + {file = "multidict-6.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:7c8d5882ba25ac0282258be435d8a05aa0cbcacfce15799154a338f847f159b9"}, + {file = "multidict-6.9.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ac348379cf4de5538a0a213be1532d289aa801ec5d267c5909446b9ea2f8e2c3"}, + {file = "multidict-6.9.1-cp315-cp315-android_24_x86_64.whl", hash = "sha256:f04551dce5a7db8c9659f2e4245494c182d0663b83661803e08d46bfcae5eda1"}, + {file = "multidict-6.9.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c0c88085affd35c33e124e36930c5ad96aff9294ce195eaa0fd9cec962b64a82"}, + {file = "multidict-6.9.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a8b75dd3d3638d9a19f23e84af4ffab3b8940422c0df2da2a77005ef5aa3d7ea"}, + {file = "multidict-6.9.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:006c4478de0a1876f4834e14255776286f09b9846b505fe63f67f9d173a9487c"}, + {file = "multidict-6.9.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2784090c30a586d5b45197bd9c32f87fb927216cde302f6cfd76d76577e90f08"}, + {file = "multidict-6.9.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:7c708566da8014b120a64b1eb6d200c6c0c8cb36296383723cdb6fc82038270b"}, + {file = "multidict-6.9.1-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:042fb0196047e786936a730bd302de83143950da45f2c16078da8f35e1cf7919"}, + {file = "multidict-6.9.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2212a0c842c723d919ea4a22a9296cb6b244b386e4e6ea92adfc7fbf3095519"}, + {file = "multidict-6.9.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd882aa29bf402b62bf1fd7c19fd5df4b6528cf468a908864b39368572b662a9"}, + {file = "multidict-6.9.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c57541034d12b215ab0a2bfa371d1a8a198da18176d0426c27105b9161a6862d"}, + {file = "multidict-6.9.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d73fed4e37158ff00cd271871170b79e40138db51e625fd352fa17c6acb34f67"}, + {file = "multidict-6.9.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79da2491348b30810728050b4a8ec0416f85884125c2fd44655b3d01150d9c3e"}, + {file = "multidict-6.9.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b4da208a63434d21a3df64d29758e650fc4aa8cb05848554b76949c296539cca"}, + {file = "multidict-6.9.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:877ca17fdcfdf5c397493a71e5ff97a87bb181417fe717fdadc77c08c09301ac"}, + {file = "multidict-6.9.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:40aec5299e1ed71fbb988389059da381c3c1a60e0c649acceb2a35d9b128848e"}, + {file = "multidict-6.9.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:4a409ccc42aefec904038695d5d7fd6d8f3af2721b7b6401d55135ec7d6d298e"}, + {file = "multidict-6.9.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:0a5769559e3312dd96731fbe15b4abb6033368ac1cad5a98dadd21946a4c7d6c"}, + {file = "multidict-6.9.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:03fac50ddfd8302175b77863a015eccfae76767cda5506eef86df559ba861e1f"}, + {file = "multidict-6.9.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:5ecace251ccfa705bf3d7d35c5032cf750f5c629809740405697bce5c118c4a4"}, + {file = "multidict-6.9.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:ecc89dbd4155b2f8a47f4bbd89242a35ed15e8e0ec581cab5ac65fa38407329d"}, + {file = "multidict-6.9.1-cp315-cp315-win32.whl", hash = "sha256:a3ffe881246d28a862f1985824f484cb7361f44d6b99c4c620436ef56462f38d"}, + {file = "multidict-6.9.1-cp315-cp315-win_amd64.whl", hash = "sha256:59c123d0e948d760a5f930f316cfefa07e8d632ab84327c0693ee6a88171154f"}, + {file = "multidict-6.9.1-cp315-cp315-win_arm64.whl", hash = "sha256:31199204b3ced121ff5407a2c342326a5d27e3870abbf94bd80dbd2451b7bc8f"}, + {file = "multidict-6.9.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:8879510a76940670517ea1cb589978da44b86e286ec3e50d664ef330817afce7"}, + {file = "multidict-6.9.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:aec65b53a07f580606593f877eefbb29a45939bfc0d3fe6e6d9f42b41b749f68"}, + {file = "multidict-6.9.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:14c56f73e78faa1f68bbb826197cd5871994e70e841b8590829c35912ece5c64"}, + {file = "multidict-6.9.1-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:38c9986f9ce50c459b10de216a05f4bd7ed5ac63887d56e500a52bb464b861ce"}, + {file = "multidict-6.9.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15db6a102cbaf1949cf028ecf080aac76d20bcd29ad4e092574db6c6b7af78a5"}, + {file = "multidict-6.9.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd82c4977196681a499bb6ca9e462afbc5c91c1c15b6a991dfbd72733fea4dd2"}, + {file = "multidict-6.9.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bb58ba73a3f96f9a3e46b1fab69929d7edbbddb3133ee74b5c3c54074a53c4f1"}, + {file = "multidict-6.9.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c6eecfab7ce4cd9487ff8ba936fe38cdfd68c04faf3d5c710363c6a8e695659"}, + {file = "multidict-6.9.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c62e71e6289d78c0108d8aeb495f8bd3cad4bc1632fedddc9297ddf287ecc20"}, + {file = "multidict-6.9.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:539c2cd5fed0947c135cd7eabaaac55f48300dfa1de0f3ca4edb5efa6606f471"}, + {file = "multidict-6.9.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:3100d169ceb7bc8f05f89a6db11d1b21f119975fc26f29dc45a472e3569f0879"}, + {file = "multidict-6.9.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:696477ad71385c4795e3b8e4cf10b0d2c28c2a1ca6a955e031cb1e62993e9ee3"}, + {file = "multidict-6.9.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:f5844e7befc707367807586f550fa97e23dcfef0728f02b98c7bc498a961a5df"}, + {file = "multidict-6.9.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:abc7c2e4b47bfe6a9aea434d3fdebb9597ee636e914e92ba352f2068b9142f4c"}, + {file = "multidict-6.9.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:7ab379f95caee071a37cbd8be86d4c65accc651d391f9f97748fef006f38769c"}, + {file = "multidict-6.9.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:557a4e1708df428ebe6c3081c83a273d275dad2446ce0d81fc648ac71afdb18d"}, + {file = "multidict-6.9.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:475d04d5192eba487a3e2f935976340baa24529046e9c1c9c7a3b7bf80445ae1"}, + {file = "multidict-6.9.1-cp315-cp315t-win32.whl", hash = "sha256:10083a8a0f4e1b26b599889e90b9802504ce5d3f7722f925bbb7ca47dd22a7c1"}, + {file = "multidict-6.9.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e96ca64383efa107262ee3949f047af5ee4f1845ba09463466c04d35a83bd3ec"}, + {file = "multidict-6.9.1-cp315-cp315t-win_arm64.whl", hash = "sha256:501ed8b02a5990c67a91c732843609d43a6be1f7576fcdfc867331239f37fbd3"}, + {file = "multidict-6.9.1-py3-none-any.whl", hash = "sha256:7bf6478188f4e47bf5686e8a33da4ae28bf43b1b2528d9ee144d28492bfac60b"}, + {file = "multidict-6.9.1.tar.gz", hash = "sha256:0f06e60fa190aa7abd0914c2a766736fdc8e9f34878c4346338534b73d1b20e2"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index f9103a9e..23729fad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "deepgram-sdk" -version = "7.10.0" +version = "7.10.1" description = "" readme = "README.md" authors = [] diff --git a/src/deepgram/agent/v1/requests/agent_v1settings.py b/src/deepgram/agent/v1/requests/agent_v1settings.py index 5ada0975..2f748dcd 100644 --- a/src/deepgram/agent/v1/requests/agent_v1settings.py +++ b/src/deepgram/agent/v1/requests/agent_v1settings.py @@ -4,7 +4,6 @@ import typing_extensions from .agent_v1settings_agent import AgentV1SettingsAgentParams -from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams from .agent_v1settings_audio import AgentV1SettingsAudioParams from .agent_v1settings_flags import AgentV1SettingsFlagsParams @@ -28,4 +27,4 @@ class AgentV1SettingsParams(typing_extensions.TypedDict): """ audio: AgentV1SettingsAudioParams - agent: typing.Union[AgentV1SettingsAgentParams, AgentV1SettingsAgentContextParams, str] + agent: AgentV1SettingsAgentParams diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent.py b/src/deepgram/agent/v1/requests/agent_v1settings_agent.py index 01bffc21..0da4b0f3 100644 --- a/src/deepgram/agent/v1/requests/agent_v1settings_agent.py +++ b/src/deepgram/agent/v1/requests/agent_v1settings_agent.py @@ -1,27 +1,7 @@ # This file was auto-generated by Fern from our API Definition. -import typing_extensions -from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams -from .agent_v1settings_agent_listen import AgentV1SettingsAgentListenParams -from .agent_v1settings_agent_speak import AgentV1SettingsAgentSpeakParams -from .agent_v1settings_agent_think import AgentV1SettingsAgentThinkParams - +import typing -class AgentV1SettingsAgentParams(typing_extensions.TypedDict): - language: typing_extensions.NotRequired[str] - """ - Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. - """ - - context: typing_extensions.NotRequired[AgentV1SettingsAgentContextParams] - """ - Conversation context including the history of messages and function calls - """ +from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams - listen: typing_extensions.NotRequired[AgentV1SettingsAgentListenParams] - think: typing_extensions.NotRequired[AgentV1SettingsAgentThinkParams] - speak: typing_extensions.NotRequired[AgentV1SettingsAgentSpeakParams] - greeting: typing_extensions.NotRequired[str] - """ - Optional message that agent will speak at the start - """ +AgentV1SettingsAgentParams = typing.Union[AgentV1SettingsAgentContextParams, str] diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py index b399ef99..c52f05af 100644 --- a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py +++ b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py @@ -1,11 +1,8 @@ # This file was auto-generated by Fern from our API Definition. -import typing - import typing_extensions from .agent_v1settings_agent_context_context import AgentV1SettingsAgentContextContextParams from .agent_v1settings_agent_context_listen import AgentV1SettingsAgentContextListenParams -from .agent_v1settings_agent_context_messages_item import AgentV1SettingsAgentContextMessagesItemParams from .agent_v1settings_agent_context_speak import AgentV1SettingsAgentContextSpeakParams from .agent_v1settings_agent_context_think import AgentV1SettingsAgentContextThinkParams @@ -16,11 +13,6 @@ class AgentV1SettingsAgentContextParams(typing_extensions.TypedDict): Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. """ - messages: typing_extensions.NotRequired[typing.Sequence[AgentV1SettingsAgentContextMessagesItemParams]] - """ - Deprecated. Conversation history as a list of messages and function calls. - """ - context: typing_extensions.NotRequired[AgentV1SettingsAgentContextContextParams] """ Conversation context including the history of messages and function calls diff --git a/src/deepgram/agent/v1/socket_client.py b/src/deepgram/agent/v1/socket_client.py index 6d504d3e..20d1c3b2 100644 --- a/src/deepgram/agent/v1/socket_client.py +++ b/src/deepgram/agent/v1/socket_client.py @@ -3,7 +3,9 @@ import json import logging import typing +from json.decoder import JSONDecodeError +import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -42,18 +44,6 @@ except ImportError: from websockets import WebSocketClientProtocol # type: ignore - -def _sanitize_numeric_types(obj: typing.Any) -> typing.Any: - """Convert whole-number floats to integers for wire-compatible JSON. See: internal-api-specs/issues/205.""" - if isinstance(obj, dict): - return {key: _sanitize_numeric_types(value) for key, value in obj.items()} - if isinstance(obj, list): - return [_sanitize_numeric_types(item) for item in obj] - if isinstance(obj, float) and obj.is_integer(): - return int(obj) - return obj - - _logger = logging.getLogger(__name__) V1SocketClientResponse = typing.Union[ AgentV1ListenUpdated, @@ -122,7 +112,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -176,12 +166,12 @@ async def send_function_call_response(self, message: AgentV1SendFunctionCallResp """ await self._send_model(message) - async def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None: + async def send_keep_alive(self, message: AgentV1KeepAlive) -> None: """ Send a message to the websocket connection. The message will be sent as a AgentV1KeepAlive. """ - await self._send_model(message or AgentV1KeepAlive(type="KeepAlive")) + await self._send_model(message) async def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ @@ -190,12 +180,12 @@ async def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ await self._send_model(message) - async def send_force_end_turn(self, message: typing.Optional[AgentV1ForceEndTurn] = None) -> None: + async def send_force_end_turn(self, message: AgentV1ForceEndTurn) -> None: """ Send a message to the websocket connection. The message will be sent as a AgentV1ForceEndTurn. """ - await self._send_model(message or AgentV1ForceEndTurn(type="ForceEndTurn")) + await self._send_model(message) async def send_media(self, message: bytes) -> None: """ @@ -230,7 +220,7 @@ async def _send_model(self, data: typing.Any) -> None: """ Send a Pydantic model to the websocket connection. """ - await self._send(_sanitize_numeric_types(data.dict())) + await self._send(data.dict()) class V1SocketClient(EventEmitterMixin): @@ -276,7 +266,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -330,12 +320,12 @@ def send_function_call_response(self, message: AgentV1SendFunctionCallResponse) """ self._send_model(message) - def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None: + def send_keep_alive(self, message: AgentV1KeepAlive) -> None: """ Send a message to the websocket connection. The message will be sent as a AgentV1KeepAlive. """ - self._send_model(message or AgentV1KeepAlive(type="KeepAlive")) + self._send_model(message) def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ @@ -344,12 +334,12 @@ def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ self._send_model(message) - def send_force_end_turn(self, message: typing.Optional[AgentV1ForceEndTurn] = None) -> None: + def send_force_end_turn(self, message: AgentV1ForceEndTurn) -> None: """ Send a message to the websocket connection. The message will be sent as a AgentV1ForceEndTurn. """ - self._send_model(message or AgentV1ForceEndTurn(type="ForceEndTurn")) + self._send_model(message) def send_media(self, message: bytes) -> None: """ @@ -384,4 +374,4 @@ def _send_model(self, data: typing.Any) -> None: """ Send a Pydantic model to the websocket connection. """ - self._send(_sanitize_numeric_types(data.dict())) + self._send(data.dict()) diff --git a/src/deepgram/agent/v1/types/agent_v1settings.py b/src/deepgram/agent/v1/types/agent_v1settings.py index ff58da13..11601dfc 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings.py +++ b/src/deepgram/agent/v1/types/agent_v1settings.py @@ -6,7 +6,6 @@ from ....core.pydantic_utilities import IS_PYDANTIC_V2 from ....core.unchecked_base_model import UncheckedBaseModel from .agent_v1settings_agent import AgentV1SettingsAgent -from .agent_v1settings_agent_context import AgentV1SettingsAgentContext from .agent_v1settings_audio import AgentV1SettingsAudio from .agent_v1settings_flags import AgentV1SettingsFlags @@ -30,7 +29,7 @@ class AgentV1Settings(UncheckedBaseModel): """ audio: AgentV1SettingsAudio - agent: typing.Union[AgentV1SettingsAgent, AgentV1SettingsAgentContext, str] + agent: AgentV1SettingsAgent if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent.py b/src/deepgram/agent/v1/types/agent_v1settings_agent.py index fae21e75..c1483c35 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent.py @@ -2,61 +2,6 @@ import typing -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 from .agent_v1settings_agent_context import AgentV1SettingsAgentContext -from .agent_v1settings_agent_listen import AgentV1SettingsAgentListen - -class AgentV1SettingsAgent(AgentV1SettingsAgentContext): - # Keep the formerly callable public model name working with the schema's - # current nested context format. - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_legacy_nested_context(cls, values: typing.Any) -> typing.Any: - if not isinstance(values, dict): - return values - - context = values.get("context") - if isinstance(context, AgentV1SettingsAgentContext) and ( - context.context is not None - and context.language is None - and context.listen is None - and context.think is None - and context.speak is None - and context.greeting is None - ): - values = dict(values) - values["context"] = context.context - - listen = values.get("listen") - if isinstance(listen, AgentV1SettingsAgentListen): - values = dict(values) - values["listen"] = listen.dict() - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_legacy_nested_context(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if not isinstance(values, dict): - return values - - context = values.get("context") - if isinstance(context, AgentV1SettingsAgentContext) and ( - context.context is not None - and context.language is None - and context.listen is None - and context.think is None - and context.speak is None - and context.greeting is None - ): - values = dict(values) - values["context"] = context.context - - listen = values.get("listen") - if isinstance(listen, AgentV1SettingsAgentListen): - values = dict(values) - values["listen"] = listen.dict() - return values +AgentV1SettingsAgent = typing.Union[AgentV1SettingsAgentContext, str] diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py b/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py index 827aad63..fcbea2f3 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py @@ -7,7 +7,6 @@ from ....core.unchecked_base_model import UncheckedBaseModel from .agent_v1settings_agent_context_context import AgentV1SettingsAgentContextContext from .agent_v1settings_agent_context_listen import AgentV1SettingsAgentContextListen -from .agent_v1settings_agent_context_messages_item import AgentV1SettingsAgentContextMessagesItem from .agent_v1settings_agent_context_speak import AgentV1SettingsAgentContextSpeak from .agent_v1settings_agent_context_think import AgentV1SettingsAgentContextThink @@ -31,30 +30,6 @@ class AgentV1SettingsAgentContext(UncheckedBaseModel): Optional message that agent will speak at the start """ - # Translate the pre-restructure top-level messages field to the current - # nested context.messages payload while retaining read-side access. - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_legacy_messages_to_context(cls, values: typing.Any) -> typing.Any: - if isinstance(values, dict) and "messages" in values and "context" not in values: - values = dict(values) - values["context"] = {"messages": values.pop("messages")} - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_legacy_messages_to_context(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if isinstance(values, dict) and "messages" in values and "context" not in values: - values = dict(values) - values["context"] = {"messages": values.pop("messages")} - return values - - @property - def messages(self) -> typing.Optional[typing.List[AgentV1SettingsAgentContextMessagesItem]]: - return None if self.context is None else self.context.messages - if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py index a13d2a63..b806f202 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py @@ -33,36 +33,11 @@ class AgentV1SettingsAgentContextListenProvider_V2(UncheckedBaseModel): type: typing.Literal["deepgram"] = "deepgram" model: str language_hints: typing.Optional[typing.List[str]] = None - language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) eot_threshold: typing.Optional[float] = None eager_eot_threshold: typing.Optional[float] = None eot_timeout_ms: typing.Optional[int] = None keyterms: typing.Optional[typing.List[str]] = None - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py index 40706b47..d053b2b9 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py @@ -33,36 +33,11 @@ class AgentV1SettingsAgentListenProvider_V2(UncheckedBaseModel): type: typing.Literal["deepgram"] = "deepgram" model: str language_hints: typing.Optional[typing.List[str]] = None - language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) eot_threshold: typing.Optional[float] = None eager_eot_threshold: typing.Optional[float] = None eot_timeout_ms: typing.Optional[int] = None keyterms: typing.Optional[typing.List[str]] = None - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py index d6ad435a..f345904c 100644 --- a/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py +++ b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py @@ -5,35 +5,8 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 from ....core.unchecked_base_model import UncheckedBaseModel -from ....types.deepgram_listen_provider_v1 import DeepgramListenProviderV1 -from ....types.deepgram_listen_provider_v2 import DeepgramListenProviderV2 from .agent_v1update_listen_listen_provider import AgentV1UpdateListenListenProvider -_V1_ONLY_KEYS = frozenset({"language", "smart_format"}) - - -def _coerce_legacy_update_listen_provider(values: typing.Any) -> typing.Any: - """Add the discriminator required by the versioned provider union.""" - if not isinstance(values, dict): - return values - provider = values.get("provider") - if isinstance(provider, DeepgramListenProviderV2): - values = dict(values) - values["provider"] = {**provider.dict(), "version": "v2"} - elif isinstance(provider, DeepgramListenProviderV1): - values = dict(values) - values["provider"] = {**provider.dict(), "version": "v1"} - elif isinstance(provider, dict) and "version" not in provider: - values = dict(values) - version = "v1" if _V1_ONLY_KEYS & provider.keys() else "v2" - provider_cls = DeepgramListenProviderV1 if version == "v1" else DeepgramListenProviderV2 - try: - coerced = provider_cls(**provider).dict() - except Exception: - coerced = dict(provider) - values["provider"] = {**coerced, "version": version} - return values - class AgentV1UpdateListenListen(UncheckedBaseModel): """ @@ -42,18 +15,6 @@ class AgentV1UpdateListenListen(UncheckedBaseModel): provider: AgentV1UpdateListenListenProvider - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_legacy_provider(cls, values: typing.Any) -> typing.Any: - return _coerce_legacy_update_listen_provider(values) - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_legacy_provider(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - return _coerce_legacy_update_listen_provider(values) - if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/deepgram/core/api_error.py b/src/deepgram/core/api_error.py index 2aa93914..6f850a60 100644 --- a/src/deepgram/core/api_error.py +++ b/src/deepgram/core/api_error.py @@ -2,8 +2,6 @@ from typing import Any, Dict, Optional -from .._secure_logging import redact_sensitive_headers - class ApiError(Exception): headers: Optional[Dict[str, str]] @@ -17,7 +15,7 @@ def __init__( status_code: Optional[int] = None, body: Any = None, ) -> None: - self.headers = redact_sensitive_headers(headers) + self.headers = headers self.status_code = status_code self.body = body diff --git a/src/deepgram/core/client_wrapper.py b/src/deepgram/core/client_wrapper.py index 14de928d..7a8ca0f5 100644 --- a/src/deepgram/core/client_wrapper.py +++ b/src/deepgram/core/client_wrapper.py @@ -37,7 +37,7 @@ def get_headers(self) -> typing.Dict[str, str]: try: _sdk_version = _fern_importlib_metadata.version("deepgram-sdk") except _fern_importlib_metadata.PackageNotFoundError: - _sdk_version = "7.9.1" + _sdk_version = "7.10.1" headers: typing.Dict[str, str] = { "User-Agent": "deepgram-sdk/" + _sdk_version, diff --git a/src/deepgram/core/parse_error.py b/src/deepgram/core/parse_error.py index c510c0e8..4527c6a8 100644 --- a/src/deepgram/core/parse_error.py +++ b/src/deepgram/core/parse_error.py @@ -2,8 +2,6 @@ from typing import Any, Dict, Optional -from .._secure_logging import redact_sensitive_headers - class ParsingError(Exception): """ @@ -25,7 +23,7 @@ def __init__( body: Any = None, cause: Optional[Exception] = None, ) -> None: - self.headers = redact_sensitive_headers(headers) + self.headers = headers self.status_code = status_code self.body = body self.cause = cause diff --git a/src/deepgram/core/query_encoder.py b/src/deepgram/core/query_encoder.py index 58088258..3183001d 100644 --- a/src/deepgram/core/query_encoder.py +++ b/src/deepgram/core/query_encoder.py @@ -5,13 +5,6 @@ import pydantic -def _coerce_query_value(value: Any) -> Any: - # urllib.parse.urlencode would otherwise turn bools into "True"/"False". - if isinstance(value, bool): - return "true" if value else "false" - return value - - # Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: result = [] @@ -24,9 +17,9 @@ def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = N if isinstance(arr_v, dict): result.extend(traverse_query_dict(arr_v, key)) else: - result.append((key, _coerce_query_value(arr_v))) + result.append((key, arr_v)) else: - result.append((key, _coerce_query_value(v))) + result.append((key, v)) return result @@ -48,11 +41,11 @@ def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, An encoded_values.extend(single_query_encoder(query_key, obj_dict)) else: - encoded_values.append((query_key, _coerce_query_value(value))) + encoded_values.append((query_key, value)) return encoded_values - return [(query_key, _coerce_query_value(query_value))] + return [(query_key, query_value)] def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: diff --git a/src/deepgram/listen/v1/socket_client.py b/src/deepgram/listen/v1/socket_client.py index 5954d116..cffa82b7 100644 --- a/src/deepgram/listen/v1/socket_client.py +++ b/src/deepgram/listen/v1/socket_client.py @@ -3,7 +3,9 @@ import json import logging import typing +from json.decoder import JSONDecodeError +import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -67,7 +69,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -79,26 +81,26 @@ async def send_media(self, message: bytes) -> None: """ await self._send(message) - async def send_finalize(self, message: typing.Optional[ListenV1Finalize] = None) -> None: + async def send_finalize(self, message: ListenV1Finalize) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1Finalize. """ - await self._send_model(message or ListenV1Finalize(type="Finalize")) + await self._send_model(message) - async def send_close_stream(self, message: typing.Optional[ListenV1CloseStream] = None) -> None: + async def send_close_stream(self, message: ListenV1CloseStream) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1CloseStream. """ - await self._send_model(message or ListenV1CloseStream(type="CloseStream")) + await self._send_model(message) - async def send_keep_alive(self, message: typing.Optional[ListenV1KeepAlive] = None) -> None: + async def send_keep_alive(self, message: ListenV1KeepAlive) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1KeepAlive. """ - await self._send_model(message or ListenV1KeepAlive(type="KeepAlive")) + await self._send_model(message) async def recv(self) -> V1SocketClientResponse: """ @@ -172,7 +174,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -184,26 +186,26 @@ def send_media(self, message: bytes) -> None: """ self._send(message) - def send_finalize(self, message: typing.Optional[ListenV1Finalize] = None) -> None: + def send_finalize(self, message: ListenV1Finalize) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1Finalize. """ - self._send_model(message or ListenV1Finalize(type="Finalize")) + self._send_model(message) - def send_close_stream(self, message: typing.Optional[ListenV1CloseStream] = None) -> None: + def send_close_stream(self, message: ListenV1CloseStream) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1CloseStream. """ - self._send_model(message or ListenV1CloseStream(type="CloseStream")) + self._send_model(message) - def send_keep_alive(self, message: typing.Optional[ListenV1KeepAlive] = None) -> None: + def send_keep_alive(self, message: ListenV1KeepAlive) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1KeepAlive. """ - self._send_model(message or ListenV1KeepAlive(type="KeepAlive")) + self._send_model(message) def recv(self) -> V1SocketClientResponse: """ diff --git a/src/deepgram/listen/v2/requests/listen_v2configure.py b/src/deepgram/listen/v2/requests/listen_v2configure.py index 4e3fe35a..d169e307 100644 --- a/src/deepgram/listen/v2/requests/listen_v2configure.py +++ b/src/deepgram/listen/v2/requests/listen_v2configure.py @@ -4,6 +4,7 @@ import typing_extensions from ....requests.listen_v2keyterm import ListenV2KeytermParams +from ....types.listen_v2configure_numerals import ListenV2ConfigureNumerals from .listen_v2configure_thresholds import ListenV2ConfigureThresholdsParams @@ -26,3 +27,5 @@ class ListenV2ConfigureParams(typing_extensions.TypedDict): Only valid when the model is flux-general-multi. If this field is not supplied, the session will continue to use the currently configured value. """ + + numerals: typing_extensions.NotRequired[ListenV2ConfigureNumerals] diff --git a/src/deepgram/listen/v2/socket_client.py b/src/deepgram/listen/v2/socket_client.py index e4e09468..33d419b3 100644 --- a/src/deepgram/listen/v2/socket_client.py +++ b/src/deepgram/listen/v2/socket_client.py @@ -3,7 +3,9 @@ import json import logging import typing +from json.decoder import JSONDecodeError +import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -70,7 +72,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -82,31 +84,26 @@ async def send_media(self, message: bytes) -> None: """ await self._send(message) - async def send_close_stream(self, message: typing.Optional[ListenV2CloseStream] = None) -> None: + async def send_close_stream(self, message: ListenV2CloseStream) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2CloseStream. """ - await self._send_model(message or ListenV2CloseStream(type="CloseStream")) + await self._send_model(message) - async def send_force_end_turn(self, message: typing.Optional[ListenV2ForceEndTurn] = None) -> None: + async def send_force_end_turn(self, message: ListenV2ForceEndTurn) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2ForceEndTurn. - This requires server-side enablement. On deployments without the feature, the server returns - UNPARSABLE_CLIENT_MESSAGE and closes the connection. """ - await self._send_model(message or ListenV2ForceEndTurn(type="ForceEndTurn")) + await self._send_model(message) - async def send_configure(self, message: typing.Union[ListenV2Configure, typing.Dict[str, typing.Any]]) -> None: + async def send_configure(self, message: ListenV2Configure) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2Configure. """ - if isinstance(message, dict): - await self._send(message) - else: - await self._send_model(message) + await self._send_model(message) async def recv(self) -> V2SocketClientResponse: """ @@ -180,7 +177,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -192,31 +189,26 @@ def send_media(self, message: bytes) -> None: """ self._send(message) - def send_close_stream(self, message: typing.Optional[ListenV2CloseStream] = None) -> None: + def send_close_stream(self, message: ListenV2CloseStream) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2CloseStream. """ - self._send_model(message or ListenV2CloseStream(type="CloseStream")) + self._send_model(message) - def send_force_end_turn(self, message: typing.Optional[ListenV2ForceEndTurn] = None) -> None: + def send_force_end_turn(self, message: ListenV2ForceEndTurn) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2ForceEndTurn. - This requires server-side enablement. On deployments without the feature, the server returns - UNPARSABLE_CLIENT_MESSAGE and closes the connection. """ - self._send_model(message or ListenV2ForceEndTurn(type="ForceEndTurn")) + self._send_model(message) - def send_configure(self, message: typing.Union[ListenV2Configure, typing.Dict[str, typing.Any]]) -> None: + def send_configure(self, message: ListenV2Configure) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2Configure. """ - if isinstance(message, dict): - self._send(message) - else: - self._send_model(message) + self._send_model(message) def recv(self) -> V2SocketClientResponse: """ diff --git a/src/deepgram/listen/v2/types/listen_v2configure.py b/src/deepgram/listen/v2/types/listen_v2configure.py index 6ecb59ca..487ed99c 100644 --- a/src/deepgram/listen/v2/types/listen_v2configure.py +++ b/src/deepgram/listen/v2/types/listen_v2configure.py @@ -5,6 +5,7 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 from ....core.unchecked_base_model import UncheckedBaseModel +from ....types.listen_v2configure_numerals import ListenV2ConfigureNumerals from ....types.listen_v2keyterm import ListenV2Keyterm from .listen_v2configure_thresholds import ListenV2ConfigureThresholds @@ -29,6 +30,8 @@ class ListenV2Configure(UncheckedBaseModel): the session will continue to use the currently configured value. """ + numerals: typing.Optional[ListenV2ConfigureNumerals] = None + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/deepgram/listen/v2/types/listen_v2configure_failure.py b/src/deepgram/listen/v2/types/listen_v2configure_failure.py index bf30e353..2d0cb34f 100644 --- a/src/deepgram/listen/v2/types/listen_v2configure_failure.py +++ b/src/deepgram/listen/v2/types/listen_v2configure_failure.py @@ -4,10 +4,10 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel +from ....core.unchecked_base_model import UncheckedBaseModel -class ListenV2ConfigureFailure(ListenV2ResponseDictCompatModel): +class ListenV2ConfigureFailure(UncheckedBaseModel): type: typing.Literal["ConfigureFailure"] = pydantic.Field(default="ConfigureFailure") """ Message type identifier diff --git a/src/deepgram/listen/v2/types/listen_v2configure_success.py b/src/deepgram/listen/v2/types/listen_v2configure_success.py index bde84d6f..ce20724a 100644 --- a/src/deepgram/listen/v2/types/listen_v2configure_success.py +++ b/src/deepgram/listen/v2/types/listen_v2configure_success.py @@ -4,12 +4,12 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel from ....types.listen_v2keyterm import ListenV2Keyterm -from ._dict_compat import ListenV2ResponseDictCompatModel from .listen_v2configure_success_thresholds import ListenV2ConfigureSuccessThresholds -class ListenV2ConfigureSuccess(ListenV2ResponseDictCompatModel): +class ListenV2ConfigureSuccess(UncheckedBaseModel): type: typing.Literal["ConfigureSuccess"] = pydantic.Field(default="ConfigureSuccess") """ Message type identifier diff --git a/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py b/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py index e0353ec9..6c719233 100644 --- a/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py +++ b/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py @@ -4,13 +4,13 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel from ....types.listen_v2eager_eot_threshold import ListenV2EagerEotThreshold from ....types.listen_v2eot_threshold import ListenV2EotThreshold from ....types.listen_v2eot_timeout_ms import ListenV2EotTimeoutMs -from ._dict_compat import ListenV2ResponseDictCompatModel -class ListenV2ConfigureSuccessThresholds(ListenV2ResponseDictCompatModel): +class ListenV2ConfigureSuccessThresholds(UncheckedBaseModel): """ Updates each parameter, if it is supplied. If a particular threshold parameter is not supplied, the configuration continues using the currently configured value. diff --git a/src/deepgram/listen/v2/types/listen_v2connected.py b/src/deepgram/listen/v2/types/listen_v2connected.py index b253d86c..cefc0236 100644 --- a/src/deepgram/listen/v2/types/listen_v2connected.py +++ b/src/deepgram/listen/v2/types/listen_v2connected.py @@ -4,10 +4,10 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel +from ....core.unchecked_base_model import UncheckedBaseModel -class ListenV2Connected(ListenV2ResponseDictCompatModel): +class ListenV2Connected(UncheckedBaseModel): type: typing.Literal["Connected"] = pydantic.Field(default="Connected") """ Message type identifier diff --git a/src/deepgram/listen/v2/types/listen_v2fatal_error.py b/src/deepgram/listen/v2/types/listen_v2fatal_error.py index 2202c82e..c5106f07 100644 --- a/src/deepgram/listen/v2/types/listen_v2fatal_error.py +++ b/src/deepgram/listen/v2/types/listen_v2fatal_error.py @@ -4,10 +4,10 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel +from ....core.unchecked_base_model import UncheckedBaseModel -class ListenV2FatalError(ListenV2ResponseDictCompatModel): +class ListenV2FatalError(UncheckedBaseModel): type: typing.Literal["Error"] = pydantic.Field(default="Error") """ Message type identifier diff --git a/src/deepgram/listen/v2/types/listen_v2turn_info.py b/src/deepgram/listen/v2/types/listen_v2turn_info.py index 969d6d0c..d87a1032 100644 --- a/src/deepgram/listen/v2/types/listen_v2turn_info.py +++ b/src/deepgram/listen/v2/types/listen_v2turn_info.py @@ -4,12 +4,12 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel +from ....core.unchecked_base_model import UncheckedBaseModel from .listen_v2turn_info_event import ListenV2TurnInfoEvent from .listen_v2turn_info_words_item import ListenV2TurnInfoWordsItem -class ListenV2TurnInfo(ListenV2ResponseDictCompatModel): +class ListenV2TurnInfo(UncheckedBaseModel): """ Describes the current turn and latest state of the turn """ diff --git a/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py b/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py index 88240cd9..07ada673 100644 --- a/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py +++ b/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py @@ -4,10 +4,10 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel +from ....core.unchecked_base_model import UncheckedBaseModel -class ListenV2TurnInfoWordsItem(ListenV2ResponseDictCompatModel): +class ListenV2TurnInfoWordsItem(UncheckedBaseModel): word: str = pydantic.Field() """ The individual punctuated, properly-cased word from the transcript diff --git a/src/deepgram/requests/deepgram_listen_provider_v2.py b/src/deepgram/requests/deepgram_listen_provider_v2.py index 02fdf65a..30eb07a4 100644 --- a/src/deepgram/requests/deepgram_listen_provider_v2.py +++ b/src/deepgram/requests/deepgram_listen_provider_v2.py @@ -28,7 +28,7 @@ class DeepgramListenProviderV2Params(typing_extensions.TypedDict): eot_threshold: typing_extensions.NotRequired[float] """ - End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it for full manual turn control with the ForceEndTurn message. + End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it when using ForceEndTurn for full manual turn control. """ eager_eot_threshold: typing_extensions.NotRequired[float] diff --git a/src/deepgram/speak/v1/socket_client.py b/src/deepgram/speak/v1/socket_client.py index e3c28bb5..9f1cad28 100644 --- a/src/deepgram/speak/v1/socket_client.py +++ b/src/deepgram/speak/v1/socket_client.py @@ -3,7 +3,9 @@ import json import logging import typing +from json.decoder import JSONDecodeError +import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -68,7 +70,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -80,26 +82,26 @@ async def send_text(self, message: SpeakV1Text) -> None: """ await self._send_model(message) - async def send_flush(self, message: typing.Optional[SpeakV1Flush] = None) -> None: + async def send_flush(self, message: SpeakV1Flush) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Flush. """ - await self._send_model(message or SpeakV1Flush(type="Flush")) + await self._send_model(message) - async def send_clear(self, message: typing.Optional[SpeakV1Clear] = None) -> None: + async def send_clear(self, message: SpeakV1Clear) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Clear. """ - await self._send_model(message or SpeakV1Clear(type="Clear")) + await self._send_model(message) - async def send_close(self, message: typing.Optional[SpeakV1Close] = None) -> None: + async def send_close(self, message: SpeakV1Close) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Close. """ - await self._send_model(message or SpeakV1Close(type="Close")) + await self._send_model(message) async def recv(self) -> V1SocketClientResponse: """ @@ -173,7 +175,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -185,26 +187,26 @@ def send_text(self, message: SpeakV1Text) -> None: """ self._send_model(message) - def send_flush(self, message: typing.Optional[SpeakV1Flush] = None) -> None: + def send_flush(self, message: SpeakV1Flush) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Flush. """ - self._send_model(message or SpeakV1Flush(type="Flush")) + self._send_model(message) - def send_clear(self, message: typing.Optional[SpeakV1Clear] = None) -> None: + def send_clear(self, message: SpeakV1Clear) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Clear. """ - self._send_model(message or SpeakV1Clear(type="Clear")) + self._send_model(message) - def send_close(self, message: typing.Optional[SpeakV1Close] = None) -> None: + def send_close(self, message: SpeakV1Close) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Close. """ - self._send_model(message or SpeakV1Close(type="Close")) + self._send_model(message) def recv(self) -> V1SocketClientResponse: """ diff --git a/src/deepgram/speak/v2/socket_client.py b/src/deepgram/speak/v2/socket_client.py index c6729c32..b9d4d1a2 100644 --- a/src/deepgram/speak/v2/socket_client.py +++ b/src/deepgram/speak/v2/socket_client.py @@ -3,7 +3,9 @@ import json import logging import typing +from json.decoder import JSONDecodeError +import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -87,7 +89,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -99,19 +101,19 @@ async def send_speak(self, message: SpeakV2Speak) -> None: """ await self._send_model(message) - async def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: + async def send_flush(self, message: SpeakV2Flush) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Flush. """ - await self._send_model(message or SpeakV2Flush(type="Flush")) + await self._send_model(message) - async def send_interrupt(self, message: typing.Optional[SpeakV2Interrupt] = None) -> None: + async def send_interrupt(self, message: SpeakV2Interrupt) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Interrupt. """ - await self._send_model(message or SpeakV2Interrupt(type="Interrupt")) + await self._send_model(message) async def send_configure(self, message: SpeakV2Configure) -> None: """ @@ -120,12 +122,12 @@ async def send_configure(self, message: SpeakV2Configure) -> None: """ await self._send_model(message) - async def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: + async def send_close(self, message: SpeakV2Close) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Close. """ - await self._send_model(message or SpeakV2Close(type="Close")) + await self._send_model(message) async def recv(self) -> V2SocketClientResponse: """ @@ -199,7 +201,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except Exception as exc: + except (websockets.WebSocketException, JSONDecodeError) as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -211,19 +213,19 @@ def send_speak(self, message: SpeakV2Speak) -> None: """ self._send_model(message) - def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: + def send_flush(self, message: SpeakV2Flush) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Flush. """ - self._send_model(message or SpeakV2Flush(type="Flush")) + self._send_model(message) - def send_interrupt(self, message: typing.Optional[SpeakV2Interrupt] = None) -> None: + def send_interrupt(self, message: SpeakV2Interrupt) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Interrupt. """ - self._send_model(message or SpeakV2Interrupt(type="Interrupt")) + self._send_model(message) def send_configure(self, message: SpeakV2Configure) -> None: """ @@ -232,12 +234,12 @@ def send_configure(self, message: SpeakV2Configure) -> None: """ self._send_model(message) - def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: + def send_close(self, message: SpeakV2Close) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Close. """ - self._send_model(message or SpeakV2Close(type="Close")) + self._send_model(message) def recv(self) -> V2SocketClientResponse: """ diff --git a/src/deepgram/types/deepgram.py b/src/deepgram/types/deepgram.py index 994f7a2c..ec242762 100644 --- a/src/deepgram/types/deepgram.py +++ b/src/deepgram/types/deepgram.py @@ -29,7 +29,7 @@ class Deepgram(UncheckedBaseModel): Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Aura (version v1) accepts any value from 0.7 to 1.5. Flux TTS (version v2) accepts values from 0.5 to 1.5 in 0.05 increments; a value the family does not accept ends the session with FAILED_TO_SPEAK. Not yet supported in all languages. """ - expressivity: typing.Optional[pydantic.StrictInt] = pydantic.Field(default=None) + expressivity: typing.Optional[int] = pydantic.Field(default=None) """ Delivery register of the generated speech, on a calm-to-animated axis. Flux TTS (version v2) only, on every Flux voice. Accepts the whole numbers -2 to 2, where 0 (the default) is the voice's tuned delivery and the only value validated for production, -2 the calm end of the range and 2 the animated end. Fixed for the session. Beta: behavior may change in future model versions, and non-default values increase the risk of hallucinations and pronunciation errors. See [Expressivity](/docs/tts-expressivity). """ diff --git a/src/deepgram/types/deepgram_listen_provider_v2.py b/src/deepgram/types/deepgram_listen_provider_v2.py index c166bfac..f7a155fb 100644 --- a/src/deepgram/types/deepgram_listen_provider_v2.py +++ b/src/deepgram/types/deepgram_listen_provider_v2.py @@ -28,15 +28,9 @@ class DeepgramListenProviderV2(UncheckedBaseModel): An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. """ - language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) - """ - Deprecated. Use `language_hints`. Accepted for backward compatibility and - remapped before serialization so the singular field never reaches the API. - """ - eot_threshold: typing.Optional[float] = pydantic.Field(default=None) """ - End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it for full manual turn control with the ForceEndTurn message. + End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it when using ForceEndTurn for full manual turn control. """ eager_eot_threshold: typing.Optional[float] = pydantic.Field(default=None) @@ -54,30 +48,6 @@ class DeepgramListenProviderV2(UncheckedBaseModel): Prompt keyterm recognition to improve Keyword Recall Rate """ - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/deepgram/types/listen_v2configure_numerals.py b/src/deepgram/types/listen_v2configure_numerals.py new file mode 100644 index 00000000..9c4299b1 --- /dev/null +++ b/src/deepgram/types/listen_v2configure_numerals.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +ListenV2ConfigureNumerals = bool +""" +Numerals converts numbers from written format to numerical format. Applies to turns transcribed after the update. +""" diff --git a/src/deepgram/types/listen_v2eot_threshold.py b/src/deepgram/types/listen_v2eot_threshold.py index 9f6e231c..22fb3aff 100644 --- a/src/deepgram/types/listen_v2eot_threshold.py +++ b/src/deepgram/types/listen_v2eot_threshold.py @@ -5,7 +5,7 @@ ListenV2EotThreshold = typing.Any """ End-of-turn confidence required to finish a turn. Valid Values 0.5 - -1.0. Set to 1.0 to fully suppress confidence-based end-of-turn detection. -`eot_timeout_ms` still ends idle turns; increase it for full manual -turn control with the `ForceEndTurn` message. +1.0. Set to 1.0 to suppress confidence-based end-of-turn detection. +`eot_timeout_ms` still ends idle turns; increase it when using +`ForceEndTurn` for full manual turn control. """ diff --git a/src/deepgram/types/speak_settings_v1provider.py b/src/deepgram/types/speak_settings_v1provider.py index 3fbef168..305cdba8 100644 --- a/src/deepgram/types/speak_settings_v1provider.py +++ b/src/deepgram/types/speak_settings_v1provider.py @@ -24,7 +24,7 @@ class SpeakSettingsV1Provider_Deepgram(UncheckedBaseModel): version: typing.Optional[str] = None model: DeepgramModel speed: typing.Optional[float] = None - expressivity: typing.Optional[pydantic.StrictInt] = None + expressivity: typing.Optional[int] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/tests/wire/test_listen_v1_media.py b/tests/wire/test_listen_v1_media.py index d46dbc2a..e4264985 100644 --- a/tests/wire/test_listen_v1_media.py +++ b/tests/wire/test_listen_v1_media.py @@ -9,94 +9,3 @@ def test_listen_v1_media_transcribe_url() -> None: url="https://dpgr.am/spacewalk.wav", ) verify_request_count(test_id, "POST", "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/v1/listen", None, 1) - - -def test_listen_v1_media_transcribe_url_serializes_all_query_params() -> None: - """All optional transcribe-url query parameters reach the wire.""" - test_id = "listen.v1.media.transcribe_url.query_params" - client = get_client(test_id) - client.listen.v1.media.transcribe_url( - callback="callback", - callback_method="POST", - extra=["extra"], - sentiment=True, - summarize="v2", - tag=["tag"], - topics=True, - custom_topic=["custom_topic"], - custom_topic_mode="extended", - intents=True, - custom_intent=["custom_intent"], - custom_intent_mode="extended", - detect_entities=True, - detect_language=True, - diarize=True, - diarize_model="latest", - dictation=True, - encoding="linear16", - filler_words=True, - keyterm=["keyterm"], - keywords=["keywords"], - language="language", - measurements=True, - model="nova-3", - multichannel=True, - numerals=True, - paragraphs=True, - profanity_filter=True, - punctuate=True, - redact="redact", - replace=["replace"], - search=["search"], - smart_format=True, - utterances=True, - utt_split=1.1, - version="latest", - mip_opt_out=True, - url="https://dpgr.am/spacewalk.wav", - ) - verify_request_count( - test_id, - "POST", - "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/v1/listen", - { - "callback": "callback", - "callback_method": "POST", - "extra": "extra", - "sentiment": "true", - "summarize": "v2", - "tag": "tag", - "topics": "true", - "custom_topic": "custom_topic", - "custom_topic_mode": "extended", - "intents": "true", - "custom_intent": "custom_intent", - "custom_intent_mode": "extended", - "detect_entities": "true", - "detect_language": "true", - "diarize": "true", - "diarize_model": "latest", - "dictation": "true", - "encoding": "linear16", - "filler_words": "true", - "keyterm": "keyterm", - "keywords": "keywords", - "language": "language", - "measurements": "true", - "model": "nova-3", - "multichannel": "true", - "numerals": "true", - "paragraphs": "true", - "profanity_filter": "true", - "punctuate": "true", - "redact": "redact", - "replace": "replace", - "search": "search", - "smart_format": "true", - "utterances": "true", - "utt_split": "1.1", - "version": "latest", - "mip_opt_out": "true", - }, - 1, - ) diff --git a/tests/wire/test_manage_v1_projects_keys.py b/tests/wire/test_manage_v1_projects_keys.py index db30e0e0..7976bf7d 100644 --- a/tests/wire/test_manage_v1_projects_keys.py +++ b/tests/wire/test_manage_v1_projects_keys.py @@ -1,7 +1,5 @@ from .conftest import get_client, verify_request_count -from deepgram.requests import CreateKeyV1RequestOneParams - def test_manage_v1_projects_keys_list_() -> None: """Test list endpoint with WireMock""" @@ -25,15 +23,6 @@ def test_manage_v1_projects_keys_create() -> None: verify_request_count(test_id, "POST", "/v1/projects/project_id/keys", None, 1) -def test_manage_v1_projects_keys_create_with_old_request_alias() -> None: - """Test create endpoint with the legacy request alias.""" - test_id = "manage.v1.projects.keys.create.compat" - client = get_client(test_id) - request: CreateKeyV1RequestOneParams = {"key": "value"} - client.manage.v1.projects.keys.create(project_id="project_id", request=request) - verify_request_count(test_id, "POST", "/v1/projects/project_id/keys", None, 1) - - def test_manage_v1_projects_keys_get() -> None: """Test get endpoint with WireMock""" test_id = "manage.v1.projects.keys.get.0" diff --git a/tests/wire/test_manage_v1_projects_requests.py b/tests/wire/test_manage_v1_projects_requests.py index f95ac71c..71a888c7 100644 --- a/tests/wire/test_manage_v1_projects_requests.py +++ b/tests/wire/test_manage_v1_projects_requests.py @@ -1,5 +1,3 @@ -import datetime - from .conftest import get_client, verify_request_count @@ -13,43 +11,6 @@ def test_manage_v1_projects_requests_list_() -> None: verify_request_count(test_id, "GET", "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests", None, 1) -def test_manage_v1_projects_requests_list_serializes_all_query_params() -> None: - """All optional request-list query parameters reach the wire.""" - test_id = "manage.v1.projects.requests.list_.query_params" - client = get_client(test_id) - client.manage.v1.projects.requests.list( - project_id="12345678-90ab-cdef-1234-567890abcdef", - start=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"), - end=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"), - limit=1.1, - page=1.1, - accessor="12345678-1234-1234-1234-123456789012", - request_id="12345678-1234-1234-1234-123456789012", - deployment="hosted", - endpoint="listen", - method="sync", - status="succeeded", - ) - verify_request_count( - test_id, - "GET", - "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests", - { - "start": "2024-01-15T09:30:00Z", - "end": "2024-01-15T09:30:00Z", - "limit": "1.1", - "page": "1.1", - "accessor": "12345678-1234-1234-1234-123456789012", - "request_id": "12345678-1234-1234-1234-123456789012", - "deployment": "hosted", - "endpoint": "listen", - "method": "sync", - "status": "succeeded", - }, - 1, - ) - - def test_manage_v1_projects_requests_get() -> None: """Test get endpoint with WireMock""" test_id = "manage.v1.projects.requests.get.0" From 624184442b4bdd9fdd24bebade3d5e44d70f4797 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 23 Sep 2026 11:35:47 +0100 Subject: [PATCH 4/8] chore: re-apply manual patches after regen --- .fern/metadata.json | 4 +- .fernignore | 77 ++-- .gitignore | 3 + .gitignore.bak | 8 - AGENTS.md | 2 +- pyproject.toml | 2 +- .../agent/v1/requests/agent_v1settings.py | 3 +- .../agent/v1/requests/agent_v1settings.py.bak | 31 -- .../v1/requests/agent_v1settings_agent.py | 26 +- .../v1/requests/agent_v1settings_agent.py.bak | 27 -- .../agent_v1settings_agent_context.py | 8 + .../agent_v1settings_agent_context.py.bak | 35 -- src/deepgram/agent/v1/socket_client.py | 37 +- src/deepgram/agent/v1/socket_client.py.bak | 387 ------------------ .../agent/v1/types/agent_v1settings.py | 3 +- .../agent/v1/types/agent_v1settings.py.bak | 42 -- .../agent/v1/types/agent_v1settings_agent.py | 57 ++- .../v1/types/agent_v1settings_agent.py.bak | 62 --- .../types/agent_v1settings_agent_context.py | 25 ++ .../agent_v1settings_agent_context.py.bak | 65 --- ...1settings_agent_context_listen_provider.py | 23 ++ ...tings_agent_context_listen_provider.py.bak | 79 ---- .../agent_v1settings_agent_listen_provider.py | 23 ++ ...nt_v1settings_agent_listen_provider.py.bak | 79 ---- .../v1/types/agent_v1update_listen_listen.py | 37 ++ .../types/agent_v1update_listen_listen.py.bak | 64 --- src/deepgram/core/api_error.py | 4 +- src/deepgram/core/api_error.py.bak | 25 -- src/deepgram/core/parse_error.py | 4 +- src/deepgram/core/parse_error.py.bak | 38 -- src/deepgram/core/query_encoder.py | 15 +- src/deepgram/core/query_encoder.py.bak | 65 --- src/deepgram/listen/v1/socket_client.py | 30 +- src/deepgram/listen/v1/socket_client.py.bak | 234 ----------- src/deepgram/listen/v2/socket_client.py | 40 +- src/deepgram/listen/v2/socket_client.py.bak | 247 ----------- .../v2/types/listen_v2configure_failure.py | 4 +- .../types/listen_v2configure_failure.py.bak | 35 -- .../v2/types/listen_v2configure_success.py | 4 +- .../types/listen_v2configure_success.py.bak | 49 --- .../listen_v2configure_success_thresholds.py | 4 +- ...sten_v2configure_success_thresholds.py.bak | 30 -- .../listen/v2/types/listen_v2connected.py | 4 +- .../listen/v2/types/listen_v2connected.py.bak | 35 -- .../listen/v2/types/listen_v2fatal_error.py | 4 +- .../v2/types/listen_v2fatal_error.py.bak | 40 -- .../listen/v2/types/listen_v2turn_info.py | 4 +- .../listen/v2/types/listen_v2turn_info.py.bak | 102 ----- .../v2/types/listen_v2turn_info_words_item.py | 4 +- .../listen_v2turn_info_words_item.py.bak | 38 -- .../deepgram_listen_provider_v2.py.bak | 47 --- src/deepgram/speak/v1/socket_client.py | 30 +- src/deepgram/speak/v1/socket_client.py.bak | 235 ----------- src/deepgram/speak/v2/socket_client.py | 30 +- src/deepgram/speak/v2/socket_client.py.bak | 268 ------------ src/deepgram/types/deepgram.py | 2 +- src/deepgram/types/deepgram.py.bak | 44 -- .../types/deepgram_listen_provider_v2.py | 30 ++ .../types/deepgram_listen_provider_v2.py.bak | 88 ---- .../types/listen_v2eot_threshold.py.bak | 11 - .../types/speak_settings_v1provider.py | 2 +- .../types/speak_settings_v1provider.py.bak | 117 ------ tests/custom/test_listen_v2_connect_wire.py | 4 +- tests/custom/test_socket_client_shims.py | 5 + tests/wire/test_listen_v1_media.py | 91 ++++ tests/wire/test_listen_v1_media.py.bak | 102 ----- tests/wire/test_manage_v1_projects_keys.py | 11 + .../wire/test_manage_v1_projects_keys.py.bak | 60 --- .../wire/test_manage_v1_projects_requests.py | 40 +- .../test_manage_v1_projects_requests.py.bak | 67 --- 70 files changed, 539 insertions(+), 3013 deletions(-) delete mode 100644 .gitignore.bak delete mode 100644 src/deepgram/agent/v1/requests/agent_v1settings.py.bak delete mode 100644 src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak delete mode 100644 src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak delete mode 100644 src/deepgram/agent/v1/socket_client.py.bak delete mode 100644 src/deepgram/agent/v1/types/agent_v1settings.py.bak delete mode 100644 src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak delete mode 100644 src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak delete mode 100644 src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak delete mode 100644 src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak delete mode 100644 src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak delete mode 100644 src/deepgram/core/api_error.py.bak delete mode 100644 src/deepgram/core/parse_error.py.bak delete mode 100644 src/deepgram/core/query_encoder.py.bak delete mode 100644 src/deepgram/listen/v1/socket_client.py.bak delete mode 100644 src/deepgram/listen/v2/socket_client.py.bak delete mode 100644 src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak delete mode 100644 src/deepgram/listen/v2/types/listen_v2configure_success.py.bak delete mode 100644 src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak delete mode 100644 src/deepgram/listen/v2/types/listen_v2connected.py.bak delete mode 100644 src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak delete mode 100644 src/deepgram/listen/v2/types/listen_v2turn_info.py.bak delete mode 100644 src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak delete mode 100644 src/deepgram/requests/deepgram_listen_provider_v2.py.bak delete mode 100644 src/deepgram/speak/v1/socket_client.py.bak delete mode 100644 src/deepgram/speak/v2/socket_client.py.bak delete mode 100644 src/deepgram/types/deepgram.py.bak delete mode 100644 src/deepgram/types/deepgram_listen_provider_v2.py.bak delete mode 100644 src/deepgram/types/listen_v2eot_threshold.py.bak delete mode 100644 src/deepgram/types/speak_settings_v1provider.py.bak delete mode 100644 tests/wire/test_listen_v1_media.py.bak delete mode 100644 tests/wire/test_manage_v1_projects_keys.py.bak delete mode 100644 tests/wire/test_manage_v1_projects_requests.py.bak diff --git a/.fern/metadata.json b/.fern/metadata.json index 76ba541c..c4657ecc 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -24,5 +24,5 @@ "originGitCommit": "460af9f2b10e5a374517a3a9e40e3045aaa59a08", "originGitCommitIsDirty": true, "invokedBy": "manual", - "sdkVersion": "7.10.1" -} \ No newline at end of file + "sdkVersion": "7.10.0" +} diff --git a/.fernignore b/.fernignore index 3f8fe93d..256a4ae3 100644 --- a/.fernignore +++ b/.fernignore @@ -27,25 +27,17 @@ src/deepgram/listen/v2/types/_dict_compat.py # - listen/v2 send_configure: runtime tolerance for a raw dict alongside the # generated ListenV2Configure model # [temporarily frozen — manual patches listed above] -src/deepgram/agent/v1/socket_client.py.bak -src/deepgram/listen/v1/socket_client.py.bak -src/deepgram/listen/v2/socket_client.py.bak -src/deepgram/speak/v1/socket_client.py.bak -src/deepgram/speak/v2/socket_client.py.bak - -# The generated eot_threshold descriptions omit that eot_timeout_ms still ends -# idle turns. The DeepgramListenProviderV2 model below shares this patch with -# its language_hint compatibility shim. Remove these patches after the API -# definition includes the timeout requirement and Fern regenerates them. -# [temporarily frozen] -src/deepgram/types/listen_v2eot_threshold.py.bak -src/deepgram/requests/deepgram_listen_provider_v2.py.bak +src/deepgram/agent/v1/socket_client.py +src/deepgram/listen/v1/socket_client.py +src/deepgram/listen/v2/socket_client.py +src/deepgram/speak/v1/socket_client.py +src/deepgram/speak/v2/socket_client.py # Agent TTS provider expressivity must reject fractional values rather than # allowing Pydantic v1 to truncate them before the API sees the request. # [temporarily frozen — remove when Fern emits a strict integer] -src/deepgram/types/speak_settings_v1provider.py.bak -src/deepgram/types/deepgram.py.bak +src/deepgram/types/speak_settings_v1provider.py +src/deepgram/types/deepgram.py # Backward-compat patch: AgentV1SettingsAgentContext schema restructure as of # 2026-05-05. The new schema nests messages under .context.messages; this file @@ -53,12 +45,12 @@ src/deepgram/types/deepgram.py.bak # kwarg and remaps it to `context={"messages": ...}` so existing callers keep # producing the correct wire payload, plus a read-side `.messages` property so # existing attribute access keeps working. -src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak -src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak -src/deepgram/agent/v1/types/agent_v1settings.py.bak -src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak -src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak -src/deepgram/agent/v1/requests/agent_v1settings.py.bak +src/deepgram/agent/v1/types/agent_v1settings_agent_context.py +src/deepgram/agent/v1/types/agent_v1settings_agent.py +src/deepgram/agent/v1/types/agent_v1settings.py +src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py +src/deepgram/agent/v1/requests/agent_v1settings_agent.py +src/deepgram/agent/v1/requests/agent_v1settings.py # Backward-compatibility alias shims for renamed generated public types/params. # These are hand-written wrappers around the current generated names and must not be regenerated. @@ -131,14 +123,11 @@ src/deepgram/requests/deepgram_listen_provider_v2language_hint.py # deny_unknown_fields so the singular key is rejected on the wire). These three generated # models carry a hand-added model_validator(mode='before') / root_validator(pre=True) that # remaps a legacy `language_hint=` kwarg to `language_hints` and drops the dead singular -# key. This DeepgramListenProviderV2 model also carries the EOT description correction: -# eot_threshold=1.0 suppresses confidence-based detection only; eot_timeout_ms still ends -# idle turns and must be increased for manual ForceEndTurn control. Remove that correction -# when the API definition provides it; remove the shim and unfreeze when the singular alias -# is intentionally retired in a future major release. -src/deepgram/types/deepgram_listen_provider_v2.py.bak -src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak -src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak +# key. Remove and unfreeze these shims when the singular alias is intentionally retired in a +# future major release. +src/deepgram/types/deepgram_listen_provider_v2.py +src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py +src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py # Backward-compat patch for the 2026-07-31 AgentV1UpdateListen provider retype. The # generated `provider` field is now the required discriminated union @@ -147,7 +136,7 @@ src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.ba # that coerces a legacy DeepgramListenProviderV1/V2 (or a dict lacking the version # discriminant) into the new shape so existing callers keep working. Remove and unfreeze # when the old provider payloads are retired in a future major. -src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak +src/deepgram/agent/v1/types/agent_v1update_listen_listen.py # Hand-written compat shim recreating ListenV2CloseStreamType, which Fern removed in the # 2026-06-15 regen (docs #946). The original generated type wrongly allowed @@ -188,23 +177,23 @@ src/deepgram/listen/v2/types/__init__.py # the websockets DEBUG handshake logs; this is the other path to it. Unfreeze if the # generator starts redacting credentials itself. -src/deepgram/core/api_error.py.bak -src/deepgram/core/parse_error.py.bak +src/deepgram/core/api_error.py +src/deepgram/core/parse_error.py -src/deepgram/core/query_encoder.py.bak +src/deepgram/core/query_encoder.py # Read-side compatibility for the SDK 7.7 Listen V2 response retype. These # generated response models inherit the hand-written base above so callers can # temporarily use both attribute and read-only subscript access. Remove the custom # inheritance and unfreeze these files in the next major release. # [temporarily frozen -- manual patch described above] -src/deepgram/listen/v2/types/listen_v2connected.py.bak -src/deepgram/listen/v2/types/listen_v2turn_info.py.bak -src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak -src/deepgram/listen/v2/types/listen_v2configure_success.py.bak -src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak -src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak -src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak +src/deepgram/listen/v2/types/listen_v2connected.py +src/deepgram/listen/v2/types/listen_v2turn_info.py +src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py +src/deepgram/listen/v2/types/listen_v2configure_success.py +src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py +src/deepgram/listen/v2/types/listen_v2configure_failure.py +src/deepgram/listen/v2/types/listen_v2fatal_error.py # Hand-written custom tests tests/custom/test_api_error_redaction.py @@ -240,14 +229,14 @@ tests/custom/test_websocket_streaming_coverage.py tests/typecheck/compat_aliases.py # Wire test with restored compatibility coverage for legacy create-key request alias -tests/wire/test_manage_v1_projects_keys.py.bak +tests/wire/test_manage_v1_projects_keys.py # Wire test with restored query-parameter coverage for manage.v1.projects.requests.list. # The 2026-08-11 regen simplified the upstream spec *example* for this endpoint, and Fern # derives the wire test from the example, so the generated test dropped all ten optional # query params (including the datetime -> ISO-8601 Z encoding) while the client signature # still forwards them. Frozen so future regens do not silently drop that check again. -tests/wire/test_manage_v1_projects_requests.py.bak +tests/wire/test_manage_v1_projects_requests.py # Wire test with restored query-parameter coverage for listen.v1.media.transcribe_url. # Third instance of the same mechanism as the two entries above: the 2026-08-18 regen @@ -256,7 +245,7 @@ tests/wire/test_manage_v1_projects_requests.py.bak # dropped all 37 optional query params -- including the bool -> lowercase "true"/"false" # assertions -- while the client signature still forwards every one of them. Frozen so # future regens cannot silently drop that check again. -tests/wire/test_listen_v1_media.py.bak +tests/wire/test_listen_v1_media.py # Manual standalone tests tests/manual @@ -306,7 +295,7 @@ AGENTS.md # file before (fdcce88, ad93815, 4bee463) and would strip them. # [temporarily frozen — unfreeze + re-diff before each regen, then re-apply the # three coverage lines] -.gitignore.bak +.gitignore # Folders to ignore .github diff --git a/.gitignore b/.gitignore index d2e4ca80..765eb109 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ __pycache__/ dist/ poetry.toml +.coverage +htmlcov/ +coverage.xml diff --git a/.gitignore.bak b/.gitignore.bak deleted file mode 100644 index 765eb109..00000000 --- a/.gitignore.bak +++ /dev/null @@ -1,8 +0,0 @@ -.mypy_cache/ -.ruff_cache/ -__pycache__/ -dist/ -poetry.toml -.coverage -htmlcov/ -coverage.xml diff --git a/AGENTS.md b/AGENTS.md index 356dbfb9..b8cf0904 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,7 +62,6 @@ Current temporarily frozen files: - `.gitignore` — Fern generates a baseline version and has regenerated it before (`fdcce88`, `ad93815`, `4bee463`). We hand-add the local coverage artifact ignores (`.coverage`, `htmlcov/`, `coverage.xml`). Before each regen, unfreeze and re-diff so Fern's own additions are picked up, then re-apply the three coverage lines. - `src/deepgram/speak/v1/socket_client.py` — optional message param defaults, broad exception catch - `src/deepgram/speak/v2/socket_client.py` — same (optional `send_flush`/`send_close`/`send_interrupt` defaults, broad exception catch); new websocket TTS client added in the 2026-07-08 regen. `send_interrupt` carries no required payload so it takes the same optional-default treatment as the other control sends; `send_configure` deliberately keeps its required argument (a Configure with no settings is meaningless) -- `src/deepgram/types/listen_v2eot_threshold.py`, `src/deepgram/requests/deepgram_listen_provider_v2.py` — complete the generated `eot_threshold=1.0` description: it suppresses confidence-based detection, while `eot_timeout_ms` still ends idle turns. Unfreeze when the API definition provides that qualifier. - `src/deepgram/types/speak_settings_v1provider.py`, `src/deepgram/types/deepgram.py` — validate Agent TTS `expressivity` as `pydantic.StrictInt` so Pydantic v1 rejects fractional values instead of truncating them before they reach the API. Regression coverage in `tests/custom/test_socket_client_shims.py`. Unfreeze when Fern emits a strict integer. - `src/deepgram/listen/v1/socket_client.py` — same - `src/deepgram/listen/v2/socket_client.py` — same (broad except, optional `send_close_stream` default). As of the 2026-08-11 regen the generator properly types `send_configure(ListenV2Configure)` and puts `ListenV2ConfigureSuccess` in the response Union, so those are taken from the generator; the only `send_configure` patch retained is runtime tolerance for a raw dict (sent verbatim) for back-compat with pre-typed-model callers @@ -72,6 +71,7 @@ Current temporarily frozen files: - `src/deepgram/core/query_encoder.py` — coerces Python bools to lowercase `"true"`/`"false"` before they reach `urllib.parse.urlencode` (which would otherwise produce `"True"`/`"False"` via `str()` and break websocket query strings). Only the four `*/connect()` paths call `urlencode`; HTTP raw clients hand params to httpx, which lowercases bools itself, so the patch is a no-op for the HTTP path. Once Fern's websocket codegen normalizes bools (or the spec types these as `boolean` end-to-end), this can be unfrozen. - `src/deepgram/listen/v2/types/listen_v2connected.py`, `src/deepgram/listen/v2/types/listen_v2turn_info.py`, `src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py`, `src/deepgram/listen/v2/types/listen_v2configure_success.py`, `src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py`, `src/deepgram/listen/v2/types/listen_v2configure_failure.py`, `src/deepgram/listen/v2/types/listen_v2fatal_error.py` — read-side compatibility for the SDK 7.7 Listen V2 response retype. Through 7.6, `V2SocketClientResponse` contained `typing.Any`, so every response was returned as a raw dict; fixing the union made responses typed models and broke callers using `response["field"]`. These generated response classes inherit the hand-written base above, preserving read-only wire-key subscript access alongside canonical attribute access. Restore direct `UncheckedBaseModel` inheritance and unfreeze these files in the next major release. - `src/deepgram/types/deepgram_listen_provider_v2.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py` — behavioural back-compat shim for the `language_hint` -> `language_hints` rename (2026-06-15 regen). The public field was historically (incorrectly) singular and accepted a str or a list; the API field is `language_hints` (a list, and the server uses `deny_unknown_fields` so the singular key is rejected on the wire). Each carries a hand-added `model_validator(mode='before')` / `root_validator(pre=True)` that remaps a legacy `language_hint=` kwarg and drops the dead singular key. `deepgram_listen_provider_v2.py` also corrects the EOT description: `eot_threshold=1.0` suppresses confidence-based detection only, while `eot_timeout_ms` still ends idle turns and must be increased for manual ForceEndTurn control. Remove the EOT patch when the API definition provides it; remove and unfreeze the language alias when it is retired in a future major. +- Fern emits the `eot_threshold=1.0` timeout and ForceEndTurn documentation natively as of the 2026-09-23 regeneration; those two temporary documentation patches were removed. - `src/deepgram/agent/v1/types/agent_v1update_listen_listen.py` — backward-compat patch for the 2026-07-31 `AgentV1UpdateListen` provider retype. The `provider` field changed from a bare `DeepgramListenProviderV2` to the required discriminated union `AgentV1UpdateListenListenProvider` (`_V1`/`_V2`, discriminant `version`). Carries a hand-added `model_validator(mode='before')` / `root_validator(pre=True)` that coerces a legacy `DeepgramListenProviderV1`/`V2` (or a dict lacking the `version` discriminant) into the new shape so existing callers keep working. Remove and unfreeze when the old provider payloads are retired in a future major. NOTE: this patch was silently lost once (it was absent from `.fernignore`, so a regen overwrote it) — keep it frozen. - `tests/wire/test_manage_v1_projects_keys.py` — restored wire coverage for the legacy `CreateKeyV1RequestOneParams` request alias so future regens do not silently drop that compatibility check - `tests/wire/test_manage_v1_projects_requests.py` — restored query-parameter coverage for `manage.v1.projects.requests.list`. The 2026-08-11 regen simplified the upstream spec *example*, and Fern derives the wire test from the example, so all ten optional query params (and the `datetime` → ISO-8601 `Z` encoding) lost their assertions while the client signature still forwarded them. Frozen for the same reason as the `_keys.py` entry above. diff --git a/pyproject.toml b/pyproject.toml index 23729fad..f9103a9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "deepgram-sdk" -version = "7.10.1" +version = "7.10.0" description = "" readme = "README.md" authors = [] diff --git a/src/deepgram/agent/v1/requests/agent_v1settings.py b/src/deepgram/agent/v1/requests/agent_v1settings.py index 2f748dcd..5ada0975 100644 --- a/src/deepgram/agent/v1/requests/agent_v1settings.py +++ b/src/deepgram/agent/v1/requests/agent_v1settings.py @@ -4,6 +4,7 @@ import typing_extensions from .agent_v1settings_agent import AgentV1SettingsAgentParams +from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams from .agent_v1settings_audio import AgentV1SettingsAudioParams from .agent_v1settings_flags import AgentV1SettingsFlagsParams @@ -27,4 +28,4 @@ class AgentV1SettingsParams(typing_extensions.TypedDict): """ audio: AgentV1SettingsAudioParams - agent: AgentV1SettingsAgentParams + agent: typing.Union[AgentV1SettingsAgentParams, AgentV1SettingsAgentContextParams, str] diff --git a/src/deepgram/agent/v1/requests/agent_v1settings.py.bak b/src/deepgram/agent/v1/requests/agent_v1settings.py.bak deleted file mode 100644 index 5ada0975..00000000 --- a/src/deepgram/agent/v1/requests/agent_v1settings.py.bak +++ /dev/null @@ -1,31 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import typing_extensions -from .agent_v1settings_agent import AgentV1SettingsAgentParams -from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams -from .agent_v1settings_audio import AgentV1SettingsAudioParams -from .agent_v1settings_flags import AgentV1SettingsFlagsParams - - -class AgentV1SettingsParams(typing_extensions.TypedDict): - type: typing.Literal["Settings"] - tags: typing_extensions.NotRequired[typing.Sequence[str]] - """ - Tags to associate with the request - """ - - experimental: typing_extensions.NotRequired[bool] - """ - To enable experimental features - """ - - flags: typing_extensions.NotRequired[AgentV1SettingsFlagsParams] - mip_opt_out: typing_extensions.NotRequired[bool] - """ - To opt out of Deepgram Model Improvement Program - """ - - audio: AgentV1SettingsAudioParams - agent: typing.Union[AgentV1SettingsAgentParams, AgentV1SettingsAgentContextParams, str] diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent.py b/src/deepgram/agent/v1/requests/agent_v1settings_agent.py index 0da4b0f3..01bffc21 100644 --- a/src/deepgram/agent/v1/requests/agent_v1settings_agent.py +++ b/src/deepgram/agent/v1/requests/agent_v1settings_agent.py @@ -1,7 +1,27 @@ # This file was auto-generated by Fern from our API Definition. -import typing - +import typing_extensions from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams +from .agent_v1settings_agent_listen import AgentV1SettingsAgentListenParams +from .agent_v1settings_agent_speak import AgentV1SettingsAgentSpeakParams +from .agent_v1settings_agent_think import AgentV1SettingsAgentThinkParams + + +class AgentV1SettingsAgentParams(typing_extensions.TypedDict): + language: typing_extensions.NotRequired[str] + """ + Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. + """ + + context: typing_extensions.NotRequired[AgentV1SettingsAgentContextParams] + """ + Conversation context including the history of messages and function calls + """ -AgentV1SettingsAgentParams = typing.Union[AgentV1SettingsAgentContextParams, str] + listen: typing_extensions.NotRequired[AgentV1SettingsAgentListenParams] + think: typing_extensions.NotRequired[AgentV1SettingsAgentThinkParams] + speak: typing_extensions.NotRequired[AgentV1SettingsAgentSpeakParams] + greeting: typing_extensions.NotRequired[str] + """ + Optional message that agent will speak at the start + """ diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak b/src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak deleted file mode 100644 index 01bffc21..00000000 --- a/src/deepgram/agent/v1/requests/agent_v1settings_agent.py.bak +++ /dev/null @@ -1,27 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing_extensions -from .agent_v1settings_agent_context import AgentV1SettingsAgentContextParams -from .agent_v1settings_agent_listen import AgentV1SettingsAgentListenParams -from .agent_v1settings_agent_speak import AgentV1SettingsAgentSpeakParams -from .agent_v1settings_agent_think import AgentV1SettingsAgentThinkParams - - -class AgentV1SettingsAgentParams(typing_extensions.TypedDict): - language: typing_extensions.NotRequired[str] - """ - Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. - """ - - context: typing_extensions.NotRequired[AgentV1SettingsAgentContextParams] - """ - Conversation context including the history of messages and function calls - """ - - listen: typing_extensions.NotRequired[AgentV1SettingsAgentListenParams] - think: typing_extensions.NotRequired[AgentV1SettingsAgentThinkParams] - speak: typing_extensions.NotRequired[AgentV1SettingsAgentSpeakParams] - greeting: typing_extensions.NotRequired[str] - """ - Optional message that agent will speak at the start - """ diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py index c52f05af..b399ef99 100644 --- a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py +++ b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py @@ -1,8 +1,11 @@ # This file was auto-generated by Fern from our API Definition. +import typing + import typing_extensions from .agent_v1settings_agent_context_context import AgentV1SettingsAgentContextContextParams from .agent_v1settings_agent_context_listen import AgentV1SettingsAgentContextListenParams +from .agent_v1settings_agent_context_messages_item import AgentV1SettingsAgentContextMessagesItemParams from .agent_v1settings_agent_context_speak import AgentV1SettingsAgentContextSpeakParams from .agent_v1settings_agent_context_think import AgentV1SettingsAgentContextThinkParams @@ -13,6 +16,11 @@ class AgentV1SettingsAgentContextParams(typing_extensions.TypedDict): Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. """ + messages: typing_extensions.NotRequired[typing.Sequence[AgentV1SettingsAgentContextMessagesItemParams]] + """ + Deprecated. Conversation history as a list of messages and function calls. + """ + context: typing_extensions.NotRequired[AgentV1SettingsAgentContextContextParams] """ Conversation context including the history of messages and function calls diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak deleted file mode 100644 index b399ef99..00000000 --- a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py.bak +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import typing_extensions -from .agent_v1settings_agent_context_context import AgentV1SettingsAgentContextContextParams -from .agent_v1settings_agent_context_listen import AgentV1SettingsAgentContextListenParams -from .agent_v1settings_agent_context_messages_item import AgentV1SettingsAgentContextMessagesItemParams -from .agent_v1settings_agent_context_speak import AgentV1SettingsAgentContextSpeakParams -from .agent_v1settings_agent_context_think import AgentV1SettingsAgentContextThinkParams - - -class AgentV1SettingsAgentContextParams(typing_extensions.TypedDict): - language: typing_extensions.NotRequired[str] - """ - Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. - """ - - messages: typing_extensions.NotRequired[typing.Sequence[AgentV1SettingsAgentContextMessagesItemParams]] - """ - Deprecated. Conversation history as a list of messages and function calls. - """ - - context: typing_extensions.NotRequired[AgentV1SettingsAgentContextContextParams] - """ - Conversation context including the history of messages and function calls - """ - - listen: typing_extensions.NotRequired[AgentV1SettingsAgentContextListenParams] - think: typing_extensions.NotRequired[AgentV1SettingsAgentContextThinkParams] - speak: typing_extensions.NotRequired[AgentV1SettingsAgentContextSpeakParams] - greeting: typing_extensions.NotRequired[str] - """ - Optional message that agent will speak at the start - """ diff --git a/src/deepgram/agent/v1/socket_client.py b/src/deepgram/agent/v1/socket_client.py index 20d1c3b2..98076e89 100644 --- a/src/deepgram/agent/v1/socket_client.py +++ b/src/deepgram/agent/v1/socket_client.py @@ -3,9 +3,7 @@ import json import logging import typing -from json.decoder import JSONDecodeError -import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -44,6 +42,17 @@ except ImportError: from websockets import WebSocketClientProtocol # type: ignore + +def _sanitize_numeric_types(obj: typing.Any) -> typing.Any: + """Convert whole-number floats to integers for wire-compatible JSON. See: internal-api-specs/issues/205.""" + if isinstance(obj, dict): + return {key: _sanitize_numeric_types(value) for key, value in obj.items()} + if isinstance(obj, list): + return [_sanitize_numeric_types(item) for item in obj] + if isinstance(obj, float) and obj.is_integer(): + return int(obj) + return obj + _logger = logging.getLogger(__name__) V1SocketClientResponse = typing.Union[ AgentV1ListenUpdated, @@ -112,7 +121,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -166,12 +175,12 @@ async def send_function_call_response(self, message: AgentV1SendFunctionCallResp """ await self._send_model(message) - async def send_keep_alive(self, message: AgentV1KeepAlive) -> None: + async def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a AgentV1KeepAlive. """ - await self._send_model(message) + await self._send_model(message or AgentV1KeepAlive(type="KeepAlive")) async def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ @@ -180,12 +189,12 @@ async def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ await self._send_model(message) - async def send_force_end_turn(self, message: AgentV1ForceEndTurn) -> None: + async def send_force_end_turn(self, message: typing.Optional[AgentV1ForceEndTurn] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a AgentV1ForceEndTurn. """ - await self._send_model(message) + await self._send_model(message or AgentV1ForceEndTurn(type="ForceEndTurn")) async def send_media(self, message: bytes) -> None: """ @@ -220,7 +229,7 @@ async def _send_model(self, data: typing.Any) -> None: """ Send a Pydantic model to the websocket connection. """ - await self._send(data.dict()) + await self._send(_sanitize_numeric_types(data.dict())) class V1SocketClient(EventEmitterMixin): @@ -266,7 +275,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -320,12 +329,12 @@ def send_function_call_response(self, message: AgentV1SendFunctionCallResponse) """ self._send_model(message) - def send_keep_alive(self, message: AgentV1KeepAlive) -> None: + def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a AgentV1KeepAlive. """ - self._send_model(message) + self._send_model(message or AgentV1KeepAlive(type="KeepAlive")) def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ @@ -334,12 +343,12 @@ def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ self._send_model(message) - def send_force_end_turn(self, message: AgentV1ForceEndTurn) -> None: + def send_force_end_turn(self, message: typing.Optional[AgentV1ForceEndTurn] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a AgentV1ForceEndTurn. """ - self._send_model(message) + self._send_model(message or AgentV1ForceEndTurn(type="ForceEndTurn")) def send_media(self, message: bytes) -> None: """ @@ -374,4 +383,4 @@ def _send_model(self, data: typing.Any) -> None: """ Send a Pydantic model to the websocket connection. """ - self._send(data.dict()) + self._send(_sanitize_numeric_types(data.dict())) diff --git a/src/deepgram/agent/v1/socket_client.py.bak b/src/deepgram/agent/v1/socket_client.py.bak deleted file mode 100644 index 6d504d3e..00000000 --- a/src/deepgram/agent/v1/socket_client.py.bak +++ /dev/null @@ -1,387 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import json -import logging -import typing - -import websockets.sync.connection as websockets_sync_connection -from ...core.events import EventEmitterMixin, EventType -from ...core.unchecked_base_model import construct_type -from .types.agent_v1agent_audio_done import AgentV1AgentAudioDone -from .types.agent_v1agent_started_speaking import AgentV1AgentStartedSpeaking -from .types.agent_v1agent_thinking import AgentV1AgentThinking -from .types.agent_v1conversation_text import AgentV1ConversationText -from .types.agent_v1error import AgentV1Error -from .types.agent_v1force_end_turn import AgentV1ForceEndTurn -from .types.agent_v1function_call_cancelled import AgentV1FunctionCallCancelled -from .types.agent_v1function_call_request import AgentV1FunctionCallRequest -from .types.agent_v1history import AgentV1History -from .types.agent_v1inject_agent_message import AgentV1InjectAgentMessage -from .types.agent_v1inject_user_message import AgentV1InjectUserMessage -from .types.agent_v1injection_refused import AgentV1InjectionRefused -from .types.agent_v1keep_alive import AgentV1KeepAlive -from .types.agent_v1latency_report import AgentV1LatencyReport -from .types.agent_v1listen_updated import AgentV1ListenUpdated -from .types.agent_v1prompt_updated import AgentV1PromptUpdated -from .types.agent_v1receive_function_call_response import AgentV1ReceiveFunctionCallResponse -from .types.agent_v1send_function_call_response import AgentV1SendFunctionCallResponse -from .types.agent_v1settings import AgentV1Settings -from .types.agent_v1settings_applied import AgentV1SettingsApplied -from .types.agent_v1speak_updated import AgentV1SpeakUpdated -from .types.agent_v1think_updated import AgentV1ThinkUpdated -from .types.agent_v1update_listen import AgentV1UpdateListen -from .types.agent_v1update_prompt import AgentV1UpdatePrompt -from .types.agent_v1update_speak import AgentV1UpdateSpeak -from .types.agent_v1update_think import AgentV1UpdateThink -from .types.agent_v1user_started_speaking import AgentV1UserStartedSpeaking -from .types.agent_v1warning import AgentV1Warning -from .types.agent_v1welcome import AgentV1Welcome - -try: - from websockets.legacy.client import WebSocketClientProtocol # type: ignore -except ImportError: - from websockets import WebSocketClientProtocol # type: ignore - - -def _sanitize_numeric_types(obj: typing.Any) -> typing.Any: - """Convert whole-number floats to integers for wire-compatible JSON. See: internal-api-specs/issues/205.""" - if isinstance(obj, dict): - return {key: _sanitize_numeric_types(value) for key, value in obj.items()} - if isinstance(obj, list): - return [_sanitize_numeric_types(item) for item in obj] - if isinstance(obj, float) and obj.is_integer(): - return int(obj) - return obj - - -_logger = logging.getLogger(__name__) -V1SocketClientResponse = typing.Union[ - AgentV1ListenUpdated, - AgentV1ThinkUpdated, - AgentV1ReceiveFunctionCallResponse, - AgentV1PromptUpdated, - AgentV1SpeakUpdated, - AgentV1InjectionRefused, - AgentV1Welcome, - AgentV1SettingsApplied, - AgentV1ConversationText, - AgentV1UserStartedSpeaking, - AgentV1AgentThinking, - AgentV1LatencyReport, - AgentV1FunctionCallRequest, - AgentV1FunctionCallCancelled, - AgentV1AgentStartedSpeaking, - AgentV1AgentAudioDone, - AgentV1Error, - AgentV1Warning, - AgentV1History, - bytes, -] - - -class AsyncV1SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: WebSocketClientProtocol): - super().__init__() - self._websocket = websocket - - async def __aiter__(self): - async for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - async def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - await self._emit_async(EventType.OPEN, None) - try: - async for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: - await self._emit_async(EventType.ERROR, exc) - finally: - await self._emit_async(EventType.CLOSE, None) - - async def send_settings(self, message: AgentV1Settings) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1Settings. - """ - await self._send_model(message) - - async def send_update_listen(self, message: AgentV1UpdateListen) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdateListen. - """ - await self._send_model(message) - - async def send_update_think(self, message: AgentV1UpdateThink) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdateThink. - """ - await self._send_model(message) - - async def send_update_speak(self, message: AgentV1UpdateSpeak) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdateSpeak. - """ - await self._send_model(message) - - async def send_inject_user_message(self, message: AgentV1InjectUserMessage) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1InjectUserMessage. - """ - await self._send_model(message) - - async def send_inject_agent_message(self, message: AgentV1InjectAgentMessage) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1InjectAgentMessage. - """ - await self._send_model(message) - - async def send_function_call_response(self, message: AgentV1SendFunctionCallResponse) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1SendFunctionCallResponse. - """ - await self._send_model(message) - - async def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1KeepAlive. - """ - await self._send_model(message or AgentV1KeepAlive(type="KeepAlive")) - - async def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdatePrompt. - """ - await self._send_model(message) - - async def send_force_end_turn(self, message: typing.Optional[AgentV1ForceEndTurn] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1ForceEndTurn. - """ - await self._send_model(message or AgentV1ForceEndTurn(type="ForceEndTurn")) - - async def send_media(self, message: bytes) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a bytes. - """ - await self._send(message) - - async def recv(self) -> V1SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = await self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - async def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - await self._websocket.send(data) - - async def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - await self._send(_sanitize_numeric_types(data.dict())) - - -class V1SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: websockets_sync_connection.Connection): - super().__init__() - self._websocket = websocket - - def __iter__(self): - for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - self._emit(EventType.OPEN, None) - try: - for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - self._emit(EventType.MESSAGE, parsed) - except Exception as exc: - self._emit(EventType.ERROR, exc) - finally: - self._emit(EventType.CLOSE, None) - - def send_settings(self, message: AgentV1Settings) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1Settings. - """ - self._send_model(message) - - def send_update_listen(self, message: AgentV1UpdateListen) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdateListen. - """ - self._send_model(message) - - def send_update_think(self, message: AgentV1UpdateThink) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdateThink. - """ - self._send_model(message) - - def send_update_speak(self, message: AgentV1UpdateSpeak) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdateSpeak. - """ - self._send_model(message) - - def send_inject_user_message(self, message: AgentV1InjectUserMessage) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1InjectUserMessage. - """ - self._send_model(message) - - def send_inject_agent_message(self, message: AgentV1InjectAgentMessage) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1InjectAgentMessage. - """ - self._send_model(message) - - def send_function_call_response(self, message: AgentV1SendFunctionCallResponse) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1SendFunctionCallResponse. - """ - self._send_model(message) - - def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1KeepAlive. - """ - self._send_model(message or AgentV1KeepAlive(type="KeepAlive")) - - def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdatePrompt. - """ - self._send_model(message) - - def send_force_end_turn(self, message: typing.Optional[AgentV1ForceEndTurn] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1ForceEndTurn. - """ - self._send_model(message or AgentV1ForceEndTurn(type="ForceEndTurn")) - - def send_media(self, message: bytes) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a bytes. - """ - self._send(message) - - def recv(self) -> V1SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - self._websocket.send(data) - - def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - self._send(_sanitize_numeric_types(data.dict())) diff --git a/src/deepgram/agent/v1/types/agent_v1settings.py b/src/deepgram/agent/v1/types/agent_v1settings.py index 11601dfc..ff58da13 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings.py +++ b/src/deepgram/agent/v1/types/agent_v1settings.py @@ -6,6 +6,7 @@ from ....core.pydantic_utilities import IS_PYDANTIC_V2 from ....core.unchecked_base_model import UncheckedBaseModel from .agent_v1settings_agent import AgentV1SettingsAgent +from .agent_v1settings_agent_context import AgentV1SettingsAgentContext from .agent_v1settings_audio import AgentV1SettingsAudio from .agent_v1settings_flags import AgentV1SettingsFlags @@ -29,7 +30,7 @@ class AgentV1Settings(UncheckedBaseModel): """ audio: AgentV1SettingsAudio - agent: AgentV1SettingsAgent + agent: typing.Union[AgentV1SettingsAgent, AgentV1SettingsAgentContext, str] if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/deepgram/agent/v1/types/agent_v1settings.py.bak b/src/deepgram/agent/v1/types/agent_v1settings.py.bak deleted file mode 100644 index ff58da13..00000000 --- a/src/deepgram/agent/v1/types/agent_v1settings.py.bak +++ /dev/null @@ -1,42 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel -from .agent_v1settings_agent import AgentV1SettingsAgent -from .agent_v1settings_agent_context import AgentV1SettingsAgentContext -from .agent_v1settings_audio import AgentV1SettingsAudio -from .agent_v1settings_flags import AgentV1SettingsFlags - - -class AgentV1Settings(UncheckedBaseModel): - type: typing.Literal["Settings"] = "Settings" - tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) - """ - Tags to associate with the request - """ - - experimental: typing.Optional[bool] = pydantic.Field(default=None) - """ - To enable experimental features - """ - - flags: typing.Optional[AgentV1SettingsFlags] = None - mip_opt_out: typing.Optional[bool] = pydantic.Field(default=None) - """ - To opt out of Deepgram Model Improvement Program - """ - - audio: AgentV1SettingsAudio - agent: typing.Union[AgentV1SettingsAgent, AgentV1SettingsAgentContext, str] - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent.py b/src/deepgram/agent/v1/types/agent_v1settings_agent.py index c1483c35..fae21e75 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent.py @@ -2,6 +2,61 @@ import typing +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 from .agent_v1settings_agent_context import AgentV1SettingsAgentContext +from .agent_v1settings_agent_listen import AgentV1SettingsAgentListen -AgentV1SettingsAgent = typing.Union[AgentV1SettingsAgentContext, str] + +class AgentV1SettingsAgent(AgentV1SettingsAgentContext): + # Keep the formerly callable public model name working with the schema's + # current nested context format. + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_legacy_nested_context(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, dict): + return values + + context = values.get("context") + if isinstance(context, AgentV1SettingsAgentContext) and ( + context.context is not None + and context.language is None + and context.listen is None + and context.think is None + and context.speak is None + and context.greeting is None + ): + values = dict(values) + values["context"] = context.context + + listen = values.get("listen") + if isinstance(listen, AgentV1SettingsAgentListen): + values = dict(values) + values["listen"] = listen.dict() + return values + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_legacy_nested_context(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if not isinstance(values, dict): + return values + + context = values.get("context") + if isinstance(context, AgentV1SettingsAgentContext) and ( + context.context is not None + and context.language is None + and context.listen is None + and context.think is None + and context.speak is None + and context.greeting is None + ): + values = dict(values) + values["context"] = context.context + + listen = values.get("listen") + if isinstance(listen, AgentV1SettingsAgentListen): + values = dict(values) + values["listen"] = listen.dict() + return values diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak b/src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak deleted file mode 100644 index fae21e75..00000000 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent.py.bak +++ /dev/null @@ -1,62 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from .agent_v1settings_agent_context import AgentV1SettingsAgentContext -from .agent_v1settings_agent_listen import AgentV1SettingsAgentListen - - -class AgentV1SettingsAgent(AgentV1SettingsAgentContext): - # Keep the formerly callable public model name working with the schema's - # current nested context format. - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_legacy_nested_context(cls, values: typing.Any) -> typing.Any: - if not isinstance(values, dict): - return values - - context = values.get("context") - if isinstance(context, AgentV1SettingsAgentContext) and ( - context.context is not None - and context.language is None - and context.listen is None - and context.think is None - and context.speak is None - and context.greeting is None - ): - values = dict(values) - values["context"] = context.context - - listen = values.get("listen") - if isinstance(listen, AgentV1SettingsAgentListen): - values = dict(values) - values["listen"] = listen.dict() - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_legacy_nested_context(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if not isinstance(values, dict): - return values - - context = values.get("context") - if isinstance(context, AgentV1SettingsAgentContext) and ( - context.context is not None - and context.language is None - and context.listen is None - and context.think is None - and context.speak is None - and context.greeting is None - ): - values = dict(values) - values["context"] = context.context - - listen = values.get("listen") - if isinstance(listen, AgentV1SettingsAgentListen): - values = dict(values) - values["listen"] = listen.dict() - return values diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py b/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py index fcbea2f3..827aad63 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py @@ -7,6 +7,7 @@ from ....core.unchecked_base_model import UncheckedBaseModel from .agent_v1settings_agent_context_context import AgentV1SettingsAgentContextContext from .agent_v1settings_agent_context_listen import AgentV1SettingsAgentContextListen +from .agent_v1settings_agent_context_messages_item import AgentV1SettingsAgentContextMessagesItem from .agent_v1settings_agent_context_speak import AgentV1SettingsAgentContextSpeak from .agent_v1settings_agent_context_think import AgentV1SettingsAgentContextThink @@ -30,6 +31,30 @@ class AgentV1SettingsAgentContext(UncheckedBaseModel): Optional message that agent will speak at the start """ + # Translate the pre-restructure top-level messages field to the current + # nested context.messages payload while retaining read-side access. + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_legacy_messages_to_context(cls, values: typing.Any) -> typing.Any: + if isinstance(values, dict) and "messages" in values and "context" not in values: + values = dict(values) + values["context"] = {"messages": values.pop("messages")} + return values + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_legacy_messages_to_context(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if isinstance(values, dict) and "messages" in values and "context" not in values: + values = dict(values) + values["context"] = {"messages": values.pop("messages")} + return values + + @property + def messages(self) -> typing.Optional[typing.List[AgentV1SettingsAgentContextMessagesItem]]: + return None if self.context is None else self.context.messages + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak b/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak deleted file mode 100644 index 827aad63..00000000 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_context.py.bak +++ /dev/null @@ -1,65 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel -from .agent_v1settings_agent_context_context import AgentV1SettingsAgentContextContext -from .agent_v1settings_agent_context_listen import AgentV1SettingsAgentContextListen -from .agent_v1settings_agent_context_messages_item import AgentV1SettingsAgentContextMessagesItem -from .agent_v1settings_agent_context_speak import AgentV1SettingsAgentContextSpeak -from .agent_v1settings_agent_context_think import AgentV1SettingsAgentContextThink - - -class AgentV1SettingsAgentContext(UncheckedBaseModel): - language: typing.Optional[str] = pydantic.Field(default=None) - """ - Deprecated. Use `listen.provider.language` and `speak.provider.language` fields instead. - """ - - context: typing.Optional[AgentV1SettingsAgentContextContext] = pydantic.Field(default=None) - """ - Conversation context including the history of messages and function calls - """ - - listen: typing.Optional[AgentV1SettingsAgentContextListen] = None - think: typing.Optional[AgentV1SettingsAgentContextThink] = None - speak: typing.Optional[AgentV1SettingsAgentContextSpeak] = None - greeting: typing.Optional[str] = pydantic.Field(default=None) - """ - Optional message that agent will speak at the start - """ - - # Translate the pre-restructure top-level messages field to the current - # nested context.messages payload while retaining read-side access. - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_legacy_messages_to_context(cls, values: typing.Any) -> typing.Any: - if isinstance(values, dict) and "messages" in values and "context" not in values: - values = dict(values) - values["context"] = {"messages": values.pop("messages")} - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_legacy_messages_to_context(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if isinstance(values, dict) and "messages" in values and "context" not in values: - values = dict(values) - values["context"] = {"messages": values.pop("messages")} - return values - - @property - def messages(self) -> typing.Optional[typing.List[AgentV1SettingsAgentContextMessagesItem]]: - return None if self.context is None else self.context.messages - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py index b806f202..222c18a3 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py @@ -33,15 +33,38 @@ class AgentV1SettingsAgentContextListenProvider_V2(UncheckedBaseModel): type: typing.Literal["deepgram"] = "deepgram" model: str language_hints: typing.Optional[typing.List[str]] = None + language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) eot_threshold: typing.Optional[float] = None eager_eot_threshold: typing.Optional[float] = None eot_timeout_ms: typing.Optional[int] = None keyterms: typing.Optional[typing.List[str]] = None if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + class Config: frozen = True smart_union = True diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak deleted file mode 100644 index a13d2a63..00000000 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py.bak +++ /dev/null @@ -1,79 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import typing - -import pydantic -import typing_extensions -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel, UnionMetadata - - -class AgentV1SettingsAgentContextListenProvider_V1(UncheckedBaseModel): - version: typing.Literal["v1"] = "v1" - type: typing.Literal["deepgram"] = "deepgram" - model: typing.Optional[str] = None - language: typing.Optional[str] = None - keyterms: typing.Optional[typing.List[str]] = None - smart_format: typing.Optional[bool] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -class AgentV1SettingsAgentContextListenProvider_V2(UncheckedBaseModel): - version: typing.Literal["v2"] = "v2" - type: typing.Literal["deepgram"] = "deepgram" - model: str - language_hints: typing.Optional[typing.List[str]] = None - language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) - eot_threshold: typing.Optional[float] = None - eager_eot_threshold: typing.Optional[float] = None - eot_timeout_ms: typing.Optional[int] = None - keyterms: typing.Optional[typing.List[str]] = None - - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -AgentV1SettingsAgentContextListenProvider = typing_extensions.Annotated[ - typing.Union[AgentV1SettingsAgentContextListenProvider_V1, AgentV1SettingsAgentContextListenProvider_V2], - UnionMetadata(discriminant="version"), -] diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py index d053b2b9..d225e261 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py @@ -33,15 +33,38 @@ class AgentV1SettingsAgentListenProvider_V2(UncheckedBaseModel): type: typing.Literal["deepgram"] = "deepgram" model: str language_hints: typing.Optional[typing.List[str]] = None + language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) eot_threshold: typing.Optional[float] = None eager_eot_threshold: typing.Optional[float] = None eot_timeout_ms: typing.Optional[int] = None keyterms: typing.Optional[typing.List[str]] = None if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + class Config: frozen = True smart_union = True diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak deleted file mode 100644 index 40706b47..00000000 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py.bak +++ /dev/null @@ -1,79 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import typing - -import pydantic -import typing_extensions -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel, UnionMetadata - - -class AgentV1SettingsAgentListenProvider_V1(UncheckedBaseModel): - version: typing.Literal["v1"] = "v1" - type: typing.Literal["deepgram"] = "deepgram" - model: typing.Optional[str] = None - language: typing.Optional[str] = None - keyterms: typing.Optional[typing.List[str]] = None - smart_format: typing.Optional[bool] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -class AgentV1SettingsAgentListenProvider_V2(UncheckedBaseModel): - version: typing.Literal["v2"] = "v2" - type: typing.Literal["deepgram"] = "deepgram" - model: str - language_hints: typing.Optional[typing.List[str]] = None - language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) - eot_threshold: typing.Optional[float] = None - eager_eot_threshold: typing.Optional[float] = None - eot_timeout_ms: typing.Optional[int] = None - keyterms: typing.Optional[typing.List[str]] = None - - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -AgentV1SettingsAgentListenProvider = typing_extensions.Annotated[ - typing.Union[AgentV1SettingsAgentListenProvider_V1, AgentV1SettingsAgentListenProvider_V2], - UnionMetadata(discriminant="version"), -] diff --git a/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py index f345904c..57f0751a 100644 --- a/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py +++ b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py @@ -5,8 +5,35 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 from ....core.unchecked_base_model import UncheckedBaseModel +from ....types.deepgram_listen_provider_v1 import DeepgramListenProviderV1 +from ....types.deepgram_listen_provider_v2 import DeepgramListenProviderV2 from .agent_v1update_listen_listen_provider import AgentV1UpdateListenListenProvider +_V1_ONLY_KEYS = frozenset({"language", "smart_format"}) + + +def _coerce_legacy_update_listen_provider(values: typing.Any) -> typing.Any: + """Add the discriminator required by the versioned provider union.""" + if not isinstance(values, dict): + return values + provider = values.get("provider") + if isinstance(provider, DeepgramListenProviderV2): + values = dict(values) + values["provider"] = {**provider.dict(), "version": "v2"} + elif isinstance(provider, DeepgramListenProviderV1): + values = dict(values) + values["provider"] = {**provider.dict(), "version": "v1"} + elif isinstance(provider, dict) and "version" not in provider: + values = dict(values) + version = "v1" if _V1_ONLY_KEYS & provider.keys() else "v2" + provider_cls = DeepgramListenProviderV1 if version == "v1" else DeepgramListenProviderV2 + try: + coerced = provider_cls(**provider).dict() + except Exception: + coerced = dict(provider) + values["provider"] = {**coerced, "version": version} + return values + class AgentV1UpdateListenListen(UncheckedBaseModel): """ @@ -16,9 +43,19 @@ class AgentV1UpdateListenListen(UncheckedBaseModel): provider: AgentV1UpdateListenListenProvider if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_legacy_provider(cls, values: typing.Any) -> typing.Any: + return _coerce_legacy_update_listen_provider(values) + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_legacy_provider(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + return _coerce_legacy_update_listen_provider(values) + class Config: frozen = True smart_union = True diff --git a/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak deleted file mode 100644 index d6ad435a..00000000 --- a/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py.bak +++ /dev/null @@ -1,64 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel -from ....types.deepgram_listen_provider_v1 import DeepgramListenProviderV1 -from ....types.deepgram_listen_provider_v2 import DeepgramListenProviderV2 -from .agent_v1update_listen_listen_provider import AgentV1UpdateListenListenProvider - -_V1_ONLY_KEYS = frozenset({"language", "smart_format"}) - - -def _coerce_legacy_update_listen_provider(values: typing.Any) -> typing.Any: - """Add the discriminator required by the versioned provider union.""" - if not isinstance(values, dict): - return values - provider = values.get("provider") - if isinstance(provider, DeepgramListenProviderV2): - values = dict(values) - values["provider"] = {**provider.dict(), "version": "v2"} - elif isinstance(provider, DeepgramListenProviderV1): - values = dict(values) - values["provider"] = {**provider.dict(), "version": "v1"} - elif isinstance(provider, dict) and "version" not in provider: - values = dict(values) - version = "v1" if _V1_ONLY_KEYS & provider.keys() else "v2" - provider_cls = DeepgramListenProviderV1 if version == "v1" else DeepgramListenProviderV2 - try: - coerced = provider_cls(**provider).dict() - except Exception: - coerced = dict(provider) - values["provider"] = {**coerced, "version": version} - return values - - -class AgentV1UpdateListenListen(UncheckedBaseModel): - """ - Listen configuration to update. Contains a provider object with the same schema as Settings. The model and language can be changed mid-session. Keyterms can only be updated mid-session for Flux models. - """ - - provider: AgentV1UpdateListenListenProvider - - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_legacy_provider(cls, values: typing.Any) -> typing.Any: - return _coerce_legacy_update_listen_provider(values) - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_legacy_provider(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - return _coerce_legacy_update_listen_provider(values) - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/core/api_error.py b/src/deepgram/core/api_error.py index 6f850a60..2aa93914 100644 --- a/src/deepgram/core/api_error.py +++ b/src/deepgram/core/api_error.py @@ -2,6 +2,8 @@ from typing import Any, Dict, Optional +from .._secure_logging import redact_sensitive_headers + class ApiError(Exception): headers: Optional[Dict[str, str]] @@ -15,7 +17,7 @@ def __init__( status_code: Optional[int] = None, body: Any = None, ) -> None: - self.headers = headers + self.headers = redact_sensitive_headers(headers) self.status_code = status_code self.body = body diff --git a/src/deepgram/core/api_error.py.bak b/src/deepgram/core/api_error.py.bak deleted file mode 100644 index 2aa93914..00000000 --- a/src/deepgram/core/api_error.py.bak +++ /dev/null @@ -1,25 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, Optional - -from .._secure_logging import redact_sensitive_headers - - -class ApiError(Exception): - headers: Optional[Dict[str, str]] - status_code: Optional[int] - body: Any - - def __init__( - self, - *, - headers: Optional[Dict[str, str]] = None, - status_code: Optional[int] = None, - body: Any = None, - ) -> None: - self.headers = redact_sensitive_headers(headers) - self.status_code = status_code - self.body = body - - def __str__(self) -> str: - return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/src/deepgram/core/parse_error.py b/src/deepgram/core/parse_error.py index 4527c6a8..c510c0e8 100644 --- a/src/deepgram/core/parse_error.py +++ b/src/deepgram/core/parse_error.py @@ -2,6 +2,8 @@ from typing import Any, Dict, Optional +from .._secure_logging import redact_sensitive_headers + class ParsingError(Exception): """ @@ -23,7 +25,7 @@ def __init__( body: Any = None, cause: Optional[Exception] = None, ) -> None: - self.headers = headers + self.headers = redact_sensitive_headers(headers) self.status_code = status_code self.body = body self.cause = cause diff --git a/src/deepgram/core/parse_error.py.bak b/src/deepgram/core/parse_error.py.bak deleted file mode 100644 index c510c0e8..00000000 --- a/src/deepgram/core/parse_error.py.bak +++ /dev/null @@ -1,38 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, Optional - -from .._secure_logging import redact_sensitive_headers - - -class ParsingError(Exception): - """ - Raised when the SDK fails to parse/validate a response from the server. - This typically indicates that the server returned a response whose shape - does not match the expected schema. - """ - - headers: Optional[Dict[str, str]] - status_code: Optional[int] - body: Any - cause: Optional[Exception] - - def __init__( - self, - *, - headers: Optional[Dict[str, str]] = None, - status_code: Optional[int] = None, - body: Any = None, - cause: Optional[Exception] = None, - ) -> None: - self.headers = redact_sensitive_headers(headers) - self.status_code = status_code - self.body = body - self.cause = cause - super().__init__() - if cause is not None: - self.__cause__ = cause - - def __str__(self) -> str: - cause_str = f", cause: {self.cause}" if self.cause is not None else "" - return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}" diff --git a/src/deepgram/core/query_encoder.py b/src/deepgram/core/query_encoder.py index 3183001d..58088258 100644 --- a/src/deepgram/core/query_encoder.py +++ b/src/deepgram/core/query_encoder.py @@ -5,6 +5,13 @@ import pydantic +def _coerce_query_value(value: Any) -> Any: + # urllib.parse.urlencode would otherwise turn bools into "True"/"False". + if isinstance(value, bool): + return "true" if value else "false" + return value + + # Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: result = [] @@ -17,9 +24,9 @@ def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = N if isinstance(arr_v, dict): result.extend(traverse_query_dict(arr_v, key)) else: - result.append((key, arr_v)) + result.append((key, _coerce_query_value(arr_v))) else: - result.append((key, v)) + result.append((key, _coerce_query_value(v))) return result @@ -41,11 +48,11 @@ def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, An encoded_values.extend(single_query_encoder(query_key, obj_dict)) else: - encoded_values.append((query_key, value)) + encoded_values.append((query_key, _coerce_query_value(value))) return encoded_values - return [(query_key, query_value)] + return [(query_key, _coerce_query_value(query_value))] def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: diff --git a/src/deepgram/core/query_encoder.py.bak b/src/deepgram/core/query_encoder.py.bak deleted file mode 100644 index 58088258..00000000 --- a/src/deepgram/core/query_encoder.py.bak +++ /dev/null @@ -1,65 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from typing import Any, Dict, List, Optional, Tuple - -import pydantic - - -def _coerce_query_value(value: Any) -> Any: - # urllib.parse.urlencode would otherwise turn bools into "True"/"False". - if isinstance(value, bool): - return "true" if value else "false" - return value - - -# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict -def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: - result = [] - for k, v in dict_flat.items(): - key = f"{key_prefix}[{k}]" if key_prefix is not None else k - if isinstance(v, dict): - result.extend(traverse_query_dict(v, key)) - elif isinstance(v, list): - for arr_v in v: - if isinstance(arr_v, dict): - result.extend(traverse_query_dict(arr_v, key)) - else: - result.append((key, _coerce_query_value(arr_v))) - else: - result.append((key, _coerce_query_value(v))) - return result - - -def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: - if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): - if isinstance(query_value, pydantic.BaseModel): - obj_dict = query_value.dict(by_alias=True) - else: - obj_dict = query_value - return traverse_query_dict(obj_dict, query_key) - elif isinstance(query_value, list): - encoded_values: List[Tuple[str, Any]] = [] - for value in query_value: - if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): - if isinstance(value, pydantic.BaseModel): - obj_dict = value.dict(by_alias=True) - elif isinstance(value, dict): - obj_dict = value - - encoded_values.extend(single_query_encoder(query_key, obj_dict)) - else: - encoded_values.append((query_key, _coerce_query_value(value))) - - return encoded_values - - return [(query_key, _coerce_query_value(query_value))] - - -def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: - if query is None: - return None - - encoded_query = [] - for k, v in query.items(): - encoded_query.extend(single_query_encoder(k, v)) - return encoded_query diff --git a/src/deepgram/listen/v1/socket_client.py b/src/deepgram/listen/v1/socket_client.py index cffa82b7..5954d116 100644 --- a/src/deepgram/listen/v1/socket_client.py +++ b/src/deepgram/listen/v1/socket_client.py @@ -3,9 +3,7 @@ import json import logging import typing -from json.decoder import JSONDecodeError -import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -69,7 +67,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -81,26 +79,26 @@ async def send_media(self, message: bytes) -> None: """ await self._send(message) - async def send_finalize(self, message: ListenV1Finalize) -> None: + async def send_finalize(self, message: typing.Optional[ListenV1Finalize] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1Finalize. """ - await self._send_model(message) + await self._send_model(message or ListenV1Finalize(type="Finalize")) - async def send_close_stream(self, message: ListenV1CloseStream) -> None: + async def send_close_stream(self, message: typing.Optional[ListenV1CloseStream] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1CloseStream. """ - await self._send_model(message) + await self._send_model(message or ListenV1CloseStream(type="CloseStream")) - async def send_keep_alive(self, message: ListenV1KeepAlive) -> None: + async def send_keep_alive(self, message: typing.Optional[ListenV1KeepAlive] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1KeepAlive. """ - await self._send_model(message) + await self._send_model(message or ListenV1KeepAlive(type="KeepAlive")) async def recv(self) -> V1SocketClientResponse: """ @@ -174,7 +172,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -186,26 +184,26 @@ def send_media(self, message: bytes) -> None: """ self._send(message) - def send_finalize(self, message: ListenV1Finalize) -> None: + def send_finalize(self, message: typing.Optional[ListenV1Finalize] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1Finalize. """ - self._send_model(message) + self._send_model(message or ListenV1Finalize(type="Finalize")) - def send_close_stream(self, message: ListenV1CloseStream) -> None: + def send_close_stream(self, message: typing.Optional[ListenV1CloseStream] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1CloseStream. """ - self._send_model(message) + self._send_model(message or ListenV1CloseStream(type="CloseStream")) - def send_keep_alive(self, message: ListenV1KeepAlive) -> None: + def send_keep_alive(self, message: typing.Optional[ListenV1KeepAlive] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV1KeepAlive. """ - self._send_model(message) + self._send_model(message or ListenV1KeepAlive(type="KeepAlive")) def recv(self) -> V1SocketClientResponse: """ diff --git a/src/deepgram/listen/v1/socket_client.py.bak b/src/deepgram/listen/v1/socket_client.py.bak deleted file mode 100644 index 5954d116..00000000 --- a/src/deepgram/listen/v1/socket_client.py.bak +++ /dev/null @@ -1,234 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import json -import logging -import typing - -import websockets.sync.connection as websockets_sync_connection -from ...core.events import EventEmitterMixin, EventType -from ...core.unchecked_base_model import construct_type -from .types.listen_v1close_stream import ListenV1CloseStream -from .types.listen_v1finalize import ListenV1Finalize -from .types.listen_v1keep_alive import ListenV1KeepAlive -from .types.listen_v1metadata import ListenV1Metadata -from .types.listen_v1results import ListenV1Results -from .types.listen_v1speech_started import ListenV1SpeechStarted -from .types.listen_v1utterance_end import ListenV1UtteranceEnd - -try: - from websockets.legacy.client import WebSocketClientProtocol # type: ignore -except ImportError: - from websockets import WebSocketClientProtocol # type: ignore - -_logger = logging.getLogger(__name__) -V1SocketClientResponse = typing.Union[ListenV1Results, ListenV1Metadata, ListenV1UtteranceEnd, ListenV1SpeechStarted] - - -class AsyncV1SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: WebSocketClientProtocol): - super().__init__() - self._websocket = websocket - - async def __aiter__(self): - async for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - async def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - await self._emit_async(EventType.OPEN, None) - try: - async for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: - await self._emit_async(EventType.ERROR, exc) - finally: - await self._emit_async(EventType.CLOSE, None) - - async def send_media(self, message: bytes) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a bytes. - """ - await self._send(message) - - async def send_finalize(self, message: typing.Optional[ListenV1Finalize] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV1Finalize. - """ - await self._send_model(message or ListenV1Finalize(type="Finalize")) - - async def send_close_stream(self, message: typing.Optional[ListenV1CloseStream] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV1CloseStream. - """ - await self._send_model(message or ListenV1CloseStream(type="CloseStream")) - - async def send_keep_alive(self, message: typing.Optional[ListenV1KeepAlive] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV1KeepAlive. - """ - await self._send_model(message or ListenV1KeepAlive(type="KeepAlive")) - - async def recv(self) -> V1SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = await self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - async def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - await self._websocket.send(data) - - async def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - await self._send(data.dict()) - - -class V1SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: websockets_sync_connection.Connection): - super().__init__() - self._websocket = websocket - - def __iter__(self): - for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - self._emit(EventType.OPEN, None) - try: - for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - self._emit(EventType.MESSAGE, parsed) - except Exception as exc: - self._emit(EventType.ERROR, exc) - finally: - self._emit(EventType.CLOSE, None) - - def send_media(self, message: bytes) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a bytes. - """ - self._send(message) - - def send_finalize(self, message: typing.Optional[ListenV1Finalize] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV1Finalize. - """ - self._send_model(message or ListenV1Finalize(type="Finalize")) - - def send_close_stream(self, message: typing.Optional[ListenV1CloseStream] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV1CloseStream. - """ - self._send_model(message or ListenV1CloseStream(type="CloseStream")) - - def send_keep_alive(self, message: typing.Optional[ListenV1KeepAlive] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV1KeepAlive. - """ - self._send_model(message or ListenV1KeepAlive(type="KeepAlive")) - - def recv(self) -> V1SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - self._websocket.send(data) - - def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - self._send(data.dict()) diff --git a/src/deepgram/listen/v2/socket_client.py b/src/deepgram/listen/v2/socket_client.py index 33d419b3..e4e09468 100644 --- a/src/deepgram/listen/v2/socket_client.py +++ b/src/deepgram/listen/v2/socket_client.py @@ -3,9 +3,7 @@ import json import logging import typing -from json.decoder import JSONDecodeError -import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -72,7 +70,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -84,26 +82,31 @@ async def send_media(self, message: bytes) -> None: """ await self._send(message) - async def send_close_stream(self, message: ListenV2CloseStream) -> None: + async def send_close_stream(self, message: typing.Optional[ListenV2CloseStream] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2CloseStream. """ - await self._send_model(message) + await self._send_model(message or ListenV2CloseStream(type="CloseStream")) - async def send_force_end_turn(self, message: ListenV2ForceEndTurn) -> None: + async def send_force_end_turn(self, message: typing.Optional[ListenV2ForceEndTurn] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2ForceEndTurn. + This requires server-side enablement. On deployments without the feature, the server returns + UNPARSABLE_CLIENT_MESSAGE and closes the connection. """ - await self._send_model(message) + await self._send_model(message or ListenV2ForceEndTurn(type="ForceEndTurn")) - async def send_configure(self, message: ListenV2Configure) -> None: + async def send_configure(self, message: typing.Union[ListenV2Configure, typing.Dict[str, typing.Any]]) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2Configure. """ - await self._send_model(message) + if isinstance(message, dict): + await self._send(message) + else: + await self._send_model(message) async def recv(self) -> V2SocketClientResponse: """ @@ -177,7 +180,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -189,26 +192,31 @@ def send_media(self, message: bytes) -> None: """ self._send(message) - def send_close_stream(self, message: ListenV2CloseStream) -> None: + def send_close_stream(self, message: typing.Optional[ListenV2CloseStream] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2CloseStream. """ - self._send_model(message) + self._send_model(message or ListenV2CloseStream(type="CloseStream")) - def send_force_end_turn(self, message: ListenV2ForceEndTurn) -> None: + def send_force_end_turn(self, message: typing.Optional[ListenV2ForceEndTurn] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2ForceEndTurn. + This requires server-side enablement. On deployments without the feature, the server returns + UNPARSABLE_CLIENT_MESSAGE and closes the connection. """ - self._send_model(message) + self._send_model(message or ListenV2ForceEndTurn(type="ForceEndTurn")) - def send_configure(self, message: ListenV2Configure) -> None: + def send_configure(self, message: typing.Union[ListenV2Configure, typing.Dict[str, typing.Any]]) -> None: """ Send a message to the websocket connection. The message will be sent as a ListenV2Configure. """ - self._send_model(message) + if isinstance(message, dict): + self._send(message) + else: + self._send_model(message) def recv(self) -> V2SocketClientResponse: """ diff --git a/src/deepgram/listen/v2/socket_client.py.bak b/src/deepgram/listen/v2/socket_client.py.bak deleted file mode 100644 index e4e09468..00000000 --- a/src/deepgram/listen/v2/socket_client.py.bak +++ /dev/null @@ -1,247 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import json -import logging -import typing - -import websockets.sync.connection as websockets_sync_connection -from ...core.events import EventEmitterMixin, EventType -from ...core.unchecked_base_model import construct_type -from .types.listen_v2close_stream import ListenV2CloseStream -from .types.listen_v2configure import ListenV2Configure -from .types.listen_v2configure_failure import ListenV2ConfigureFailure -from .types.listen_v2configure_success import ListenV2ConfigureSuccess -from .types.listen_v2connected import ListenV2Connected -from .types.listen_v2fatal_error import ListenV2FatalError -from .types.listen_v2force_end_turn import ListenV2ForceEndTurn -from .types.listen_v2turn_info import ListenV2TurnInfo - -try: - from websockets.legacy.client import WebSocketClientProtocol # type: ignore -except ImportError: - from websockets import WebSocketClientProtocol # type: ignore - -_logger = logging.getLogger(__name__) -V2SocketClientResponse = typing.Union[ - ListenV2Connected, ListenV2TurnInfo, ListenV2ConfigureSuccess, ListenV2ConfigureFailure, ListenV2FatalError -] - - -class AsyncV2SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: WebSocketClientProtocol): - super().__init__() - self._websocket = websocket - - async def __aiter__(self): - async for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - async def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - await self._emit_async(EventType.OPEN, None) - try: - async for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: - await self._emit_async(EventType.ERROR, exc) - finally: - await self._emit_async(EventType.CLOSE, None) - - async def send_media(self, message: bytes) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a bytes. - """ - await self._send(message) - - async def send_close_stream(self, message: typing.Optional[ListenV2CloseStream] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV2CloseStream. - """ - await self._send_model(message or ListenV2CloseStream(type="CloseStream")) - - async def send_force_end_turn(self, message: typing.Optional[ListenV2ForceEndTurn] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV2ForceEndTurn. - This requires server-side enablement. On deployments without the feature, the server returns - UNPARSABLE_CLIENT_MESSAGE and closes the connection. - """ - await self._send_model(message or ListenV2ForceEndTurn(type="ForceEndTurn")) - - async def send_configure(self, message: typing.Union[ListenV2Configure, typing.Dict[str, typing.Any]]) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV2Configure. - """ - if isinstance(message, dict): - await self._send(message) - else: - await self._send_model(message) - - async def recv(self) -> V2SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = await self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - async def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - await self._websocket.send(data) - - async def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - await self._send(data.dict()) - - -class V2SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: websockets_sync_connection.Connection): - super().__init__() - self._websocket = websocket - - def __iter__(self): - for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - self._emit(EventType.OPEN, None) - try: - for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - self._emit(EventType.MESSAGE, parsed) - except Exception as exc: - self._emit(EventType.ERROR, exc) - finally: - self._emit(EventType.CLOSE, None) - - def send_media(self, message: bytes) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a bytes. - """ - self._send(message) - - def send_close_stream(self, message: typing.Optional[ListenV2CloseStream] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV2CloseStream. - """ - self._send_model(message or ListenV2CloseStream(type="CloseStream")) - - def send_force_end_turn(self, message: typing.Optional[ListenV2ForceEndTurn] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV2ForceEndTurn. - This requires server-side enablement. On deployments without the feature, the server returns - UNPARSABLE_CLIENT_MESSAGE and closes the connection. - """ - self._send_model(message or ListenV2ForceEndTurn(type="ForceEndTurn")) - - def send_configure(self, message: typing.Union[ListenV2Configure, typing.Dict[str, typing.Any]]) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a ListenV2Configure. - """ - if isinstance(message, dict): - self._send(message) - else: - self._send_model(message) - - def recv(self) -> V2SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - self._websocket.send(data) - - def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - self._send(data.dict()) diff --git a/src/deepgram/listen/v2/types/listen_v2configure_failure.py b/src/deepgram/listen/v2/types/listen_v2configure_failure.py index 2d0cb34f..bf30e353 100644 --- a/src/deepgram/listen/v2/types/listen_v2configure_failure.py +++ b/src/deepgram/listen/v2/types/listen_v2configure_failure.py @@ -4,10 +4,10 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel +from ._dict_compat import ListenV2ResponseDictCompatModel -class ListenV2ConfigureFailure(UncheckedBaseModel): +class ListenV2ConfigureFailure(ListenV2ResponseDictCompatModel): type: typing.Literal["ConfigureFailure"] = pydantic.Field(default="ConfigureFailure") """ Message type identifier diff --git a/src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak b/src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak deleted file mode 100644 index bf30e353..00000000 --- a/src/deepgram/listen/v2/types/listen_v2configure_failure.py.bak +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel - - -class ListenV2ConfigureFailure(ListenV2ResponseDictCompatModel): - type: typing.Literal["ConfigureFailure"] = pydantic.Field(default="ConfigureFailure") - """ - Message type identifier - """ - - request_id: str = pydantic.Field() - """ - The unique identifier of the request - """ - - sequence_id: int = pydantic.Field() - """ - Starts at `0` and increments for each message the server sends - to the client. This includes messages of other types, like - `TurnInfo` messages. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2configure_success.py b/src/deepgram/listen/v2/types/listen_v2configure_success.py index ce20724a..bde84d6f 100644 --- a/src/deepgram/listen/v2/types/listen_v2configure_success.py +++ b/src/deepgram/listen/v2/types/listen_v2configure_success.py @@ -4,12 +4,12 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel from ....types.listen_v2keyterm import ListenV2Keyterm +from ._dict_compat import ListenV2ResponseDictCompatModel from .listen_v2configure_success_thresholds import ListenV2ConfigureSuccessThresholds -class ListenV2ConfigureSuccess(UncheckedBaseModel): +class ListenV2ConfigureSuccess(ListenV2ResponseDictCompatModel): type: typing.Literal["ConfigureSuccess"] = pydantic.Field(default="ConfigureSuccess") """ Message type identifier diff --git a/src/deepgram/listen/v2/types/listen_v2configure_success.py.bak b/src/deepgram/listen/v2/types/listen_v2configure_success.py.bak deleted file mode 100644 index bde84d6f..00000000 --- a/src/deepgram/listen/v2/types/listen_v2configure_success.py.bak +++ /dev/null @@ -1,49 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....types.listen_v2keyterm import ListenV2Keyterm -from ._dict_compat import ListenV2ResponseDictCompatModel -from .listen_v2configure_success_thresholds import ListenV2ConfigureSuccessThresholds - - -class ListenV2ConfigureSuccess(ListenV2ResponseDictCompatModel): - type: typing.Literal["ConfigureSuccess"] = pydantic.Field(default="ConfigureSuccess") - """ - Message type identifier - """ - - request_id: str = pydantic.Field() - """ - The unique identifier of the request - """ - - thresholds: ListenV2ConfigureSuccessThresholds = pydantic.Field() - """ - Updates each parameter, if it is supplied. If a particular threshold parameter - is not supplied, the configuration continues using the currently configured value. - """ - - keyterms: ListenV2Keyterm - language_hints: typing.Optional[typing.List[str]] = pydantic.Field(default=None) - """ - The currently active language hints. Only applicable to the flux-general-multi model. - """ - - sequence_id: int = pydantic.Field() - """ - Starts at `0` and increments for each message the server sends - to the client. This includes messages of other types, like - `TurnInfo` messages. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py b/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py index 6c719233..e0353ec9 100644 --- a/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py +++ b/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py @@ -4,13 +4,13 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel from ....types.listen_v2eager_eot_threshold import ListenV2EagerEotThreshold from ....types.listen_v2eot_threshold import ListenV2EotThreshold from ....types.listen_v2eot_timeout_ms import ListenV2EotTimeoutMs +from ._dict_compat import ListenV2ResponseDictCompatModel -class ListenV2ConfigureSuccessThresholds(UncheckedBaseModel): +class ListenV2ConfigureSuccessThresholds(ListenV2ResponseDictCompatModel): """ Updates each parameter, if it is supplied. If a particular threshold parameter is not supplied, the configuration continues using the currently configured value. diff --git a/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak b/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak deleted file mode 100644 index e0353ec9..00000000 --- a/src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py.bak +++ /dev/null @@ -1,30 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....types.listen_v2eager_eot_threshold import ListenV2EagerEotThreshold -from ....types.listen_v2eot_threshold import ListenV2EotThreshold -from ....types.listen_v2eot_timeout_ms import ListenV2EotTimeoutMs -from ._dict_compat import ListenV2ResponseDictCompatModel - - -class ListenV2ConfigureSuccessThresholds(ListenV2ResponseDictCompatModel): - """ - Updates each parameter, if it is supplied. If a particular threshold parameter - is not supplied, the configuration continues using the currently configured value. - """ - - eager_eot_threshold: typing.Optional[ListenV2EagerEotThreshold] = None - eot_threshold: typing.Optional[ListenV2EotThreshold] = None - eot_timeout_ms: typing.Optional[ListenV2EotTimeoutMs] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2connected.py b/src/deepgram/listen/v2/types/listen_v2connected.py index cefc0236..b253d86c 100644 --- a/src/deepgram/listen/v2/types/listen_v2connected.py +++ b/src/deepgram/listen/v2/types/listen_v2connected.py @@ -4,10 +4,10 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel +from ._dict_compat import ListenV2ResponseDictCompatModel -class ListenV2Connected(UncheckedBaseModel): +class ListenV2Connected(ListenV2ResponseDictCompatModel): type: typing.Literal["Connected"] = pydantic.Field(default="Connected") """ Message type identifier diff --git a/src/deepgram/listen/v2/types/listen_v2connected.py.bak b/src/deepgram/listen/v2/types/listen_v2connected.py.bak deleted file mode 100644 index b253d86c..00000000 --- a/src/deepgram/listen/v2/types/listen_v2connected.py.bak +++ /dev/null @@ -1,35 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel - - -class ListenV2Connected(ListenV2ResponseDictCompatModel): - type: typing.Literal["Connected"] = pydantic.Field(default="Connected") - """ - Message type identifier - """ - - request_id: str = pydantic.Field() - """ - The unique identifier of the request - """ - - sequence_id: int = pydantic.Field() - """ - Starts at `0` and increments for each message the server sends - to the client. This includes messages of other types, like - `TurnInfo` messages. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2fatal_error.py b/src/deepgram/listen/v2/types/listen_v2fatal_error.py index c5106f07..2202c82e 100644 --- a/src/deepgram/listen/v2/types/listen_v2fatal_error.py +++ b/src/deepgram/listen/v2/types/listen_v2fatal_error.py @@ -4,10 +4,10 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel +from ._dict_compat import ListenV2ResponseDictCompatModel -class ListenV2FatalError(UncheckedBaseModel): +class ListenV2FatalError(ListenV2ResponseDictCompatModel): type: typing.Literal["Error"] = pydantic.Field(default="Error") """ Message type identifier diff --git a/src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak b/src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak deleted file mode 100644 index 2202c82e..00000000 --- a/src/deepgram/listen/v2/types/listen_v2fatal_error.py.bak +++ /dev/null @@ -1,40 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel - - -class ListenV2FatalError(ListenV2ResponseDictCompatModel): - type: typing.Literal["Error"] = pydantic.Field(default="Error") - """ - Message type identifier - """ - - sequence_id: int = pydantic.Field() - """ - Starts at `0` and increments for each message the server sends - to the client. This includes messages of other types, like - `Connected` messages. - """ - - code: str = pydantic.Field() - """ - A string code describing the error, e.g. `INTERNAL_SERVER_ERROR` - """ - - description: str = pydantic.Field() - """ - Prose description of the error - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2turn_info.py b/src/deepgram/listen/v2/types/listen_v2turn_info.py index d87a1032..969d6d0c 100644 --- a/src/deepgram/listen/v2/types/listen_v2turn_info.py +++ b/src/deepgram/listen/v2/types/listen_v2turn_info.py @@ -4,12 +4,12 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel +from ._dict_compat import ListenV2ResponseDictCompatModel from .listen_v2turn_info_event import ListenV2TurnInfoEvent from .listen_v2turn_info_words_item import ListenV2TurnInfoWordsItem -class ListenV2TurnInfo(UncheckedBaseModel): +class ListenV2TurnInfo(ListenV2ResponseDictCompatModel): """ Describes the current turn and latest state of the turn """ diff --git a/src/deepgram/listen/v2/types/listen_v2turn_info.py.bak b/src/deepgram/listen/v2/types/listen_v2turn_info.py.bak deleted file mode 100644 index 969d6d0c..00000000 --- a/src/deepgram/listen/v2/types/listen_v2turn_info.py.bak +++ /dev/null @@ -1,102 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel -from .listen_v2turn_info_event import ListenV2TurnInfoEvent -from .listen_v2turn_info_words_item import ListenV2TurnInfoWordsItem - - -class ListenV2TurnInfo(ListenV2ResponseDictCompatModel): - """ - Describes the current turn and latest state of the turn - """ - - type: typing.Literal["TurnInfo"] = "TurnInfo" - request_id: str = pydantic.Field() - """ - The unique identifier of the request - """ - - sequence_id: int = pydantic.Field() - """ - Starts at `0` and increments for each message the server sends to the client. This includes messages of other types, like `Connected` messages. - """ - - event: ListenV2TurnInfoEvent = pydantic.Field() - """ - The type of event being reported. - - - **Update** - Additional audio has been transcribed, but the turn state hasn't changed - - **StartOfTurn** - The user has begun speaking for the first time in the turn - - **EagerEndOfTurn** - The system has moderate confidence that the user has finished speaking for the turn. This is an opportunity to begin preparing an agent reply - - **TurnResumed** - The system detected that speech had ended and therefore sent an **EagerEndOfTurn** event, but speech is actually continuing for this turn - - **EndOfTurn** - The user has finished speaking for the turn - """ - - turn_index: int = pydantic.Field() - """ - The index of the current turn - """ - - audio_window_start: float = pydantic.Field() - """ - Start time in seconds of the audio range that was transcribed - """ - - audio_window_end: float = pydantic.Field() - """ - End time in seconds of the audio range that was transcribed - """ - - transcript: str = pydantic.Field() - """ - Text that was said over the course of the current turn - """ - - words: typing.List[ListenV2TurnInfoWordsItem] = pydantic.Field() - """ - The words in the `transcript` - """ - - end_of_turn_confidence: float = pydantic.Field() - """ - Confidence that no more speech is coming in this turn - """ - - trigger: typing.Optional[str] = pydantic.Field(default=None) - """ - The cause of the turn ending. Present on every `EndOfTurn` event and only there. - - - **model** - the turn ended by Flux's native end-of-turn detection - - - **manual** - the turn ended because a `ForceEndTurn` message was sent - - - **timeout** - the turn ended because `eot_timeout_ms` elapsed - - This is an open enum. New values may be added over time, so clients must tolerate values they do not recognize. - """ - - languages: typing.Optional[typing.List[str]] = pydantic.Field(default=None) - """ - Detected languages sorted by descending frequency in the - transcript. Only present when the flux-general-multi model - detects languages in the audio. - """ - - languages_hinted: typing.Optional[typing.List[str]] = pydantic.Field(default=None) - """ - The language hints that were supplied for this turn. Only - present when language hints are configured. - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py b/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py index 07ada673..88240cd9 100644 --- a/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py +++ b/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py @@ -4,10 +4,10 @@ import pydantic from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ....core.unchecked_base_model import UncheckedBaseModel +from ._dict_compat import ListenV2ResponseDictCompatModel -class ListenV2TurnInfoWordsItem(UncheckedBaseModel): +class ListenV2TurnInfoWordsItem(ListenV2ResponseDictCompatModel): word: str = pydantic.Field() """ The individual punctuated, properly-cased word from the transcript diff --git a/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak b/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak deleted file mode 100644 index 88240cd9..00000000 --- a/src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py.bak +++ /dev/null @@ -1,38 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ....core.pydantic_utilities import IS_PYDANTIC_V2 -from ._dict_compat import ListenV2ResponseDictCompatModel - - -class ListenV2TurnInfoWordsItem(ListenV2ResponseDictCompatModel): - word: str = pydantic.Field() - """ - The individual punctuated, properly-cased word from the transcript - """ - - confidence: float = pydantic.Field() - """ - Confidence that this word was transcribed correctly - """ - - start: typing.Optional[float] = pydantic.Field(default=None) - """ - The start time of the word - """ - - end: typing.Optional[float] = pydantic.Field(default=None) - """ - The end time of the word - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/requests/deepgram_listen_provider_v2.py.bak b/src/deepgram/requests/deepgram_listen_provider_v2.py.bak deleted file mode 100644 index 02fdf65a..00000000 --- a/src/deepgram/requests/deepgram_listen_provider_v2.py.bak +++ /dev/null @@ -1,47 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import typing_extensions - - -class DeepgramListenProviderV2Params(typing_extensions.TypedDict): - type: typing.Literal["deepgram"] - """ - Provider type for speech-to-text - """ - - version: typing_extensions.NotRequired[typing.Literal["v2"]] - """ - Specifies usage of the V2 Deepgram speech-to-text API (e.g. Flux) - """ - - model: str - """ - Model to use for speech to text using the V2 API (e.g. flux-general-en, flux-general-multi) - """ - - language_hints: typing_extensions.NotRequired[typing.Sequence[str]] - """ - An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. - """ - - eot_threshold: typing_extensions.NotRequired[float] - """ - End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it for full manual turn control with the ForceEndTurn message. - """ - - eager_eot_threshold: typing_extensions.NotRequired[float] - """ - End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid range: 0.3 - 0.9. - """ - - eot_timeout_ms: typing_extensions.NotRequired[int] - """ - A turn will be finished when this much time in milliseconds has passed after speech, regardless of EOT confidence. Defaults to 5000. - """ - - keyterms: typing_extensions.NotRequired[typing.Sequence[str]] - """ - Prompt keyterm recognition to improve Keyword Recall Rate - """ diff --git a/src/deepgram/speak/v1/socket_client.py b/src/deepgram/speak/v1/socket_client.py index 9f1cad28..e3c28bb5 100644 --- a/src/deepgram/speak/v1/socket_client.py +++ b/src/deepgram/speak/v1/socket_client.py @@ -3,9 +3,7 @@ import json import logging import typing -from json.decoder import JSONDecodeError -import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -70,7 +68,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -82,26 +80,26 @@ async def send_text(self, message: SpeakV1Text) -> None: """ await self._send_model(message) - async def send_flush(self, message: SpeakV1Flush) -> None: + async def send_flush(self, message: typing.Optional[SpeakV1Flush] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Flush. """ - await self._send_model(message) + await self._send_model(message or SpeakV1Flush(type="Flush")) - async def send_clear(self, message: SpeakV1Clear) -> None: + async def send_clear(self, message: typing.Optional[SpeakV1Clear] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Clear. """ - await self._send_model(message) + await self._send_model(message or SpeakV1Clear(type="Clear")) - async def send_close(self, message: SpeakV1Close) -> None: + async def send_close(self, message: typing.Optional[SpeakV1Close] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Close. """ - await self._send_model(message) + await self._send_model(message or SpeakV1Close(type="Close")) async def recv(self) -> V1SocketClientResponse: """ @@ -175,7 +173,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -187,26 +185,26 @@ def send_text(self, message: SpeakV1Text) -> None: """ self._send_model(message) - def send_flush(self, message: SpeakV1Flush) -> None: + def send_flush(self, message: typing.Optional[SpeakV1Flush] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Flush. """ - self._send_model(message) + self._send_model(message or SpeakV1Flush(type="Flush")) - def send_clear(self, message: SpeakV1Clear) -> None: + def send_clear(self, message: typing.Optional[SpeakV1Clear] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Clear. """ - self._send_model(message) + self._send_model(message or SpeakV1Clear(type="Clear")) - def send_close(self, message: SpeakV1Close) -> None: + def send_close(self, message: typing.Optional[SpeakV1Close] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV1Close. """ - self._send_model(message) + self._send_model(message or SpeakV1Close(type="Close")) def recv(self) -> V1SocketClientResponse: """ diff --git a/src/deepgram/speak/v1/socket_client.py.bak b/src/deepgram/speak/v1/socket_client.py.bak deleted file mode 100644 index e3c28bb5..00000000 --- a/src/deepgram/speak/v1/socket_client.py.bak +++ /dev/null @@ -1,235 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import json -import logging -import typing - -import websockets.sync.connection as websockets_sync_connection -from ...core.events import EventEmitterMixin, EventType -from ...core.unchecked_base_model import construct_type -from .types.speak_v1clear import SpeakV1Clear -from .types.speak_v1cleared import SpeakV1Cleared -from .types.speak_v1close import SpeakV1Close -from .types.speak_v1flush import SpeakV1Flush -from .types.speak_v1flushed import SpeakV1Flushed -from .types.speak_v1metadata import SpeakV1Metadata -from .types.speak_v1text import SpeakV1Text -from .types.speak_v1warning import SpeakV1Warning - -try: - from websockets.legacy.client import WebSocketClientProtocol # type: ignore -except ImportError: - from websockets import WebSocketClientProtocol # type: ignore - -_logger = logging.getLogger(__name__) -V1SocketClientResponse = typing.Union[bytes, SpeakV1Metadata, SpeakV1Flushed, SpeakV1Cleared, SpeakV1Warning] - - -class AsyncV1SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: WebSocketClientProtocol): - super().__init__() - self._websocket = websocket - - async def __aiter__(self): - async for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - async def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - await self._emit_async(EventType.OPEN, None) - try: - async for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: - await self._emit_async(EventType.ERROR, exc) - finally: - await self._emit_async(EventType.CLOSE, None) - - async def send_text(self, message: SpeakV1Text) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV1Text. - """ - await self._send_model(message) - - async def send_flush(self, message: typing.Optional[SpeakV1Flush] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV1Flush. - """ - await self._send_model(message or SpeakV1Flush(type="Flush")) - - async def send_clear(self, message: typing.Optional[SpeakV1Clear] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV1Clear. - """ - await self._send_model(message or SpeakV1Clear(type="Clear")) - - async def send_close(self, message: typing.Optional[SpeakV1Close] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV1Close. - """ - await self._send_model(message or SpeakV1Close(type="Close")) - - async def recv(self) -> V1SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = await self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - async def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - await self._websocket.send(data) - - async def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - await self._send(data.dict()) - - -class V1SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: websockets_sync_connection.Connection): - super().__init__() - self._websocket = websocket - - def __iter__(self): - for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - self._emit(EventType.OPEN, None) - try: - for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - self._emit(EventType.MESSAGE, parsed) - except Exception as exc: - self._emit(EventType.ERROR, exc) - finally: - self._emit(EventType.CLOSE, None) - - def send_text(self, message: SpeakV1Text) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV1Text. - """ - self._send_model(message) - - def send_flush(self, message: typing.Optional[SpeakV1Flush] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV1Flush. - """ - self._send_model(message or SpeakV1Flush(type="Flush")) - - def send_clear(self, message: typing.Optional[SpeakV1Clear] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV1Clear. - """ - self._send_model(message or SpeakV1Clear(type="Clear")) - - def send_close(self, message: typing.Optional[SpeakV1Close] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV1Close. - """ - self._send_model(message or SpeakV1Close(type="Close")) - - def recv(self) -> V1SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - self._websocket.send(data) - - def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - self._send(data.dict()) diff --git a/src/deepgram/speak/v2/socket_client.py b/src/deepgram/speak/v2/socket_client.py index b9d4d1a2..c6729c32 100644 --- a/src/deepgram/speak/v2/socket_client.py +++ b/src/deepgram/speak/v2/socket_client.py @@ -3,9 +3,7 @@ import json import logging import typing -from json.decoder import JSONDecodeError -import websockets import websockets.sync.connection as websockets_sync_connection from ...core.events import EventEmitterMixin, EventType from ...core.unchecked_base_model import construct_type @@ -89,7 +87,7 @@ async def start_listening(self): ) continue await self._emit_async(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: await self._emit_async(EventType.ERROR, exc) finally: await self._emit_async(EventType.CLOSE, None) @@ -101,19 +99,19 @@ async def send_speak(self, message: SpeakV2Speak) -> None: """ await self._send_model(message) - async def send_flush(self, message: SpeakV2Flush) -> None: + async def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Flush. """ - await self._send_model(message) + await self._send_model(message or SpeakV2Flush(type="Flush")) - async def send_interrupt(self, message: SpeakV2Interrupt) -> None: + async def send_interrupt(self, message: typing.Optional[SpeakV2Interrupt] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Interrupt. """ - await self._send_model(message) + await self._send_model(message or SpeakV2Interrupt(type="Interrupt")) async def send_configure(self, message: SpeakV2Configure) -> None: """ @@ -122,12 +120,12 @@ async def send_configure(self, message: SpeakV2Configure) -> None: """ await self._send_model(message) - async def send_close(self, message: SpeakV2Close) -> None: + async def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Close. """ - await self._send_model(message) + await self._send_model(message or SpeakV2Close(type="Close")) async def recv(self) -> V2SocketClientResponse: """ @@ -201,7 +199,7 @@ def start_listening(self): ) continue self._emit(EventType.MESSAGE, parsed) - except (websockets.WebSocketException, JSONDecodeError) as exc: + except Exception as exc: self._emit(EventType.ERROR, exc) finally: self._emit(EventType.CLOSE, None) @@ -213,19 +211,19 @@ def send_speak(self, message: SpeakV2Speak) -> None: """ self._send_model(message) - def send_flush(self, message: SpeakV2Flush) -> None: + def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Flush. """ - self._send_model(message) + self._send_model(message or SpeakV2Flush(type="Flush")) - def send_interrupt(self, message: SpeakV2Interrupt) -> None: + def send_interrupt(self, message: typing.Optional[SpeakV2Interrupt] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Interrupt. """ - self._send_model(message) + self._send_model(message or SpeakV2Interrupt(type="Interrupt")) def send_configure(self, message: SpeakV2Configure) -> None: """ @@ -234,12 +232,12 @@ def send_configure(self, message: SpeakV2Configure) -> None: """ self._send_model(message) - def send_close(self, message: SpeakV2Close) -> None: + def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: """ Send a message to the websocket connection. The message will be sent as a SpeakV2Close. """ - self._send_model(message) + self._send_model(message or SpeakV2Close(type="Close")) def recv(self) -> V2SocketClientResponse: """ diff --git a/src/deepgram/speak/v2/socket_client.py.bak b/src/deepgram/speak/v2/socket_client.py.bak deleted file mode 100644 index c6729c32..00000000 --- a/src/deepgram/speak/v2/socket_client.py.bak +++ /dev/null @@ -1,268 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import json -import logging -import typing - -import websockets.sync.connection as websockets_sync_connection -from ...core.events import EventEmitterMixin, EventType -from ...core.unchecked_base_model import construct_type -from .types.speak_v2close import SpeakV2Close -from .types.speak_v2configure import SpeakV2Configure -from .types.speak_v2configure_failure import SpeakV2ConfigureFailure -from .types.speak_v2configure_success import SpeakV2ConfigureSuccess -from .types.speak_v2connected import SpeakV2Connected -from .types.speak_v2error import SpeakV2Error -from .types.speak_v2flush import SpeakV2Flush -from .types.speak_v2flushed import SpeakV2Flushed -from .types.speak_v2interrupt import SpeakV2Interrupt -from .types.speak_v2session_metadata import SpeakV2SessionMetadata -from .types.speak_v2speak import SpeakV2Speak -from .types.speak_v2speech_interrupted import SpeakV2SpeechInterrupted -from .types.speak_v2speech_metadata import SpeakV2SpeechMetadata -from .types.speak_v2speech_started import SpeakV2SpeechStarted -from .types.speak_v2warning import SpeakV2Warning - -try: - from websockets.legacy.client import WebSocketClientProtocol # type: ignore -except ImportError: - from websockets import WebSocketClientProtocol # type: ignore - -_logger = logging.getLogger(__name__) -V2SocketClientResponse = typing.Union[ - bytes, - SpeakV2Connected, - SpeakV2SpeechStarted, - SpeakV2SpeechMetadata, - SpeakV2SpeechInterrupted, - SpeakV2Flushed, - SpeakV2SessionMetadata, - SpeakV2ConfigureSuccess, - SpeakV2ConfigureFailure, - SpeakV2Warning, - SpeakV2Error, -] - - -class AsyncV2SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: WebSocketClientProtocol): - super().__init__() - self._websocket = websocket - - async def __aiter__(self): - async for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - async def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - await self._emit_async(EventType.OPEN, None) - try: - async for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - await self._emit_async(EventType.MESSAGE, parsed) - except Exception as exc: - await self._emit_async(EventType.ERROR, exc) - finally: - await self._emit_async(EventType.CLOSE, None) - - async def send_speak(self, message: SpeakV2Speak) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Speak. - """ - await self._send_model(message) - - async def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Flush. - """ - await self._send_model(message or SpeakV2Flush(type="Flush")) - - async def send_interrupt(self, message: typing.Optional[SpeakV2Interrupt] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Interrupt. - """ - await self._send_model(message or SpeakV2Interrupt(type="Interrupt")) - - async def send_configure(self, message: SpeakV2Configure) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Configure. - """ - await self._send_model(message) - - async def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Close. - """ - await self._send_model(message or SpeakV2Close(type="Close")) - - async def recv(self) -> V2SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = await self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - async def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - await self._websocket.send(data) - - async def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - await self._send(data.dict()) - - -class V2SocketClient(EventEmitterMixin): - def __init__(self, *, websocket: websockets_sync_connection.Connection): - super().__init__() - self._websocket = websocket - - def __iter__(self): - for message in self._websocket: - if isinstance(message, bytes): - yield message - else: - try: - yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - - def start_listening(self): - """ - Start listening for messages on the websocket connection. - - Emits events in the following order: - - EventType.OPEN when connection is established - - EventType.MESSAGE for each message received - - EventType.ERROR if an error occurs - - EventType.CLOSE when connection is closed - """ - self._emit(EventType.OPEN, None) - try: - for raw_message in self._websocket: - if isinstance(raw_message, bytes): - parsed = raw_message - else: - json_data = json.loads(raw_message) - try: - parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning( - "Skipping unknown WebSocket message; update your SDK version to support new message types." - ) - continue - self._emit(EventType.MESSAGE, parsed) - except Exception as exc: - self._emit(EventType.ERROR, exc) - finally: - self._emit(EventType.CLOSE, None) - - def send_speak(self, message: SpeakV2Speak) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Speak. - """ - self._send_model(message) - - def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Flush. - """ - self._send_model(message or SpeakV2Flush(type="Flush")) - - def send_interrupt(self, message: typing.Optional[SpeakV2Interrupt] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Interrupt. - """ - self._send_model(message or SpeakV2Interrupt(type="Interrupt")) - - def send_configure(self, message: SpeakV2Configure) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Configure. - """ - self._send_model(message) - - def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a SpeakV2Close. - """ - self._send_model(message or SpeakV2Close(type="Close")) - - def recv(self) -> V2SocketClientResponse: - """ - Receive a message from the websocket connection. - """ - data = self._websocket.recv() - if isinstance(data, bytes): - return data # type: ignore - json_data = json.loads(data) - try: - return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore - except Exception: - _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") - return json_data # type: ignore - - def _send(self, data: typing.Any) -> None: - """ - Send a message to the websocket connection. - """ - if isinstance(data, dict): - data = json.dumps(data) - self._websocket.send(data) - - def _send_model(self, data: typing.Any) -> None: - """ - Send a Pydantic model to the websocket connection. - """ - self._send(data.dict()) diff --git a/src/deepgram/types/deepgram.py b/src/deepgram/types/deepgram.py index ec242762..994f7a2c 100644 --- a/src/deepgram/types/deepgram.py +++ b/src/deepgram/types/deepgram.py @@ -29,7 +29,7 @@ class Deepgram(UncheckedBaseModel): Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Aura (version v1) accepts any value from 0.7 to 1.5. Flux TTS (version v2) accepts values from 0.5 to 1.5 in 0.05 increments; a value the family does not accept ends the session with FAILED_TO_SPEAK. Not yet supported in all languages. """ - expressivity: typing.Optional[int] = pydantic.Field(default=None) + expressivity: typing.Optional[pydantic.StrictInt] = pydantic.Field(default=None) """ Delivery register of the generated speech, on a calm-to-animated axis. Flux TTS (version v2) only, on every Flux voice. Accepts the whole numbers -2 to 2, where 0 (the default) is the voice's tuned delivery and the only value validated for production, -2 the calm end of the range and 2 the animated end. Fixed for the session. Beta: behavior may change in future model versions, and non-default values increase the risk of hallucinations and pronunciation errors. See [Expressivity](/docs/tts-expressivity). """ diff --git a/src/deepgram/types/deepgram.py.bak b/src/deepgram/types/deepgram.py.bak deleted file mode 100644 index 994f7a2c..00000000 --- a/src/deepgram/types/deepgram.py.bak +++ /dev/null @@ -1,44 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2 -from ..core.unchecked_base_model import UncheckedBaseModel -from .deepgram_model import DeepgramModel - - -class Deepgram(UncheckedBaseModel): - """ - Deepgram text-to-speech provider. Aura models use version v1 (default); Flux TTS uses version v2 and a flux-* model. Flux TTS is the default when agent.speak is omitted, using the flux-kit-en voice. - """ - - type: typing.Literal["deepgram"] = "deepgram" - version: typing.Optional[str] = pydantic.Field(default=None) - """ - The Deepgram text-to-speech model family. Accepted values: `v1` (Aura, the default) and `v2` (Flux TTS). Use `v1` with an aura-* model and `v2` with a flux-* model. Defaults to `v1` when omitted. - """ - - model: DeepgramModel = pydantic.Field() - """ - Deepgram TTS model. Aura models (version v1) use the aura-* voices; Flux TTS (version v2) uses the flux-{voice}-{language} voices (e.g. flux-alexis-en). Defaults to flux-kit-en when agent.speak is omitted. - """ - - speed: typing.Optional[float] = pydantic.Field(default=None) - """ - Speaking rate multiplier that adjusts the pace of generated speech while preserving natural prosody and voice quality. Aura (version v1) accepts any value from 0.7 to 1.5. Flux TTS (version v2) accepts values from 0.5 to 1.5 in 0.05 increments; a value the family does not accept ends the session with FAILED_TO_SPEAK. Not yet supported in all languages. - """ - - expressivity: typing.Optional[pydantic.StrictInt] = pydantic.Field(default=None) - """ - Delivery register of the generated speech, on a calm-to-animated axis. Flux TTS (version v2) only, on every Flux voice. Accepts the whole numbers -2 to 2, where 0 (the default) is the voice's tuned delivery and the only value validated for production, -2 the calm end of the range and 2 the animated end. Fixed for the session. Beta: behavior may change in future model versions, and non-default values increase the risk of hallucinations and pronunciation errors. See [Expressivity](/docs/tts-expressivity). - """ - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/types/deepgram_listen_provider_v2.py b/src/deepgram/types/deepgram_listen_provider_v2.py index f7a155fb..4eacc10e 100644 --- a/src/deepgram/types/deepgram_listen_provider_v2.py +++ b/src/deepgram/types/deepgram_listen_provider_v2.py @@ -28,6 +28,12 @@ class DeepgramListenProviderV2(UncheckedBaseModel): An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. """ + language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) + """ + Deprecated. Use `language_hints`. Accepted for backward compatibility and + remapped before serialization so the singular field never reaches the API. + """ + eot_threshold: typing.Optional[float] = pydantic.Field(default=None) """ End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it when using ForceEndTurn for full manual turn control. @@ -48,6 +54,30 @@ class DeepgramListenProviderV2(UncheckedBaseModel): Prompt keyterm recognition to improve Keyword Recall Rate """ + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + else: + + @pydantic.root_validator(pre=True) # type: ignore[deprecated] + def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] + if not isinstance(values, dict) or "language_hint" not in values: + return values + values = dict(values) + hint = values.pop("language_hint") + if hint is not None and values.get("language_hints") is None: + values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) + return values + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/deepgram/types/deepgram_listen_provider_v2.py.bak b/src/deepgram/types/deepgram_listen_provider_v2.py.bak deleted file mode 100644 index c166bfac..00000000 --- a/src/deepgram/types/deepgram_listen_provider_v2.py.bak +++ /dev/null @@ -1,88 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2 -from ..core.unchecked_base_model import UncheckedBaseModel - - -class DeepgramListenProviderV2(UncheckedBaseModel): - type: typing.Literal["deepgram"] = pydantic.Field(default="deepgram") - """ - Provider type for speech-to-text - """ - - version: typing.Optional[typing.Literal["v2"]] = pydantic.Field(default=None) - """ - Specifies usage of the V2 Deepgram speech-to-text API (e.g. Flux) - """ - - model: str = pydantic.Field() - """ - Model to use for speech to text using the V2 API (e.g. flux-general-en, flux-general-multi) - """ - - language_hints: typing.Optional[typing.List[str]] = pydantic.Field(default=None) - """ - An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. - """ - - language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) - """ - Deprecated. Use `language_hints`. Accepted for backward compatibility and - remapped before serialization so the singular field never reaches the API. - """ - - eot_threshold: typing.Optional[float] = pydantic.Field(default=None) - """ - End-of-turn confidence required to finish a turn. Valid range: 0.5 - 1.0. Defaults to 0.7. Set to 1.0 to fully suppress confidence-based end-of-turn detection. `eot_timeout_ms` still ends idle turns; increase it for full manual turn control with the ForceEndTurn message. - """ - - eager_eot_threshold: typing.Optional[float] = pydantic.Field(default=None) - """ - End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid range: 0.3 - 0.9. - """ - - eot_timeout_ms: typing.Optional[int] = pydantic.Field(default=None) - """ - A turn will be finished when this much time in milliseconds has passed after speech, regardless of EOT confidence. Defaults to 5000. - """ - - keyterms: typing.Optional[typing.List[str]] = pydantic.Field(default=None) - """ - Prompt keyterm recognition to improve Keyword Recall Rate - """ - - if IS_PYDANTIC_V2: - - @pydantic.model_validator(mode="before") - @classmethod - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - else: - - @pydantic.root_validator(pre=True) # type: ignore[deprecated] - def _migrate_language_hint(cls, values: typing.Any) -> typing.Any: # type: ignore[no-redef] - if not isinstance(values, dict) or "language_hint" not in values: - return values - values = dict(values) - hint = values.pop("language_hint") - if hint is not None and values.get("language_hints") is None: - values["language_hints"] = [hint] if isinstance(hint, str) else list(hint) - return values - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/src/deepgram/types/listen_v2eot_threshold.py.bak b/src/deepgram/types/listen_v2eot_threshold.py.bak deleted file mode 100644 index 9f6e231c..00000000 --- a/src/deepgram/types/listen_v2eot_threshold.py.bak +++ /dev/null @@ -1,11 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ListenV2EotThreshold = typing.Any -""" -End-of-turn confidence required to finish a turn. Valid Values 0.5 - -1.0. Set to 1.0 to fully suppress confidence-based end-of-turn detection. -`eot_timeout_ms` still ends idle turns; increase it for full manual -turn control with the `ForceEndTurn` message. -""" diff --git a/src/deepgram/types/speak_settings_v1provider.py b/src/deepgram/types/speak_settings_v1provider.py index 305cdba8..3fbef168 100644 --- a/src/deepgram/types/speak_settings_v1provider.py +++ b/src/deepgram/types/speak_settings_v1provider.py @@ -24,7 +24,7 @@ class SpeakSettingsV1Provider_Deepgram(UncheckedBaseModel): version: typing.Optional[str] = None model: DeepgramModel speed: typing.Optional[float] = None - expressivity: typing.Optional[int] = None + expressivity: typing.Optional[pydantic.StrictInt] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/deepgram/types/speak_settings_v1provider.py.bak b/src/deepgram/types/speak_settings_v1provider.py.bak deleted file mode 100644 index 3fbef168..00000000 --- a/src/deepgram/types/speak_settings_v1provider.py.bak +++ /dev/null @@ -1,117 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from __future__ import annotations - -import typing - -import pydantic -import typing_extensions -from ..core.pydantic_utilities import IS_PYDANTIC_V2 -from ..core.unchecked_base_model import UncheckedBaseModel, UnionMetadata -from .aws_polly_speak_provider_credentials import AwsPollySpeakProviderCredentials -from .aws_polly_speak_provider_engine import AwsPollySpeakProviderEngine -from .aws_polly_speak_provider_voice import AwsPollySpeakProviderVoice -from .cartesia_model_id import CartesiaModelId -from .cartesia_voice import CartesiaVoice -from .deepgram_model import DeepgramModel -from .eleven_labs_speak_provider_model_id import ElevenLabsSpeakProviderModelId -from .open_ai_speak_provider_model import OpenAiSpeakProviderModel -from .open_ai_speak_provider_voice import OpenAiSpeakProviderVoice - - -class SpeakSettingsV1Provider_Deepgram(UncheckedBaseModel): - type: typing.Literal["deepgram"] = "deepgram" - version: typing.Optional[str] = None - model: DeepgramModel - speed: typing.Optional[float] = None - expressivity: typing.Optional[pydantic.StrictInt] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -class SpeakSettingsV1Provider_ElevenLabs(UncheckedBaseModel): - type: typing.Literal["eleven_labs"] = "eleven_labs" - version: typing.Optional[typing.Literal["v1"]] = None - model_id: ElevenLabsSpeakProviderModelId - language: typing.Optional[str] = None - language_code: typing.Optional[str] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -class SpeakSettingsV1Provider_Cartesia(UncheckedBaseModel): - type: typing.Literal["cartesia"] = "cartesia" - version: typing.Optional[typing.Literal["2025-03-17"]] = None - model_id: CartesiaModelId - voice: CartesiaVoice - language: typing.Optional[str] = None - volume: typing.Optional[float] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -class SpeakSettingsV1Provider_OpenAi(UncheckedBaseModel): - type: typing.Literal["open_ai"] = "open_ai" - version: typing.Optional[typing.Literal["v1"]] = None - model: OpenAiSpeakProviderModel - voice: OpenAiSpeakProviderVoice - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -class SpeakSettingsV1Provider_AwsPolly(UncheckedBaseModel): - type: typing.Literal["aws_polly"] = "aws_polly" - voice: AwsPollySpeakProviderVoice - language: str - language_code: typing.Optional[str] = None - engine: AwsPollySpeakProviderEngine - credentials: AwsPollySpeakProviderCredentials - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow - - -SpeakSettingsV1Provider = typing_extensions.Annotated[ - typing.Union[ - SpeakSettingsV1Provider_Deepgram, - SpeakSettingsV1Provider_ElevenLabs, - SpeakSettingsV1Provider_Cartesia, - SpeakSettingsV1Provider_OpenAi, - SpeakSettingsV1Provider_AwsPolly, - ], - UnionMetadata(discriminant="type"), -] diff --git a/tests/custom/test_listen_v2_connect_wire.py b/tests/custom/test_listen_v2_connect_wire.py index ca72185e..c08e30f9 100644 --- a/tests/custom/test_listen_v2_connect_wire.py +++ b/tests/custom/test_listen_v2_connect_wire.py @@ -7,8 +7,8 @@ * the connection targets the ``/v2/listen`` path, and * query parameters serialize correctly — in particular the ``numerals`` param added in the 2026-07-20 regen (docs deepgram-docs#1020) must reach the wire - as ``numerals=true``. ``numerals`` is a connection-time query param only (it - is not toggleable via the ``Configure`` control message). + as ``numerals=true``. The connection-time value is complemented by the + runtime ``Configure`` control-message coverage in ``test_socket_client_shims``. No network / WireMock: the public ``connect`` builds the URL and hands it to the ``websockets`` connect entrypoint, so we replace that entrypoint with a capture diff --git a/tests/custom/test_socket_client_shims.py b/tests/custom/test_socket_client_shims.py index 37776ca1..c34aaf07 100644 --- a/tests/custom/test_socket_client_shims.py +++ b/tests/custom/test_socket_client_shims.py @@ -269,6 +269,11 @@ def test_typed_model_is_serialized_and_sent(self): assert sent["type"] == "Configure" assert sent["language_hints"] == ["en"] + def test_typed_numerals_model_is_serialized_and_sent(self): + ws = _FakeWebSocket() + V2SocketClient(websocket=ws).send_configure(ListenV2Configure(numerals=True)) + assert _sent_json(ws) == {"type": "Configure", "numerals": True} + async def test_typed_model_is_serialized_and_sent_async(self): ws = _FakeAsyncWebSocket() await AsyncV2SocketClient(websocket=ws).send_configure(ListenV2Configure(language_hints=["en"])) diff --git a/tests/wire/test_listen_v1_media.py b/tests/wire/test_listen_v1_media.py index e4264985..d46dbc2a 100644 --- a/tests/wire/test_listen_v1_media.py +++ b/tests/wire/test_listen_v1_media.py @@ -9,3 +9,94 @@ def test_listen_v1_media_transcribe_url() -> None: url="https://dpgr.am/spacewalk.wav", ) verify_request_count(test_id, "POST", "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/v1/listen", None, 1) + + +def test_listen_v1_media_transcribe_url_serializes_all_query_params() -> None: + """All optional transcribe-url query parameters reach the wire.""" + test_id = "listen.v1.media.transcribe_url.query_params" + client = get_client(test_id) + client.listen.v1.media.transcribe_url( + callback="callback", + callback_method="POST", + extra=["extra"], + sentiment=True, + summarize="v2", + tag=["tag"], + topics=True, + custom_topic=["custom_topic"], + custom_topic_mode="extended", + intents=True, + custom_intent=["custom_intent"], + custom_intent_mode="extended", + detect_entities=True, + detect_language=True, + diarize=True, + diarize_model="latest", + dictation=True, + encoding="linear16", + filler_words=True, + keyterm=["keyterm"], + keywords=["keywords"], + language="language", + measurements=True, + model="nova-3", + multichannel=True, + numerals=True, + paragraphs=True, + profanity_filter=True, + punctuate=True, + redact="redact", + replace=["replace"], + search=["search"], + smart_format=True, + utterances=True, + utt_split=1.1, + version="latest", + mip_opt_out=True, + url="https://dpgr.am/spacewalk.wav", + ) + verify_request_count( + test_id, + "POST", + "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/v1/listen", + { + "callback": "callback", + "callback_method": "POST", + "extra": "extra", + "sentiment": "true", + "summarize": "v2", + "tag": "tag", + "topics": "true", + "custom_topic": "custom_topic", + "custom_topic_mode": "extended", + "intents": "true", + "custom_intent": "custom_intent", + "custom_intent_mode": "extended", + "detect_entities": "true", + "detect_language": "true", + "diarize": "true", + "diarize_model": "latest", + "dictation": "true", + "encoding": "linear16", + "filler_words": "true", + "keyterm": "keyterm", + "keywords": "keywords", + "language": "language", + "measurements": "true", + "model": "nova-3", + "multichannel": "true", + "numerals": "true", + "paragraphs": "true", + "profanity_filter": "true", + "punctuate": "true", + "redact": "redact", + "replace": "replace", + "search": "search", + "smart_format": "true", + "utterances": "true", + "utt_split": "1.1", + "version": "latest", + "mip_opt_out": "true", + }, + 1, + ) diff --git a/tests/wire/test_listen_v1_media.py.bak b/tests/wire/test_listen_v1_media.py.bak deleted file mode 100644 index d46dbc2a..00000000 --- a/tests/wire/test_listen_v1_media.py.bak +++ /dev/null @@ -1,102 +0,0 @@ -from .conftest import get_client, verify_request_count - - -def test_listen_v1_media_transcribe_url() -> None: - """Test transcribeUrl endpoint with WireMock""" - test_id = "listen.v1.media.transcribe_url.0" - client = get_client(test_id) - client.listen.v1.media.transcribe_url( - url="https://dpgr.am/spacewalk.wav", - ) - verify_request_count(test_id, "POST", "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/v1/listen", None, 1) - - -def test_listen_v1_media_transcribe_url_serializes_all_query_params() -> None: - """All optional transcribe-url query parameters reach the wire.""" - test_id = "listen.v1.media.transcribe_url.query_params" - client = get_client(test_id) - client.listen.v1.media.transcribe_url( - callback="callback", - callback_method="POST", - extra=["extra"], - sentiment=True, - summarize="v2", - tag=["tag"], - topics=True, - custom_topic=["custom_topic"], - custom_topic_mode="extended", - intents=True, - custom_intent=["custom_intent"], - custom_intent_mode="extended", - detect_entities=True, - detect_language=True, - diarize=True, - diarize_model="latest", - dictation=True, - encoding="linear16", - filler_words=True, - keyterm=["keyterm"], - keywords=["keywords"], - language="language", - measurements=True, - model="nova-3", - multichannel=True, - numerals=True, - paragraphs=True, - profanity_filter=True, - punctuate=True, - redact="redact", - replace=["replace"], - search=["search"], - smart_format=True, - utterances=True, - utt_split=1.1, - version="latest", - mip_opt_out=True, - url="https://dpgr.am/spacewalk.wav", - ) - verify_request_count( - test_id, - "POST", - "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/v1/listen", - { - "callback": "callback", - "callback_method": "POST", - "extra": "extra", - "sentiment": "true", - "summarize": "v2", - "tag": "tag", - "topics": "true", - "custom_topic": "custom_topic", - "custom_topic_mode": "extended", - "intents": "true", - "custom_intent": "custom_intent", - "custom_intent_mode": "extended", - "detect_entities": "true", - "detect_language": "true", - "diarize": "true", - "diarize_model": "latest", - "dictation": "true", - "encoding": "linear16", - "filler_words": "true", - "keyterm": "keyterm", - "keywords": "keywords", - "language": "language", - "measurements": "true", - "model": "nova-3", - "multichannel": "true", - "numerals": "true", - "paragraphs": "true", - "profanity_filter": "true", - "punctuate": "true", - "redact": "redact", - "replace": "replace", - "search": "search", - "smart_format": "true", - "utterances": "true", - "utt_split": "1.1", - "version": "latest", - "mip_opt_out": "true", - }, - 1, - ) diff --git a/tests/wire/test_manage_v1_projects_keys.py b/tests/wire/test_manage_v1_projects_keys.py index 7976bf7d..db30e0e0 100644 --- a/tests/wire/test_manage_v1_projects_keys.py +++ b/tests/wire/test_manage_v1_projects_keys.py @@ -1,5 +1,7 @@ from .conftest import get_client, verify_request_count +from deepgram.requests import CreateKeyV1RequestOneParams + def test_manage_v1_projects_keys_list_() -> None: """Test list endpoint with WireMock""" @@ -23,6 +25,15 @@ def test_manage_v1_projects_keys_create() -> None: verify_request_count(test_id, "POST", "/v1/projects/project_id/keys", None, 1) +def test_manage_v1_projects_keys_create_with_old_request_alias() -> None: + """Test create endpoint with the legacy request alias.""" + test_id = "manage.v1.projects.keys.create.compat" + client = get_client(test_id) + request: CreateKeyV1RequestOneParams = {"key": "value"} + client.manage.v1.projects.keys.create(project_id="project_id", request=request) + verify_request_count(test_id, "POST", "/v1/projects/project_id/keys", None, 1) + + def test_manage_v1_projects_keys_get() -> None: """Test get endpoint with WireMock""" test_id = "manage.v1.projects.keys.get.0" diff --git a/tests/wire/test_manage_v1_projects_keys.py.bak b/tests/wire/test_manage_v1_projects_keys.py.bak deleted file mode 100644 index db30e0e0..00000000 --- a/tests/wire/test_manage_v1_projects_keys.py.bak +++ /dev/null @@ -1,60 +0,0 @@ -from .conftest import get_client, verify_request_count - -from deepgram.requests import CreateKeyV1RequestOneParams - - -def test_manage_v1_projects_keys_list_() -> None: - """Test list endpoint with WireMock""" - test_id = "manage.v1.projects.keys.list_.0" - client = get_client(test_id) - client.manage.v1.projects.keys.list( - project_id="123456-7890-1234-5678-901234", - status="active", - ) - verify_request_count(test_id, "GET", "/v1/projects/123456-7890-1234-5678-901234/keys", {"status": "active"}, 1) - - -def test_manage_v1_projects_keys_create() -> None: - """Test create endpoint with WireMock""" - test_id = "manage.v1.projects.keys.create.0" - client = get_client(test_id) - client.manage.v1.projects.keys.create( - project_id="project_id", - request={"key": "value"}, - ) - verify_request_count(test_id, "POST", "/v1/projects/project_id/keys", None, 1) - - -def test_manage_v1_projects_keys_create_with_old_request_alias() -> None: - """Test create endpoint with the legacy request alias.""" - test_id = "manage.v1.projects.keys.create.compat" - client = get_client(test_id) - request: CreateKeyV1RequestOneParams = {"key": "value"} - client.manage.v1.projects.keys.create(project_id="project_id", request=request) - verify_request_count(test_id, "POST", "/v1/projects/project_id/keys", None, 1) - - -def test_manage_v1_projects_keys_get() -> None: - """Test get endpoint with WireMock""" - test_id = "manage.v1.projects.keys.get.0" - client = get_client(test_id) - client.manage.v1.projects.keys.get( - project_id="123456-7890-1234-5678-901234", - key_id="123456789012345678901234", - ) - verify_request_count( - test_id, "GET", "/v1/projects/123456-7890-1234-5678-901234/keys/123456789012345678901234", None, 1 - ) - - -def test_manage_v1_projects_keys_delete() -> None: - """Test delete endpoint with WireMock""" - test_id = "manage.v1.projects.keys.delete.0" - client = get_client(test_id) - client.manage.v1.projects.keys.delete( - project_id="123456-7890-1234-5678-901234", - key_id="123456789012345678901234", - ) - verify_request_count( - test_id, "DELETE", "/v1/projects/123456-7890-1234-5678-901234/keys/123456789012345678901234", None, 1 - ) diff --git a/tests/wire/test_manage_v1_projects_requests.py b/tests/wire/test_manage_v1_projects_requests.py index 71a888c7..dbbe7cfa 100644 --- a/tests/wire/test_manage_v1_projects_requests.py +++ b/tests/wire/test_manage_v1_projects_requests.py @@ -1,5 +1,6 @@ -from .conftest import get_client, verify_request_count +import datetime +from .conftest import get_client, verify_request_count def test_manage_v1_projects_requests_list_() -> None: """Test list endpoint with WireMock""" @@ -11,6 +12,43 @@ def test_manage_v1_projects_requests_list_() -> None: verify_request_count(test_id, "GET", "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests", None, 1) +def test_manage_v1_projects_requests_list_serializes_all_query_params() -> None: + """All optional request-list query parameters reach the wire.""" + test_id = "manage.v1.projects.requests.list_.query_params" + client = get_client(test_id) + client.manage.v1.projects.requests.list( + project_id="12345678-90ab-cdef-1234-567890abcdef", + start=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"), + end=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"), + limit=1.1, + page=1.1, + accessor="12345678-1234-1234-1234-123456789012", + request_id="12345678-1234-1234-1234-123456789012", + deployment="hosted", + endpoint="listen", + method="sync", + status="succeeded", + ) + verify_request_count( + test_id, + "GET", + "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests", + { + "start": "2024-01-15T09:30:00Z", + "end": "2024-01-15T09:30:00Z", + "limit": "1.1", + "page": "1.1", + "accessor": "12345678-1234-1234-1234-123456789012", + "request_id": "12345678-1234-1234-1234-123456789012", + "deployment": "hosted", + "endpoint": "listen", + "method": "sync", + "status": "succeeded", + }, + 1, + ) + + def test_manage_v1_projects_requests_get() -> None: """Test get endpoint with WireMock""" test_id = "manage.v1.projects.requests.get.0" diff --git a/tests/wire/test_manage_v1_projects_requests.py.bak b/tests/wire/test_manage_v1_projects_requests.py.bak deleted file mode 100644 index f95ac71c..00000000 --- a/tests/wire/test_manage_v1_projects_requests.py.bak +++ /dev/null @@ -1,67 +0,0 @@ -import datetime - -from .conftest import get_client, verify_request_count - - -def test_manage_v1_projects_requests_list_() -> None: - """Test list endpoint with WireMock""" - test_id = "manage.v1.projects.requests.list_.0" - client = get_client(test_id) - client.manage.v1.projects.requests.list( - project_id="12345678-90ab-cdef-1234-567890abcdef", - ) - verify_request_count(test_id, "GET", "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests", None, 1) - - -def test_manage_v1_projects_requests_list_serializes_all_query_params() -> None: - """All optional request-list query parameters reach the wire.""" - test_id = "manage.v1.projects.requests.list_.query_params" - client = get_client(test_id) - client.manage.v1.projects.requests.list( - project_id="12345678-90ab-cdef-1234-567890abcdef", - start=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"), - end=datetime.datetime.fromisoformat("2024-01-15T09:30:00+00:00"), - limit=1.1, - page=1.1, - accessor="12345678-1234-1234-1234-123456789012", - request_id="12345678-1234-1234-1234-123456789012", - deployment="hosted", - endpoint="listen", - method="sync", - status="succeeded", - ) - verify_request_count( - test_id, - "GET", - "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests", - { - "start": "2024-01-15T09:30:00Z", - "end": "2024-01-15T09:30:00Z", - "limit": "1.1", - "page": "1.1", - "accessor": "12345678-1234-1234-1234-123456789012", - "request_id": "12345678-1234-1234-1234-123456789012", - "deployment": "hosted", - "endpoint": "listen", - "method": "sync", - "status": "succeeded", - }, - 1, - ) - - -def test_manage_v1_projects_requests_get() -> None: - """Test get endpoint with WireMock""" - test_id = "manage.v1.projects.requests.get.0" - client = get_client(test_id) - client.manage.v1.projects.requests.get( - project_id="12345678-90ab-cdef-1234-567890abcdef", - request_id="a3f1c9d2-4b7e-4f9a-8c3d-2e5f7b9a1c0d", - ) - verify_request_count( - test_id, - "GET", - "/v1/projects/12345678-90ab-cdef-1234-567890abcdef/requests/a3f1c9d2-4b7e-4f9a-8c3d-2e5f7b9a1c0d", - None, - 1, - ) From 40ea51bfd9ac442596bfb57429736c0969b9b361 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 23 Sep 2026 11:47:28 +0100 Subject: [PATCH 5/8] test(manage): restore wire test import layout --- tests/wire/test_manage_v1_projects_requests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/wire/test_manage_v1_projects_requests.py b/tests/wire/test_manage_v1_projects_requests.py index dbbe7cfa..f95ac71c 100644 --- a/tests/wire/test_manage_v1_projects_requests.py +++ b/tests/wire/test_manage_v1_projects_requests.py @@ -2,6 +2,7 @@ from .conftest import get_client, verify_request_count + def test_manage_v1_projects_requests_list_() -> None: """Test list endpoint with WireMock""" test_id = "manage.v1.projects.requests.list_.0" From 687bacfbd57429d67f0bbf79886c48d392e6a109 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 23 Sep 2026 12:00:22 +0100 Subject: [PATCH 6/8] fix: align source fallback version --- src/deepgram/core/client_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deepgram/core/client_wrapper.py b/src/deepgram/core/client_wrapper.py index 7a8ca0f5..775720e0 100644 --- a/src/deepgram/core/client_wrapper.py +++ b/src/deepgram/core/client_wrapper.py @@ -37,7 +37,7 @@ def get_headers(self) -> typing.Dict[str, str]: try: _sdk_version = _fern_importlib_metadata.version("deepgram-sdk") except _fern_importlib_metadata.PackageNotFoundError: - _sdk_version = "7.10.1" + _sdk_version = "7.10.0" headers: typing.Dict[str, str] = { "User-Agent": "deepgram-sdk/" + _sdk_version, From 1b85f961932a7c4635e0e0da0b82283a63682d4c Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 23 Sep 2026 13:49:47 +0100 Subject: [PATCH 7/8] fix(listen): export runtime numerals type --- .fernignore | 7 +++---- AGENTS.md | 5 +++-- examples/14-transcription-live-websocket-v2.py | 2 +- src/deepgram/__init__.py | 3 +++ src/deepgram/types/__init__.py | 3 +++ tests/custom/test_listen_v2_regen_constraints.py | 8 ++++++++ 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.fernignore b/.fernignore index 256a4ae3..977c2623 100644 --- a/.fernignore +++ b/.fernignore @@ -146,10 +146,9 @@ src/deepgram/agent/v1/types/agent_v1update_listen_listen.py # resurrecting the invalid values. Frozen so Fern won't delete it again. src/deepgram/listen/v2/types/listen_v2close_stream_type.py -# Package __init__.py files that carry hand-applied legacy alias re-exports for the -# above shims. Fern would otherwise regenerate these and strip the legacy entries on -# every regen. Frozen to preserve the public-import surface for renamed types/params. -# Re-apply genuine new generator additions to these files manually after each regen. +# Temporarily frozen generated package barrels that carry hand-applied legacy alias +# re-exports. Before each regen, swap these originals to .bak paths so Fern can add new +# exports; then restore the aliases alongside every genuine generated addition. src/deepgram/__init__.py src/deepgram/agent/__init__.py src/deepgram/agent/v1/__init__.py diff --git a/AGENTS.md b/AGENTS.md index b8cf0904..08f519bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,8 +70,7 @@ Current temporarily frozen files: - `src/deepgram/core/api_error.py`, `src/deepgram/core/parse_error.py` — credential redaction. Every websocket `connect()` path raises `ApiError(headers=dict(headers), ...)` with the full request headers, and both error types stringify that dict, so an unredacted `Authorization` reached `str(e)`, tracebacks, log aggregators and error trackers (which serialise attributes as well as the message). Both now mask credential values at construction via `_secure_logging.redact_sensitive_headers`, preserving non-sensitive headers (`dg-request-id`) for debugging. This is the same threat `_secure_logging.py` covers for the `websockets` DEBUG handshake logs, via the other path to it. Regression coverage in `tests/custom/test_api_error_redaction.py`. Unfreeze if the generator starts redacting credentials itself. - `src/deepgram/core/query_encoder.py` — coerces Python bools to lowercase `"true"`/`"false"` before they reach `urllib.parse.urlencode` (which would otherwise produce `"True"`/`"False"` via `str()` and break websocket query strings). Only the four `*/connect()` paths call `urlencode`; HTTP raw clients hand params to httpx, which lowercases bools itself, so the patch is a no-op for the HTTP path. Once Fern's websocket codegen normalizes bools (or the spec types these as `boolean` end-to-end), this can be unfrozen. - `src/deepgram/listen/v2/types/listen_v2connected.py`, `src/deepgram/listen/v2/types/listen_v2turn_info.py`, `src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py`, `src/deepgram/listen/v2/types/listen_v2configure_success.py`, `src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py`, `src/deepgram/listen/v2/types/listen_v2configure_failure.py`, `src/deepgram/listen/v2/types/listen_v2fatal_error.py` — read-side compatibility for the SDK 7.7 Listen V2 response retype. Through 7.6, `V2SocketClientResponse` contained `typing.Any`, so every response was returned as a raw dict; fixing the union made responses typed models and broke callers using `response["field"]`. These generated response classes inherit the hand-written base above, preserving read-only wire-key subscript access alongside canonical attribute access. Restore direct `UncheckedBaseModel` inheritance and unfreeze these files in the next major release. -- `src/deepgram/types/deepgram_listen_provider_v2.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py` — behavioural back-compat shim for the `language_hint` -> `language_hints` rename (2026-06-15 regen). The public field was historically (incorrectly) singular and accepted a str or a list; the API field is `language_hints` (a list, and the server uses `deny_unknown_fields` so the singular key is rejected on the wire). Each carries a hand-added `model_validator(mode='before')` / `root_validator(pre=True)` that remaps a legacy `language_hint=` kwarg and drops the dead singular key. `deepgram_listen_provider_v2.py` also corrects the EOT description: `eot_threshold=1.0` suppresses confidence-based detection only, while `eot_timeout_ms` still ends idle turns and must be increased for manual ForceEndTurn control. Remove the EOT patch when the API definition provides it; remove and unfreeze the language alias when it is retired in a future major. -- Fern emits the `eot_threshold=1.0` timeout and ForceEndTurn documentation natively as of the 2026-09-23 regeneration; those two temporary documentation patches were removed. +- `src/deepgram/types/deepgram_listen_provider_v2.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py` — behavioural back-compat shim for the `language_hint` -> `language_hints` rename (2026-06-15 regen). The public field was historically (incorrectly) singular and accepted a str or a list; the API field is `language_hints` (a list, and the server uses `deny_unknown_fields` so the singular key is rejected on the wire). Each carries a hand-added `model_validator(mode='before')` / `root_validator(pre=True)` that remaps a legacy `language_hint=` kwarg and drops the dead singular key. Remove and unfreeze the language alias when it is retired in a future major. - `src/deepgram/agent/v1/types/agent_v1update_listen_listen.py` — backward-compat patch for the 2026-07-31 `AgentV1UpdateListen` provider retype. The `provider` field changed from a bare `DeepgramListenProviderV2` to the required discriminated union `AgentV1UpdateListenListenProvider` (`_V1`/`_V2`, discriminant `version`). Carries a hand-added `model_validator(mode='before')` / `root_validator(pre=True)` that coerces a legacy `DeepgramListenProviderV1`/`V2` (or a dict lacking the `version` discriminant) into the new shape so existing callers keep working. Remove and unfreeze when the old provider payloads are retired in a future major. NOTE: this patch was silently lost once (it was absent from `.fernignore`, so a regen overwrote it) — keep it frozen. - `tests/wire/test_manage_v1_projects_keys.py` — restored wire coverage for the legacy `CreateKeyV1RequestOneParams` request alias so future regens do not silently drop that compatibility check - `tests/wire/test_manage_v1_projects_requests.py` — restored query-parameter coverage for `manage.v1.projects.requests.list`. The 2026-08-11 regen simplified the upstream spec *example*, and Fern derives the wire test from the example, so all ten optional query params (and the `datetime` → ISO-8601 `Z` encoding) lost their assertions while the client signature still forwarded them. Frozen for the same reason as the `_keys.py` entry above. @@ -83,6 +82,8 @@ Current temporarily frozen files: Files Fern now owns outright, but that carry a caveat worth knowing before the next regen: +- `src/deepgram/types/listen_v2eot_threshold.py`, `src/deepgram/requests/deepgram_listen_provider_v2.py` — Fern emits the `eot_threshold=1.0` timeout and ForceEndTurn documentation natively as of the 2026-09-23 regeneration, so the prior temporary documentation patches were removed. + - `src/deepgram/core/client_wrapper.py` — unfrozen in the 2026-08-11 regen. The generator's `runtime_version: true` mode now derives the wire version from `importlib.metadata.version("deepgram-sdk")`, which supersedes the old hand-applied `# x-release-please-version` annotations (its `generic` entry was removed from `.github/release-please-config.json` at the same time, since with the annotations gone it was a no-op). **Caveat:** the `except PackageNotFoundError` fallback is a hardcoded version literal that nothing bumps — release-please's `extra-files` now covers only `pyproject.toml` and `.fern/metadata.json`. It only applies where package metadata is undiscoverable (source checkout on `sys.path`, vendored copy, zipapp, some frozen bundles), and it is refreshed only by the next regen. Accepted deliberately rather than re-freezing the file; if that fallback ever needs to be accurate on those paths, re-add a release-please `generic` entry with an annotation on the fallback line. - `src/deepgram/agent/v1/types/agent_v1latency_report.py` and its `requests/` twin — unfrozen in the 2026-08-11 regen because the spec restored `stt_latency` natively, making the hand-added read-side field redundant. `tests/custom/test_latency_report_stt_compat.py` stays frozen and still guards the field's presence, so a future spec removal is caught rather than silently re-breaking readers. diff --git a/examples/14-transcription-live-websocket-v2.py b/examples/14-transcription-live-websocket-v2.py index a49d4ff1..da6f66b7 100644 --- a/examples/14-transcription-live-websocket-v2.py +++ b/examples/14-transcription-live-websocket-v2.py @@ -35,7 +35,7 @@ with client.listen.v2.connect( model="flux-general-en", # Flux STT numerals: format spoken numbers as digits (e.g. "one twenty" -> "120"). - # Connection-time only — it cannot be toggled mid-stream via Configure. + # Can also be changed mid-stream with send_configure(ListenV2Configure(numerals=...)). numerals="true", ) as connection: diff --git a/src/deepgram/__init__.py b/src/deepgram/__init__.py index 0e610bef..f143f308 100644 --- a/src/deepgram/__init__.py +++ b/src/deepgram/__init__.py @@ -163,6 +163,7 @@ ListenV1UtteranceEndMs, ListenV1VadEvents, ListenV1Version, + ListenV2ConfigureNumerals, ListenV2EagerEotThreshold, ListenV2Encoding, ListenV2EotThreshold, @@ -709,6 +710,7 @@ "ListenV1UtteranceEndMs": ".types", "ListenV1VadEvents": ".types", "ListenV1Version": ".types", + "ListenV2ConfigureNumerals": ".types", "ListenV2EagerEotThreshold": ".types", "ListenV2Encoding": ".types", "ListenV2EotThreshold": ".types", @@ -1178,6 +1180,7 @@ def __dir__(): "ListenV1UtteranceEndMs", "ListenV1VadEvents", "ListenV1Version", + "ListenV2ConfigureNumerals", "ListenV2EagerEotThreshold", "ListenV2Encoding", "ListenV2EotThreshold", diff --git a/src/deepgram/types/__init__.py b/src/deepgram/types/__init__.py index 264a979b..57674025 100644 --- a/src/deepgram/types/__init__.py +++ b/src/deepgram/types/__init__.py @@ -194,6 +194,7 @@ from .listen_v1utterance_end_ms import ListenV1UtteranceEndMs from .listen_v1vad_events import ListenV1VadEvents from .listen_v1version import ListenV1Version + from .listen_v2configure_numerals import ListenV2ConfigureNumerals from .listen_v2eager_eot_threshold import ListenV2EagerEotThreshold from .listen_v2encoding import ListenV2Encoding from .listen_v2eot_threshold import ListenV2EotThreshold @@ -458,6 +459,7 @@ "ListenV1UtteranceEndMs": ".listen_v1utterance_end_ms", "ListenV1VadEvents": ".listen_v1vad_events", "ListenV1Version": ".listen_v1version", + "ListenV2ConfigureNumerals": ".listen_v2configure_numerals", "ListenV2EagerEotThreshold": ".listen_v2eager_eot_threshold", "ListenV2Encoding": ".listen_v2encoding", "ListenV2EotThreshold": ".listen_v2eot_threshold", @@ -740,6 +742,7 @@ def __dir__(): "ListenV1UtteranceEndMs", "ListenV1VadEvents", "ListenV1Version", + "ListenV2ConfigureNumerals", "ListenV2EagerEotThreshold", "ListenV2Encoding", "ListenV2EotThreshold", diff --git a/tests/custom/test_listen_v2_regen_constraints.py b/tests/custom/test_listen_v2_regen_constraints.py index af0476e5..aabf70e3 100644 --- a/tests/custom/test_listen_v2_regen_constraints.py +++ b/tests/custom/test_listen_v2_regen_constraints.py @@ -14,9 +14,11 @@ import typing +from deepgram import ListenV2ConfigureNumerals as RootListenV2ConfigureNumerals from deepgram.listen.v2.types.listen_v2close_stream import ListenV2CloseStream from deepgram.listen.v2.types.listen_v2close_stream_type import ListenV2CloseStreamType from deepgram.listen.v2.types.listen_v2turn_info_words_item import ListenV2TurnInfoWordsItem +from deepgram.types import ListenV2ConfigureNumerals from deepgram.types.listen_v2profanity_filter import ListenV2ProfanityFilter @@ -55,3 +57,9 @@ def test_literal_values(self): # Union[Literal["true", "false"], Any] — assert the documented literals. literal = typing.get_args(ListenV2ProfanityFilter)[0] assert set(typing.get_args(literal)) == {"true", "false"} + + +class TestConfigureNumeralsExport: + def test_runtime_configure_numerals_is_exported_from_both_package_barrels(self): + assert ListenV2ConfigureNumerals is bool + assert RootListenV2ConfigureNumerals is bool From 7fa7f6c8255994fc22e0c66d00298d27f3c0fbab Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Wed, 23 Sep 2026 13:59:25 +0100 Subject: [PATCH 8/8] docs(listen): demonstrate runtime numerals update --- examples/14-transcription-live-websocket-v2.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/14-transcription-live-websocket-v2.py b/examples/14-transcription-live-websocket-v2.py index da6f66b7..f9500516 100644 --- a/examples/14-transcription-live-websocket-v2.py +++ b/examples/14-transcription-live-websocket-v2.py @@ -22,6 +22,7 @@ from deepgram.core.events import EventType from deepgram.listen.v2.types import ( ListenV2CloseStream, + ListenV2Configure, ListenV2Connected, ListenV2FatalError, ListenV2TurnInfo, @@ -35,8 +36,7 @@ with client.listen.v2.connect( model="flux-general-en", # Flux STT numerals: format spoken numbers as digits (e.g. "one twenty" -> "120"). - # Can also be changed mid-stream with send_configure(ListenV2Configure(numerals=...)). - numerals="true", + numerals="false", ) as connection: def on_message(message: ListenV2SocketClientResponse) -> None: @@ -62,6 +62,9 @@ def on_message(message: ListenV2SocketClientResponse) -> None: connection.on(EventType.CLOSE, lambda _: print("Connection closed")) connection.on(EventType.ERROR, lambda error: print(f"Error: {type(error).__name__}: {error}")) + # Update formatting for turns transcribed after this Configure message. + connection.send_configure(ListenV2Configure(numerals=True)) + # Send audio in a background thread so start_listening can process responses def send_audio(): audio_path = Path(__file__).parent / "fixtures" / "audio.wav"