fix(python): stop defaulting env-var credentials to an empty string in usage snippets
LU
1d ago+17+17+01234567890123456789-0-0-0123456789
Why
When envVarPrefix is configured, Python usage snippets (README, USAGE.md, docs/sdks/*/README.md) read security values from the environment like this:
with Acme(
api_key=os.getenv("ACME_API_KEY", ""),
) as a_client:
If the variable is unset, the snippet passes an explicit "". The SDK treats that as a provided credential, so it never takes its "no credential" path and instead sends an empty value. For a bearer scheme that is a literal Authorization: Bearer header. Passing None leaves the credential absent, which is what the snippet means.
Checked against an SDK generated from a single bearer-scheme spec (envVarPrefix: ACME, flattenGlobalSecurity: true), with ACME_API_KEY unset unless noted:
| Constructor argument | Authorization sent |
|---|---|
api_key=os.getenv("ACME_API_KEY", "") | Bearer |
api_key=os.getenv("ACME_API_KEY") | not sent |
api_key=os.getenv("ACME_API_KEY"), variable set to secret | Bearer secret |
What changed
languageSpecificEnvVarWrappingintemplates/templates/pythonv2/includes/sanitization.tsnow takes thefieldDefthat the shared security-usage code already passes. It rendersos.getenv("NAME")with no default when thex-envdirective has no default and the target field is optional or nullable.- Typing: when the env-var security feature is enabled, global security is optional. A flattened single-scheme global security parameter is then generated as
Optional[Union[Optional[str], Callable[[], Optional[str]]]] = None, soOptional[str]fromos.getenvis accepted. A usage snippet extracted from the generated USAGE.md passespyrightandmypy --strict. - Required
strfields keep the""default. This covers fields inside security option models and required per-operation security models. PassingOptional[str]there would fail type checking and pydantic validation. In those positions an explicit security object is already being built, so the SDK's environment fallback does not apply either way. - Server URL and other non-security
x-envusages are unchanged, because nofieldDefreaches the wrapper on those paths. - Generated review SDK:
zSDKs/sdk-pythonv2regenerates byte-identical. Its env-var snippets all target requiredstrfields inside security option models or per-operation security models, so none of them change. - Changeset added for
envVarSecurityUsage/pythonv2.
Testing
npm run format,make check-template-pythonv2,git diff --check: clean.mise run test:generator: passed.TARGET=review make build-pythonv2: no diff inzSDKs/; generation-timepyright,mypyandpylintpassed.TARGET=quaternary make test-pythonv2(flattened security withenvVarPrefix): 247 passed, 1 skipped. Generated tests already use a non-empty"value"default and are unaffected.- Generated a Python SDK from a minimal single bearer-scheme spec before and after the change. Snippets changed from
os.getenv("ACME_API_KEY", "")toos.getenv("ACME_API_KEY"), SDK-levelpyright/mypy/pylintpassed, and the header behavior in the table above was observed with anhttpx.MockTransport. - Not run: the full
make testmatrix.
Public-safety check
- This change contains no credentials, customer documents, private repository URLs, private filesystem paths, or unredacted private logs.
- Title, body, comments, and commit messages name no customers or customer-derived identifiers, private paths or trackers, or workflow provenance, and are understandable without private context (
.claude/skills/public-repo-communication/SKILL.md). - Generated fixtures and review SDK changes are public-safe.
- I reviewed
git diff --check.
Summary by cubic
Fixes Python usage snippets so unset environment credentials are left absent instead of being sent as empty strings.
- With
envVarPrefixset, snippets emittedos.getenv("ACME_API_KEY", ""), which the SDK treats as a provided credential and sends as an empty value, such as anAuthorization: Bearerheader. - Optional or nullable security fields now render
os.getenv("ACME_API_KEY")without a default; the generated constructor already acceptsNone. - Required
strfields keep the""default so the snippets remain type-correct underpyrightandmypy --strict. - Non-security
x-envusages such as server URLs are unchanged.