mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 14:04:18 +02:00
fix: prevent schema mutation in _prune_param and _convert_nullable_field (#3927)
🤖 Generated with Claude Code
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4ea102b433
commit
8d1b28958f
4 changed files with 27 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from collections import defaultdict
|
||||
from typing import Any
|
||||
|
||||
|
|
@ -287,6 +288,7 @@ def _prune_param(schema: dict[str, Any], param: str) -> dict[str, Any]:
|
|||
"""Return a new schema with *param* removed from `properties`, `required`,
|
||||
and (if no longer referenced) `$defs`.
|
||||
"""
|
||||
schema = copy.deepcopy(schema)
|
||||
|
||||
# ── 1. drop from properties/required ──────────────────────────────
|
||||
props = schema.get("properties", {})
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ def _convert_nullable_field(schema: dict[str, Any]) -> dict[str, Any]:
|
|||
elif "anyOf" in result:
|
||||
# Add null to anyOf if not present
|
||||
if not any(item.get("type") == "null" for item in result["anyOf"]):
|
||||
result["anyOf"].append({"type": "null"})
|
||||
result["anyOf"] = [*result["anyOf"], {"type": "null"}]
|
||||
elif "allOf" in result:
|
||||
# Wrap allOf in anyOf with null option
|
||||
result["anyOf"] = [{"allOf": result.pop("allOf")}, {"type": "null"}]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from fastmcp import FastMCP
|
|||
from fastmcp.client import Client
|
||||
from fastmcp.server.providers.openapi import OpenAPIProvider
|
||||
from fastmcp.utilities.openapi.json_schema_converter import (
|
||||
_convert_nullable_field,
|
||||
convert_openapi_schema_to_json_schema,
|
||||
)
|
||||
|
||||
|
|
@ -556,3 +557,14 @@ class TestNullableInputSchemaIntegration:
|
|||
|
||||
assert "nullable" not in bio_prop
|
||||
assert bio_prop["type"] == ["string", "null"]
|
||||
|
||||
|
||||
class TestConvertNullableFieldMutation:
|
||||
"""Test that _convert_nullable_field does not mutate its input."""
|
||||
|
||||
def test_does_not_mutate_anyof_list(self):
|
||||
"""_convert_nullable_field should not append to the original anyOf list."""
|
||||
schema = {"anyOf": [{"type": "string"}], "nullable": True}
|
||||
original_len = len(schema["anyOf"])
|
||||
_convert_nullable_field(schema)
|
||||
assert len(schema["anyOf"]) == original_len
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import copy
|
||||
from unittest.mock import patch
|
||||
|
||||
from jsonref import replace_refs
|
||||
|
|
@ -51,6 +52,17 @@ class TestPruneParam:
|
|||
result = _prune_param(schema, "foo")
|
||||
assert "required" not in result
|
||||
|
||||
def test_does_not_mutate_input(self):
|
||||
"""Test that _prune_param does not mutate the original schema."""
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {"a": {"type": "string"}, "b": {"type": "integer"}},
|
||||
"required": ["a", "b"],
|
||||
}
|
||||
original = copy.deepcopy(schema)
|
||||
_prune_param(schema, "a")
|
||||
assert schema == original
|
||||
|
||||
|
||||
class TestDereferenceRefs:
|
||||
"""Tests for the dereference_refs function."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue