Repository navigation
fix(models): do not cache a discriminator mapping built from unbuilt schemas - #2025
Open
sushant-me wants to merge 1 commit into
Open
sushant-me wants to merge 1 commit into
sushant-me wants to merge 1 commit into
Conversation
…schemas Reading a discriminated union caches the key -> variant mapping on the union for the life of the process. `_build_discriminated_union_meta` reads each variant's `__pydantic_core_schema__`, and the SDK defers Pydantic builds, so a variant that another thread is still building has no readable schema yet. That variant was silently skipped and the resulting incomplete mapping was cached anyway, so the union then decoded to the wrong variant until the process restarted, with the first-variant fallback that exists for exactly this kind of mismatch made useless because the discriminator lookup silently pointed at the wrong type. The skip in that loop conflated two different situations, which is why the `None` return from `_extract_field_schema_pv2` could not be reused: a variant whose schema has not finished building cannot contribute, whereas a variant that genuinely does not declare the discriminator legitimately contributes nothing. `_has_built_model_schema` separates them. A variant whose schema is not yet a model/model-fields schema marks the mapping incomplete, and an incomplete mapping is returned to the caller but not cached, so the next call rebuilds it once the build has finished. Complete mappings cache exactly as before. The test is deterministic rather than threaded: it replaces one variant's `__pydantic_core_schema__` with an unbuilt one, which is the state the racing thread produces, so no thread timing is involved.
This branch has not been deployed
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
A discriminated union caches its key → variant mapping on the union for the life of the process.
_build_discriminated_union_metareads each variant's__pydantic_core_schema__, and the SDK defers Pydantic builds — so a variant that another thread is still building has no readable schema yet. That variant is silently skipped, and the resulting incomplete mapping is cached anyway, which leaves the union decoding to the wrong variant until the process restarts.The first-variant fallback in
construct_typeexists to absorb a discriminator/literal mismatch, but it cannot help here: the discriminator lookup succeeds and silently returns the wrong type.The fix
An incomplete mapping is returned to the caller but not cached, so the next call rebuilds it once the deferred build has finished. Complete mappings cache exactly as before.
The part that makes this more than a one-line change, and worth a reviewer's attention: the
if not field: continuein that loop conflates two different situations, so theNonereturn from_extract_field_schema_pv2could not simply be reused.Treating both as incomplete would mean never caching any real mapping, so
_has_built_model_schemadistinguishes them by inspecting whether the variant's schema is amodel/model-fieldsschema yet.Tests
test_discriminated_unions_incomplete_mapping_is_not_cachedis deterministic rather than threaded — it replaces one variant's__pydantic_core_schema__with an unbuilt one ({"type": "definition-ref"}), which is exactly the state the racing thread sees, so nothing depends on thread timing. It asserts:Verified:
uv run --isolated --all-extras pytest tests/test_models.py -q→ 62 passed.Notes
completepath via the missing-field_infobranch.DEFER_PYDANTIC_BUILD, and this does not make the first concurrent decodes faster. It only stops one unlucky thread from poisoning the union permanently.This mirrors a bug reported against the equivalent generated SDK at openai/openai-python#4036, where the same code shape exists; that repository restricts PR creation to collaborators, so the analysis went into the issue there instead. I am flagging it here because the defect is present in this codebase independently of that one.