Spaces:
Running
Running
Jeremiah Lowin commited on
Clean up complexity from PR #1426 (#1435)
Browse files- pyproject.toml +0 -1
- src/fastmcp/experimental/utilities/openapi/parser.py +3 -3
- src/fastmcp/experimental/utilities/openapi/schemas.py +12 -25
- tests/experimental/README.md +3 -0
- tests/experimental/openapi_parser/README.md +5 -0
- tests/experimental/{server → openapi_parser}/__init__.py +0 -0
- tests/experimental/{server → openapi_parser}/conftest.py +0 -0
- tests/experimental/{utilities → openapi_parser/server}/__init__.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/__init__.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/test_comprehensive.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/test_deepobject_style.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/test_end_to_end_compatibility.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/test_openapi_features.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/test_openapi_performance.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/test_parameter_collisions.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/test_performance_comparison.py +0 -0
- tests/experimental/{server → openapi_parser/server}/openapi/test_server.py +0 -0
- tests/experimental/openapi_parser/utilities/__init__.py +0 -0
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/__init__.py +0 -0
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/conftest.py +0 -8
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_allof_requestbody.py +0 -0
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_director.py +0 -0
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_legacy_compatibility.py +0 -0
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_models.py +0 -0
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_nullable_fields.py +0 -0
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_parser.py +0 -0
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_schemas.py +3 -3
- tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_transitive_references.py +0 -0
- uv.lock +0 -38
pyproject.toml
CHANGED
|
@@ -15,7 +15,6 @@ dependencies = [
|
|
| 15 |
"pydantic[email]>=2.11.7",
|
| 16 |
"pyperclip>=1.9.0",
|
| 17 |
"openapi-core>=0.19.5",
|
| 18 |
-
"msgspec>=0.19.0",
|
| 19 |
]
|
| 20 |
requires-python = ">=3.10"
|
| 21 |
readme = "README.md"
|
|
|
|
| 15 |
"pydantic[email]>=2.11.7",
|
| 16 |
"pyperclip>=1.9.0",
|
| 17 |
"openapi-core>=0.19.5",
|
|
|
|
| 18 |
]
|
| 19 |
requires-python = ">=3.10"
|
| 20 |
readme = "README.md"
|
src/fastmcp/experimental/utilities/openapi/parser.py
CHANGED
|
@@ -36,7 +36,7 @@ from .models import (
|
|
| 36 |
)
|
| 37 |
from .schemas import (
|
| 38 |
_combine_schemas_and_map_params,
|
| 39 |
-
|
| 40 |
)
|
| 41 |
|
| 42 |
logger = get_logger(__name__)
|
|
@@ -225,7 +225,7 @@ class OpenAPIParser(
|
|
| 225 |
|
| 226 |
# Convert refs from OpenAPI format to JSON Schema format using recursive approach
|
| 227 |
|
| 228 |
-
result =
|
| 229 |
return result
|
| 230 |
except ValueError as e:
|
| 231 |
# Re-raise ValueError for external reference errors and other validation issues
|
|
@@ -606,7 +606,7 @@ class OpenAPIParser(
|
|
| 606 |
# Convert each schema definition recursively
|
| 607 |
for name, schema in schema_definitions.items():
|
| 608 |
if isinstance(schema, dict):
|
| 609 |
-
schema_definitions[name] =
|
| 610 |
|
| 611 |
# Process paths and operations
|
| 612 |
for path_str, path_item_obj in self.openapi.paths.items():
|
|
|
|
| 36 |
)
|
| 37 |
from .schemas import (
|
| 38 |
_combine_schemas_and_map_params,
|
| 39 |
+
_replace_ref_with_defs,
|
| 40 |
)
|
| 41 |
|
| 42 |
logger = get_logger(__name__)
|
|
|
|
| 225 |
|
| 226 |
# Convert refs from OpenAPI format to JSON Schema format using recursive approach
|
| 227 |
|
| 228 |
+
result = _replace_ref_with_defs(result)
|
| 229 |
return result
|
| 230 |
except ValueError as e:
|
| 231 |
# Re-raise ValueError for external reference errors and other validation issues
|
|
|
|
| 606 |
# Convert each schema definition recursively
|
| 607 |
for name, schema in schema_definitions.items():
|
| 608 |
if isinstance(schema, dict):
|
| 609 |
+
schema_definitions[name] = _replace_ref_with_defs(schema)
|
| 610 |
|
| 611 |
# Process paths and operations
|
| 612 |
for path_str, path_item_obj in self.openapi.paths.items():
|
src/fastmcp/experimental/utilities/openapi/schemas.py
CHANGED
|
@@ -71,7 +71,7 @@ def clean_schema_for_display(schema: JsonSchema | None) -> JsonSchema | None:
|
|
| 71 |
return cleaned
|
| 72 |
|
| 73 |
|
| 74 |
-
def
|
| 75 |
info: dict[str, Any], description: str | None = None
|
| 76 |
) -> dict[str, Any]:
|
| 77 |
"""
|
|
@@ -106,35 +106,22 @@ def _replace_ref_with_defs_recursive(
|
|
| 106 |
)
|
| 107 |
elif properties := schema.get("properties"):
|
| 108 |
if "$ref" in properties:
|
| 109 |
-
schema["properties"] =
|
| 110 |
else:
|
| 111 |
schema["properties"] = {
|
| 112 |
-
prop_name:
|
| 113 |
for prop_name, prop_schema in properties.items()
|
| 114 |
}
|
| 115 |
elif item_schema := schema.get("items"):
|
| 116 |
-
schema["items"] =
|
| 117 |
for section in ["anyOf", "allOf", "oneOf"]:
|
| 118 |
for i, item in enumerate(schema.get(section, [])):
|
| 119 |
-
schema[section][i] =
|
| 120 |
if info.get("description", description) and not schema.get("description"):
|
| 121 |
schema["description"] = description
|
| 122 |
return schema
|
| 123 |
|
| 124 |
|
| 125 |
-
def _ensure_refs_converted(schema: dict[str, Any]) -> dict[str, Any]:
|
| 126 |
-
"""
|
| 127 |
-
Ensure all OpenAPI refs are converted to JSON Schema format using recursive approach.
|
| 128 |
-
|
| 129 |
-
Args:
|
| 130 |
-
schema: Schema that may contain OpenAPI refs
|
| 131 |
-
|
| 132 |
-
Returns:
|
| 133 |
-
Schema with all refs converted to JSON Schema format
|
| 134 |
-
"""
|
| 135 |
-
return _replace_ref_with_defs_recursive(schema)
|
| 136 |
-
|
| 137 |
-
|
| 138 |
def _make_optional_parameter_nullable(schema: dict[str, Any]) -> dict[str, Any]:
|
| 139 |
"""
|
| 140 |
Make an optional parameter schema nullable to allow None values.
|
|
@@ -250,7 +237,7 @@ def _combine_schemas_and_map_params(
|
|
| 250 |
|
| 251 |
# Convert refs if needed
|
| 252 |
if convert_refs:
|
| 253 |
-
body_schema =
|
| 254 |
route.request_body.content_schema[content_type]
|
| 255 |
)
|
| 256 |
else:
|
|
@@ -310,7 +297,7 @@ def _combine_schemas_and_map_params(
|
|
| 310 |
|
| 311 |
# Convert refs if needed
|
| 312 |
if convert_refs:
|
| 313 |
-
param_schema =
|
| 314 |
else:
|
| 315 |
param_schema = param.schema_
|
| 316 |
original_desc = param_schema.get("description", "")
|
|
@@ -337,7 +324,7 @@ def _combine_schemas_and_map_params(
|
|
| 337 |
|
| 338 |
# Convert refs if needed
|
| 339 |
if convert_refs:
|
| 340 |
-
param_schema =
|
| 341 |
else:
|
| 342 |
param_schema = param.schema_
|
| 343 |
|
|
@@ -384,7 +371,7 @@ def _combine_schemas_and_map_params(
|
|
| 384 |
# Convert each schema definition recursively
|
| 385 |
for name, schema in all_defs.items():
|
| 386 |
if isinstance(schema, dict):
|
| 387 |
-
all_defs[name] =
|
| 388 |
|
| 389 |
# Prune to only needed schemas
|
| 390 |
used_refs = set()
|
|
@@ -514,7 +501,7 @@ def extract_output_schema_from_responses(
|
|
| 514 |
return None
|
| 515 |
|
| 516 |
# Convert refs if needed
|
| 517 |
-
output_schema =
|
| 518 |
|
| 519 |
# If schema has a $ref, resolve it first before processing nullable fields
|
| 520 |
if "$ref" in output_schema and schema_definitions:
|
|
@@ -523,7 +510,7 @@ def extract_output_schema_from_responses(
|
|
| 523 |
schema_name = ref_path.split("/")[-1]
|
| 524 |
if schema_name in schema_definitions:
|
| 525 |
# Replace $ref with the actual schema definition
|
| 526 |
-
output_schema =
|
| 527 |
|
| 528 |
# Convert OpenAPI schema to JSON Schema format
|
| 529 |
# Only needed for OpenAPI 3.0 - 3.1 uses standard JSON Schema null types
|
|
@@ -553,7 +540,7 @@ def extract_output_schema_from_responses(
|
|
| 553 |
# Convert each schema definition recursively
|
| 554 |
for name, schema in processed_defs.items():
|
| 555 |
if isinstance(schema, dict):
|
| 556 |
-
processed_defs[name] =
|
| 557 |
|
| 558 |
# Convert OpenAPI schema definitions to JSON Schema format if needed
|
| 559 |
if openapi_version and openapi_version.startswith("3.0"):
|
|
|
|
| 71 |
return cleaned
|
| 72 |
|
| 73 |
|
| 74 |
+
def _replace_ref_with_defs(
|
| 75 |
info: dict[str, Any], description: str | None = None
|
| 76 |
) -> dict[str, Any]:
|
| 77 |
"""
|
|
|
|
| 106 |
)
|
| 107 |
elif properties := schema.get("properties"):
|
| 108 |
if "$ref" in properties:
|
| 109 |
+
schema["properties"] = _replace_ref_with_defs(properties)
|
| 110 |
else:
|
| 111 |
schema["properties"] = {
|
| 112 |
+
prop_name: _replace_ref_with_defs(prop_schema)
|
| 113 |
for prop_name, prop_schema in properties.items()
|
| 114 |
}
|
| 115 |
elif item_schema := schema.get("items"):
|
| 116 |
+
schema["items"] = _replace_ref_with_defs(item_schema)
|
| 117 |
for section in ["anyOf", "allOf", "oneOf"]:
|
| 118 |
for i, item in enumerate(schema.get(section, [])):
|
| 119 |
+
schema[section][i] = _replace_ref_with_defs(item)
|
| 120 |
if info.get("description", description) and not schema.get("description"):
|
| 121 |
schema["description"] = description
|
| 122 |
return schema
|
| 123 |
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
def _make_optional_parameter_nullable(schema: dict[str, Any]) -> dict[str, Any]:
|
| 126 |
"""
|
| 127 |
Make an optional parameter schema nullable to allow None values.
|
|
|
|
| 237 |
|
| 238 |
# Convert refs if needed
|
| 239 |
if convert_refs:
|
| 240 |
+
body_schema = _replace_ref_with_defs(
|
| 241 |
route.request_body.content_schema[content_type]
|
| 242 |
)
|
| 243 |
else:
|
|
|
|
| 297 |
|
| 298 |
# Convert refs if needed
|
| 299 |
if convert_refs:
|
| 300 |
+
param_schema = _replace_ref_with_defs(param.schema_)
|
| 301 |
else:
|
| 302 |
param_schema = param.schema_
|
| 303 |
original_desc = param_schema.get("description", "")
|
|
|
|
| 324 |
|
| 325 |
# Convert refs if needed
|
| 326 |
if convert_refs:
|
| 327 |
+
param_schema = _replace_ref_with_defs(param.schema_)
|
| 328 |
else:
|
| 329 |
param_schema = param.schema_
|
| 330 |
|
|
|
|
| 371 |
# Convert each schema definition recursively
|
| 372 |
for name, schema in all_defs.items():
|
| 373 |
if isinstance(schema, dict):
|
| 374 |
+
all_defs[name] = _replace_ref_with_defs(schema)
|
| 375 |
|
| 376 |
# Prune to only needed schemas
|
| 377 |
used_refs = set()
|
|
|
|
| 501 |
return None
|
| 502 |
|
| 503 |
# Convert refs if needed
|
| 504 |
+
output_schema = _replace_ref_with_defs(schema)
|
| 505 |
|
| 506 |
# If schema has a $ref, resolve it first before processing nullable fields
|
| 507 |
if "$ref" in output_schema and schema_definitions:
|
|
|
|
| 510 |
schema_name = ref_path.split("/")[-1]
|
| 511 |
if schema_name in schema_definitions:
|
| 512 |
# Replace $ref with the actual schema definition
|
| 513 |
+
output_schema = _replace_ref_with_defs(schema_definitions[schema_name])
|
| 514 |
|
| 515 |
# Convert OpenAPI schema to JSON Schema format
|
| 516 |
# Only needed for OpenAPI 3.0 - 3.1 uses standard JSON Schema null types
|
|
|
|
| 540 |
# Convert each schema definition recursively
|
| 541 |
for name, schema in processed_defs.items():
|
| 542 |
if isinstance(schema, dict):
|
| 543 |
+
processed_defs[name] = _replace_ref_with_defs(schema)
|
| 544 |
|
| 545 |
# Convert OpenAPI schema definitions to JSON Schema format if needed
|
| 546 |
if openapi_version and openapi_version.startswith("3.0"):
|
tests/experimental/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Experimental Tests
|
| 2 |
+
|
| 3 |
+
Each directory in this folder tests a discrete experimental feature. The directory structure within each experiment should approximate the "normal" test directory structure to facilitate moving tests into place once/if the experiment is merged into the main codebase.
|
tests/experimental/openapi_parser/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenAPI Parser Experiment
|
| 2 |
+
|
| 3 |
+
Performance-optimized OpenAPI parser with better transitive reference resolution.
|
| 4 |
+
|
| 5 |
+
Enabled via `experimental.enable_new_openapi_parser = True`
|
tests/experimental/{server → openapi_parser}/__init__.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser}/conftest.py
RENAMED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/server}/__init__.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/__init__.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/test_comprehensive.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/test_deepobject_style.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/test_end_to_end_compatibility.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/test_openapi_features.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/test_openapi_performance.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/test_parameter_collisions.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/test_performance_comparison.py
RENAMED
|
File without changes
|
tests/experimental/{server → openapi_parser/server}/openapi/test_server.py
RENAMED
|
File without changes
|
tests/experimental/openapi_parser/utilities/__init__.py
ADDED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/__init__.py
RENAMED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/conftest.py
RENAMED
|
@@ -2,14 +2,6 @@
|
|
| 2 |
|
| 3 |
import pytest
|
| 4 |
|
| 5 |
-
from fastmcp.utilities.tests import temporary_settings
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
@pytest.fixture(autouse=True)
|
| 9 |
-
def use_new_openapi_parser():
|
| 10 |
-
with temporary_settings(experimental__enable_new_openapi_parser=True):
|
| 11 |
-
yield
|
| 12 |
-
|
| 13 |
|
| 14 |
@pytest.fixture
|
| 15 |
def basic_openapi_30_spec():
|
|
|
|
| 2 |
|
| 3 |
import pytest
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
@pytest.fixture
|
| 7 |
def basic_openapi_30_spec():
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_allof_requestbody.py
RENAMED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_director.py
RENAMED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_legacy_compatibility.py
RENAMED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_models.py
RENAMED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_nullable_fields.py
RENAMED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_parser.py
RENAMED
|
File without changes
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_schemas.py
RENAMED
|
@@ -10,7 +10,7 @@ from fastmcp.experimental.utilities.openapi.models import (
|
|
| 10 |
from fastmcp.experimental.utilities.openapi.schemas import (
|
| 11 |
_combine_schemas,
|
| 12 |
_combine_schemas_and_map_params,
|
| 13 |
-
|
| 14 |
)
|
| 15 |
from fastmcp.utilities.json_schema import compress_schema
|
| 16 |
|
|
@@ -244,7 +244,7 @@ class TestSchemaProcessing:
|
|
| 244 |
}
|
| 245 |
|
| 246 |
# Use our recursive replacement approach
|
| 247 |
-
result =
|
| 248 |
|
| 249 |
assert result["properties"]["user"]["$ref"] == "#/$defs/User"
|
| 250 |
assert result["properties"]["items"]["items"]["$ref"] == "#/$defs/Item"
|
|
@@ -274,7 +274,7 @@ class TestSchemaProcessing:
|
|
| 274 |
}
|
| 275 |
|
| 276 |
# Use our recursive replacement approach
|
| 277 |
-
result =
|
| 278 |
|
| 279 |
# Check nested object property
|
| 280 |
nested_prop = result["properties"]["data"]["properties"]["nested"]
|
|
|
|
| 10 |
from fastmcp.experimental.utilities.openapi.schemas import (
|
| 11 |
_combine_schemas,
|
| 12 |
_combine_schemas_and_map_params,
|
| 13 |
+
_replace_ref_with_defs,
|
| 14 |
)
|
| 15 |
from fastmcp.utilities.json_schema import compress_schema
|
| 16 |
|
|
|
|
| 244 |
}
|
| 245 |
|
| 246 |
# Use our recursive replacement approach
|
| 247 |
+
result = _replace_ref_with_defs(schema_with_ref)
|
| 248 |
|
| 249 |
assert result["properties"]["user"]["$ref"] == "#/$defs/User"
|
| 250 |
assert result["properties"]["items"]["items"]["$ref"] == "#/$defs/Item"
|
|
|
|
| 274 |
}
|
| 275 |
|
| 276 |
# Use our recursive replacement approach
|
| 277 |
+
result = _replace_ref_with_defs(nested_schema)
|
| 278 |
|
| 279 |
# Check nested object property
|
| 280 |
nested_prop = result["properties"]["data"]["properties"]["nested"]
|
tests/experimental/{utilities → openapi_parser/utilities}/openapi/test_transitive_references.py
RENAMED
|
File without changes
|
uv.lock
CHANGED
|
@@ -530,7 +530,6 @@ dependencies = [
|
|
| 530 |
{ name = "exceptiongroup" },
|
| 531 |
{ name = "httpx" },
|
| 532 |
{ name = "mcp" },
|
| 533 |
-
{ name = "msgspec" },
|
| 534 |
{ name = "openapi-core" },
|
| 535 |
{ name = "openapi-pydantic" },
|
| 536 |
{ name = "pydantic", extra = ["email"] },
|
|
@@ -575,7 +574,6 @@ requires-dist = [
|
|
| 575 |
{ name = "exceptiongroup", specifier = ">=1.2.2" },
|
| 576 |
{ name = "httpx", specifier = ">=0.28.1" },
|
| 577 |
{ name = "mcp", specifier = ">=1.12.4,<2.0.0" },
|
| 578 |
-
{ name = "msgspec", specifier = ">=0.19.0" },
|
| 579 |
{ name = "openapi-core", specifier = ">=0.19.5" },
|
| 580 |
{ name = "openapi-pydantic", specifier = ">=0.5.1" },
|
| 581 |
{ name = "pydantic", extras = ["email"], specifier = ">=2.11.7" },
|
|
@@ -981,42 +979,6 @@ wheels = [
|
|
| 981 |
{ url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" },
|
| 982 |
]
|
| 983 |
|
| 984 |
-
[[package]]
|
| 985 |
-
name = "msgspec"
|
| 986 |
-
version = "0.19.0"
|
| 987 |
-
source = { registry = "https://pypi.org/simple" }
|
| 988 |
-
sdist = { url = "https://files.pythonhosted.org/packages/cf/9b/95d8ce458462b8b71b8a70fa94563b2498b89933689f3a7b8911edfae3d7/msgspec-0.19.0.tar.gz", hash = "sha256:604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e", size = 216934, upload-time = "2024-12-27T17:40:28.597Z" }
|
| 989 |
-
wheels = [
|
| 990 |
-
{ url = "https://files.pythonhosted.org/packages/13/40/817282b42f58399762267b30deb8ac011d8db373f8da0c212c85fbe62b8f/msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259", size = 190019, upload-time = "2024-12-27T17:39:13.803Z" },
|
| 991 |
-
{ url = "https://files.pythonhosted.org/packages/92/99/bd7ed738c00f223a8119928661167a89124140792af18af513e6519b0d54/msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36", size = 183680, upload-time = "2024-12-27T17:39:17.847Z" },
|
| 992 |
-
{ url = "https://files.pythonhosted.org/packages/e5/27/322badde18eb234e36d4a14122b89edd4e2973cdbc3da61ca7edf40a1ccd/msgspec-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe2c4bf29bf4e89790b3117470dea2c20b59932772483082c468b990d45fb947", size = 209334, upload-time = "2024-12-27T17:39:19.065Z" },
|
| 993 |
-
{ url = "https://files.pythonhosted.org/packages/c6/65/080509c5774a1592b2779d902a70b5fe008532759927e011f068145a16cb/msgspec-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e87ecfa9795ee5214861eab8326b0e75475c2e68a384002aa135ea2a27d909", size = 211551, upload-time = "2024-12-27T17:39:21.767Z" },
|
| 994 |
-
{ url = "https://files.pythonhosted.org/packages/6f/2e/1c23c6b4ca6f4285c30a39def1054e2bee281389e4b681b5e3711bd5a8c9/msgspec-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c4ec642689da44618f68c90855a10edbc6ac3ff7c1d94395446c65a776e712a", size = 215099, upload-time = "2024-12-27T17:39:24.71Z" },
|
| 995 |
-
{ url = "https://files.pythonhosted.org/packages/83/fe/95f9654518879f3359d1e76bc41189113aa9102452170ab7c9a9a4ee52f6/msgspec-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2719647625320b60e2d8af06b35f5b12d4f4d281db30a15a1df22adb2295f633", size = 218211, upload-time = "2024-12-27T17:39:27.396Z" },
|
| 996 |
-
{ url = "https://files.pythonhosted.org/packages/79/f6/71ca7e87a1fb34dfe5efea8156c9ef59dd55613aeda2ca562f122cd22012/msgspec-0.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:695b832d0091edd86eeb535cd39e45f3919f48d997685f7ac31acb15e0a2ed90", size = 186174, upload-time = "2024-12-27T17:39:29.647Z" },
|
| 997 |
-
{ url = "https://files.pythonhosted.org/packages/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa77046904db764b0462036bc63ef71f02b75b8f72e9c9dd4c447d6da1ed8f8e", size = 187939, upload-time = "2024-12-27T17:39:32.347Z" },
|
| 998 |
-
{ url = "https://files.pythonhosted.org/packages/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:047cfa8675eb3bad68722cfe95c60e7afabf84d1bd8938979dd2b92e9e4a9551", size = 182202, upload-time = "2024-12-27T17:39:33.633Z" },
|
| 999 |
-
{ url = "https://files.pythonhosted.org/packages/81/25/3a4b24d468203d8af90d1d351b77ea3cffb96b29492855cf83078f16bfe4/msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e78f46ff39a427e10b4a61614a2777ad69559cc8d603a7c05681f5a595ea98f7", size = 209029, upload-time = "2024-12-27T17:39:35.023Z" },
|
| 1000 |
-
{ url = "https://files.pythonhosted.org/packages/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c7adf191e4bd3be0e9231c3b6dc20cf1199ada2af523885efc2ed218eafd011", size = 210682, upload-time = "2024-12-27T17:39:36.384Z" },
|
| 1001 |
-
{ url = "https://files.pythonhosted.org/packages/03/97/7c8895c9074a97052d7e4a1cc1230b7b6e2ca2486714eb12c3f08bb9d284/msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f04cad4385e20be7c7176bb8ae3dca54a08e9756cfc97bcdb4f18560c3042063", size = 214003, upload-time = "2024-12-27T17:39:39.097Z" },
|
| 1002 |
-
{ url = "https://files.pythonhosted.org/packages/61/61/e892997bcaa289559b4d5869f066a8021b79f4bf8e955f831b095f47a4cd/msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45c8fb410670b3b7eb884d44a75589377c341ec1392b778311acdbfa55187716", size = 216833, upload-time = "2024-12-27T17:39:41.203Z" },
|
| 1003 |
-
{ url = "https://files.pythonhosted.org/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:70eaef4934b87193a27d802534dc466778ad8d536e296ae2f9334e182ac27b6c", size = 186184, upload-time = "2024-12-27T17:39:43.702Z" },
|
| 1004 |
-
{ url = "https://files.pythonhosted.org/packages/b2/5f/a70c24f075e3e7af2fae5414c7048b0e11389685b7f717bb55ba282a34a7/msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f", size = 190485, upload-time = "2024-12-27T17:39:44.974Z" },
|
| 1005 |
-
{ url = "https://files.pythonhosted.org/packages/89/b0/1b9763938cfae12acf14b682fcf05c92855974d921a5a985ecc197d1c672/msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2", size = 183910, upload-time = "2024-12-27T17:39:46.401Z" },
|
| 1006 |
-
{ url = "https://files.pythonhosted.org/packages/87/81/0c8c93f0b92c97e326b279795f9c5b956c5a97af28ca0fbb9fd86c83737a/msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12", size = 210633, upload-time = "2024-12-27T17:39:49.099Z" },
|
| 1007 |
-
{ url = "https://files.pythonhosted.org/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc", size = 213594, upload-time = "2024-12-27T17:39:51.204Z" },
|
| 1008 |
-
{ url = "https://files.pythonhosted.org/packages/19/2b/4137bc2ed45660444842d042be2cf5b18aa06efd2cda107cff18253b9653/msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c", size = 214053, upload-time = "2024-12-27T17:39:52.866Z" },
|
| 1009 |
-
{ url = "https://files.pythonhosted.org/packages/9d/e6/8ad51bdc806aac1dc501e8fe43f759f9ed7284043d722b53323ea421c360/msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537", size = 219081, upload-time = "2024-12-27T17:39:55.142Z" },
|
| 1010 |
-
{ url = "https://files.pythonhosted.org/packages/b1/ef/27dd35a7049c9a4f4211c6cd6a8c9db0a50647546f003a5867827ec45391/msgspec-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0", size = 187467, upload-time = "2024-12-27T17:39:56.531Z" },
|
| 1011 |
-
{ url = "https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f12d30dd6266557aaaf0aa0f9580a9a8fbeadfa83699c487713e355ec5f0bd86", size = 190498, upload-time = "2024-12-27T17:40:00.427Z" },
|
| 1012 |
-
{ url = "https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82b2c42c1b9ebc89e822e7e13bbe9d17ede0c23c187469fdd9505afd5a481314", size = 183950, upload-time = "2024-12-27T17:40:04.219Z" },
|
| 1013 |
-
{ url = "https://files.pythonhosted.org/packages/e8/f0/5b764e066ce9aba4b70d1db8b087ea66098c7c27d59b9dd8a3532774d48f/msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19746b50be214a54239aab822964f2ac81e38b0055cca94808359d779338c10e", size = 210647, upload-time = "2024-12-27T17:40:05.606Z" },
|
| 1014 |
-
{ url = "https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60ef4bdb0ec8e4ad62e5a1f95230c08efb1f64f32e6e8dd2ced685bcc73858b5", size = 213563, upload-time = "2024-12-27T17:40:10.516Z" },
|
| 1015 |
-
{ url = "https://files.pythonhosted.org/packages/53/2f/2b1c2b056894fbaa975f68f81e3014bb447516a8b010f1bed3fb0e016ed7/msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac7f7c377c122b649f7545810c6cd1b47586e3aa3059126ce3516ac7ccc6a6a9", size = 213996, upload-time = "2024-12-27T17:40:12.244Z" },
|
| 1016 |
-
{ url = "https://files.pythonhosted.org/packages/aa/5a/4cd408d90d1417e8d2ce6a22b98a6853c1b4d7cb7669153e4424d60087f6/msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5bc1472223a643f5ffb5bf46ccdede7f9795078194f14edd69e3aab7020d327", size = 219087, upload-time = "2024-12-27T17:40:14.881Z" },
|
| 1017 |
-
{ url = "https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:317050bc0f7739cb30d257ff09152ca309bf5a369854bbf1e57dffc310c1f20f", size = 187432, upload-time = "2024-12-27T17:40:16.256Z" },
|
| 1018 |
-
]
|
| 1019 |
-
|
| 1020 |
[[package]]
|
| 1021 |
name = "nodeenv"
|
| 1022 |
version = "1.9.1"
|
|
|
|
| 530 |
{ name = "exceptiongroup" },
|
| 531 |
{ name = "httpx" },
|
| 532 |
{ name = "mcp" },
|
|
|
|
| 533 |
{ name = "openapi-core" },
|
| 534 |
{ name = "openapi-pydantic" },
|
| 535 |
{ name = "pydantic", extra = ["email"] },
|
|
|
|
| 574 |
{ name = "exceptiongroup", specifier = ">=1.2.2" },
|
| 575 |
{ name = "httpx", specifier = ">=0.28.1" },
|
| 576 |
{ name = "mcp", specifier = ">=1.12.4,<2.0.0" },
|
|
|
|
| 577 |
{ name = "openapi-core", specifier = ">=0.19.5" },
|
| 578 |
{ name = "openapi-pydantic", specifier = ">=0.5.1" },
|
| 579 |
{ name = "pydantic", extras = ["email"], specifier = ">=2.11.7" },
|
|
|
|
| 979 |
{ url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" },
|
| 980 |
]
|
| 981 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 982 |
[[package]]
|
| 983 |
name = "nodeenv"
|
| 984 |
version = "1.9.1"
|