mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-28 10:18:08 +02:00
Move OpenAPI to providers/openapi submodule (#2672)
* Move OpenAPI to providers/openapi/ submodule Migrates the OpenAPI implementation to the provider pattern: - Creates src/fastmcp/server/providers/openapi/ submodule with provider.py, components.py, and routing.py - Converts server/openapi/ to deprecated re-export stubs - FastMCPOpenAPI remains as deprecated wrapper that uses OpenAPIProvider - Updates experimental/server/openapi to import from new canonical location * Update tests and FastMCP methods to use OpenAPIProvider - Update FastMCP.from_openapi() and from_fastapi() to use OpenAPIProvider directly, returning FastMCP instead of deprecated FastMCPOpenAPI - Move tests from tests/server/openapi/ to tests/server/providers/openapi/ - Update all test imports to use new canonical location - Add tests/deprecated/openapi/ for backward compatibility testing - Update tests/client/test_openapi.py to use new imports * Fix deprecation warning tests and address CodeRabbit feedback - Use importlib.reload() to ensure deprecation warnings fire in tests - Change logger.error to logger.exception for better stack traces
This commit is contained in:
parent
748d25eede
commit
96c7a74b27
25 changed files with 1692 additions and 1417 deletions
|
|
@ -1,285 +0,0 @@
|
|||
"""Performance comparison between legacy and new OpenAPI implementations."""
|
||||
|
||||
import time
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from fastmcp.server.openapi import FastMCPOpenAPI
|
||||
from fastmcp.server.openapi import FastMCPOpenAPI as LegacyFastMCPOpenAPI
|
||||
|
||||
|
||||
class TestPerformanceComparison:
|
||||
"""Compare performance between legacy and new implementations."""
|
||||
|
||||
@pytest.fixture
|
||||
def comprehensive_spec(self):
|
||||
"""Comprehensive OpenAPI spec for performance testing."""
|
||||
return {
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "Performance Test API", "version": "1.0.0"},
|
||||
"paths": {
|
||||
"/users": {
|
||||
"get": {
|
||||
"operationId": "list_users",
|
||||
"summary": "List users",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "integer", "default": 10},
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "integer", "default": 0},
|
||||
},
|
||||
],
|
||||
"responses": {"200": {"description": "Users listed"}},
|
||||
},
|
||||
"post": {
|
||||
"operationId": "create_user",
|
||||
"summary": "Create user",
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"email": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
},
|
||||
"required": ["name", "email"],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"responses": {"201": {"description": "User created"}},
|
||||
},
|
||||
},
|
||||
"/users/{id}": {
|
||||
"get": {
|
||||
"operationId": "get_user",
|
||||
"summary": "Get user",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"schema": {"type": "integer"},
|
||||
}
|
||||
],
|
||||
"responses": {"200": {"description": "User found"}},
|
||||
},
|
||||
"put": {
|
||||
"operationId": "update_user",
|
||||
"summary": "Update user",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"schema": {"type": "integer"},
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"email": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"responses": {"200": {"description": "User updated"}},
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "delete_user",
|
||||
"summary": "Delete user",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"schema": {"type": "integer"},
|
||||
}
|
||||
],
|
||||
"responses": {"204": {"description": "User deleted"}},
|
||||
},
|
||||
},
|
||||
"/search": {
|
||||
"get": {
|
||||
"operationId": "search_users",
|
||||
"summary": "Search users",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": True,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
{
|
||||
"name": "filters",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"style": "deepObject",
|
||||
"explode": True,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"age_min": {"type": "integer"},
|
||||
"age_max": {"type": "integer"},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["active", "inactive"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"responses": {"200": {"description": "Search results"}},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def test_server_initialization_performance(self, comprehensive_spec):
|
||||
"""Test that new implementation is significantly faster than legacy."""
|
||||
num_iterations = 5
|
||||
|
||||
# Measure legacy implementation
|
||||
legacy_times = []
|
||||
for _ in range(num_iterations):
|
||||
client = httpx.AsyncClient(base_url="https://api.example.com")
|
||||
start_time = time.time()
|
||||
server = LegacyFastMCPOpenAPI(
|
||||
openapi_spec=comprehensive_spec,
|
||||
client=client,
|
||||
name="Legacy Performance Test",
|
||||
)
|
||||
# Ensure server is fully initialized
|
||||
assert server is not None
|
||||
end_time = time.time()
|
||||
legacy_times.append(end_time - start_time)
|
||||
|
||||
# Measure new implementation
|
||||
new_times = []
|
||||
for _ in range(num_iterations):
|
||||
client = httpx.AsyncClient(base_url="https://api.example.com")
|
||||
start_time = time.time()
|
||||
server = FastMCPOpenAPI(
|
||||
openapi_spec=comprehensive_spec,
|
||||
client=client,
|
||||
name="New Performance Test",
|
||||
)
|
||||
# Ensure server is fully initialized
|
||||
assert server is not None
|
||||
end_time = time.time()
|
||||
new_times.append(end_time - start_time)
|
||||
|
||||
# Calculate averages
|
||||
legacy_avg = sum(legacy_times) / len(legacy_times)
|
||||
new_avg = sum(new_times) / len(new_times)
|
||||
|
||||
print(f"Legacy implementation average: {legacy_avg:.4f}s")
|
||||
print(f"New implementation average: {new_avg:.4f}s")
|
||||
print(f"Speedup: {legacy_avg / new_avg:.2f}x")
|
||||
|
||||
# Both implementations should be very fast for moderate specs
|
||||
# The key achievement is eliminating the 100-200ms latency issue for serverless
|
||||
max_acceptable_time = 0.1 # 100ms
|
||||
|
||||
print(f"Legacy performance: {'✓' if legacy_avg < max_acceptable_time else '✗'}")
|
||||
print(f"New performance: {'✓' if new_avg < max_acceptable_time else '✗'}")
|
||||
|
||||
# New implementation should be under 100ms for reasonable specs (serverless requirement)
|
||||
assert new_avg < max_acceptable_time, (
|
||||
f"New implementation should initialize in under 100ms, got {new_avg:.4f}s"
|
||||
)
|
||||
|
||||
# Legacy might be slightly faster or slower on small specs, but both should be fast
|
||||
# The real improvement shows up with larger specs where code generation was the bottleneck
|
||||
assert legacy_avg < max_acceptable_time, (
|
||||
f"Legacy should also be fast on small specs, got {legacy_avg:.4f}s"
|
||||
)
|
||||
|
||||
def test_functionality_identical_after_optimization(self, comprehensive_spec):
|
||||
"""Verify that performance optimization doesn't break functionality."""
|
||||
client = httpx.AsyncClient(base_url="https://api.example.com")
|
||||
|
||||
# Create both servers
|
||||
legacy_server = LegacyFastMCPOpenAPI(
|
||||
openapi_spec=comprehensive_spec,
|
||||
client=client,
|
||||
name="Legacy Server",
|
||||
)
|
||||
new_server = FastMCPOpenAPI(
|
||||
openapi_spec=comprehensive_spec,
|
||||
client=client,
|
||||
name="New Server",
|
||||
)
|
||||
|
||||
# Both should have the same number of tools
|
||||
legacy_tool_count = len(legacy_server._tool_manager._tools)
|
||||
new_tool_count = len(new_server._tool_manager._tools)
|
||||
|
||||
assert legacy_tool_count == new_tool_count
|
||||
assert legacy_tool_count == 6 # 6 operations in the spec
|
||||
|
||||
# Tool names should be identical
|
||||
legacy_tool_names = set(legacy_server._tool_manager._tools.keys())
|
||||
new_tool_names = set(new_server._tool_manager._tools.keys())
|
||||
|
||||
assert legacy_tool_names == new_tool_names
|
||||
|
||||
# Expected operations
|
||||
expected_operations = {
|
||||
"list_users",
|
||||
"create_user",
|
||||
"get_user",
|
||||
"update_user",
|
||||
"delete_user",
|
||||
"search_users",
|
||||
}
|
||||
assert legacy_tool_names == expected_operations
|
||||
|
||||
def test_memory_efficiency(self, comprehensive_spec):
|
||||
"""Test that new implementation doesn't significantly increase memory usage."""
|
||||
import gc
|
||||
|
||||
# This is a basic test - in practice you'd use more sophisticated memory profiling
|
||||
gc.collect() # Clean up before baseline
|
||||
baseline_refs = len(gc.get_objects())
|
||||
|
||||
servers = []
|
||||
for i in range(10):
|
||||
client = httpx.AsyncClient(base_url="https://api.example.com")
|
||||
server = FastMCPOpenAPI(
|
||||
openapi_spec=comprehensive_spec,
|
||||
client=client,
|
||||
name=f"Memory Test Server {i}",
|
||||
)
|
||||
servers.append(server)
|
||||
|
||||
# Servers should all be functional
|
||||
assert len(servers) == 10
|
||||
assert all(len(s._tool_manager._tools) == 6 for s in servers)
|
||||
|
||||
# Memory usage shouldn't explode (this is a basic check)
|
||||
gc.collect() # Clean up
|
||||
current_refs = len(gc.get_objects())
|
||||
# Allow reasonable memory growth but not exponential
|
||||
growth_ratio = current_refs / max(baseline_refs, 1)
|
||||
assert growth_ratio < 3.0, (
|
||||
f"Memory usage grew by {growth_ratio}x, which seems excessive"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue