mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
Convert OpenAPI provider to the OpenAPI plugin (#4015)
This commit is contained in:
parent
03f4a90e60
commit
a82979e433
26 changed files with 1700 additions and 1021 deletions
137
tests/server/plugins/openapi/test_openapi_performance.py
Normal file
137
tests/server/plugins/openapi/test_openapi_performance.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
"""Performance regression tests for OpenAPI parsing.
|
||||
|
||||
These tests ensure that large OpenAPI schemas (like GitHub's API) parse quickly
|
||||
and don't regress to the slow performance we had before optimization.
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from fastmcp import FastMCP
|
||||
|
||||
|
||||
class TestOpenAPIPerformance:
|
||||
"""Performance tests for OpenAPI parsing with real-world large schemas."""
|
||||
|
||||
# 10 second maximum timeout for this test no matter what
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.timeout(10)
|
||||
async def test_github_api_schema_performance(self):
|
||||
"""
|
||||
Test that GitHub's full API schema parses quickly.
|
||||
|
||||
This is a regression test to ensure our performance optimizations
|
||||
(eliminating deepcopy, single-pass optimization, smart union adjustment)
|
||||
continue to work. Without these optimizations, this test would take
|
||||
multiple minutes to parse.
|
||||
|
||||
On a local machine, this tests passes in ~2 seconds, but in GHA CI we see
|
||||
times as high as 6-7 seconds, so the test is asserted to pass in under
|
||||
10. Given that, this isn't intended to be a strict performance test, but
|
||||
rather a canary to ensure we don't regress significantly.
|
||||
"""
|
||||
|
||||
# Download the full GitHub API schema (typically ~10MB)
|
||||
response = httpx.get(
|
||||
"https://raw.githubusercontent.com/github/rest-api-description/refs/heads/main/descriptions-next/ghes-3.17/ghes-3.17.json",
|
||||
timeout=30.0, # Allow time for download
|
||||
)
|
||||
response.raise_for_status()
|
||||
schema = response.json()
|
||||
|
||||
# Time the parsing operation
|
||||
start_time = time.time()
|
||||
|
||||
# This should complete quickly with our optimizations
|
||||
mcp_server = FastMCP.from_openapi(schema, httpx.AsyncClient())
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
print(f"OpenAPI parsing took {elapsed_time:.2f}s")
|
||||
|
||||
# Verify the server was created successfully
|
||||
assert mcp_server is not None
|
||||
|
||||
# Performance regression test: should complete in under 10 seconds
|
||||
assert elapsed_time < 10.0, (
|
||||
f"OpenAPI parsing took {elapsed_time:.2f}s, exceeding 10s limit. "
|
||||
f"This suggests a performance regression."
|
||||
)
|
||||
|
||||
# Verify server and tools were created successfully
|
||||
tools = await mcp_server.list_tools()
|
||||
assert len(tools) > 500
|
||||
|
||||
def test_medium_schema_performance(self):
|
||||
"""
|
||||
Test parsing performance with a smaller synthetic schema.
|
||||
|
||||
This test doesn't require network access and provides a baseline
|
||||
for performance testing in CI environments.
|
||||
"""
|
||||
# Create a medium-sized synthetic schema
|
||||
schema: dict[str, Any] = {
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "Test API", "version": "1.0.0"},
|
||||
"paths": {},
|
||||
}
|
||||
|
||||
# Generate multiple paths to create a reasonably sized schema
|
||||
for i in range(100):
|
||||
path = f"/test/{i}"
|
||||
schema["paths"][path] = {
|
||||
"get": {
|
||||
"operationId": f"test_{i}",
|
||||
"parameters": [
|
||||
{"name": "param1", "in": "query", "schema": {"type": "string"}}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "integer"},
|
||||
"name": {"type": "string"},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"value": {"type": "string"},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# Time the parsing
|
||||
start_time = time.time()
|
||||
mcp_server = FastMCP.from_openapi(schema, httpx.AsyncClient())
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
# Should be very fast for medium schemas (well under 1 second)
|
||||
assert elapsed_time < 1.0, (
|
||||
f"Medium schema parsing took {elapsed_time:.3f}s, expected <1s"
|
||||
)
|
||||
assert mcp_server is not None
|
||||
Loading…
Add table
Add a link
Reference in a new issue