Skip to content
Open
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
4 changes: 2 additions & 2 deletions .fern/metadata.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"cliVersion": "5.95.1",
"generatorName": "fernapi/fern-python-sdk",
"generatorVersion": "5.27.1",
"generatorVersion": "5.39.3",
"generatorConfig": {
"client": {
"class_name": "BaseClient",
Expand All @@ -21,7 +21,7 @@
"skip_validation": true
}
},
"originGitCommit": "596e7bdfa377f40f3301b1d394c9f0215af5f6d4",
"originGitCommit": "330b1dab41a5004329de2c1ea6396dc4b1ea930c",
"originGitCommitIsDirty": true,
"invokedBy": "manual",
"sdkVersion": "7.17.0"
Expand Down
4 changes: 1 addition & 3 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,7 @@ src/deepgram/listen/v2/types/__init__.py
src/deepgram/core/api_error.py
src/deepgram/core/parse_error.py

# Pydantic 2.14+ requires explicit generic annotations for these adapters or
# mypy rejects the generated module with var-annotated errors.
src/deepgram/core/pydantic_utilities.py
# Pydantic 2.14+ adapter annotations are now generated natively.

src/deepgram/core/query_encoder.py

Expand Down
1 change: 0 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ Current temporarily frozen files:
- `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-compat patches for the 2026-05-05 Agent Settings schema restructure. These preserve callable `AgentV1SettingsAgent(...)`, keep `AgentV1Settings.agent` accepting both that wrapper and `agent_id` strings, restore the legacy request TypedDict shapes, remap legacy `messages=[...]` / nested `context=AgentV1SettingsAgentContext(messages=[...])` usage into the new `context={"messages": [...]}` wire shape, and keep read-side `obj.messages` access working.
- `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/core/pydantic_utilities.py` — Pydantic 2.14+ makes mypy reject Fern's unannotated module-level `TypeAdapter` globals. Retain explicit `TypeAdapter[datetime]` / `TypeAdapter[date]` annotations until Fern emits them.
- `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_v2warning.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. Remove and unfreeze the language alias when it is retired in a future major.
- `src/deepgram/types/shared_topics.py`, `src/deepgram/types/shared_intents.py`, `src/deepgram/requests/shared_topics.py`, `src/deepgram/requests/shared_intents.py`, and the generated root/type/request barrels — direct Topics and Intents segments plus their generated exports remain Fern-owned, but carry the v7 deprecated `results` construction facade and Params shape for the hand-written wrappers above. The models normalize legacy `results.{topics,intents}.segments` input to direct `segments` while preserving direct fields and deprecated read facades. Freeze these generated files during the parent reconciliation; retain all direct generated segments and exports alongside the compatibility surface.
Expand Down
28 changes: 14 additions & 14 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[project]
name = "deepgram-sdk"
dynamic = ["version"]
license = "MIT"
license-files = ["LICENSE"]

[tool.poetry]
name = "deepgram-sdk"
Expand All @@ -26,8 +28,7 @@ classifiers = [
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
"License :: OSI Approved :: MIT License"
"Typing :: Typed"
]
packages = [
{ include = "deepgram", from = "src"}
Expand Down
1 change: 0 additions & 1 deletion src/deepgram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,6 @@ def __dir__():
"voice_agent",
]


__all__.extend(
[
"SharedIntentsResults",
Expand Down
1 change: 0 additions & 1 deletion src/deepgram/agent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,6 @@ def __dir__():
"v1",
]


__all__.extend(
[
"AgentV1HistoryContent",
Expand Down
1 change: 0 additions & 1 deletion src/deepgram/agent/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,6 @@ def __dir__():
"settings",
]


__all__.extend(
[
"AgentV1HistoryContent",
Expand Down
1 change: 0 additions & 1 deletion src/deepgram/agent/v1/requests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,6 @@ def __dir__():
"FunctionCallHistoryMessageParams",
]


__all__.extend(
[
"AgentV1HistoryContentParams",
Expand Down
10 changes: 5 additions & 5 deletions src/deepgram/agent/v1/socket_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,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."""
if isinstance(obj, dict):
Expand Down Expand Up @@ -85,6 +84,7 @@ def _sanitize_numeric_types(obj: typing.Any) -> typing.Any:
AgentV1Warning,
AgentV1History,
bytes,
typing.Dict[str, typing.Any],
]


Expand All @@ -93,10 +93,10 @@ def __init__(self, *, websocket: WebSocketClientProtocol):
super().__init__()
self._websocket = websocket

async def __aiter__(self):
async def __aiter__(self) -> typing.AsyncIterator[V1SocketClientResponse]:
async for message in self._websocket:
if isinstance(message, bytes):
yield message
yield message # type: ignore
else:
try:
yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore
Expand Down Expand Up @@ -272,10 +272,10 @@ def __init__(self, *, websocket: websockets_sync_connection.Connection):
super().__init__()
self._websocket = websocket

def __iter__(self):
def __iter__(self) -> typing.Iterator[V1SocketClientResponse]:
for message in self._websocket:
if isinstance(message, bytes):
yield message
yield message # type: ignore
else:
try:
yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore
Expand Down
1 change: 0 additions & 1 deletion src/deepgram/agent/v1/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ def __dir__():
"FunctionCallHistoryMessageFunctionCallsItem",
]


__all__.extend(
[
"AgentV1HistoryContent",
Expand Down
6 changes: 4 additions & 2 deletions src/deepgram/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def __init__(
self,
*,
environment: DeepgramClientEnvironment = DeepgramClientEnvironment.PRODUCTION,
api_key: typing.Optional[str] = os.getenv("DEEPGRAM_API_KEY"),
api_key: typing.Optional[str] = None,
headers: typing.Optional[typing.Dict[str, str]] = None,
timeout: typing.Optional[float] = None,
max_retries: typing.Optional[int] = None,
Expand All @@ -87,6 +87,7 @@ def __init__(
):
_defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None
_defaulted_max_retries = max_retries if max_retries is not None else 2
api_key = api_key if api_key is not None else os.getenv("DEEPGRAM_API_KEY")
if api_key is None:
raise ApiError(
body="The client must be instantiated be either passing in api_key or setting DEEPGRAM_API_KEY"
Expand Down Expand Up @@ -251,7 +252,7 @@ def __init__(
self,
*,
environment: DeepgramClientEnvironment = DeepgramClientEnvironment.PRODUCTION,
api_key: typing.Optional[str] = os.getenv("DEEPGRAM_API_KEY"),
api_key: typing.Optional[str] = None,
headers: typing.Optional[typing.Dict[str, str]] = None,
timeout: typing.Optional[float] = None,
max_retries: typing.Optional[int] = None,
Expand All @@ -263,6 +264,7 @@ def __init__(
):
_defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None
_defaulted_max_retries = max_retries if max_retries is not None else 2
api_key = api_key if api_key is not None else os.getenv("DEEPGRAM_API_KEY")
if api_key is None:
raise ApiError(
body="The client must be instantiated be either passing in api_key or setting DEEPGRAM_API_KEY"
Expand Down
13 changes: 13 additions & 0 deletions src/deepgram/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from ._secure_logging import install_websocket_log_redaction
from .base_client import AsyncBaseClient, BaseClient
from .core.api_error import ApiError
from .transport import install_transport

from deepgram.core.client_wrapper import BaseClientWrapper
Expand Down Expand Up @@ -115,6 +116,12 @@ def __init__(self, *args, **kwargs) -> None:
# Set a placeholder api_key if none provided (base client requires it)
if kwargs.get("api_key") is None:
kwargs["api_key"] = "token"
elif "api_key" in kwargs and kwargs["api_key"] is None:
# Fern now resolves None to DEEPGRAM_API_KEY in BaseClient. Preserve
# the SDK contract that an explicit None disables ambient lookup.
raise ApiError(
body="The client must be instantiated by either passing in api_key or setting DEEPGRAM_API_KEY"
)
elif "api_key" not in kwargs:
# The generated base client takes os.getenv("DEEPGRAM_API_KEY") as a default
# argument, so it is read once at import. Re-read it here so a key set after
Expand Down Expand Up @@ -199,6 +206,12 @@ def __init__(self, *args, **kwargs) -> None:
# Set a placeholder api_key if none provided (base client requires it)
if kwargs.get("api_key") is None:
kwargs["api_key"] = "token"
elif "api_key" in kwargs and kwargs["api_key"] is None:
# Fern now resolves None to DEEPGRAM_API_KEY in AsyncBaseClient.
# Preserve the SDK contract that explicit None disables ambient lookup.
raise ApiError(
body="The client must be instantiated by either passing in api_key or setting DEEPGRAM_API_KEY"
)
elif "api_key" not in kwargs:
# The generated base client takes os.getenv("DEEPGRAM_API_KEY") as a default
# argument, so it is read once at import. Re-read it here so a key set after
Expand Down
Loading
Loading