Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Current permanently frozen files:
- `src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider_v1.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider_v2.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider_v2language_hint.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider_v1.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider_v2.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider_v1.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider_v2.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider_v2language_hint.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent_listen_provider_v1.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent_listen_provider_v2.py` — hand-written compatibility aliases for the 2026-05-14 spec dedup that consolidated `AgentV1SettingsAgent[Context]ListenProviderV{1,2,V2LanguageHint}` into top-level `DeepgramListenProvider*` types
- `src/deepgram/types/deepgram_listen_provider_v2language_hint.py`, `src/deepgram/requests/deepgram_listen_provider_v2language_hint.py` — hand-written shims recreating the top-level `DeepgramListenProviderV2LanguageHint`/`...Params` type (`Union[str, List[str]]`) that Fern removed in the 2026-06-15 regen. The `*V2LanguageHint` listen-provider aliases above import from these and they remain part of the public import surface, so they are recreated by hand and frozen so Fern won't delete them again
- `src/deepgram/listen/v2/types/listen_v2close_stream_type.py` — hand-written shim recreating `ListenV2CloseStreamType`, which Fern removed in the 2026-06-15 regen (docs #946). The original generated type wrongly allowed `Union[Literal["Finalize","CloseStream","KeepAlive"], Any]` (v2 copied v1's control-message enum); a CloseStream message's `type` can only ever be `"CloseStream"`. Recreated as the corrected `Literal["CloseStream"]` to preserve the public import path without resurrecting the invalid values. Re-exported from the three `listen` `__init__.py` files (temporarily frozen, below).
- `src/deepgram/transport_interface.py`, `src/deepgram/transport.py`, `src/deepgram/transports/` — custom transport layer
- `src/deepgram/transport_interface.py`, `src/deepgram/transport.py`, `src/deepgram/transports/` — custom transport layer. `_TARGET_MODULES` in `transport.py` must include every generated `client.py` and `raw_client.py` that uses the patchable WebSocket connector symbols; update it whenever a regen adds a WebSocket surface. `tests/custom/test_transport.py` enforces this so `transport_factory` cannot silently fall back to Deepgram Cloud.
- `tests/custom/test_agent_history.py` — hand-written regression test for Agent History websocket payload parsing
- `tests/custom/test_compat_aliases.py` — hand-written regression test for backward-compatible alias imports after regen renames
- `tests/custom/test_query_encoder.py` — hand-written regression test that `core/query_encoder.py` coerces Python bools to lowercase `"true"`/`"false"` before `urlencode` so websocket query strings stay wire-correct
Expand Down
6 changes: 4 additions & 2 deletions src/deepgram/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

# ---------------------------------------------------------------------------
# Module paths that contain the websocket references we need to patch.
# All 8 are auto-generated by Fern — we never modify their source.
# All 10 are auto-generated by Fern — we never modify their source.
# ---------------------------------------------------------------------------
_TARGET_MODULES = [
"deepgram.listen.v1.raw_client",
Expand All @@ -36,6 +36,8 @@
"deepgram.listen.v2.client",
"deepgram.speak.v1.raw_client",
"deepgram.speak.v1.client",
"deepgram.speak.v2.raw_client",
"deepgram.speak.v2.client",
"deepgram.agent.v1.raw_client",
"deepgram.agent.v1.client",
]
Expand Down Expand Up @@ -102,7 +104,7 @@ def install_transport(
sync_factory: Optional[Callable] = None,
async_factory: Optional[Callable] = None,
) -> None:
"""Monkey-patch the 8 auto-generated modules to use custom transports.
"""Monkey-patch the 10 auto-generated modules to use custom transports.

Parameters
----------
Expand Down
54 changes: 51 additions & 3 deletions tests/custom/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@

import json
import sys
from typing import Any, Dict, Iterator, List
from pathlib import Path
from typing import Any, Dict, Iterator, List, Set
from unittest.mock import MagicMock

import pytest

import deepgram
from deepgram.transport import (
_TARGET_MODULES,
AsyncTransport,
SyncTransport,
_AsyncTransportShim,
_SyncTransportShim,
_TARGET_MODULES,
install_transport,
restore_transport,
)


# ---------------------------------------------------------------------------
# Mock transport implementations
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -530,3 +531,50 @@ def test_async_transport_factory_auto_disables_reconnect(self):
from deepgram.client import AsyncDeepgramClient
client = AsyncDeepgramClient(api_key="test-key", transport_factory=factory)
assert client.reconnect is False


# ---------------------------------------------------------------------------
# _TARGET_MODULES completeness
# ---------------------------------------------------------------------------

_PATCHED_SYMBOLS = ("websockets_sync_client", "websockets_client_connect")


def _discover_websocket_modules() -> Set[str]:
"""Return every `deepgram` module that references a patchable websocket symbol.

Derived from the package source, deliberately not from `_TARGET_MODULES`. A
check that iterates `_TARGET_MODULES` can only confirm the list is internally
consistent; it cannot detect a websocket client missing from the list,
because the missing entry is never iterated.
"""
package_root = Path(deepgram.__file__).parent
discovered: Set[str] = set()

for path in sorted(package_root.rglob("*.py")):
# transport.py names both symbols as its patch targets, so including it
# here would make the scan match itself.
if path.name == "transport.py":
continue

source = path.read_text(encoding="utf-8")
if any(symbol in source for symbol in _PATCHED_SYMBOLS):
relative = path.relative_to(package_root).with_suffix("")
discovered.add(".".join(("deepgram",) + relative.parts))

return discovered


class TestTargetModuleCompleteness:
def test_every_websocket_module_is_registered_for_patching(self):
missing = sorted(_discover_websocket_modules() - set(_TARGET_MODULES))

assert not missing, (
"websocket client module(s) absent from _TARGET_MODULES, so a custom "
"transport_factory is silently not applied to them: " + ", ".join(missing)
)

def test_discovery_locates_a_known_websocket_module(self):
# Guards the guard: if the scan silently found nothing, the completeness
# check above would pass vacuously.
assert "deepgram.listen.v1.raw_client" in _discover_websocket_modules()
Loading