Fix array response RootModel and config BASE_URL example (issue #248)#249
Merged
Conversation
phalt
commented
Jun 4, 2026
Contributor
There was a problem hiding this comment.
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 importedschemasmodule. - Generate
pydantic.RootModel[...]subclasses for top-level array schemas instead oflist[...]type aliases, so they satisfyresponse_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.
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
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
2477426 to
80641c3
Compare
80641c3 to
0325946
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 viaresolve_forward_refs_for_client().Bug 2 — Array responses emitted as type aliases, rejected by pydantic in
response_mapArray response schemas were generated as plain type aliases:
A
GenericAlias(list[...]) is not atype, so pydantic raises aValidationErrorwhen it appears inresponse_map: dict[int, type[Any]]:Fix: emit a
pydantic.RootModelsubclass instead:Bug 3 — Generated
config.pydocuments wrong environment variable nameThe generated
config.pydocstring example showed:But
BaseConfigdeclares the field asbase_url, so pydantic-settings reads the env varBASE_URL. BecauseSettingsConfigDicthasextra="ignore", settingAPI_BASE_URLis silently ignored and the client always falls back tohttp://localhost.Fix: updated
config_py.jinja2to documentBASE_URLandbase_url=in the example.Test plan
TestBug2ArrayResponseRootModel— asserts RootModel subclass is generated, passesisinstance(cls, type), and is instantiable with a listTestBug3ConfigEnvVarName— asserts generated config documentsBASE_URLandbase_url=test_complex_schemas.pyupdated to assert RootModel output for existing array response tests