fix(python): don't send empty-string security credentials
Why
In a generated Python SDK, a security credential passed as an empty string (for example SDK(api_key_auth="") or Security(access_token="")) was serialized as-is. For bearer, OAuth2 and OpenID Connect schemes this produced Authorization: Bearer , which httpx rejects as an illegal header value (LocalProtocolError). For API keys it sent an empty header or query parameter.
What changed
_parse_security_scheme_value in templates/templates/pythonv2/auxiliary/{{getSourcePath}}/utils/security.py.stmpl returns without writing a header or query parameter when the value is "". This covers top-level fields as well as values nested in security options and scheme models.
What stays the same:
- Scheme selection.
get_securitystill selects the first non-Nonefield in the declared security order, so an explicit""keeps its scheme selected and the request is sent without a credential. It does not fall back to a later scheme. - Environment fallback. An explicit
""does not fall back to theenvVarPrefixenvironment variable. - HTTP Basic and custom schemes. These go through
_parse_basic_auth_schemeand custom handling, so an empty username or password is still sent as before (the existingauth-basic-auth-emptytest covers this). - Only exact
""is affected. Whitespace-only strings and non-string values are not changed.
Generated output: zSDKs/sdk-pythonv2/src/speakeasy/new_openapi/utils/security.py (plus its gen.lock checksum) is the only review SDK change. Usage snippets are not touched.
Changeset: globalSecurity / pythonv2, patch.
Testing
make check-template-pythonv2,npm run format,git diff --check: pass.TARGET=primary make test-pythonv2: 672 passed. Adds:test_global_security_fields_ordering_keeps_empty_string_selected:Security(access_token="", oauth2="global")keeps the first field selected and sends noAuthorizationheader.test_global_security_empty_ignores_env_vars: withSPEAKEASY_ACCESS_TOKENset, bothSecurity()andSecurity(access_token="")send noAuthorizationheader.
TARGET=quaternary make test-pythonv2(flattened global security withenvVarPrefix): 248 passed, 1 skipped. Addstest_global_security_flattening_empty_string_ignores_env_var(SDK(api_key_auth="")withSPEAKEASY_API_KEY_AUTHset sends noAuthorizationheader).TARGET=security-options make test-pythonv2: 6 passed, 194 skipped.- With the template change removed, the empty-string tests fail because an
Authorizationheader is sent. Theomittedcase already passes onmain. It checks that the environment is not consulted when a security object is passed. TARGET=review make build-pythonv2: the review SDK diff contains only the change above.- The new tests record the existing
auth-global-security-fields-orderingandauth-global-security-flattening-env-var-fallbacktest IDs instead of adding new IDs, so other targets' skip lists don't need to change. - Not run: other targets (no template changes there) and 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 generated Python SDKs so empty-string security credentials are never serialized as an empty bearer token, API key header, or query parameter, which httpx rejects.
- Empty-string credentials are skipped when building request security, letting a later non-empty scheme win.
- An explicitly empty global security object now stays unauthenticated; the environment fallback only applies when no security is passed at all.
- Only exact
""counts as unset; whitespace-only values, non-string values, and HTTP Basic/custom schemes keep existing behavior.