mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
fix: strip discriminator after dereferencing schemas (#3682)
This commit is contained in:
parent
5338629474
commit
923695bd9a
2 changed files with 95 additions and 0 deletions
|
|
@ -73,6 +73,33 @@ def _strip_remote_refs(obj: Any) -> Any:
|
|||
return obj
|
||||
|
||||
|
||||
def _strip_discriminator(obj: Any) -> Any:
|
||||
"""Recursively remove OpenAPI ``discriminator`` keys from a schema.
|
||||
|
||||
Pydantic emits ``discriminator.mapping`` with values like
|
||||
``#/$defs/ClassName``. After ``$defs`` are inlined and removed by
|
||||
``dereference_refs``, those mapping entries dangle. The keyword is an
|
||||
OpenAPI extension — the ``anyOf`` variants already carry ``const`` on
|
||||
the discriminant field, so the mapping is redundant.
|
||||
|
||||
Only strips ``discriminator`` when it appears alongside ``anyOf`` or
|
||||
``oneOf``, which is where the OpenAPI keyword lives. A property
|
||||
*named* ``discriminator`` (inside ``properties``) is left alone.
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
skip = "discriminator" in obj and ("anyOf" in obj or "oneOf" in obj)
|
||||
# Keys that hold instance data, not sub-schemas — don't recurse.
|
||||
_DATA_KEYS = {"default", "const", "examples", "enum"}
|
||||
return {
|
||||
k: (v if k in _DATA_KEYS else _strip_discriminator(v))
|
||||
for k, v in obj.items()
|
||||
if not (k == "discriminator" and skip)
|
||||
}
|
||||
if isinstance(obj, list):
|
||||
return [_strip_discriminator(item) for item in obj]
|
||||
return obj
|
||||
|
||||
|
||||
def dereference_refs(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Resolve all $ref references in a JSON schema by inlining definitions.
|
||||
|
||||
|
|
@ -135,6 +162,13 @@ def dereference_refs(schema: dict[str, Any]) -> dict[str, Any]:
|
|||
if "$defs" in dereferenced:
|
||||
dereferenced = {k: v for k, v in dereferenced.items() if k != "$defs"}
|
||||
|
||||
# Strip `discriminator` keys — they contain `mapping` values that
|
||||
# point at `#/$defs/...` entries we just removed. `discriminator`
|
||||
# is an OpenAPI extension; after inlining, the `anyOf` variants
|
||||
# already carry `const` on the discriminant field, making the
|
||||
# mapping redundant.
|
||||
dereferenced = _strip_discriminator(dereferenced)
|
||||
|
||||
return dereferenced
|
||||
|
||||
except JsonRefError:
|
||||
|
|
|
|||
|
|
@ -197,6 +197,67 @@ class TestDereferenceRefs:
|
|||
assert country["default"] == "US"
|
||||
assert "$defs" not in result
|
||||
|
||||
def test_strips_discriminator_mapping_after_inlining(self):
|
||||
"""Discriminator.mapping refs dangle after $defs are inlined (#3679)."""
|
||||
schema = {
|
||||
"$defs": {
|
||||
"IdentifyPerson": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {"const": "identify", "type": "string"},
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
"required": ["action", "name"],
|
||||
},
|
||||
"PersonDelete": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {"const": "delete", "type": "string"},
|
||||
},
|
||||
"required": ["action"],
|
||||
},
|
||||
},
|
||||
"anyOf": [
|
||||
{"$ref": "#/$defs/IdentifyPerson"},
|
||||
{"$ref": "#/$defs/PersonDelete"},
|
||||
],
|
||||
"discriminator": {
|
||||
"mapping": {
|
||||
"identify": "#/$defs/IdentifyPerson",
|
||||
"delete": "#/$defs/PersonDelete",
|
||||
},
|
||||
"propertyName": "action",
|
||||
},
|
||||
}
|
||||
result = dereference_refs(schema)
|
||||
|
||||
assert "$defs" not in result
|
||||
assert "discriminator" not in result
|
||||
# The anyOf variants should be inlined with their const values intact
|
||||
assert len(result["anyOf"]) == 2
|
||||
actions = {v["properties"]["action"]["const"] for v in result["anyOf"]}
|
||||
assert actions == {"identify", "delete"}
|
||||
|
||||
def test_preserves_property_named_discriminator(self):
|
||||
"""A field *named* 'discriminator' inside properties must survive."""
|
||||
schema = {
|
||||
"$defs": {
|
||||
"Inner": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"discriminator": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"properties": {
|
||||
"item": {"$ref": "#/$defs/Inner"},
|
||||
},
|
||||
}
|
||||
result = dereference_refs(schema)
|
||||
|
||||
assert "$defs" not in result
|
||||
assert "discriminator" in result["properties"]["item"]["properties"]
|
||||
|
||||
|
||||
class TestCompressSchema:
|
||||
"""Tests for the compress_schema function."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue