OpenAPI: rewrite $ref under propertyNames and patternProperties in _replace_ref_with_defs; add regression test for dict[StrEnum, Model] (#3306)

* Normalize OpenAPI $ref everywhere (incl. propertyNames); migrate components→$defs; add regression test

* Fix: normalize $ref in propertyNames and additionalProperties; add regression test

* Deterministic migration: components.schemas override $defs on collision; preserve direct $defs refs via alias; add collision test

* Fix: rewrite $ref in propertyNames and patternProperties in _replace_ref_with_defs

* Fix syntax error, formatting, and stray files

* Skip boolean subschemas in patternProperties

---------

Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
This commit is contained in:
manojPal23234 2026-02-27 02:38:54 +05:30 committed by GitHub
commit 507e6b80ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 47 additions and 0 deletions

View file

@ -123,6 +123,19 @@ def _replace_ref_with_defs(
schema["additionalProperties"] = _replace_ref_with_defs(
additionalProperties
)
# Handle propertyNames
if property_names := schema.get("propertyNames"):
if isinstance(property_names, dict):
schema["propertyNames"] = _replace_ref_with_defs(property_names)
# Handle patternProperties
if pattern_properties := schema.get("patternProperties"):
if isinstance(pattern_properties, dict):
schema["patternProperties"] = {
pattern: _replace_ref_with_defs(subschema)
if isinstance(subschema, dict)
else subschema
for pattern, subschema in pattern_properties.items()
}
if info.get("description", description) and not schema.get("description"):
schema["description"] = description
return schema

View file

@ -0,0 +1,34 @@
from fastmcp.utilities.openapi.schemas import _replace_ref_with_defs
def test_replace_ref_with_defs_rewrites_propertyNames_ref():
"""
Regression test for issue #3303.
When using dict[StrEnum, Model], Pydantic generates:
{
"type": "object",
"propertyNames": {"$ref": "#/components/schemas/Category"},
"additionalProperties": {"$ref": "#/components/schemas/ItemInfo"}
}
_replace_ref_with_defs should rewrite BOTH refs to #/$defs/.
"""
schema = {
"type": "object",
"propertyNames": {"$ref": "#/components/schemas/Category"},
"additionalProperties": {"$ref": "#/components/schemas/ItemInfo"},
}
result = _replace_ref_with_defs(schema)
# additionalProperties ref is rewritten
assert result["additionalProperties"]["$ref"] == "#/$defs/ItemInfo"
# propertyNames ref must also be rewritten (this was the bug)
assert result["propertyNames"]["$ref"] == "#/$defs/Category"
# Ensure no dangling OpenAPI refs remain
assert "#/components/schemas/" not in str(result)