Spaces:
Running
Running
Jeremiah Lowin commited on
Add tests for headers with both legacy and experimental openapi parser (#1259)
Browse files- src/fastmcp/experimental/server/openapi/__init__.py +0 -2
- src/fastmcp/experimental/server/openapi/components.py +21 -16
- src/fastmcp/experimental/server/openapi/routing.py +2 -66
- src/fastmcp/experimental/server/openapi/server.py +3 -3
- tests/client/test_openapi.py +0 -166
- tests/client/test_openapi_experimental.py +201 -0
- tests/client/test_openapi_legacy.py +198 -0
- uv.lock +184 -130
src/fastmcp/experimental/server/openapi/__init__.py
CHANGED
|
@@ -6,7 +6,6 @@ from .server import FastMCPOpenAPI
|
|
| 6 |
# Import from routing
|
| 7 |
from .routing import (
|
| 8 |
MCPType,
|
| 9 |
-
RouteType, # Deprecated but kept for backward compatibility
|
| 10 |
RouteMap,
|
| 11 |
RouteMapFn,
|
| 12 |
ComponentFn,
|
|
@@ -27,7 +26,6 @@ __all__ = [
|
|
| 27 |
"FastMCPOpenAPI",
|
| 28 |
# Routing
|
| 29 |
"MCPType",
|
| 30 |
-
"RouteType", # Deprecated but kept for backward compatibility
|
| 31 |
"RouteMap",
|
| 32 |
"RouteMapFn",
|
| 33 |
"ComponentFn",
|
|
|
|
| 6 |
# Import from routing
|
| 7 |
from .routing import (
|
| 8 |
MCPType,
|
|
|
|
| 9 |
RouteMap,
|
| 10 |
RouteMapFn,
|
| 11 |
ComponentFn,
|
|
|
|
| 26 |
"FastMCPOpenAPI",
|
| 27 |
# Routing
|
| 28 |
"MCPType",
|
|
|
|
| 29 |
"RouteMap",
|
| 30 |
"RouteMapFn",
|
| 31 |
"ComponentFn",
|
src/fastmcp/experimental/server/openapi/components.py
CHANGED
|
@@ -78,25 +78,29 @@ class OpenAPITool(Tool):
|
|
| 78 |
# Build the request using RequestDirector
|
| 79 |
request = self._director.build(self._route, arguments, base_url)
|
| 80 |
|
| 81 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
mcp_headers = get_http_headers()
|
| 83 |
if mcp_headers:
|
| 84 |
-
# Merge with existing headers, MCP headers take precedence
|
| 85 |
if request.headers:
|
| 86 |
request.headers.update(mcp_headers)
|
| 87 |
else:
|
| 88 |
# Create new headers from mcp_headers
|
| 89 |
for key, value in mcp_headers.items():
|
| 90 |
request.headers[key] = value
|
| 91 |
-
|
| 92 |
-
if cli_headers:
|
| 93 |
-
# Merge with existing headers, _client headers take precedence
|
| 94 |
-
if request.headers:
|
| 95 |
-
request.headers.update(cli_headers)
|
| 96 |
-
else:
|
| 97 |
-
# Create new headers from cli_headers
|
| 98 |
-
for key, value in cli_headers.items():
|
| 99 |
-
request.headers[key] = value
|
| 100 |
# print logger
|
| 101 |
logger.debug(f"run - sending request; headers: {request.headers}")
|
| 102 |
|
|
@@ -224,19 +228,20 @@ class OpenAPIResource(Resource):
|
|
| 224 |
if value is not None and value != "":
|
| 225 |
query_params[param.name] = value
|
| 226 |
|
| 227 |
-
# Prepare headers
|
| 228 |
headers = {}
|
| 229 |
-
|
| 230 |
-
headers.update(mcp_headers)
|
| 231 |
-
# Get Headers from client
|
| 232 |
cli_headers = (
|
| 233 |
self._client.headers
|
| 234 |
if hasattr(self._client, "headers") and self._client.headers
|
| 235 |
else {}
|
| 236 |
)
|
| 237 |
-
# Merge with existing headers, _client headers take precedence
|
| 238 |
headers.update(cli_headers)
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
response = await self._client.request(
|
| 241 |
method=self._route.method,
|
| 242 |
url=path,
|
|
|
|
| 78 |
# Build the request using RequestDirector
|
| 79 |
request = self._director.build(self._route, arguments, base_url)
|
| 80 |
|
| 81 |
+
# First add server headers (lowest precedence)
|
| 82 |
+
if cli_headers:
|
| 83 |
+
# Merge with existing headers, _client headers as base
|
| 84 |
+
if request.headers:
|
| 85 |
+
# Start with request headers, then add client headers
|
| 86 |
+
for key, value in cli_headers.items():
|
| 87 |
+
if key not in request.headers:
|
| 88 |
+
request.headers[key] = value
|
| 89 |
+
else:
|
| 90 |
+
# Create new headers from cli_headers
|
| 91 |
+
for key, value in cli_headers.items():
|
| 92 |
+
request.headers[key] = value
|
| 93 |
+
|
| 94 |
+
# Then add MCP client transport headers (highest precedence)
|
| 95 |
mcp_headers = get_http_headers()
|
| 96 |
if mcp_headers:
|
| 97 |
+
# Merge with existing headers, MCP headers take precedence over all
|
| 98 |
if request.headers:
|
| 99 |
request.headers.update(mcp_headers)
|
| 100 |
else:
|
| 101 |
# Create new headers from mcp_headers
|
| 102 |
for key, value in mcp_headers.items():
|
| 103 |
request.headers[key] = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
# print logger
|
| 105 |
logger.debug(f"run - sending request; headers: {request.headers}")
|
| 106 |
|
|
|
|
| 228 |
if value is not None and value != "":
|
| 229 |
query_params[param.name] = value
|
| 230 |
|
| 231 |
+
# Prepare headers with correct precedence: server < client transport
|
| 232 |
headers = {}
|
| 233 |
+
# Start with server headers (lowest precedence)
|
|
|
|
|
|
|
| 234 |
cli_headers = (
|
| 235 |
self._client.headers
|
| 236 |
if hasattr(self._client, "headers") and self._client.headers
|
| 237 |
else {}
|
| 238 |
)
|
|
|
|
| 239 |
headers.update(cli_headers)
|
| 240 |
|
| 241 |
+
# Add MCP client transport headers (highest precedence)
|
| 242 |
+
mcp_headers = get_http_headers()
|
| 243 |
+
headers.update(mcp_headers)
|
| 244 |
+
|
| 245 |
response = await self._client.request(
|
| 246 |
method=self._route.method,
|
| 247 |
url=path,
|
src/fastmcp/experimental/server/openapi/routing.py
CHANGED
|
@@ -2,14 +2,11 @@
|
|
| 2 |
|
| 3 |
import enum
|
| 4 |
import re
|
| 5 |
-
import warnings
|
| 6 |
from collections.abc import Callable
|
| 7 |
from dataclasses import dataclass, field
|
| 8 |
from re import Pattern
|
| 9 |
from typing import TYPE_CHECKING, Literal
|
| 10 |
|
| 11 |
-
import fastmcp
|
| 12 |
-
|
| 13 |
if TYPE_CHECKING:
|
| 14 |
from .components import (
|
| 15 |
OpenAPIResource,
|
|
@@ -41,7 +38,6 @@ class MCPType(enum.Enum):
|
|
| 41 |
RESOURCE: Convert the route to a Resource (typically GET endpoints)
|
| 42 |
RESOURCE_TEMPLATE: Convert the route to a ResourceTemplate (typically GET with path params)
|
| 43 |
EXCLUDE: Exclude the route from being converted to any MCP component
|
| 44 |
-
IGNORE: Deprecated, use EXCLUDE instead
|
| 45 |
"""
|
| 46 |
|
| 47 |
TOOL = "TOOL"
|
|
@@ -51,33 +47,18 @@ class MCPType(enum.Enum):
|
|
| 51 |
EXCLUDE = "EXCLUDE"
|
| 52 |
|
| 53 |
|
| 54 |
-
# Keep RouteType as an alias to MCPType for backward compatibility
|
| 55 |
-
class RouteType(enum.Enum):
|
| 56 |
-
"""
|
| 57 |
-
Deprecated: Use MCPType instead.
|
| 58 |
-
|
| 59 |
-
This enum is kept for backward compatibility and will be removed in a future version.
|
| 60 |
-
"""
|
| 61 |
-
|
| 62 |
-
TOOL = "TOOL"
|
| 63 |
-
RESOURCE = "RESOURCE"
|
| 64 |
-
RESOURCE_TEMPLATE = "RESOURCE_TEMPLATE"
|
| 65 |
-
IGNORE = "IGNORE"
|
| 66 |
-
|
| 67 |
-
|
| 68 |
@dataclass(kw_only=True)
|
| 69 |
class RouteMap:
|
| 70 |
"""Mapping configuration for HTTP routes to FastMCP component types."""
|
| 71 |
|
| 72 |
methods: list[HttpMethod] | Literal["*"] = field(default="*")
|
| 73 |
pattern: Pattern[str] | str = field(default=r".*")
|
| 74 |
-
|
| 75 |
tags: set[str] = field(
|
| 76 |
default_factory=set,
|
| 77 |
metadata={"description": "A set of tags to match. All tags must match."},
|
| 78 |
)
|
| 79 |
-
mcp_type: MCPType
|
| 80 |
-
default=None,
|
| 81 |
metadata={"description": "The type of FastMCP component to create."},
|
| 82 |
)
|
| 83 |
mcp_tags: set[str] = field(
|
|
@@ -87,50 +68,6 @@ class RouteMap:
|
|
| 87 |
},
|
| 88 |
)
|
| 89 |
|
| 90 |
-
def __post_init__(self):
|
| 91 |
-
"""Validate and process the route map after initialization."""
|
| 92 |
-
# Handle backward compatibility for route_type, deprecated in 2.5.0
|
| 93 |
-
if self.mcp_type is None and self.route_type is not None:
|
| 94 |
-
if fastmcp.settings.deprecation_warnings:
|
| 95 |
-
warnings.warn(
|
| 96 |
-
"The 'route_type' parameter is deprecated and will be removed in a future version. "
|
| 97 |
-
"Use 'mcp_type' instead with the appropriate MCPType value.",
|
| 98 |
-
DeprecationWarning,
|
| 99 |
-
stacklevel=2,
|
| 100 |
-
)
|
| 101 |
-
if isinstance(self.route_type, RouteType):
|
| 102 |
-
if fastmcp.settings.deprecation_warnings:
|
| 103 |
-
warnings.warn(
|
| 104 |
-
"The RouteType class is deprecated and will be removed in a future version. "
|
| 105 |
-
"Use MCPType instead.",
|
| 106 |
-
DeprecationWarning,
|
| 107 |
-
stacklevel=2,
|
| 108 |
-
)
|
| 109 |
-
# Check for the deprecated IGNORE value
|
| 110 |
-
if self.route_type == RouteType.IGNORE:
|
| 111 |
-
if fastmcp.settings.deprecation_warnings:
|
| 112 |
-
warnings.warn(
|
| 113 |
-
"RouteType.IGNORE is deprecated and will be removed in a future version. "
|
| 114 |
-
"Use MCPType.EXCLUDE instead.",
|
| 115 |
-
DeprecationWarning,
|
| 116 |
-
stacklevel=2,
|
| 117 |
-
)
|
| 118 |
-
|
| 119 |
-
# Convert from RouteType to MCPType if needed
|
| 120 |
-
if isinstance(self.route_type, RouteType):
|
| 121 |
-
route_type_name = self.route_type.name
|
| 122 |
-
if route_type_name == "IGNORE":
|
| 123 |
-
route_type_name = "EXCLUDE"
|
| 124 |
-
self.mcp_type = getattr(MCPType, route_type_name)
|
| 125 |
-
else:
|
| 126 |
-
self.mcp_type = self.route_type
|
| 127 |
-
elif self.mcp_type is None:
|
| 128 |
-
raise ValueError("`mcp_type` must be provided")
|
| 129 |
-
|
| 130 |
-
# Set route_type to match mcp_type for backward compatibility
|
| 131 |
-
if self.route_type is None:
|
| 132 |
-
self.route_type = self.mcp_type
|
| 133 |
-
|
| 134 |
|
| 135 |
# Default route mapping: all routes become tools.
|
| 136 |
# Users can provide custom route_maps to override this behavior.
|
|
@@ -187,7 +124,6 @@ def _determine_route_type(
|
|
| 187 |
# Export public symbols
|
| 188 |
__all__ = [
|
| 189 |
"MCPType",
|
| 190 |
-
"RouteType", # Deprecated but kept for backward compatibility
|
| 191 |
"RouteMap",
|
| 192 |
"RouteMapFn",
|
| 193 |
"ComponentFn",
|
|
|
|
| 2 |
|
| 3 |
import enum
|
| 4 |
import re
|
|
|
|
| 5 |
from collections.abc import Callable
|
| 6 |
from dataclasses import dataclass, field
|
| 7 |
from re import Pattern
|
| 8 |
from typing import TYPE_CHECKING, Literal
|
| 9 |
|
|
|
|
|
|
|
| 10 |
if TYPE_CHECKING:
|
| 11 |
from .components import (
|
| 12 |
OpenAPIResource,
|
|
|
|
| 38 |
RESOURCE: Convert the route to a Resource (typically GET endpoints)
|
| 39 |
RESOURCE_TEMPLATE: Convert the route to a ResourceTemplate (typically GET with path params)
|
| 40 |
EXCLUDE: Exclude the route from being converted to any MCP component
|
|
|
|
| 41 |
"""
|
| 42 |
|
| 43 |
TOOL = "TOOL"
|
|
|
|
| 47 |
EXCLUDE = "EXCLUDE"
|
| 48 |
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
@dataclass(kw_only=True)
|
| 51 |
class RouteMap:
|
| 52 |
"""Mapping configuration for HTTP routes to FastMCP component types."""
|
| 53 |
|
| 54 |
methods: list[HttpMethod] | Literal["*"] = field(default="*")
|
| 55 |
pattern: Pattern[str] | str = field(default=r".*")
|
| 56 |
+
|
| 57 |
tags: set[str] = field(
|
| 58 |
default_factory=set,
|
| 59 |
metadata={"description": "A set of tags to match. All tags must match."},
|
| 60 |
)
|
| 61 |
+
mcp_type: MCPType = field(
|
|
|
|
| 62 |
metadata={"description": "The type of FastMCP component to create."},
|
| 63 |
)
|
| 64 |
mcp_tags: set[str] = field(
|
|
|
|
| 68 |
},
|
| 69 |
)
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
# Default route mapping: all routes become tools.
|
| 73 |
# Users can provide custom route_maps to override this behavior.
|
|
|
|
| 124 |
# Export public symbols
|
| 125 |
__all__ = [
|
| 126 |
"MCPType",
|
|
|
|
| 127 |
"RouteMap",
|
| 128 |
"RouteMapFn",
|
| 129 |
"ComponentFn",
|
src/fastmcp/experimental/server/openapi/server.py
CHANGED
|
@@ -151,7 +151,7 @@ class FastMCPOpenAPI(FastMCP):
|
|
| 151 |
try:
|
| 152 |
self._spec = Spec.from_dict(openapi_spec) # type: ignore[arg-type]
|
| 153 |
self._director = RequestDirector(self._spec)
|
| 154 |
-
logger.
|
| 155 |
"Initialized OpenAPI RequestDirector for stateless request building"
|
| 156 |
)
|
| 157 |
except Exception as e:
|
|
@@ -199,9 +199,9 @@ class FastMCPOpenAPI(FastMCP):
|
|
| 199 |
elif route_type == MCPType.RESOURCE_TEMPLATE:
|
| 200 |
self._create_openapi_template(route, component_name, tags=route_tags)
|
| 201 |
elif route_type == MCPType.EXCLUDE:
|
| 202 |
-
logger.
|
| 203 |
|
| 204 |
-
logger.
|
| 205 |
|
| 206 |
def _generate_default_name(
|
| 207 |
self, route: HTTPRoute, mcp_names_map: dict[str, str] | None = None
|
|
|
|
| 151 |
try:
|
| 152 |
self._spec = Spec.from_dict(openapi_spec) # type: ignore[arg-type]
|
| 153 |
self._director = RequestDirector(self._spec)
|
| 154 |
+
logger.debug(
|
| 155 |
"Initialized OpenAPI RequestDirector for stateless request building"
|
| 156 |
)
|
| 157 |
except Exception as e:
|
|
|
|
| 199 |
elif route_type == MCPType.RESOURCE_TEMPLATE:
|
| 200 |
self._create_openapi_template(route, component_name, tags=route_tags)
|
| 201 |
elif route_type == MCPType.EXCLUDE:
|
| 202 |
+
logger.debug(f"Excluding route: {route.method} {route.path}")
|
| 203 |
|
| 204 |
+
logger.debug(f"Created FastMCP OpenAPI server with {len(http_routes)} routes")
|
| 205 |
|
| 206 |
def _generate_default_name(
|
| 207 |
self, route: HTTPRoute, mcp_names_map: dict[str, str] | None = None
|
tests/client/test_openapi.py
DELETED
|
@@ -1,166 +0,0 @@
|
|
| 1 |
-
import json
|
| 2 |
-
from collections.abc import Generator
|
| 3 |
-
|
| 4 |
-
import pytest
|
| 5 |
-
from fastapi import FastAPI, Request
|
| 6 |
-
|
| 7 |
-
from fastmcp import Client, FastMCP
|
| 8 |
-
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
| 9 |
-
from fastmcp.server.openapi import MCPType, RouteMap
|
| 10 |
-
from fastmcp.utilities.tests import run_server_in_process
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def fastmcp_server_for_headers() -> FastMCP:
|
| 14 |
-
app = FastAPI()
|
| 15 |
-
|
| 16 |
-
@app.get("/headers")
|
| 17 |
-
def get_headers(request: Request):
|
| 18 |
-
return request.headers
|
| 19 |
-
|
| 20 |
-
@app.get("/headers/{header_name}")
|
| 21 |
-
def get_header_by_name(header_name: str, request: Request):
|
| 22 |
-
return request.headers[header_name]
|
| 23 |
-
|
| 24 |
-
@app.post("/headers")
|
| 25 |
-
def post_headers(request: Request):
|
| 26 |
-
return request.headers
|
| 27 |
-
|
| 28 |
-
mcp = FastMCP.from_fastapi(
|
| 29 |
-
app,
|
| 30 |
-
httpx_client_kwargs={"headers": {"x-server-header": "test-abc"}},
|
| 31 |
-
route_maps=[
|
| 32 |
-
# GET requests with path parameters go to ResourceTemplate
|
| 33 |
-
RouteMap(
|
| 34 |
-
methods=["GET"],
|
| 35 |
-
pattern=r".*\{.*\}.*",
|
| 36 |
-
mcp_type=MCPType.RESOURCE_TEMPLATE,
|
| 37 |
-
),
|
| 38 |
-
# GET requests without path parameters go to Resource
|
| 39 |
-
RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
|
| 40 |
-
],
|
| 41 |
-
)
|
| 42 |
-
|
| 43 |
-
return mcp
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
def run_server(host: str, port: int, **kwargs) -> None:
|
| 47 |
-
fastmcp_server_for_headers().run(host=host, port=port, **kwargs)
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
def run_proxy_server(host: str, port: int, shttp_url: str, **kwargs) -> None:
|
| 51 |
-
app = FastMCP.as_proxy(StreamableHttpTransport(shttp_url))
|
| 52 |
-
app.run(host=host, port=port, **kwargs)
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
class TestClientHeaders:
|
| 56 |
-
@pytest.fixture(scope="class")
|
| 57 |
-
def shttp_server(self) -> Generator[str, None, None]:
|
| 58 |
-
with run_server_in_process(run_server, transport="http") as url:
|
| 59 |
-
yield f"{url}/mcp/"
|
| 60 |
-
|
| 61 |
-
@pytest.fixture(scope="class")
|
| 62 |
-
def sse_server(self) -> Generator[str, None, None]:
|
| 63 |
-
with run_server_in_process(run_server, transport="sse") as url:
|
| 64 |
-
yield f"{url}/sse/"
|
| 65 |
-
|
| 66 |
-
@pytest.fixture(scope="class")
|
| 67 |
-
def proxy_server(self, shttp_server: str) -> Generator[str, None, None]:
|
| 68 |
-
with run_server_in_process(
|
| 69 |
-
run_proxy_server,
|
| 70 |
-
shttp_url=shttp_server,
|
| 71 |
-
transport="http",
|
| 72 |
-
) as url:
|
| 73 |
-
yield f"{url}/mcp/"
|
| 74 |
-
|
| 75 |
-
async def test_client_headers_sse_resource(self, sse_server: str):
|
| 76 |
-
async with Client(
|
| 77 |
-
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 78 |
-
) as client:
|
| 79 |
-
result = await client.read_resource("resource://get_headers_headers_get")
|
| 80 |
-
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 81 |
-
assert headers["x-test"] == "test-123"
|
| 82 |
-
|
| 83 |
-
async def test_client_headers_shttp_resource(self, shttp_server: str):
|
| 84 |
-
async with Client(
|
| 85 |
-
transport=StreamableHttpTransport(
|
| 86 |
-
shttp_server, headers={"X-TEST": "test-123"}
|
| 87 |
-
)
|
| 88 |
-
) as client:
|
| 89 |
-
result = await client.read_resource("resource://get_headers_headers_get")
|
| 90 |
-
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 91 |
-
assert headers["x-test"] == "test-123"
|
| 92 |
-
|
| 93 |
-
async def test_client_headers_sse_resource_template(self, sse_server: str):
|
| 94 |
-
async with Client(
|
| 95 |
-
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 96 |
-
) as client:
|
| 97 |
-
result = await client.read_resource(
|
| 98 |
-
"resource://get_header_by_name_headers/x-test"
|
| 99 |
-
)
|
| 100 |
-
header = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 101 |
-
assert header == "test-123"
|
| 102 |
-
|
| 103 |
-
async def test_client_headers_shttp_resource_template(self, shttp_server: str):
|
| 104 |
-
async with Client(
|
| 105 |
-
transport=StreamableHttpTransport(
|
| 106 |
-
shttp_server, headers={"X-TEST": "test-123"}
|
| 107 |
-
)
|
| 108 |
-
) as client:
|
| 109 |
-
result = await client.read_resource(
|
| 110 |
-
"resource://get_header_by_name_headers/x-test"
|
| 111 |
-
)
|
| 112 |
-
header = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 113 |
-
assert header == "test-123"
|
| 114 |
-
|
| 115 |
-
async def test_client_headers_sse_tool(self, sse_server: str):
|
| 116 |
-
async with Client(
|
| 117 |
-
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 118 |
-
) as client:
|
| 119 |
-
result = await client.call_tool("post_headers_headers_post")
|
| 120 |
-
headers: dict[str, str] = result.data
|
| 121 |
-
assert headers["x-test"] == "test-123"
|
| 122 |
-
|
| 123 |
-
async def test_client_headers_shttp_tool(self, shttp_server: str):
|
| 124 |
-
async with Client(
|
| 125 |
-
transport=StreamableHttpTransport(
|
| 126 |
-
shttp_server, headers={"X-TEST": "test-123"}
|
| 127 |
-
)
|
| 128 |
-
) as client:
|
| 129 |
-
result = await client.call_tool("post_headers_headers_post")
|
| 130 |
-
headers: dict[str, str] = result.data
|
| 131 |
-
assert headers["x-test"] == "test-123"
|
| 132 |
-
|
| 133 |
-
async def test_client_overrides_server_headers(self, shttp_server: str):
|
| 134 |
-
async with Client(
|
| 135 |
-
transport=StreamableHttpTransport(
|
| 136 |
-
shttp_server, headers={"x-server-header": "test-client"}
|
| 137 |
-
)
|
| 138 |
-
) as client:
|
| 139 |
-
result = await client.read_resource("resource://get_headers_headers_get")
|
| 140 |
-
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 141 |
-
assert headers["x-server-header"] == "test-client"
|
| 142 |
-
|
| 143 |
-
async def test_client_with_excluded_header_is_ignored(self, sse_server: str):
|
| 144 |
-
async with Client(
|
| 145 |
-
transport=SSETransport(
|
| 146 |
-
sse_server,
|
| 147 |
-
headers={
|
| 148 |
-
"x-server-header": "test-client",
|
| 149 |
-
"host": "1.2.3.4",
|
| 150 |
-
"not-host": "1.2.3.4",
|
| 151 |
-
},
|
| 152 |
-
)
|
| 153 |
-
) as client:
|
| 154 |
-
result = await client.read_resource("resource://get_headers_headers_get")
|
| 155 |
-
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 156 |
-
assert headers["not-host"] == "1.2.3.4"
|
| 157 |
-
assert headers["host"] == "fastapi"
|
| 158 |
-
|
| 159 |
-
async def test_client_headers_proxy(self, proxy_server: str):
|
| 160 |
-
"""
|
| 161 |
-
Test that client headers are passed through the proxy to the remove server.
|
| 162 |
-
"""
|
| 163 |
-
async with Client(transport=StreamableHttpTransport(proxy_server)) as client:
|
| 164 |
-
result = await client.read_resource("resource://get_headers_headers_get")
|
| 165 |
-
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 166 |
-
assert headers["x-server-header"] == "test-abc"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/client/test_openapi_experimental.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from collections.abc import Generator
|
| 3 |
+
|
| 4 |
+
import pytest
|
| 5 |
+
from fastapi import FastAPI, Request
|
| 6 |
+
|
| 7 |
+
import fastmcp
|
| 8 |
+
from fastmcp import Client, FastMCP
|
| 9 |
+
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
| 10 |
+
from fastmcp.experimental.server.openapi import MCPType, RouteMap
|
| 11 |
+
from fastmcp.utilities.tests import run_server_in_process
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def fastmcp_server_for_headers() -> FastMCP:
|
| 15 |
+
fastmcp.settings.experimental.enable_new_openapi_parser = True
|
| 16 |
+
|
| 17 |
+
app = FastAPI()
|
| 18 |
+
|
| 19 |
+
@app.get("/headers")
|
| 20 |
+
def get_headers(request: Request):
|
| 21 |
+
return request.headers
|
| 22 |
+
|
| 23 |
+
@app.get("/headers/{header_name}")
|
| 24 |
+
def get_header_by_name(header_name: str, request: Request):
|
| 25 |
+
return request.headers[header_name]
|
| 26 |
+
|
| 27 |
+
@app.post("/headers")
|
| 28 |
+
def post_headers(request: Request):
|
| 29 |
+
return request.headers
|
| 30 |
+
|
| 31 |
+
mcp = FastMCP.from_fastapi(
|
| 32 |
+
app,
|
| 33 |
+
httpx_client_kwargs={"headers": {"x-server-header": "test-abc"}},
|
| 34 |
+
route_maps=[
|
| 35 |
+
# GET requests with path parameters go to ResourceTemplate
|
| 36 |
+
RouteMap(
|
| 37 |
+
methods=["GET"],
|
| 38 |
+
pattern=r".*\{.*\}.*",
|
| 39 |
+
mcp_type=MCPType.RESOURCE_TEMPLATE,
|
| 40 |
+
),
|
| 41 |
+
# GET requests without path parameters go to Resource
|
| 42 |
+
RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
|
| 43 |
+
],
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
return mcp
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def run_server(host: str, port: int, **kwargs) -> None:
|
| 50 |
+
fastmcp_server_for_headers().run(host=host, port=port, **kwargs)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def run_proxy_server(host: str, port: int, shttp_url: str, **kwargs) -> None:
|
| 54 |
+
app = FastMCP.as_proxy(StreamableHttpTransport(shttp_url))
|
| 55 |
+
app.run(host=host, port=port, **kwargs)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
@pytest.fixture(scope="module")
|
| 59 |
+
def shttp_server() -> Generator[str, None, None]:
|
| 60 |
+
with run_server_in_process(run_server, transport="http") as url:
|
| 61 |
+
yield f"{url}/mcp/"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@pytest.fixture(scope="module")
|
| 65 |
+
def sse_server() -> Generator[str, None, None]:
|
| 66 |
+
with run_server_in_process(run_server, transport="sse") as url:
|
| 67 |
+
yield f"{url}/sse/"
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
@pytest.fixture(scope="module")
|
| 71 |
+
def proxy_server(shttp_server: str) -> Generator[str, None, None]:
|
| 72 |
+
with run_server_in_process(
|
| 73 |
+
run_proxy_server,
|
| 74 |
+
shttp_url=shttp_server,
|
| 75 |
+
transport="http",
|
| 76 |
+
) as url:
|
| 77 |
+
yield f"{url}/mcp/"
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
async def test_fastapi_client_headers_streamable_http_resource(shttp_server: str):
|
| 81 |
+
async with Client(transport=StreamableHttpTransport(shttp_server)) as client:
|
| 82 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 83 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 84 |
+
assert headers["x-server-header"] == "test-abc"
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
async def test_fastapi_client_headers_sse_resource(sse_server: str):
|
| 88 |
+
async with Client(transport=SSETransport(sse_server)) as client:
|
| 89 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 90 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 91 |
+
assert headers["x-server-header"] == "test-abc"
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
async def test_fastapi_client_headers_streamable_http_tool(shttp_server: str):
|
| 95 |
+
async with Client(transport=StreamableHttpTransport(shttp_server)) as client:
|
| 96 |
+
result = await client.call_tool("post_headers_headers_post")
|
| 97 |
+
headers: dict[str, str] = result.data
|
| 98 |
+
assert headers["x-server-header"] == "test-abc"
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
async def test_fastapi_client_headers_sse_tool(sse_server: str):
|
| 102 |
+
async with Client(transport=SSETransport(sse_server)) as client:
|
| 103 |
+
result = await client.call_tool("post_headers_headers_post")
|
| 104 |
+
headers: dict[str, str] = result.data
|
| 105 |
+
assert headers["x-server-header"] == "test-abc"
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
async def test_client_headers_sse_resource(sse_server: str):
|
| 109 |
+
async with Client(
|
| 110 |
+
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 111 |
+
) as client:
|
| 112 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 113 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 114 |
+
assert headers["x-test"] == "test-123"
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
async def test_client_headers_shttp_resource(shttp_server: str):
|
| 118 |
+
async with Client(
|
| 119 |
+
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
|
| 120 |
+
) as client:
|
| 121 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 122 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 123 |
+
assert headers["x-test"] == "test-123"
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
async def test_client_headers_sse_resource_template(sse_server: str):
|
| 127 |
+
async with Client(
|
| 128 |
+
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 129 |
+
) as client:
|
| 130 |
+
result = await client.read_resource(
|
| 131 |
+
"resource://get_header_by_name_headers/x-test"
|
| 132 |
+
)
|
| 133 |
+
header = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 134 |
+
assert header == "test-123"
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
async def test_client_headers_shttp_resource_template(shttp_server: str):
|
| 138 |
+
async with Client(
|
| 139 |
+
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
|
| 140 |
+
) as client:
|
| 141 |
+
result = await client.read_resource(
|
| 142 |
+
"resource://get_header_by_name_headers/x-test"
|
| 143 |
+
)
|
| 144 |
+
header = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 145 |
+
assert header == "test-123"
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
async def test_client_headers_sse_tool(sse_server: str):
|
| 149 |
+
async with Client(
|
| 150 |
+
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 151 |
+
) as client:
|
| 152 |
+
result = await client.call_tool("post_headers_headers_post")
|
| 153 |
+
headers: dict[str, str] = result.data
|
| 154 |
+
assert headers["x-test"] == "test-123"
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
async def test_client_headers_shttp_tool(shttp_server: str):
|
| 158 |
+
async with Client(
|
| 159 |
+
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
|
| 160 |
+
) as client:
|
| 161 |
+
result = await client.call_tool("post_headers_headers_post")
|
| 162 |
+
headers: dict[str, str] = result.data
|
| 163 |
+
assert headers["x-test"] == "test-123"
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
async def test_client_overrides_server_headers(shttp_server: str):
|
| 167 |
+
async with Client(
|
| 168 |
+
transport=StreamableHttpTransport(
|
| 169 |
+
shttp_server, headers={"x-server-header": "test-client"}
|
| 170 |
+
)
|
| 171 |
+
) as client:
|
| 172 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 173 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 174 |
+
assert headers["x-server-header"] == "test-client"
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
async def test_client_with_excluded_header_is_ignored(sse_server: str):
|
| 178 |
+
async with Client(
|
| 179 |
+
transport=SSETransport(
|
| 180 |
+
sse_server,
|
| 181 |
+
headers={
|
| 182 |
+
"x-server-header": "test-client",
|
| 183 |
+
"host": "1.2.3.4",
|
| 184 |
+
"not-host": "1.2.3.4",
|
| 185 |
+
},
|
| 186 |
+
)
|
| 187 |
+
) as client:
|
| 188 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 189 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 190 |
+
assert headers["not-host"] == "1.2.3.4"
|
| 191 |
+
assert headers["host"] == "fastapi"
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
async def test_client_headers_proxy(proxy_server: str):
|
| 195 |
+
"""
|
| 196 |
+
Test that client headers are passed through the proxy to the remove server.
|
| 197 |
+
"""
|
| 198 |
+
async with Client(transport=StreamableHttpTransport(proxy_server)) as client:
|
| 199 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 200 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 201 |
+
assert headers["x-server-header"] == "test-abc"
|
tests/client/test_openapi_legacy.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from collections.abc import Generator
|
| 3 |
+
|
| 4 |
+
import pytest
|
| 5 |
+
from fastapi import FastAPI, Request
|
| 6 |
+
|
| 7 |
+
from fastmcp import Client, FastMCP
|
| 8 |
+
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
| 9 |
+
from fastmcp.server.openapi import MCPType, RouteMap
|
| 10 |
+
from fastmcp.utilities.tests import run_server_in_process
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def fastmcp_server_for_headers() -> FastMCP:
|
| 14 |
+
app = FastAPI()
|
| 15 |
+
|
| 16 |
+
@app.get("/headers")
|
| 17 |
+
def get_headers(request: Request):
|
| 18 |
+
return request.headers
|
| 19 |
+
|
| 20 |
+
@app.get("/headers/{header_name}")
|
| 21 |
+
def get_header_by_name(header_name: str, request: Request):
|
| 22 |
+
return request.headers[header_name]
|
| 23 |
+
|
| 24 |
+
@app.post("/headers")
|
| 25 |
+
def post_headers(request: Request):
|
| 26 |
+
return request.headers
|
| 27 |
+
|
| 28 |
+
mcp = FastMCP.from_fastapi(
|
| 29 |
+
app,
|
| 30 |
+
httpx_client_kwargs={"headers": {"x-server-header": "test-abc"}},
|
| 31 |
+
route_maps=[
|
| 32 |
+
# GET requests with path parameters go to ResourceTemplate
|
| 33 |
+
RouteMap(
|
| 34 |
+
methods=["GET"],
|
| 35 |
+
pattern=r".*\{.*\}.*",
|
| 36 |
+
mcp_type=MCPType.RESOURCE_TEMPLATE,
|
| 37 |
+
),
|
| 38 |
+
# GET requests without path parameters go to Resource
|
| 39 |
+
RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
|
| 40 |
+
],
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
return mcp
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def run_server(host: str, port: int, **kwargs) -> None:
|
| 47 |
+
fastmcp_server_for_headers().run(host=host, port=port, **kwargs)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def run_proxy_server(host: str, port: int, shttp_url: str, **kwargs) -> None:
|
| 51 |
+
app = FastMCP.as_proxy(StreamableHttpTransport(shttp_url))
|
| 52 |
+
app.run(host=host, port=port, **kwargs)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@pytest.fixture(scope="module")
|
| 56 |
+
def shttp_server() -> Generator[str, None, None]:
|
| 57 |
+
with run_server_in_process(run_server, transport="http") as url:
|
| 58 |
+
yield f"{url}/mcp/"
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@pytest.fixture(scope="module")
|
| 62 |
+
def sse_server() -> Generator[str, None, None]:
|
| 63 |
+
with run_server_in_process(run_server, transport="sse") as url:
|
| 64 |
+
yield f"{url}/sse/"
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@pytest.fixture(scope="module")
|
| 68 |
+
def proxy_server(shttp_server: str) -> Generator[str, None, None]:
|
| 69 |
+
with run_server_in_process(
|
| 70 |
+
run_proxy_server,
|
| 71 |
+
shttp_url=shttp_server,
|
| 72 |
+
transport="http",
|
| 73 |
+
) as url:
|
| 74 |
+
yield f"{url}/mcp/"
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
async def test_fastapi_client_headers_streamable_http_resource(shttp_server: str):
|
| 78 |
+
async with Client(transport=StreamableHttpTransport(shttp_server)) as client:
|
| 79 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 80 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 81 |
+
assert headers["x-server-header"] == "test-abc"
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
async def test_fastapi_client_headers_sse_resource(sse_server: str):
|
| 85 |
+
async with Client(transport=SSETransport(sse_server)) as client:
|
| 86 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 87 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 88 |
+
assert headers["x-server-header"] == "test-abc"
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
async def test_fastapi_client_headers_streamable_http_tool(shttp_server: str):
|
| 92 |
+
async with Client(transport=StreamableHttpTransport(shttp_server)) as client:
|
| 93 |
+
result = await client.call_tool("post_headers_headers_post")
|
| 94 |
+
headers: dict[str, str] = result.data
|
| 95 |
+
assert headers["x-server-header"] == "test-abc"
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
async def test_fastapi_client_headers_sse_tool(sse_server: str):
|
| 99 |
+
async with Client(transport=SSETransport(sse_server)) as client:
|
| 100 |
+
result = await client.call_tool("post_headers_headers_post")
|
| 101 |
+
headers: dict[str, str] = result.data
|
| 102 |
+
assert headers["x-server-header"] == "test-abc"
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
async def test_client_headers_sse_resource(sse_server: str):
|
| 106 |
+
async with Client(
|
| 107 |
+
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 108 |
+
) as client:
|
| 109 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 110 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 111 |
+
assert headers["x-test"] == "test-123"
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
async def test_client_headers_shttp_resource(shttp_server: str):
|
| 115 |
+
async with Client(
|
| 116 |
+
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
|
| 117 |
+
) as client:
|
| 118 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 119 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 120 |
+
assert headers["x-test"] == "test-123"
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
async def test_client_headers_sse_resource_template(sse_server: str):
|
| 124 |
+
async with Client(
|
| 125 |
+
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 126 |
+
) as client:
|
| 127 |
+
result = await client.read_resource(
|
| 128 |
+
"resource://get_header_by_name_headers/x-test"
|
| 129 |
+
)
|
| 130 |
+
header = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 131 |
+
assert header == "test-123"
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
async def test_client_headers_shttp_resource_template(shttp_server: str):
|
| 135 |
+
async with Client(
|
| 136 |
+
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
|
| 137 |
+
) as client:
|
| 138 |
+
result = await client.read_resource(
|
| 139 |
+
"resource://get_header_by_name_headers/x-test"
|
| 140 |
+
)
|
| 141 |
+
header = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 142 |
+
assert header == "test-123"
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
async def test_client_headers_sse_tool(sse_server: str):
|
| 146 |
+
async with Client(
|
| 147 |
+
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
|
| 148 |
+
) as client:
|
| 149 |
+
result = await client.call_tool("post_headers_headers_post")
|
| 150 |
+
headers: dict[str, str] = result.data
|
| 151 |
+
assert headers["x-test"] == "test-123"
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
async def test_client_headers_shttp_tool(shttp_server: str):
|
| 155 |
+
async with Client(
|
| 156 |
+
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
|
| 157 |
+
) as client:
|
| 158 |
+
result = await client.call_tool("post_headers_headers_post")
|
| 159 |
+
headers: dict[str, str] = result.data
|
| 160 |
+
assert headers["x-test"] == "test-123"
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
async def test_client_overrides_server_headers(shttp_server: str):
|
| 164 |
+
async with Client(
|
| 165 |
+
transport=StreamableHttpTransport(
|
| 166 |
+
shttp_server, headers={"x-server-header": "test-client"}
|
| 167 |
+
)
|
| 168 |
+
) as client:
|
| 169 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 170 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 171 |
+
assert headers["x-server-header"] == "test-client"
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
async def test_client_with_excluded_header_is_ignored(sse_server: str):
|
| 175 |
+
async with Client(
|
| 176 |
+
transport=SSETransport(
|
| 177 |
+
sse_server,
|
| 178 |
+
headers={
|
| 179 |
+
"x-server-header": "test-client",
|
| 180 |
+
"host": "1.2.3.4",
|
| 181 |
+
"not-host": "1.2.3.4",
|
| 182 |
+
},
|
| 183 |
+
)
|
| 184 |
+
) as client:
|
| 185 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 186 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 187 |
+
assert headers["not-host"] == "1.2.3.4"
|
| 188 |
+
assert headers["host"] == "fastapi"
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
async def test_client_headers_proxy(proxy_server: str):
|
| 192 |
+
"""
|
| 193 |
+
Test that client headers are passed through the proxy to the remove server.
|
| 194 |
+
"""
|
| 195 |
+
async with Client(transport=StreamableHttpTransport(proxy_server)) as client:
|
| 196 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 197 |
+
headers = json.loads(result[0].text) # type: ignore[attr-defined]
|
| 198 |
+
assert headers["x-server-header"] == "test-abc"
|
uv.lock
CHANGED
|
@@ -50,23 +50,32 @@ wheels = [
|
|
| 50 |
|
| 51 |
[[package]]
|
| 52 |
name = "authlib"
|
| 53 |
-
version = "1.6.
|
| 54 |
source = { registry = "https://pypi.org/simple" }
|
| 55 |
dependencies = [
|
| 56 |
{ name = "cryptography" },
|
| 57 |
]
|
| 58 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 59 |
wheels = [
|
| 60 |
-
{ url = "https://files.pythonhosted.org/packages/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
]
|
| 62 |
|
| 63 |
[[package]]
|
| 64 |
name = "certifi"
|
| 65 |
-
version = "2025.
|
| 66 |
source = { registry = "https://pypi.org/simple" }
|
| 67 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 68 |
wheels = [
|
| 69 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 70 |
]
|
| 71 |
|
| 72 |
[[package]]
|
|
@@ -237,66 +246,87 @@ wheels = [
|
|
| 237 |
|
| 238 |
[[package]]
|
| 239 |
name = "coverage"
|
| 240 |
-
version = "7.
|
| 241 |
-
source = { registry = "https://pypi.org/simple" }
|
| 242 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 243 |
-
wheels = [
|
| 244 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 245 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 246 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 247 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 248 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 249 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 250 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 251 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 252 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 253 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 254 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 255 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 256 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 257 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 258 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 259 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 260 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 261 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 262 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 263 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 264 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 265 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 266 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 267 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 268 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 269 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 270 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 271 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 272 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 273 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 274 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 275 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 276 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 277 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 278 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 279 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 280 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 281 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 282 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 283 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 284 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 285 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 286 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 287 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 288 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 289 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 290 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 291 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 292 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 293 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 294 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 295 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 296 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 297 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 298 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 299 |
-
{ url = "https://files.pythonhosted.org/packages/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
]
|
| 301 |
|
| 302 |
[package.optional-dependencies]
|
|
@@ -353,7 +383,7 @@ wheels = [
|
|
| 353 |
|
| 354 |
[[package]]
|
| 355 |
name = "cyclopts"
|
| 356 |
-
version = "3.22.
|
| 357 |
source = { registry = "https://pypi.org/simple" }
|
| 358 |
dependencies = [
|
| 359 |
{ name = "attrs" },
|
|
@@ -362,9 +392,9 @@ dependencies = [
|
|
| 362 |
{ name = "rich-rst" },
|
| 363 |
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
| 364 |
]
|
| 365 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 366 |
wheels = [
|
| 367 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 368 |
]
|
| 369 |
|
| 370 |
[[package]]
|
|
@@ -387,11 +417,11 @@ wheels = [
|
|
| 387 |
|
| 388 |
[[package]]
|
| 389 |
name = "distlib"
|
| 390 |
-
version = "0.
|
| 391 |
source = { registry = "https://pypi.org/simple" }
|
| 392 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 393 |
wheels = [
|
| 394 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 395 |
]
|
| 396 |
|
| 397 |
[[package]]
|
|
@@ -405,11 +435,11 @@ wheels = [
|
|
| 405 |
|
| 406 |
[[package]]
|
| 407 |
name = "docstring-parser"
|
| 408 |
-
version = "0.
|
| 409 |
source = { registry = "https://pypi.org/simple" }
|
| 410 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 411 |
wheels = [
|
| 412 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 413 |
]
|
| 414 |
|
| 415 |
[[package]]
|
|
@@ -479,16 +509,16 @@ wheels = [
|
|
| 479 |
|
| 480 |
[[package]]
|
| 481 |
name = "fastapi"
|
| 482 |
-
version = "0.
|
| 483 |
source = { registry = "https://pypi.org/simple" }
|
| 484 |
dependencies = [
|
| 485 |
{ name = "pydantic" },
|
| 486 |
{ name = "starlette" },
|
| 487 |
{ name = "typing-extensions" },
|
| 488 |
]
|
| 489 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 490 |
wheels = [
|
| 491 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 492 |
]
|
| 493 |
|
| 494 |
[[package]]
|
|
@@ -600,14 +630,14 @@ wheels = [
|
|
| 600 |
|
| 601 |
[[package]]
|
| 602 |
name = "gitpython"
|
| 603 |
-
version = "3.1.
|
| 604 |
source = { registry = "https://pypi.org/simple" }
|
| 605 |
dependencies = [
|
| 606 |
{ name = "gitdb" },
|
| 607 |
]
|
| 608 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 609 |
wheels = [
|
| 610 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 611 |
]
|
| 612 |
|
| 613 |
[[package]]
|
|
@@ -768,7 +798,7 @@ wheels = [
|
|
| 768 |
|
| 769 |
[[package]]
|
| 770 |
name = "jsonschema"
|
| 771 |
-
version = "4.
|
| 772 |
source = { registry = "https://pypi.org/simple" }
|
| 773 |
dependencies = [
|
| 774 |
{ name = "attrs" },
|
|
@@ -776,9 +806,9 @@ dependencies = [
|
|
| 776 |
{ name = "referencing" },
|
| 777 |
{ name = "rpds-py" },
|
| 778 |
]
|
| 779 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 780 |
wheels = [
|
| 781 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 782 |
]
|
| 783 |
|
| 784 |
[[package]]
|
|
@@ -911,7 +941,7 @@ wheels = [
|
|
| 911 |
|
| 912 |
[[package]]
|
| 913 |
name = "mcp"
|
| 914 |
-
version = "1.
|
| 915 |
source = { registry = "https://pypi.org/simple" }
|
| 916 |
dependencies = [
|
| 917 |
{ name = "anyio" },
|
|
@@ -921,13 +951,14 @@ dependencies = [
|
|
| 921 |
{ name = "pydantic" },
|
| 922 |
{ name = "pydantic-settings" },
|
| 923 |
{ name = "python-multipart" },
|
|
|
|
| 924 |
{ name = "sse-starlette" },
|
| 925 |
{ name = "starlette" },
|
| 926 |
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
| 927 |
]
|
| 928 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 929 |
wheels = [
|
| 930 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 931 |
]
|
| 932 |
|
| 933 |
[[package]]
|
|
@@ -1065,15 +1096,15 @@ wheels = [
|
|
| 1065 |
|
| 1066 |
[[package]]
|
| 1067 |
name = "pdbpp"
|
| 1068 |
-
version = "0.11.
|
| 1069 |
source = { registry = "https://pypi.org/simple" }
|
| 1070 |
dependencies = [
|
| 1071 |
{ name = "fancycompleter" },
|
| 1072 |
{ name = "pygments" },
|
| 1073 |
]
|
| 1074 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1075 |
wheels = [
|
| 1076 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1077 |
]
|
| 1078 |
|
| 1079 |
[[package]]
|
|
@@ -1348,24 +1379,24 @@ wheels = [
|
|
| 1348 |
|
| 1349 |
[[package]]
|
| 1350 |
name = "pyrepl"
|
| 1351 |
-
version = "0.11.
|
| 1352 |
source = { registry = "https://pypi.org/simple" }
|
| 1353 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1354 |
wheels = [
|
| 1355 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1356 |
]
|
| 1357 |
|
| 1358 |
[[package]]
|
| 1359 |
name = "pyright"
|
| 1360 |
-
version = "1.1.
|
| 1361 |
source = { registry = "https://pypi.org/simple" }
|
| 1362 |
dependencies = [
|
| 1363 |
{ name = "nodeenv" },
|
| 1364 |
{ name = "typing-extensions" },
|
| 1365 |
]
|
| 1366 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1367 |
wheels = [
|
| 1368 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1369 |
]
|
| 1370 |
|
| 1371 |
[[package]]
|
|
@@ -1388,14 +1419,15 @@ wheels = [
|
|
| 1388 |
|
| 1389 |
[[package]]
|
| 1390 |
name = "pytest-asyncio"
|
| 1391 |
-
version = "1.
|
| 1392 |
source = { registry = "https://pypi.org/simple" }
|
| 1393 |
dependencies = [
|
|
|
|
| 1394 |
{ name = "pytest" },
|
| 1395 |
]
|
| 1396 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1397 |
wheels = [
|
| 1398 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1399 |
]
|
| 1400 |
|
| 1401 |
[[package]]
|
|
@@ -1502,6 +1534,28 @@ wheels = [
|
|
| 1502 |
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
| 1503 |
]
|
| 1504 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1505 |
[[package]]
|
| 1506 |
name = "pyyaml"
|
| 1507 |
version = "6.0.2"
|
|
@@ -1658,16 +1712,15 @@ wheels = [
|
|
| 1658 |
|
| 1659 |
[[package]]
|
| 1660 |
name = "rich"
|
| 1661 |
-
version = "14.
|
| 1662 |
source = { registry = "https://pypi.org/simple" }
|
| 1663 |
dependencies = [
|
| 1664 |
{ name = "markdown-it-py" },
|
| 1665 |
{ name = "pygments" },
|
| 1666 |
-
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
| 1667 |
]
|
| 1668 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1669 |
wheels = [
|
| 1670 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1671 |
]
|
| 1672 |
|
| 1673 |
[[package]]
|
|
@@ -1811,27 +1864,27 @@ wheels = [
|
|
| 1811 |
|
| 1812 |
[[package]]
|
| 1813 |
name = "ruff"
|
| 1814 |
-
version = "0.12.
|
| 1815 |
-
source = { registry = "https://pypi.org/simple" }
|
| 1816 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1817 |
-
wheels = [
|
| 1818 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1819 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1820 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1821 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1822 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1823 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1824 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1825 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1826 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1827 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1828 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1829 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1830 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1831 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1832 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1833 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1834 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1835 |
]
|
| 1836 |
|
| 1837 |
[[package]]
|
|
@@ -1898,14 +1951,15 @@ wheels = [
|
|
| 1898 |
|
| 1899 |
[[package]]
|
| 1900 |
name = "starlette"
|
| 1901 |
-
version = "0.
|
| 1902 |
source = { registry = "https://pypi.org/simple" }
|
| 1903 |
dependencies = [
|
| 1904 |
{ name = "anyio" },
|
|
|
|
| 1905 |
]
|
| 1906 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1907 |
wheels = [
|
| 1908 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1909 |
]
|
| 1910 |
|
| 1911 |
[[package]]
|
|
@@ -2053,16 +2107,16 @@ wheels = [
|
|
| 2053 |
|
| 2054 |
[[package]]
|
| 2055 |
name = "virtualenv"
|
| 2056 |
-
version = "20.
|
| 2057 |
source = { registry = "https://pypi.org/simple" }
|
| 2058 |
dependencies = [
|
| 2059 |
{ name = "distlib" },
|
| 2060 |
{ name = "filelock" },
|
| 2061 |
{ name = "platformdirs" },
|
| 2062 |
]
|
| 2063 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 2064 |
wheels = [
|
| 2065 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2066 |
]
|
| 2067 |
|
| 2068 |
[[package]]
|
|
|
|
| 50 |
|
| 51 |
[[package]]
|
| 52 |
name = "authlib"
|
| 53 |
+
version = "1.6.1"
|
| 54 |
source = { registry = "https://pypi.org/simple" }
|
| 55 |
dependencies = [
|
| 56 |
{ name = "cryptography" },
|
| 57 |
]
|
| 58 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" }
|
| 59 |
wheels = [
|
| 60 |
+
{ url = "https://files.pythonhosted.org/packages/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" },
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
[[package]]
|
| 64 |
+
name = "backports-asyncio-runner"
|
| 65 |
+
version = "1.2.0"
|
| 66 |
+
source = { registry = "https://pypi.org/simple" }
|
| 67 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
|
| 68 |
+
wheels = [
|
| 69 |
+
{ url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
|
| 70 |
]
|
| 71 |
|
| 72 |
[[package]]
|
| 73 |
name = "certifi"
|
| 74 |
+
version = "2025.7.14"
|
| 75 |
source = { registry = "https://pypi.org/simple" }
|
| 76 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" }
|
| 77 |
wheels = [
|
| 78 |
+
{ url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" },
|
| 79 |
]
|
| 80 |
|
| 81 |
[[package]]
|
|
|
|
| 246 |
|
| 247 |
[[package]]
|
| 248 |
name = "coverage"
|
| 249 |
+
version = "7.10.0"
|
| 250 |
+
source = { registry = "https://pypi.org/simple" }
|
| 251 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6d/8f/6ac7fbb29e35645065f7be835bfe3e0cce567f80390de2f3db65d83cb5e3/coverage-7.10.0.tar.gz", hash = "sha256:2768885aef484b5dcde56262cbdfba559b770bfc46994fe9485dc3614c7a5867", size = 819816, upload-time = "2025-07-24T16:53:00.896Z" }
|
| 252 |
+
wheels = [
|
| 253 |
+
{ url = "https://files.pythonhosted.org/packages/5d/f6/b2366476b1f48134757f2a42aaf00e7ce8e734eea5f3cf022df113116174/coverage-7.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbd823f7ea5286c26406ad9e54268544d82f3d1cadb6d4f3b85e9877f0cab1ef", size = 214813, upload-time = "2025-07-24T16:50:18.937Z" },
|
| 254 |
+
{ url = "https://files.pythonhosted.org/packages/19/d1/7e26bb4c41ed1b9aca4550187ca42557d79c70d318414a703d814858eacb/coverage-7.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab3f7a5dbaab937df0b9e9e8ec6eab235ba9a6f29d71fd3b24335affaed886cc", size = 215206, upload-time = "2025-07-24T16:50:21.788Z" },
|
| 255 |
+
{ url = "https://files.pythonhosted.org/packages/df/71/d5ae128557c8d0ce0156eb1e980e5c6e6f7e54ef3e998c87ab4b3679ff45/coverage-7.10.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8c63aaf850523d8cbe3f5f1a5c78f689b223797bef902635f2493ab43498f36c", size = 242171, upload-time = "2025-07-24T16:50:23.483Z" },
|
| 256 |
+
{ url = "https://files.pythonhosted.org/packages/af/87/d586a627e3b61cfe631ebcf3d8a38bf9085142800d2ac434bc20f3699880/coverage-7.10.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c3133ce3fa84023f7c6921c4dca711be0b658784c5a51a797168229eae26172", size = 243431, upload-time = "2025-07-24T16:50:24.913Z" },
|
| 257 |
+
{ url = "https://files.pythonhosted.org/packages/55/cc/ff5c6f4f99a987ebd18a3350194377c7cefee9ddd6e532ede83a0a1f332c/coverage-7.10.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3747d1d0af85b17d3a156cd30e4bbacf893815e846dc6c07050e9769da2b138e", size = 245288, upload-time = "2025-07-24T16:50:26.673Z" },
|
| 258 |
+
{ url = "https://files.pythonhosted.org/packages/94/d9/2758e73d7fe496c04dd715af8bb8856354a1ad4cc11553d9096c4b35dc86/coverage-7.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:241923b350437f6a7cb343d9df72998305ef940c3c40009f06e05029a047677c", size = 243235, upload-time = "2025-07-24T16:50:28.505Z" },
|
| 259 |
+
{ url = "https://files.pythonhosted.org/packages/9c/9b/3c273dde651d83484992d7e7bcd9cd84a363f01026caf69716390bd79e0d/coverage-7.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13e82e499309307104d58ac66f9eed237f7aaceab4325416645be34064d9a2be", size = 241909, upload-time = "2025-07-24T16:50:30.38Z" },
|
| 260 |
+
{ url = "https://files.pythonhosted.org/packages/5b/7c/006d9f66035c4d414ea642d990854a30c23145551315bd0b38100daee168/coverage-7.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf73cdde4f6c9cd4457b00bf1696236796ac3a241f859a55e0f84a4c58326a7f", size = 242202, upload-time = "2025-07-24T16:50:32.199Z" },
|
| 261 |
+
{ url = "https://files.pythonhosted.org/packages/8a/42/80d8747f77c63593a2114c7299df52f7568168e4fd882d7d5ebe8181564f/coverage-7.10.0-cp310-cp310-win32.whl", hash = "sha256:2396e13275b37870a3345f58bce8b15a7e0a985771d13a4b16ce9129954e07d6", size = 217311, upload-time = "2025-07-24T16:50:33.598Z" },
|
| 262 |
+
{ url = "https://files.pythonhosted.org/packages/e3/8b/fe04c3851e5d290524f563a8a564c7e5dcd6b5ca35ed689ce662346de230/coverage-7.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d45c7c71fb3d2da92ab893602e3f28f2d1560cec765a27e1824a6e0f7e92cfd", size = 218199, upload-time = "2025-07-24T16:50:36.751Z" },
|
| 263 |
+
{ url = "https://files.pythonhosted.org/packages/f5/5d/0d1ee021439e3b8b1e86ba92465f5a8d8e15b0222dcdd705606ef089f4fe/coverage-7.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4abc01843581a6f9dd72d4d15761861190973a2305416639435ef509288f7a04", size = 214934, upload-time = "2025-07-24T16:50:38.173Z" },
|
| 264 |
+
{ url = "https://files.pythonhosted.org/packages/f2/b2/1e0727327e473aa1a68ca1c9922818a06061d05d44e0c5330109d091b525/coverage-7.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2093297773111d7d748fe4a99b68747e57994531fb5c57bbe439af17c11c169", size = 215320, upload-time = "2025-07-24T16:50:39.617Z" },
|
| 265 |
+
{ url = "https://files.pythonhosted.org/packages/84/17/d231e37236863ae3bed7c51615af6b6fc89639c88adf35766d2880dcd7c7/coverage-7.10.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58240e27815bf105bd975c2fd42e700839f93d5aad034ef976411193ca32dbfd", size = 245321, upload-time = "2025-07-24T16:50:41.544Z" },
|
| 266 |
+
{ url = "https://files.pythonhosted.org/packages/95/77/a285aba35bf6ec12c466474931410ef0e6fa85542169009443868e98820a/coverage-7.10.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d019eac999b40ad48521ea057958b07a9f549c0c6d257a20e5c7c4ba91af8d1c", size = 247155, upload-time = "2025-07-24T16:50:43.358Z" },
|
| 267 |
+
{ url = "https://files.pythonhosted.org/packages/7b/82/50512eafdd5938a7aa1550014e37fa1c2ca85516bfd85ffeb2f03eff052a/coverage-7.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35e0a1f5454bc80faf4ceab10d1d48f025f92046c9c0f3bec2e1a9dda55137f8", size = 249320, upload-time = "2025-07-24T16:50:44.98Z" },
|
| 268 |
+
{ url = "https://files.pythonhosted.org/packages/de/7b/0ec1dc75c8f4d940d03d477b1e07269b4804dcab74ad1e294d40310aba47/coverage-7.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a93dd7759c416dd1cc754123b926d065055cb9a33b6699e64a1e5bdfae1ff459", size = 247047, upload-time = "2025-07-24T16:50:46.482Z" },
|
| 269 |
+
{ url = "https://files.pythonhosted.org/packages/d9/5b/40f9b78ae98c2f511a2b062660906e126aadcd35870b9190a4f10f2820ae/coverage-7.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7b3d737266048368a6ffd68f1ecd662c54de56535c82eb8f98a55ac216a72cbd", size = 245078, upload-time = "2025-07-24T16:50:47.904Z" },
|
| 270 |
+
{ url = "https://files.pythonhosted.org/packages/d6/f6/672c2a728e77846be7fcc4baaa003e0df86a2174aeb8921d132c14c333d4/coverage-7.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:93227c2707cb0effd9163cd0d8f0d9ab628982f7a3e915d6d64c7107867b9a07", size = 245686, upload-time = "2025-07-24T16:50:49.461Z" },
|
| 271 |
+
{ url = "https://files.pythonhosted.org/packages/a1/f3/fa078f0bfae7f0e6b14c426f9cb095f4809314d926c89b9a2641fb4ca482/coverage-7.10.0-cp311-cp311-win32.whl", hash = "sha256:69270af3014ab3058ad6108c6d0e218166f568b5a7a070dc3d62c0a63aca1c4d", size = 217350, upload-time = "2025-07-24T16:50:50.884Z" },
|
| 272 |
+
{ url = "https://files.pythonhosted.org/packages/1a/40/eefc3ebb9e458e3dc5db00e6b838969375577a09a8a39986d79cfa283175/coverage-7.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c16bbb661a7b4dafac0ab69e44d6dbcc6a64c4d93aefd89edc6f8911b6ab4a", size = 218235, upload-time = "2025-07-24T16:50:52.369Z" },
|
| 273 |
+
{ url = "https://files.pythonhosted.org/packages/e5/b8/3b53890c3ad52279eaea594a86bceaf04fcc0aed16856ff81531f75735f4/coverage-7.10.0-cp311-cp311-win_arm64.whl", hash = "sha256:14e7c23fcb74ed808efb4eb48fcd25a759f0e20f685f83266d1df174860e4733", size = 216668, upload-time = "2025-07-24T16:50:53.937Z" },
|
| 274 |
+
{ url = "https://files.pythonhosted.org/packages/b6/b4/7b419bb368c9f0b88889cb24805164f6e5550d7183fb59524f6173e0cf0b/coverage-7.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2adcfdaf3b4d69b0c64ad024fe9dd6996782b52790fb6033d90f36f39e287df", size = 215124, upload-time = "2025-07-24T16:50:55.46Z" },
|
| 275 |
+
{ url = "https://files.pythonhosted.org/packages/f4/15/d862a806734c7e50fd5350cef18e22832ba3cdad282ca5660d6fd49def92/coverage-7.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d7b27c2c0840e8eeff3f1963782bd9d3bc767488d2e67a31de18d724327f9f6", size = 215364, upload-time = "2025-07-24T16:50:57.849Z" },
|
| 276 |
+
{ url = "https://files.pythonhosted.org/packages/a6/93/4671ca5b2f3650c961a01252cbad96cb41f7c0c2b85c6062f27740a66b06/coverage-7.10.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0ed50429786e935517570b08576a661fd79032e6060985ab492b9d39ba8e66ee", size = 246369, upload-time = "2025-07-24T16:50:59.505Z" },
|
| 277 |
+
{ url = "https://files.pythonhosted.org/packages/64/79/2ca676c712d0540df0d7957a4266232980b60858a7a654846af1878cfde0/coverage-7.10.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7171c139ab6571d70460ecf788b1dcaf376bfc75a42e1946b8c031d062bbbad4", size = 248798, upload-time = "2025-07-24T16:51:01.105Z" },
|
| 278 |
+
{ url = "https://files.pythonhosted.org/packages/82/c5/67e000b03ba5291f915ddd6ba7c3333e4fdee9ba003b914c8f8f2d966dfe/coverage-7.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a726aac7e6e406e403cdee4c443a13aed3ea3d67d856414c5beacac2e70c04e", size = 250260, upload-time = "2025-07-24T16:51:02.761Z" },
|
| 279 |
+
{ url = "https://files.pythonhosted.org/packages/9d/76/196783c425b5633db5c789b02a023858377bd73e4db4c805c2503cc42bbf/coverage-7.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2886257481a14e953e96861a00c0fe7151117a523f0470a51e392f00640bba03", size = 248171, upload-time = "2025-07-24T16:51:04.651Z" },
|
| 280 |
+
{ url = "https://files.pythonhosted.org/packages/83/1f/bf86c75f42de3641b4bbeab9712ec2815a3a8f5939768077245a492fad9f/coverage-7.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:536578b79521e59c385a2e0a14a5dc2a8edd58761a966d79368413e339fc9535", size = 246368, upload-time = "2025-07-24T16:51:06.16Z" },
|
| 281 |
+
{ url = "https://files.pythonhosted.org/packages/2d/95/bfc9a3abef0b160404438e82ec778a0f38660c66a4b0ed94d0417d4d2290/coverage-7.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77fae95558f7804a9ceefabf3c38ad41af1da92b39781b87197c6440dcaaa967", size = 247578, upload-time = "2025-07-24T16:51:07.632Z" },
|
| 282 |
+
{ url = "https://files.pythonhosted.org/packages/c6/7e/4fb2a284d56fe2a3ba0c76806923014854a64e503dc8ce21e5a2e6497eea/coverage-7.10.0-cp312-cp312-win32.whl", hash = "sha256:97803e14736493eb029558e1502fe507bd6a08af277a5c8eeccf05c3e970cb84", size = 217521, upload-time = "2025-07-24T16:51:09.56Z" },
|
| 283 |
+
{ url = "https://files.pythonhosted.org/packages/f7/30/3ab51058b75e9931fc48594d79888396cf009910fabebe12a6a636ab7f9e/coverage-7.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c73ab554e54ffd38d114d6bc4a7115fb0c840cf6d8622211bee3da26e4bd25d", size = 218308, upload-time = "2025-07-24T16:51:11.115Z" },
|
| 284 |
+
{ url = "https://files.pythonhosted.org/packages/b0/34/2adc74fd132eaa1873b1688acb906b477216074ed8a37e90426eca6d2900/coverage-7.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:3ae95d5a9aedab853641026b71b2ddd01983a0a7e9bf870a20ef3c8f5d904699", size = 216706, upload-time = "2025-07-24T16:51:12.632Z" },
|
| 285 |
+
{ url = "https://files.pythonhosted.org/packages/fc/a7/a47f64718c2229b7860a334edd4e6ff41ec8513f3d3f4246284610344392/coverage-7.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d883fee92b9245c0120fa25b5d36de71ccd4cfc29735906a448271e935d8d86d", size = 215143, upload-time = "2025-07-24T16:51:14.105Z" },
|
| 286 |
+
{ url = "https://files.pythonhosted.org/packages/ea/86/14d76a409e9ffab10d5aece73ac159dbd102fc56627e203413bfc6d53b24/coverage-7.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c87e59e88268d30e33d3665ede4fbb77b513981a2df0059e7c106ca3de537586", size = 215401, upload-time = "2025-07-24T16:51:15.978Z" },
|
| 287 |
+
{ url = "https://files.pythonhosted.org/packages/f4/b3/fb5c28148a19035a3877fac4e40b044a4c97b24658c980bcf7dff18bfab8/coverage-7.10.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f669d969f669a11d6ceee0b733e491d9a50573eb92a71ffab13b15f3aa2665d4", size = 245949, upload-time = "2025-07-24T16:51:17.628Z" },
|
| 288 |
+
{ url = "https://files.pythonhosted.org/packages/6d/95/357559ecfe73970d2023845797361e6c2e6c2c05f970073fff186fe19dd7/coverage-7.10.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9582bd6c6771300a847d328c1c4204e751dbc339a9e249eecdc48cada41f72e6", size = 248295, upload-time = "2025-07-24T16:51:19.46Z" },
|
| 289 |
+
{ url = "https://files.pythonhosted.org/packages/7e/58/bac5bc43085712af201f76a24733895331c475e5ddda88ac36c1332a65e6/coverage-7.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91f97e9637dc7977842776fdb7ad142075d6fa40bc1b91cb73685265e0d31d32", size = 249733, upload-time = "2025-07-24T16:51:21.518Z" },
|
| 290 |
+
{ url = "https://files.pythonhosted.org/packages/b2/db/104b713b3b74752ee365346677fb104765923982ae7bd93b95ca41fe256b/coverage-7.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae4fa92b6601a62367c6c9967ad32ad4e28a89af54b6bb37d740946b0e0534dd", size = 247943, upload-time = "2025-07-24T16:51:23.194Z" },
|
| 291 |
+
{ url = "https://files.pythonhosted.org/packages/32/4f/bef25c797c9496cf31ae9cfa93ce96b4414cacf13688e4a6000982772fd5/coverage-7.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3a5cc8b97473e7b3623dd17a42d2194a2b49de8afecf8d7d03c8987237a9552c", size = 245914, upload-time = "2025-07-24T16:51:24.766Z" },
|
| 292 |
+
{ url = "https://files.pythonhosted.org/packages/36/6b/b3efa0b506dbb9a37830d6dc862438fe3ad2833c5f889152bce24d9577cf/coverage-7.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc1cbb7f623250e047c32bd7aa1bb62ebc62608d5004d74df095e1059141ac88", size = 247296, upload-time = "2025-07-24T16:51:26.361Z" },
|
| 293 |
+
{ url = "https://files.pythonhosted.org/packages/1f/aa/95a845266aeacab4c57b08e0f4e0e2899b07809a18fd0c1ddef2ac2c9138/coverage-7.10.0-cp313-cp313-win32.whl", hash = "sha256:1380cc5666d778e77f1587cd88cc317158111f44d54c0dd3975f0936993284e0", size = 217566, upload-time = "2025-07-24T16:51:28.961Z" },
|
| 294 |
+
{ url = "https://files.pythonhosted.org/packages/a0/d1/27b6e5073a8026b9e0f4224f1ac53217ce589a4cdab1bee878f23bff64f0/coverage-7.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:bf03cf176af098ee578b754a03add4690b82bdfe070adfb5d192d0b1cd15cf82", size = 218337, upload-time = "2025-07-24T16:51:31.45Z" },
|
| 295 |
+
{ url = "https://files.pythonhosted.org/packages/c7/06/0e3ba498b11e2245fd96bd7e8dcdf90e1dd36d57f49f308aa650ff0561b8/coverage-7.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:8041c78cd145088116db2329b2fb6e89dc338116c962fbe654b7e9f5d72ab957", size = 216740, upload-time = "2025-07-24T16:51:33.317Z" },
|
| 296 |
+
{ url = "https://files.pythonhosted.org/packages/44/8b/11529debbe3e6b39ef6e7c8912554724adc6dc10adbb617a855ecfd387eb/coverage-7.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37cc2c06052771f48651160c080a86431884db9cd62ba622cab71049b90a95b3", size = 215866, upload-time = "2025-07-24T16:51:35.339Z" },
|
| 297 |
+
{ url = "https://files.pythonhosted.org/packages/9c/6d/d8981310879e395f39af66536665b75135b1bc88dd21c7764e3340e9ce69/coverage-7.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:91f37270b16178b05fa107d85713d29bf21606e37b652d38646eef5f2dfbd458", size = 216083, upload-time = "2025-07-24T16:51:36.932Z" },
|
| 298 |
+
{ url = "https://files.pythonhosted.org/packages/c3/84/93295402de002de8b8c953bf6a1f19687174c4db7d44c1e85ffc153a772d/coverage-7.10.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f9b0b0168864d09bcb9a3837548f75121645c4cfd0efce0eb994c221955c5b10", size = 257320, upload-time = "2025-07-24T16:51:38.734Z" },
|
| 299 |
+
{ url = "https://files.pythonhosted.org/packages/02/5c/d0540db4869954dac0f69ad709adcd51f3a73ab11fcc9435ee76c518944a/coverage-7.10.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df0be435d3b616e7d3ee3f9ebbc0d784a213986fe5dff9c6f1042ee7cfd30157", size = 259182, upload-time = "2025-07-24T16:51:40.463Z" },
|
| 300 |
+
{ url = "https://files.pythonhosted.org/packages/59/b2/d7d57a41a15ca4b47290862efd6b596d0a185bfd26f15d04db9f238aa56c/coverage-7.10.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35e9aba1c4434b837b1d567a533feba5ce205e8e91179c97974b28a14c23d3a0", size = 261322, upload-time = "2025-07-24T16:51:42.44Z" },
|
| 301 |
+
{ url = "https://files.pythonhosted.org/packages/16/92/fd828ae411b3da63673305617b6fbeccc09feb7dfe397d164f55a65cd880/coverage-7.10.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a0b0c481e74dfad631bdc2c883e57d8b058e5c90ba8ef087600995daf7bbec18", size = 258914, upload-time = "2025-07-24T16:51:44.115Z" },
|
| 302 |
+
{ url = "https://files.pythonhosted.org/packages/28/49/4aa5f5464b2e1215640c0400c5b007e7f5cdade8bf39c55c33b02f3a8c7f/coverage-7.10.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8aec1b7c8922808a433c13cd44ace6fceac0609f4587773f6c8217a06102674b", size = 257051, upload-time = "2025-07-24T16:51:45.75Z" },
|
| 303 |
+
{ url = "https://files.pythonhosted.org/packages/1e/5a/ded2346098c7f48ff6e135b5005b97de4cd9daec5c39adb4ecf3a60967da/coverage-7.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:04ec59ceb3a594af0927f2e0d810e1221212abd9a2e6b5b917769ff48760b460", size = 257869, upload-time = "2025-07-24T16:51:47.41Z" },
|
| 304 |
+
{ url = "https://files.pythonhosted.org/packages/46/66/e06cedb8fc7d1c96630b2f549b8cdc084e2623dcc70c900cb3b705a36a60/coverage-7.10.0-cp313-cp313t-win32.whl", hash = "sha256:b6871e62d29646eb9b3f5f92def59e7575daea1587db21f99e2b19561187abda", size = 218243, upload-time = "2025-07-24T16:51:49.136Z" },
|
| 305 |
+
{ url = "https://files.pythonhosted.org/packages/e7/1e/e84dd5ff35ed066bd6150e5c26fe0061ded2c59c209fd4f18db0650766c0/coverage-7.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff99cff2be44f78920b76803f782e91ffb46ccc7fa89eccccc0da3ca94285b64", size = 219334, upload-time = "2025-07-24T16:51:50.789Z" },
|
| 306 |
+
{ url = "https://files.pythonhosted.org/packages/b7/e0/b7b60b5dbc4e88eac0a0e9d5b4762409a59b29bf4e772b3509c8543ccaba/coverage-7.10.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3246b63501348fe47299d12c47a27cfc221cfbffa1c2d857bcc8151323a4ae4f", size = 217196, upload-time = "2025-07-24T16:51:52.599Z" },
|
| 307 |
+
{ url = "https://files.pythonhosted.org/packages/15/c1/597b4fa7d6c0861d4916c4fe5c45bf30c11b31a3b07fedffed23dec5f765/coverage-7.10.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:1f628d91f941a375b4503cb486148dbeeffb48e17bc080e0f0adfee729361574", size = 215139, upload-time = "2025-07-24T16:51:54.381Z" },
|
| 308 |
+
{ url = "https://files.pythonhosted.org/packages/18/47/07973dcad0161355cf01ff0023ab34466b735deb460a178f37163d7c800e/coverage-7.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a0e101d5af952d233557e445f42ebace20b06b4ceb615581595ced5386caa78", size = 215419, upload-time = "2025-07-24T16:51:56.341Z" },
|
| 309 |
+
{ url = "https://files.pythonhosted.org/packages/f6/f8/c65127782da312084ef909c1531226c869bfe22dac8b92d9c609d8150131/coverage-7.10.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ec4c1abbcc53f9f650acb14ea71725d88246a9e14ed42f8dd1b4e1b694e9d842", size = 245917, upload-time = "2025-07-24T16:51:58.045Z" },
|
| 310 |
+
{ url = "https://files.pythonhosted.org/packages/05/97/a7f2fe79b6ae759ccc8740608cf9686ae406cc5e5591947ebbf1d679a325/coverage-7.10.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9c95f3a7f041b4cc68a8e3fecfa6366170c13ac773841049f1cd19c8650094e0", size = 248225, upload-time = "2025-07-24T16:51:59.745Z" },
|
| 311 |
+
{ url = "https://files.pythonhosted.org/packages/7f/d3/d2e1496d7ac3340356c5de582e08e14b02933e254924f79d18e9749269d8/coverage-7.10.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a2cd597b69c16d24e310611f2ed6fcfb8f09429316038c03a57e7b4f5345244", size = 249844, upload-time = "2025-07-24T16:52:01.799Z" },
|
| 312 |
+
{ url = "https://files.pythonhosted.org/packages/e5/7e/e26d966c9cae62500e5924107974ede2e985f7d119d10ed44d102998e509/coverage-7.10.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5e18591906a40c2b3609196c9879136aa4a47c5405052ca6b065ab10cb0b71d0", size = 247871, upload-time = "2025-07-24T16:52:03.797Z" },
|
| 313 |
+
{ url = "https://files.pythonhosted.org/packages/59/95/6a372a292dfb9d6e2cc019fc50878f7a6a5fbe704604018d7c5c1dbffb2d/coverage-7.10.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:485c55744252ed3f300cc1a0f5f365e684a0f2651a7aed301f7a67125906b80e", size = 245714, upload-time = "2025-07-24T16:52:05.966Z" },
|
| 314 |
+
{ url = "https://files.pythonhosted.org/packages/02/7f/63da22b7bc4e82e2c1df7755223291fc94fb01942cfe75e19f2bed96129e/coverage-7.10.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4dabea1516e5b0e9577282b149c8015e4dceeb606da66fb8d9d75932d5799bf5", size = 247131, upload-time = "2025-07-24T16:52:07.661Z" },
|
| 315 |
+
{ url = "https://files.pythonhosted.org/packages/3d/af/883272555e34872879f48daea4207489cb36df249e3069e6a8a664dc6ba6/coverage-7.10.0-cp314-cp314-win32.whl", hash = "sha256:ac455f0537af22333fdc23b824cff81110dff2d47300bb2490f947b7c9a16017", size = 217804, upload-time = "2025-07-24T16:52:09.328Z" },
|
| 316 |
+
{ url = "https://files.pythonhosted.org/packages/90/f6/7afc3439994b7f7311d858438d49eef8b06eadbf2322502d921a110fae1e/coverage-7.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:b3c94b532f52f95f36fbfde3e178510a4d04eea640b484b2fe8f1491338dc653", size = 218596, upload-time = "2025-07-24T16:52:11.038Z" },
|
| 317 |
+
{ url = "https://files.pythonhosted.org/packages/0b/99/7c715cfa155609ee3e71bc81b4d1265e1a9b79ad00cc3d19917ea736cbac/coverage-7.10.0-cp314-cp314-win_arm64.whl", hash = "sha256:2f807f2c3a9da99c80dfa73f09ef5fc3bd21e70c73ba1c538f23396a3a772252", size = 216960, upload-time = "2025-07-24T16:52:12.77Z" },
|
| 318 |
+
{ url = "https://files.pythonhosted.org/packages/59/18/5cb476346d3842f2e42cd92614a91921ebad38aa97aba63f2aab51919e35/coverage-7.10.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0a889ef25215990f65073c32cadf37483363a6a22914186dedc15a6b1a597d50", size = 215881, upload-time = "2025-07-24T16:52:14.492Z" },
|
| 319 |
+
{ url = "https://files.pythonhosted.org/packages/80/1b/c066d6836f4c1940a8df14894a5ec99db362838fdd9eee9fb7efe0e561d2/coverage-7.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39c638ecf3123805bacbf71aff8091e93af490c676fca10ab4e442375076e483", size = 216087, upload-time = "2025-07-24T16:52:16.216Z" },
|
| 320 |
+
{ url = "https://files.pythonhosted.org/packages/1d/57/f0996fd468e70d4d24d69eba10ecc2b913c2e85d9f3c1bb2075ad7554c05/coverage-7.10.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f2f2c0df0cbcf7dffa14f88a99c530cdef3f4fcfe935fa4f95d28be2e7ebc570", size = 257408, upload-time = "2025-07-24T16:52:18.136Z" },
|
| 321 |
+
{ url = "https://files.pythonhosted.org/packages/36/78/c9f308b2b986cc685d4964a3b829b053817a07d7ba14ff124cf06154402e/coverage-7.10.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:048d19a5d641a2296745ab59f34a27b89a08c48d6d432685f22aac0ec1ea447f", size = 259373, upload-time = "2025-07-24T16:52:20.923Z" },
|
| 322 |
+
{ url = "https://files.pythonhosted.org/packages/99/13/192827b71da71255d3554cb7dc289bce561cb281bda27e1b0dd19d88e47d/coverage-7.10.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1209b65d302d7a762004be37ab9396cbd8c99525ed572bdf455477e3a9449e06", size = 261495, upload-time = "2025-07-24T16:52:23.018Z" },
|
| 323 |
+
{ url = "https://files.pythonhosted.org/packages/0d/5c/cf4694353405abbb440a94468df8e5c4dbf884635da1f056b43be7284d28/coverage-7.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e44aa79a36a7a0aec6ea109905a4a7c28552d90f34e5941b36217ae9556657d5", size = 258970, upload-time = "2025-07-24T16:52:25.685Z" },
|
| 324 |
+
{ url = "https://files.pythonhosted.org/packages/c7/83/fb45dac65c42eff6ce4153fe51b9f2a9fdc832ce57b7902ab9ff216c3faa/coverage-7.10.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96124be864b89395770c9a14652afcddbcdafb99466f53a9281c51d1466fb741", size = 257046, upload-time = "2025-07-24T16:52:27.778Z" },
|
| 325 |
+
{ url = "https://files.pythonhosted.org/packages/60/95/577dc757c01f493a1951157475dd44561c82084387f12635974fb62e848c/coverage-7.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aad222e841f94b42bd1d6be71737fade66943853f0807cf87887c88f70883a2a", size = 257946, upload-time = "2025-07-24T16:52:29.931Z" },
|
| 326 |
+
{ url = "https://files.pythonhosted.org/packages/da/5a/14b1be12e3a71fcf4031464ae285dab7df0939976236d0462c4c5382d317/coverage-7.10.0-cp314-cp314t-win32.whl", hash = "sha256:0eed5354d28caa5c8ad60e07e938f253e4b2810ea7dd56784339b6ce98b6f104", size = 218602, upload-time = "2025-07-24T16:52:32.074Z" },
|
| 327 |
+
{ url = "https://files.pythonhosted.org/packages/a0/8d/c32890c0f4f7f71b8d4a1074ef8e9ef28e9b9c2f9fd0e2896f2cc32593bf/coverage-7.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3da35f9980058acb960b2644527cc3911f1e00f94d309d704b309fa984029109", size = 219720, upload-time = "2025-07-24T16:52:34.745Z" },
|
| 328 |
+
{ url = "https://files.pythonhosted.org/packages/22/f7/e5cc13338aa5e2780b6226fb50e9bd8f3f88da85a4b2951447b4b51109a4/coverage-7.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cb9e138dfa8a4b5c52c92a537651e2ca4f2ca48d8cb1bc01a2cbe7a5773c2426", size = 217374, upload-time = "2025-07-24T16:52:36.974Z" },
|
| 329 |
+
{ url = "https://files.pythonhosted.org/packages/09/df/7c34bada8ace39f688b3bd5bc411459a20a3204ccb0984c90169a80a9366/coverage-7.10.0-py3-none-any.whl", hash = "sha256:310a786330bb0463775c21d68e26e79973839b66d29e065c5787122b8dd4489f", size = 206777, upload-time = "2025-07-24T16:52:59.009Z" },
|
| 330 |
]
|
| 331 |
|
| 332 |
[package.optional-dependencies]
|
|
|
|
| 383 |
|
| 384 |
[[package]]
|
| 385 |
name = "cyclopts"
|
| 386 |
+
version = "3.22.3"
|
| 387 |
source = { registry = "https://pypi.org/simple" }
|
| 388 |
dependencies = [
|
| 389 |
{ name = "attrs" },
|
|
|
|
| 392 |
{ name = "rich-rst" },
|
| 393 |
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
| 394 |
]
|
| 395 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c8/05/9d5a0a8f4628f6a1230b43e0c34a7dc45c40a17045a09f4a5d7145da12e2/cyclopts-3.22.3.tar.gz", hash = "sha256:7df1d05e4b56b07079e13880b457b78522101531e2947af1a68f182e89742b34", size = 74837, upload-time = "2025-07-23T23:25:09.815Z" }
|
| 396 |
wheels = [
|
| 397 |
+
{ url = "https://files.pythonhosted.org/packages/16/1f/4b9f6986add9f6ff361c1bfffeb08fc2f2f6752f8adf8d4dcf0a988b6f28/cyclopts-3.22.3-py3-none-any.whl", hash = "sha256:771ae584868c8beeac74184a96e9fad3726c787b17e47a6f0d5f42cece1df57a", size = 84941, upload-time = "2025-07-23T23:25:08.527Z" },
|
| 398 |
]
|
| 399 |
|
| 400 |
[[package]]
|
|
|
|
| 417 |
|
| 418 |
[[package]]
|
| 419 |
name = "distlib"
|
| 420 |
+
version = "0.4.0"
|
| 421 |
source = { registry = "https://pypi.org/simple" }
|
| 422 |
+
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
| 423 |
wheels = [
|
| 424 |
+
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
| 425 |
]
|
| 426 |
|
| 427 |
[[package]]
|
|
|
|
| 435 |
|
| 436 |
[[package]]
|
| 437 |
name = "docstring-parser"
|
| 438 |
+
version = "0.17.0"
|
| 439 |
source = { registry = "https://pypi.org/simple" }
|
| 440 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
|
| 441 |
wheels = [
|
| 442 |
+
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
|
| 443 |
]
|
| 444 |
|
| 445 |
[[package]]
|
|
|
|
| 509 |
|
| 510 |
[[package]]
|
| 511 |
name = "fastapi"
|
| 512 |
+
version = "0.116.1"
|
| 513 |
source = { registry = "https://pypi.org/simple" }
|
| 514 |
dependencies = [
|
| 515 |
{ name = "pydantic" },
|
| 516 |
{ name = "starlette" },
|
| 517 |
{ name = "typing-extensions" },
|
| 518 |
]
|
| 519 |
+
sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" }
|
| 520 |
wheels = [
|
| 521 |
+
{ url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" },
|
| 522 |
]
|
| 523 |
|
| 524 |
[[package]]
|
|
|
|
| 630 |
|
| 631 |
[[package]]
|
| 632 |
name = "gitpython"
|
| 633 |
+
version = "3.1.45"
|
| 634 |
source = { registry = "https://pypi.org/simple" }
|
| 635 |
dependencies = [
|
| 636 |
{ name = "gitdb" },
|
| 637 |
]
|
| 638 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" }
|
| 639 |
wheels = [
|
| 640 |
+
{ url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" },
|
| 641 |
]
|
| 642 |
|
| 643 |
[[package]]
|
|
|
|
| 798 |
|
| 799 |
[[package]]
|
| 800 |
name = "jsonschema"
|
| 801 |
+
version = "4.25.0"
|
| 802 |
source = { registry = "https://pypi.org/simple" }
|
| 803 |
dependencies = [
|
| 804 |
{ name = "attrs" },
|
|
|
|
| 806 |
{ name = "referencing" },
|
| 807 |
{ name = "rpds-py" },
|
| 808 |
]
|
| 809 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830, upload-time = "2025-07-18T15:39:45.11Z" }
|
| 810 |
wheels = [
|
| 811 |
+
{ url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184, upload-time = "2025-07-18T15:39:42.956Z" },
|
| 812 |
]
|
| 813 |
|
| 814 |
[[package]]
|
|
|
|
| 941 |
|
| 942 |
[[package]]
|
| 943 |
name = "mcp"
|
| 944 |
+
version = "1.12.2"
|
| 945 |
source = { registry = "https://pypi.org/simple" }
|
| 946 |
dependencies = [
|
| 947 |
{ name = "anyio" },
|
|
|
|
| 951 |
{ name = "pydantic" },
|
| 952 |
{ name = "pydantic-settings" },
|
| 953 |
{ name = "python-multipart" },
|
| 954 |
+
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
| 955 |
{ name = "sse-starlette" },
|
| 956 |
{ name = "starlette" },
|
| 957 |
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
| 958 |
]
|
| 959 |
+
sdist = { url = "https://files.pythonhosted.org/packages/66/85/f36d538b1286b7758f35c1b69d93f2719d2df90c01bd074eadd35f6afc35/mcp-1.12.2.tar.gz", hash = "sha256:a4b7c742c50ce6ed6d6a6c096cca0e3893f5aecc89a59ed06d47c4e6ba41edcc", size = 426202, upload-time = "2025-07-24T18:29:05.175Z" }
|
| 960 |
wheels = [
|
| 961 |
+
{ url = "https://files.pythonhosted.org/packages/2f/cf/3fd38cfe43962452e4bfadc6966b2ea0afaf8e0286cb3991c247c8c33ebd/mcp-1.12.2-py3-none-any.whl", hash = "sha256:b86d584bb60193a42bd78aef01882c5c42d614e416cbf0480149839377ab5a5f", size = 158473, upload-time = "2025-07-24T18:29:03.419Z" },
|
| 962 |
]
|
| 963 |
|
| 964 |
[[package]]
|
|
|
|
| 1096 |
|
| 1097 |
[[package]]
|
| 1098 |
name = "pdbpp"
|
| 1099 |
+
version = "0.11.7"
|
| 1100 |
source = { registry = "https://pypi.org/simple" }
|
| 1101 |
dependencies = [
|
| 1102 |
{ name = "fancycompleter" },
|
| 1103 |
{ name = "pygments" },
|
| 1104 |
]
|
| 1105 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c6/4c/118ef9534ac0632859b48c305d8c5dc9d6f963564fdfa66bc785c560247c/pdbpp-0.11.7.tar.gz", hash = "sha256:cb6604ac31a35ed0f2a29650a8c022b26284620be3e01cfd41b683b91da1ff14", size = 76026, upload-time = "2025-07-18T09:36:02.781Z" }
|
| 1106 |
wheels = [
|
| 1107 |
+
{ url = "https://files.pythonhosted.org/packages/99/e9/704bbc08aace64fee536e4c2c20f63f64f6fdbad72938c5ed46c9723a9f1/pdbpp-0.11.7-py3-none-any.whl", hash = "sha256:51916b63693898cf4881b36b4501c83947758d73f582f1f84893662b163bdb75", size = 30545, upload-time = "2025-07-18T09:36:01.478Z" },
|
| 1108 |
]
|
| 1109 |
|
| 1110 |
[[package]]
|
|
|
|
| 1379 |
|
| 1380 |
[[package]]
|
| 1381 |
name = "pyrepl"
|
| 1382 |
+
version = "0.11.4"
|
| 1383 |
source = { registry = "https://pypi.org/simple" }
|
| 1384 |
+
sdist = { url = "https://files.pythonhosted.org/packages/08/4f/7088417e5465c53a30b918d30542aad89352ea0d635a5d077717c69a7d2b/pyrepl-0.11.4.tar.gz", hash = "sha256:efe988b4a6e5eed587e9769dc2269aeec2b6feec2f5d77995ee85b9ad7cf7063", size = 51089, upload-time = "2025-07-17T22:56:25.42Z" }
|
| 1385 |
wheels = [
|
| 1386 |
+
{ url = "https://files.pythonhosted.org/packages/bd/a5/ce97a778f096aaa27cfcb7ad09f1198cf73277dcab6c68a4b8f332d91e48/pyrepl-0.11.4-py3-none-any.whl", hash = "sha256:ac30d6340267a21c39e1b1934f92bca6b8735017d14b17e40f903b2d1563541d", size = 55596, upload-time = "2025-07-17T22:56:24.537Z" },
|
| 1387 |
]
|
| 1388 |
|
| 1389 |
[[package]]
|
| 1390 |
name = "pyright"
|
| 1391 |
+
version = "1.1.403"
|
| 1392 |
source = { registry = "https://pypi.org/simple" }
|
| 1393 |
dependencies = [
|
| 1394 |
{ name = "nodeenv" },
|
| 1395 |
{ name = "typing-extensions" },
|
| 1396 |
]
|
| 1397 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526, upload-time = "2025-07-09T07:15:52.882Z" }
|
| 1398 |
wheels = [
|
| 1399 |
+
{ url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" },
|
| 1400 |
]
|
| 1401 |
|
| 1402 |
[[package]]
|
|
|
|
| 1419 |
|
| 1420 |
[[package]]
|
| 1421 |
name = "pytest-asyncio"
|
| 1422 |
+
version = "1.1.0"
|
| 1423 |
source = { registry = "https://pypi.org/simple" }
|
| 1424 |
dependencies = [
|
| 1425 |
+
{ name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
|
| 1426 |
{ name = "pytest" },
|
| 1427 |
]
|
| 1428 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" }
|
| 1429 |
wheels = [
|
| 1430 |
+
{ url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" },
|
| 1431 |
]
|
| 1432 |
|
| 1433 |
[[package]]
|
|
|
|
| 1534 |
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
| 1535 |
]
|
| 1536 |
|
| 1537 |
+
[[package]]
|
| 1538 |
+
name = "pywin32"
|
| 1539 |
+
version = "311"
|
| 1540 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1541 |
+
wheels = [
|
| 1542 |
+
{ url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" },
|
| 1543 |
+
{ url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" },
|
| 1544 |
+
{ url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" },
|
| 1545 |
+
{ url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" },
|
| 1546 |
+
{ url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" },
|
| 1547 |
+
{ url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
|
| 1548 |
+
{ url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" },
|
| 1549 |
+
{ url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" },
|
| 1550 |
+
{ url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
|
| 1551 |
+
{ url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
|
| 1552 |
+
{ url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
|
| 1553 |
+
{ url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
|
| 1554 |
+
{ url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" },
|
| 1555 |
+
{ url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" },
|
| 1556 |
+
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
|
| 1557 |
+
]
|
| 1558 |
+
|
| 1559 |
[[package]]
|
| 1560 |
name = "pyyaml"
|
| 1561 |
version = "6.0.2"
|
|
|
|
| 1712 |
|
| 1713 |
[[package]]
|
| 1714 |
name = "rich"
|
| 1715 |
+
version = "14.1.0"
|
| 1716 |
source = { registry = "https://pypi.org/simple" }
|
| 1717 |
dependencies = [
|
| 1718 |
{ name = "markdown-it-py" },
|
| 1719 |
{ name = "pygments" },
|
|
|
|
| 1720 |
]
|
| 1721 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" }
|
| 1722 |
wheels = [
|
| 1723 |
+
{ url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" },
|
| 1724 |
]
|
| 1725 |
|
| 1726 |
[[package]]
|
|
|
|
| 1864 |
|
| 1865 |
[[package]]
|
| 1866 |
name = "ruff"
|
| 1867 |
+
version = "0.12.5"
|
| 1868 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1869 |
+
sdist = { url = "https://files.pythonhosted.org/packages/30/cd/01015eb5034605fd98d829c5839ec2c6b4582b479707f7c1c2af861e8258/ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e", size = 5170722, upload-time = "2025-07-24T13:26:37.456Z" }
|
| 1870 |
+
wheels = [
|
| 1871 |
+
{ url = "https://files.pythonhosted.org/packages/d4/de/ad2f68f0798ff15dd8c0bcc2889558970d9a685b3249565a937cd820ad34/ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92", size = 11819133, upload-time = "2025-07-24T13:25:56.369Z" },
|
| 1872 |
+
{ url = "https://files.pythonhosted.org/packages/f8/fc/c6b65cd0e7fbe60f17e7ad619dca796aa49fbca34bb9bea5f8faf1ec2643/ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a", size = 12501114, upload-time = "2025-07-24T13:25:59.471Z" },
|
| 1873 |
+
{ url = "https://files.pythonhosted.org/packages/c5/de/c6bec1dce5ead9f9e6a946ea15e8d698c35f19edc508289d70a577921b30/ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf", size = 11716873, upload-time = "2025-07-24T13:26:01.496Z" },
|
| 1874 |
+
{ url = "https://files.pythonhosted.org/packages/a1/16/cf372d2ebe91e4eb5b82a2275c3acfa879e0566a7ac94d331ea37b765ac8/ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73", size = 11958829, upload-time = "2025-07-24T13:26:03.721Z" },
|
| 1875 |
+
{ url = "https://files.pythonhosted.org/packages/25/bf/cd07e8f6a3a6ec746c62556b4c4b79eeb9b0328b362bb8431b7b8afd3856/ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac", size = 11626619, upload-time = "2025-07-24T13:26:06.118Z" },
|
| 1876 |
+
{ url = "https://files.pythonhosted.org/packages/d8/c9/c2ccb3b8cbb5661ffda6925f81a13edbb786e623876141b04919d1128370/ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08", size = 13221894, upload-time = "2025-07-24T13:26:08.292Z" },
|
| 1877 |
+
{ url = "https://files.pythonhosted.org/packages/6b/58/68a5be2c8e5590ecdad922b2bcd5583af19ba648f7648f95c51c3c1eca81/ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4", size = 14163909, upload-time = "2025-07-24T13:26:10.474Z" },
|
| 1878 |
+
{ url = "https://files.pythonhosted.org/packages/bd/d1/ef6b19622009ba8386fdb792c0743f709cf917b0b2f1400589cbe4739a33/ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b", size = 13583652, upload-time = "2025-07-24T13:26:13.381Z" },
|
| 1879 |
+
{ url = "https://files.pythonhosted.org/packages/62/e3/1c98c566fe6809a0c83751d825a03727f242cdbe0d142c9e292725585521/ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a", size = 12700451, upload-time = "2025-07-24T13:26:15.488Z" },
|
| 1880 |
+
{ url = "https://files.pythonhosted.org/packages/24/ff/96058f6506aac0fbc0d0fc0d60b0d0bd746240a0594657a2d94ad28033ba/ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a", size = 12937465, upload-time = "2025-07-24T13:26:17.808Z" },
|
| 1881 |
+
{ url = "https://files.pythonhosted.org/packages/eb/d3/68bc5e7ab96c94b3589d1789f2dd6dd4b27b263310019529ac9be1e8f31b/ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5", size = 11771136, upload-time = "2025-07-24T13:26:20.422Z" },
|
| 1882 |
+
{ url = "https://files.pythonhosted.org/packages/52/75/7356af30a14584981cabfefcf6106dea98cec9a7af4acb5daaf4b114845f/ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293", size = 11601644, upload-time = "2025-07-24T13:26:22.928Z" },
|
| 1883 |
+
{ url = "https://files.pythonhosted.org/packages/c2/67/91c71d27205871737cae11025ee2b098f512104e26ffd8656fd93d0ada0a/ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb", size = 12478068, upload-time = "2025-07-24T13:26:26.134Z" },
|
| 1884 |
+
{ url = "https://files.pythonhosted.org/packages/34/04/b6b00383cf2f48e8e78e14eb258942fdf2a9bf0287fbf5cdd398b749193a/ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb", size = 12991537, upload-time = "2025-07-24T13:26:28.533Z" },
|
| 1885 |
+
{ url = "https://files.pythonhosted.org/packages/3e/b9/053d6445dc7544fb6594785056d8ece61daae7214859ada4a152ad56b6e0/ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9", size = 11751575, upload-time = "2025-07-24T13:26:30.835Z" },
|
| 1886 |
+
{ url = "https://files.pythonhosted.org/packages/bc/0f/ab16e8259493137598b9149734fec2e06fdeda9837e6f634f5c4e35916da/ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5", size = 12882273, upload-time = "2025-07-24T13:26:32.929Z" },
|
| 1887 |
+
{ url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564, upload-time = "2025-07-24T13:26:34.994Z" },
|
| 1888 |
]
|
| 1889 |
|
| 1890 |
[[package]]
|
|
|
|
| 1951 |
|
| 1952 |
[[package]]
|
| 1953 |
name = "starlette"
|
| 1954 |
+
version = "0.47.2"
|
| 1955 |
source = { registry = "https://pypi.org/simple" }
|
| 1956 |
dependencies = [
|
| 1957 |
{ name = "anyio" },
|
| 1958 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 1959 |
]
|
| 1960 |
+
sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" }
|
| 1961 |
wheels = [
|
| 1962 |
+
{ url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" },
|
| 1963 |
]
|
| 1964 |
|
| 1965 |
[[package]]
|
|
|
|
| 2107 |
|
| 2108 |
[[package]]
|
| 2109 |
name = "virtualenv"
|
| 2110 |
+
version = "20.32.0"
|
| 2111 |
source = { registry = "https://pypi.org/simple" }
|
| 2112 |
dependencies = [
|
| 2113 |
{ name = "distlib" },
|
| 2114 |
{ name = "filelock" },
|
| 2115 |
{ name = "platformdirs" },
|
| 2116 |
]
|
| 2117 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a9/96/0834f30fa08dca3738614e6a9d42752b6420ee94e58971d702118f7cfd30/virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0", size = 6076970, upload-time = "2025-07-21T04:09:50.985Z" }
|
| 2118 |
wheels = [
|
| 2119 |
+
{ url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761, upload-time = "2025-07-21T04:09:48.059Z" },
|
| 2120 |
]
|
| 2121 |
|
| 2122 |
[[package]]
|