Skip to content

Fix array response RootModel and config BASE_URL example (issue #248)#249

Merged
phalt merged 4 commits into
mainfrom
claude/busy-cray-LvHx9
Jun 5, 2026
Merged

Fix array response RootModel and config BASE_URL example (issue #248)#249
phalt merged 4 commits into
mainfrom
claude/busy-cray-LvHx9

Conversation

@phalt

@phalt phalt commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes two remaining bugs from #248 that were not addressed by PR #247.

Bug 1 (enum forward refs in client.py) was already resolved by PR #247 via resolve_forward_refs_for_client().

Bug 2 — Array responses emitted as type aliases, rejected by pydantic in response_map

Array response schemas were generated as plain type aliases:

# before
UploadFaces200Response = list[FaceItem]

A GenericAlias (list[...]) is not a type, so pydantic raises a ValidationError when it appears in response_map: dict[int, type[Any]]:

pydantic_core.ValidationError: 1 validation error for RequestContext
response_map.200
  Input should be a type [type=is_type, input_value=list[FaceItem]]

Fix: emit a pydantic.RootModel subclass instead:

# after
class UploadFaces200Response(pydantic.RootModel[list[FaceItem]]):
    pass

Bug 3 — Generated config.py documents wrong environment variable name

The generated config.py docstring example showed:

export API_BASE_URL="https://api.example.com"
config = Config(api_base_url=..., ...)

But BaseConfig declares the field as base_url, so pydantic-settings reads the env var BASE_URL. Because SettingsConfigDict has extra="ignore", setting API_BASE_URL is silently ignored and the client always falls back to http://localhost.

Fix: updated config_py.jinja2 to document BASE_URL and base_url= in the example.

Test plan

  • TestBug2ArrayResponseRootModel — asserts RootModel subclass is generated, passes isinstance(cls, type), and is instantiable with a list
  • TestBug3ConfigEnvVarName — asserts generated config documents BASE_URL and base_url=
  • test_complex_schemas.py updated to assert RootModel output for existing array response tests

Comment thread clientele/settings.py Outdated
Comment thread tests/conftest.py Outdated
Comment thread clientele/generators/shared/utils.py Outdated

Copilot AI 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.

Pull request overview

This PR fixes multiple OpenAPI client-generation issues that caused generated clients to fail at runtime (unresolvable enum forward refs in client.py, invalid array response “types” in response_map) and corrects generated configuration documentation (env var name for base_url). It also adds regression tests and a small Python 3.14 pre-release compatibility shim for the test suite.

Changes:

  • Qualify quoted forward references in generated client parameter type strings so typing.get_type_hints() can resolve them via the imported schemas module.
  • Generate pydantic.RootModel[...] subclasses for top-level array schemas instead of list[...] type aliases, so they satisfy response_map: dict[int, type[Any]].
  • Update generated config docs to reference BASE_URL / base_url= and add regression coverage + a reproduction OpenAPI spec.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
clientele/generators/shared/utils.py Adds helper to rewrite quoted forward refs into schemas.<Name> references for generated client signatures.
clientele/generators/base_clients.py Applies forward-ref qualification to query/path parameter types during client generation.
clientele/generators/shared/generators/schemas.py Switches array schema emission to a pydantic.RootModel subclass template instead of a type alias template.
clientele/generators/api/templates/schema_root_model.jinja2 New template used to generate RootModel subclasses for array schemas.
clientele/generators/api/templates/config_py.jinja2 Fixes generated config doc examples to use BASE_URL and base_url=.
clientele/settings.py Improves Python version parsing to handle pre-release suffixes (e.g., 3.14.0rc1).
tests/conftest.py Adds a Python 3.14 RC compatibility shim for typing._eval_type keyword-arg differences.
tests/test_issue_248.py Adds regression tests covering issue #248’s three reported failures.
tests/test_complex_schemas.py Updates existing array-response tests to assert RootModel generation.
example_openapi_specs/issue_248.json Adds a minimal OpenAPI spec reproducing the reported failures.
CHANGELOG.md Documents the fixes under a new 2.2.0 UNRELEASED section.
docs/CHANGELOG.md Mirrors changelog updates in the docs changelog.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CHANGELOG.md Outdated
Comment thread docs/CHANGELOG.md Outdated
phalt pushed a commit that referenced this pull request Jun 4, 2026
- Revert settings.py to original — the pre-release version workaround
  was out of scope; CI runs on stable Python releases where the original
  int() conversion is fine.
- Remove tests/conftest.py — the typing._eval_type monkey-patch for
  Python 3.14rc2 was overblown and unclear; unnecessary for CI.
- Remove inline `import re` statements inside qualify_forward_refs_with_schemas()
  and remove_forward_ref_quotes() — re is already imported at the top of
  utils.py so the redundant local imports are dead weight.

https://claude.ai/code/session_011e1xPCeW7nD8rn9vWppPPm
claude added 3 commits June 5, 2026 07:51
Bug 1 (enum forward refs in client.py) was resolved by PR #247 via
resolve_forward_refs_for_client().

Two bugs remain unaddressed:

Bug 2 - Array response schemas are emitted as bare type aliases
  (e.g. UploadFaces200Response = list[FaceItem]).  A GenericAlias is
  not a type, so pydantic rejects it in response_map: dict[int, type[Any]].
  Fix should emit a pydantic.RootModel subclass instead.

Bug 3 - Generated config.py example says API_BASE_URL but pydantic-settings
  reads BASE_URL (the field is named base_url).  The wrong name is silently
  ignored (extra='ignore') and the client falls back to http://localhost.
  Fix should document BASE_URL and base_url= in the example.

https://claude.ai/code/session_011e1xPCeW7nD8rn9vWppPPm
… schemas

Previously the generator emitted array response schemas as plain type aliases:

    UploadFaces200Response = list[FaceItem]

A Python GenericAlias (list[...]) is not a type, so pydantic raises a
ValidationError when it is placed in response_map: dict[int, type[Any]]:

    pydantic_core.ValidationError: 1 validation error for RequestContext
    response_map.200
      Input should be a type [type=is_type, input_value=list[FaceItem]]

Fix: add schema_root_model.jinja2 template and update make_schema_class() in
SchemasGenerator to render a pydantic.RootModel subclass for any schema whose
top-level type is "array":

    class UploadFaces200Response(pydantic.RootModel[list[FaceItem]]):
        pass

A RootModel subclass is a real class so isinstance(cls, type) is True and
pydantic accepts it in response_map without error.  Instances are created with
RootModel(root=[...]) and the parsed list is available as .root.

Updated test_complex_schemas.py to assert RootModel output instead of the
old type-alias output.

https://claude.ai/code/session_011e1xPCeW7nD8rn9vWppPPm
…ASE_URL

The generated config.py docstring showed:

    export API_BASE_URL="https://api.example.com"
    config = Config(api_base_url=..., ...)

But BaseConfig declares the field as base_url, so pydantic-settings maps it
to the environment variable BASE_URL.  Because SettingsConfigDict has
extra="ignore", setting API_BASE_URL is silently ignored and the client
always falls back to http://localhost.

Fix: update config_py.jinja2 to document the correct env var name BASE_URL
and the correct kwarg name base_url= in the direct-instantiation example.

https://claude.ai/code/session_011e1xPCeW7nD8rn9vWppPPm
@phalt phalt force-pushed the claude/busy-cray-LvHx9 branch 2 times, most recently from 2477426 to 80641c3 Compare June 5, 2026 08:31
@phalt phalt force-pushed the claude/busy-cray-LvHx9 branch from 80641c3 to 0325946 Compare June 5, 2026 08:31
@phalt phalt changed the title Fix three code generation bugs: enum forward refs, array RootModel, config docs Fix array response RootModel and config BASE_URL example (issue #248) Jun 5, 2026
@phalt phalt merged commit 54f585d into main Jun 5, 2026
8 checks passed
@phalt phalt deleted the claude/busy-cray-LvHx9 branch June 5, 2026 08:47
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.

3 participants