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
1 change: 1 addition & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ src/deepgram/core/query_encoder.py
tests/custom/test_api_error_redaction.py
tests/custom/test_agent_history.py
tests/custom/test_agent_update_listen.py
tests/custom/test_api_key_env_resolution.py
tests/custom/test_client.py
tests/custom/test_client_construction_and_helpers.py
tests/custom/test_compat_aliases.py
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Current permanently frozen files:
- `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
- `tests/custom/test_agent_history.py` — hand-written regression test for Agent History websocket payload parsing
- `tests/custom/test_api_key_env_resolution.py` — hand-written regression test that `DEEPGRAM_API_KEY` is resolved when the client is constructed rather than when the module is imported. The generated base client takes `os.getenv(...)` as a default argument, which Python evaluates once at import, so `load_dotenv()` placed below the imports left the default captured as `None` (issue #734). `client.py` re-reads the variable; this pins that, plus explicit-key and `access_token` precedence and the no-key-anywhere error
- `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
- `tests/custom/test_secure_logging.py` — hand-written regression test that the `websockets` Authorization-header DEBUG logs are redacted (API key never logged in clear text)
Expand Down
11 changes: 11 additions & 0 deletions src/deepgram/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
reconnect logic.
"""

import os
import types
import uuid
from typing import Any, Callable, Dict, Optional
Expand Down Expand Up @@ -114,6 +115,11 @@ 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 kwargs.get("api_key") is None:
# 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
# import (load_dotenv below the imports) is still picked up.
kwargs["api_key"] = os.getenv("DEEPGRAM_API_KEY")

super().__init__(*args, **kwargs)
self.session_id = final_session_id
Expand Down Expand Up @@ -193,6 +199,11 @@ 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 kwargs.get("api_key") is None:
# 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
# import (load_dotenv below the imports) is still picked up.
kwargs["api_key"] = os.getenv("DEEPGRAM_API_KEY")

super().__init__(*args, **kwargs)
self.session_id = final_session_id
Expand Down
54 changes: 54 additions & 0 deletions tests/custom/test_api_key_env_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
``DEEPGRAM_API_KEY`` is read when the client is constructed, not when the module
is imported.

The generated base client takes ``os.getenv("DEEPGRAM_API_KEY")`` as a default
argument, which Python evaluates once at import. These tests pin the custom
client's re-read so the common ``load_dotenv()``-below-the-imports layout keeps
working (issue #734).
"""

import typing

import pytest

from deepgram import AsyncDeepgramClient, DeepgramClient
from deepgram.core.api_error import ApiError


@pytest.fixture
def no_env_key(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("DEEPGRAM_API_KEY", raising=False)


def _auth(client: typing.Any) -> typing.Optional[str]:
return client._client_wrapper.api_key


def test_env_key_set_after_import_is_used(
no_env_key: None, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The import already happened above with no key set; this is the load_dotenv case."""
monkeypatch.setenv("DEEPGRAM_API_KEY", "set-after-import")
assert _auth(DeepgramClient()) == "set-after-import"
assert _auth(AsyncDeepgramClient()) == "set-after-import"


def test_explicit_api_key_beats_the_environment(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("DEEPGRAM_API_KEY", "from-env")
assert _auth(DeepgramClient(api_key="explicit")) == "explicit"
assert _auth(AsyncDeepgramClient(api_key="explicit")) == "explicit"


def test_access_token_still_takes_precedence(monkeypatch: pytest.MonkeyPatch) -> None:
"""access_token callers get the placeholder, not the environment key."""
monkeypatch.setenv("DEEPGRAM_API_KEY", "from-env")
assert _auth(DeepgramClient(access_token="tok")) == "token"
assert _auth(AsyncDeepgramClient(access_token="tok")) == "token"


def test_no_key_anywhere_still_raises(no_env_key: None) -> None:
with pytest.raises(ApiError):
DeepgramClient()
with pytest.raises(ApiError):
AsyncDeepgramClient()