Spaces:
Running
Running
Fix max recursion error when pruning OpenAPI definitions (#1092)
Browse files* Fix max recursion error when pruning open api definitions
* Remove mutable default parameter in def_is_referenced function
src/fastmcp/utilities/json_schema.py
CHANGED
|
@@ -67,13 +67,24 @@ def _prune_unused_defs(schema: dict) -> dict:
|
|
| 67 |
walk(value, current_def=def_name)
|
| 68 |
|
| 69 |
# Figure out what defs were referenced directly or recursively
|
| 70 |
-
def def_is_referenced(def_name):
|
| 71 |
if def_name in root_defs:
|
| 72 |
return True
|
| 73 |
references = referenced_by.get(def_name)
|
| 74 |
if references:
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
return True
|
| 78 |
return False
|
| 79 |
|
|
|
|
| 67 |
walk(value, current_def=def_name)
|
| 68 |
|
| 69 |
# Figure out what defs were referenced directly or recursively
|
| 70 |
+
def def_is_referenced(def_name, parent_def_names: set[str] | None = None):
|
| 71 |
if def_name in root_defs:
|
| 72 |
return True
|
| 73 |
references = referenced_by.get(def_name)
|
| 74 |
if references:
|
| 75 |
+
if parent_def_names is None:
|
| 76 |
+
parent_def_names = set()
|
| 77 |
+
|
| 78 |
+
# Handle recursion by excluding references already present in parent references
|
| 79 |
+
parent_def_names = parent_def_names | {def_name}
|
| 80 |
+
valid_references = [
|
| 81 |
+
reference
|
| 82 |
+
for reference in references
|
| 83 |
+
if reference not in parent_def_names
|
| 84 |
+
]
|
| 85 |
+
|
| 86 |
+
for reference in valid_references:
|
| 87 |
+
if def_is_referenced(reference, parent_def_names):
|
| 88 |
return True
|
| 89 |
return False
|
| 90 |
|
tests/utilities/test_json_schema.py
CHANGED
|
@@ -107,6 +107,79 @@ class TestPruneUnusedDefs:
|
|
| 107 |
result = _prune_unused_defs(schema)
|
| 108 |
assert "$defs" not in result
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
def test_array_references_kept(self):
|
| 111 |
"""Test that definitions referenced in array items are kept."""
|
| 112 |
schema = {
|
|
|
|
| 107 |
result = _prune_unused_defs(schema)
|
| 108 |
assert "$defs" not in result
|
| 109 |
|
| 110 |
+
def test_nested_references_with_recursion_kept(self):
|
| 111 |
+
"""Test that definitions with recursion referenced via nesting are kept."""
|
| 112 |
+
schema = {
|
| 113 |
+
"properties": {
|
| 114 |
+
"foo": {"$ref": "#/$defs/foo_def"},
|
| 115 |
+
},
|
| 116 |
+
"$defs": {
|
| 117 |
+
"foo_def": {
|
| 118 |
+
"type": "object",
|
| 119 |
+
"properties": {"nested": {"$ref": "#/$defs/foo_def"}},
|
| 120 |
+
},
|
| 121 |
+
"unused_def": {"type": "integer"},
|
| 122 |
+
},
|
| 123 |
+
}
|
| 124 |
+
result = _prune_unused_defs(schema)
|
| 125 |
+
assert "foo_def" in result["$defs"]
|
| 126 |
+
assert "unused_def" not in result["$defs"]
|
| 127 |
+
|
| 128 |
+
def test_nested_references_with_recursion_removed(self):
|
| 129 |
+
"""Test that definitions with recursion referenced via nesting in unused defs are removed."""
|
| 130 |
+
schema = {
|
| 131 |
+
"properties": {},
|
| 132 |
+
"$defs": {
|
| 133 |
+
"foo_def": {
|
| 134 |
+
"type": "object",
|
| 135 |
+
"properties": {"nested": {"$ref": "#/$defs/foo_def"}},
|
| 136 |
+
},
|
| 137 |
+
},
|
| 138 |
+
}
|
| 139 |
+
result = _prune_unused_defs(schema)
|
| 140 |
+
assert "$defs" not in result
|
| 141 |
+
|
| 142 |
+
def test_multiple_nested_references_with_recursion_kept(self):
|
| 143 |
+
"""Test that definitions with multiple levels of recursion referenced via nesting are kept."""
|
| 144 |
+
schema = {
|
| 145 |
+
"properties": {
|
| 146 |
+
"foo": {"$ref": "#/$defs/foo_def"},
|
| 147 |
+
},
|
| 148 |
+
"$defs": {
|
| 149 |
+
"foo_def": {
|
| 150 |
+
"type": "object",
|
| 151 |
+
"properties": {"nested": {"$ref": "#/$defs/nested_def"}},
|
| 152 |
+
},
|
| 153 |
+
"nested_def": {
|
| 154 |
+
"type": "object",
|
| 155 |
+
"properties": {"nested": {"$ref": "#/$defs/foo_def"}},
|
| 156 |
+
},
|
| 157 |
+
"unused_def": {"type": "integer"},
|
| 158 |
+
},
|
| 159 |
+
}
|
| 160 |
+
result = _prune_unused_defs(schema)
|
| 161 |
+
assert "foo_def" in result["$defs"]
|
| 162 |
+
assert "nested_def" in result["$defs"]
|
| 163 |
+
assert "unused_def" not in result["$defs"]
|
| 164 |
+
|
| 165 |
+
def test_multiple_nested_references_with_recursion_removed(self):
|
| 166 |
+
"""Test that definitions with multiple levels of recursion referenced via nesting in unused defs are removed."""
|
| 167 |
+
schema = {
|
| 168 |
+
"properties": {},
|
| 169 |
+
"$defs": {
|
| 170 |
+
"foo_def": {
|
| 171 |
+
"type": "object",
|
| 172 |
+
"properties": {"nested": {"$ref": "#/$defs/nested_def"}},
|
| 173 |
+
},
|
| 174 |
+
"nested_def": {
|
| 175 |
+
"type": "object",
|
| 176 |
+
"properties": {"nested": {"$ref": "#/$defs/foo_def"}},
|
| 177 |
+
},
|
| 178 |
+
},
|
| 179 |
+
}
|
| 180 |
+
result = _prune_unused_defs(schema)
|
| 181 |
+
assert "$defs" not in result
|
| 182 |
+
|
| 183 |
def test_array_references_kept(self):
|
| 184 |
"""Test that definitions referenced in array items are kept."""
|
| 185 |
schema = {
|