Skip to content

fix: resolve DEEPGRAM_API_KEY when the client is constructed (closes #734) - #767

Merged
GregHolmes merged 1 commit into
deepgram:mainfrom
Kayvan-Zahiri:fix/734-api-key-env-at-construction
Sep 1, 2026
Merged

fix: resolve DEEPGRAM_API_KEY when the client is constructed (closes #734)#767
GregHolmes merged 1 commit into
deepgram:mainfrom
Kayvan-Zahiri:fix/734-api-key-env-at-construction

Conversation

@Kayvan-Zahiri

Copy link
Copy Markdown
Contributor

Closes #734.

api_key defaults to os.getenv("DEEPGRAM_API_KEY") in the generated base client's signature (base_client.py:78, :254). Python evaluates default arguments once, at import, so the idiomatic layout fails:

from deepgram import DeepgramClient   # default captured as None here
load_dotenv()                         # key exists only now
DeepgramClient()                      # raises, though the variable is set

The fix re-reads the variable in DeepgramClient.__init__ / AsyncDeepgramClient.__init__ when no api_key was passed.

Why client.py and not the generated file. base_client.py is Fern output and a regen would drop the change. src/deepgram/client.py is in .fernignore, and AGENTS.md lists it under "Never unfreeze (permanently frozen)", so it is the durable place for this.

Behaviour that does not change, each covered by a test: an explicit api_key= still wins over the environment, access_token= still takes precedence and still gets the "token" placeholder, and with no key anywhere the same ApiError is raised.

Test added at tests/custom/test_api_key_env_resolution.py, registered in .fernignore and in the AGENTS.md frozen-files list. Verified non-vacuous: with the fix reverted, exactly one of the four fails. pytest tests/custom is 858 passed, 1 skipped; ruff check clean.

The generated base client takes os.getenv("DEEPGRAM_API_KEY") as a default
argument, so Python evaluates it once at import. The idiomatic layout of
imports at the top and load_dotenv() below them therefore fails: the default
is captured as None before the key exists, and DeepgramClient() raises even
though the variable is set by the time it is called.

client.py is permanently frozen in .fernignore, so the re-read goes there
rather than in the generated file.

Closes deepgram#734
@GregHolmes
GregHolmes merged commit ec362ec into deepgram:main Sep 1, 2026
10 checks passed
GregHolmes added a commit that referenced this pull request Sep 3, 2026
## Summary

- resolve `DEEPGRAM_API_KEY` at construction only when the `api_key`
argument is omitted
- preserve the existing behavior where explicit `api_key=None` rejects
authentication instead of silently using an ambient process credential
- isolate the import-time environment regression in a subprocess that
starts without `DEEPGRAM_API_KEY`
- add sync and async coverage for explicit-`None` behavior

Follow-up to #767 and #734.

## Why

#767 correctly fixed delayed environment loading, but its
`kwargs.get("api_key") is None` check treated an omitted argument and
explicit `None` as equivalent. In tests, multi-tenant services, or
applications intentionally suppressing ambient authentication, explicit
`None` must not activate a process-wide credential.

## Verification

- full Docker-backed suite: `1020 passed, 1 skipped`
- `mypy src`: clean across 901 source files
- Ruff: clean for both changed files
- seven-scenario sync/async credential probe: all passed
- regression tests proven non-vacuous against both the pre-#767 baseline
and the original #767 implementation
- `git diff --check`: clean
GregHolmes added a commit that referenced this pull request Sep 3, 2026
🤖 I have created a release *beep* *boop*
---


##
[7.8.1](v7.8.0...v7.8.1)
(2026-09-03)


### Bug Fixes

* **TextBuilder:** `ssml_to_deepgram()` now preserves a `<phoneme>`
pronunciation when its valid `ph` and `alphabet` attributes appear in
either order.
([#741](#741))
([7fd4b63](7fd4b63))
* **Credentials:** Explicitly passing `api_key=None` continues to
disable ambient `DEEPGRAM_API_KEY` lookup, which is important for
multi-tenant and test environments.
([#778](#778))
([e675990](e675990))
* **Credentials:** `DeepgramClient()` and `AsyncDeepgramClient()` now
resolve `DEEPGRAM_API_KEY` when constructed, so `load_dotenv()` can run
after importing the SDK. Closes
[#734](#734).
([#767](#767))
([ec362ec](ec362ec))
* **Custom transports:** Speak V2 WebSocket connections now honor
`transport_factory`, matching the routing behavior of other WebSocket
APIs for proxies, test doubles, and custom-hosted transports.
([#766](#766))
([0980663](0980663))


### Documentation

* **Transcription:** Clarified that Nova-3 assumes English when
`language` is omitted; non-English and multilingual audio require an
explicit language such as `fr` or `multi`.
([#771](#771))
([4574337](4574337))
* **Examples:** Added Listen V1 live microphone transcription with
optional `sounddevice`, device selection, bounded audio buffering,
transcript output, and clean Ctrl-C shutdown.
([#780](#780))
([08f0471](08f0471))
* **Examples:** Added a resilient Listen V1 live transcription pattern
with exponential backoff, reconnect-aware audio buffering, timestamp
continuity, and clean shutdown.
([#776](#776))
([96b2d11](96b2d11))
* **Examples:** Added an application-owned Voice Agent session recorder
that serializes received transcripts, function calls, and latency
reports as JSON while leaving consent, redaction, retention, and storage
policy to the application. Closes
[#775](#775).
([#781](#781))
([30ad152](30ad152))
* **Text-to-Speech:** Corrected streaming synthesis snippets to iterate
the response byte chunks instead of accessing a nonexistent `.stream`
attribute.
([#749](#749))
([178724e](178724e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Greg Holmes <greg.holmes@deepgram.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeepgramClient() reads DEEPGRAM_API_KEY at import time, not at instantiation

2 participants