Skip to content

Python SDK generator emits unguarded httpx.BasicAuth(Optional[str], ...) for OR (multi-scheme) auth #17502

Description

@el2857

Component

SDKs

Priority

P2 - Medium (Would be helpful)

SDK Language(s)

Python

Bug Description

Problem

For a spec with two auth schemes under OR semantics (e.g. API key/basic or OAuth), the generated BaseClientWrapper.get_headers emits:

headers["Authorization"] = httpx.BasicAuth(self._get_api_key(), "")._auth_header
# _get_api_key(self) -> typing.Optional[str]

httpx.BasicAuth requires str | bytes, but the per-scheme getter is Optional[str] (you can satisfy auth via the other scheme). This:

  • fails the generated SDK's own mypy . (Argument 1 to "BasicAuth" has incompatible type "str | None"; expected "str | bytes" [arg-type]), and
  • crashes at runtime on the token-only path (httpx.BasicAuth(None, "") raises TypeError, and get_headers runs on every request, sync and async).

Root cause

In generators/python/src/fern_python/generators/sdk/core_utilities/client_wrapper_generator.py, _get_write_get_headers_body decides whether to null-guard the BasicAuth assignment based only on is_auth_mandatory / the optional-auth config (guarded branch ~lines 1017–1051; unguarded branch ~lines 1052–1075). It ignores whether the credential getter is actually optional. With OR auth (AuthSchemesRequirement.ANY, multiple schemes), is_auth_mandatory is True and optional-auth defaults False, so it takes the unguarded branch even though the getter is Optional[str].

Proposed fix

Guard the BasicAuth assignment whenever a non-omitted credential getter is optional, reusing the existing guarded emission:

api_key = self._get_api_key()
if api_key is not None:
    headers["Authorization"] = httpx.BasicAuth(api_key, "")._auth_header

i.e. treat per-scheme getters as optional when the requirement is ANY with multiple schemes. This is type-correct (mypy narrows to str) and runtime-correct (no None basic header; the bearer block still takes precedence). Prefer this over a typing.cast, which hides the None runtime path.

Repro

Generate a Python SDK from a spec with API-key/basic OR OAuth, is_auth_mandatory: true, no optional-auth; inspect core/client_wrapper.pyget_headers and run mypy.

Versions

Fern CLI version 5.90.1
Fern python sdk version 5.27.1

Workaround

Setting optional-auth: true in the generator config forces the guarded emission, but the generator shouldn't require that flag to produce type-safe code for a valid OR-auth spec.

Are you interested in contributing a fix?

No

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions