Skip to content

fix: preserve explicit API key semantics - #778

Merged
GregHolmes merged 1 commit into
mainfrom
gh/preserve-explicit-api-key-none
Sep 3, 2026
Merged

fix: preserve explicit API key semantics#778
GregHolmes merged 1 commit into
mainfrom
gh/preserve-explicit-api-key-none

Conversation

@GregHolmes

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
src.deepgram 97% 94% 0
src.deepgram.agent 100% 100% 0
src.deepgram.agent.v1 98% 100% 0
src.deepgram.agent.v1.settings 100% 100% 0
src.deepgram.agent.v1.settings.think 100% 100% 0
src.deepgram.agent.v1.settings.think.models 97% 100% 0
src.deepgram.auth 100% 100% 0
src.deepgram.auth.v1 100% 100% 0
src.deepgram.auth.v1.tokens 97% 100% 0
src.deepgram.core 88% 81% 0
src.deepgram.errors 100% 100% 0
src.deepgram.helpers 100% 95% 0
src.deepgram.listen 100% 100% 0
src.deepgram.listen.v1 98% 93% 0
src.deepgram.listen.v1.media 97% 100% 0
src.deepgram.listen.v2 98% 93% 0
src.deepgram.manage 100% 100% 0
src.deepgram.manage.v1 100% 100% 0
src.deepgram.manage.v1.models 96% 100% 0
src.deepgram.manage.v1.projects 97% 100% 0
src.deepgram.manage.v1.projects.billing 100% 100% 0
src.deepgram.manage.v1.projects.billing.balances 96% 100% 0
src.deepgram.manage.v1.projects.billing.breakdown 97% 100% 0
src.deepgram.manage.v1.projects.billing.fields 97% 100% 0
src.deepgram.manage.v1.projects.billing.purchases 97% 100% 0
src.deepgram.manage.v1.projects.keys 96% 100% 0
src.deepgram.manage.v1.projects.members 97% 100% 0
src.deepgram.manage.v1.projects.members.invites 96% 100% 0
src.deepgram.manage.v1.projects.members.scopes 96% 100% 0
src.deepgram.manage.v1.projects.models 96% 100% 0
src.deepgram.manage.v1.projects.usage 98% 100% 0
src.deepgram.manage.v1.projects.usage.breakdown 97% 100% 0
src.deepgram.manage.v1.projects.usage.fields 97% 100% 0
src.deepgram.read 100% 100% 0
src.deepgram.read.v1 100% 100% 0
src.deepgram.read.v1.text 98% 100% 0
src.deepgram.self_hosted 100% 100% 0
src.deepgram.self_hosted.v1 100% 100% 0
src.deepgram.self_hosted.v1.distribution_credentials 96% 100% 0
src.deepgram.speak 100% 100% 0
src.deepgram.speak.v1 98% 97% 0
src.deepgram.speak.v1.audio 91% 80% 0
src.deepgram.speak.v2 98% 93% 0
src.deepgram.speak.v2.audio 100% 100% 0
src.deepgram.voice_agent 100% 100% 0
src.deepgram.voice_agent.configurations 95% 100% 0
src.deepgram.voice_agent.variables 95% 100% 0
Summary 95% (6498 / 6813) 91% (1419 / 1552) 0

Scope: hand-maintained SDK logic. Fern-generated data models (types/, requests/), package __init__.py files, version.py, and the unused core/http_sse/ scaffolding are excluded — see .coveragerc. Unscoped whole-package coverage is ~70%.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What this PR does

PR #767 made the SDK re-read DEEPGRAM_API_KEY when the client is constructed, but its check treated an omitted api_key argument and an explicit api_key=None as the same thing, so passing None on purpose silently picked up whatever key was in the process environment. This PR changes the check ("api_key" not in kwargs) in both the sync and async clients so explicit None is rejected with an ApiError at construction — which is exactly what the released 7.8.0 does today.

What I checked

  • Does explicit None actually raise? Yes — it now passes through to the generated base client, which raises ApiError when the key is None, and that client's parameters are keyword-only, so the membership check cannot be bypassed positionally.
  • Did the regression ever reach a release? No — #767 merged after 7.8.0 shipped, so no released version ever had the explicit-None-falls-back-to-env behavior; #767 and this fix will ship together.
  • Are the new tests real? Yes — the rewritten load_dotenv-ordering test spawns a subprocess with the key stripped from the environment, which is the only way to genuinely reproduce "imported without a key" (the old in-process test could not). The new explicit-None test fails on current main, so it proves the fix rather than restating it.
  • Everything else passed: CI green across Python 3.10–3.13, the access_token path is unchanged and still covered, and no README/docs/example anywhere describes api_key=None as an environment-fallback idiom.

No changes requested.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What this PR does

PR #767 made the SDK re-read DEEPGRAM_API_KEY when the client is constructed, but its check treated an omitted api_key argument and an explicit api_key=None as the same thing, so passing None on purpose silently picked up whatever key was in the process environment. This PR changes the check ("api_key" not in kwargs) in both the sync and async clients so explicit None is rejected with an ApiError at construction — which is exactly what the released 7.8.0 does today.

What I checked

  • Does explicit None actually raise? Yes — it now passes through to the generated base client, which raises ApiError when the key is None, and that client's parameters are keyword-only, so the membership check cannot be bypassed positionally.
  • Did the regression ever reach a release? No — #767 merged after 7.8.0 shipped, so no released version ever had the explicit-None-falls-back-to-env behavior; #767 and this fix will ship together.
  • Are the new tests real? Yes — the rewritten load_dotenv-ordering test spawns a subprocess with the key stripped from the environment, which is the only way to genuinely reproduce "imported without a key" (the old in-process test could not). The new explicit-None test fails on current main, so it proves the fix rather than restating it.
  • Everything else passed: CI green across Python 3.10–3.13, the access_token path is unchanged and still covered, and no README/docs/example anywhere describes api_key=None as an environment-fallback idiom.

No changes requested.

@GregHolmes
GregHolmes merged commit e675990 into main Sep 3, 2026
10 checks passed
@GregHolmes
GregHolmes deleted the gh/preserve-explicit-api-key-none branch September 3, 2026 10:15
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.

2 participants