mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
Fix ty 0.0.5 type errors (#2676)
This commit is contained in:
parent
bca310cdde
commit
4177d8358d
34 changed files with 288 additions and 103 deletions
|
|
@ -14,7 +14,7 @@ repos:
|
|||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.1
|
||||
rev: v0.14.10
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ dev = [
|
|||
"pytest-timeout>=2.4.0",
|
||||
"pytest-xdist>=3.6.1",
|
||||
"ruff>=0.12.8",
|
||||
"ty==0.0.1a31",
|
||||
"ty==0.0.5",
|
||||
"prek>=0.2.12",
|
||||
]
|
||||
|
||||
|
|
@ -137,13 +137,6 @@ exclude = ["**/node_modules", "**/__pycache__", ".venv", ".git", "dist"]
|
|||
python-version = "3.10"
|
||||
|
||||
[tool.ty.rules]
|
||||
# Rules with too many errors to fix right now (40+ each)
|
||||
no-matching-overload = "ignore" # 126 errors
|
||||
unknown-argument = "ignore" # 61 errors
|
||||
|
||||
# Rules with moderate errors that need more investigation
|
||||
call-non-callable = "ignore" # 7 errors
|
||||
|
||||
# NOTE: ty currently doesn't support type narrowing with isinstance() on unions
|
||||
# See: https://github.com/astral-sh/ty/issues/122 and https://github.com/astral-sh/ty/issues/1113
|
||||
# Some code uses `# ty: ignore[invalid-argument-type]` for this limitation.
|
||||
|
|
@ -179,5 +172,18 @@ extend-select = [
|
|||
"SIM", # flake8-simplify
|
||||
]
|
||||
|
||||
[tool.basedpyright]
|
||||
pythonVersion = "3.10"
|
||||
typeCheckingMode = "standard"
|
||||
reportMissingTypeStubs = false
|
||||
reportUnknownParameterType = false
|
||||
reportUnknownArgumentType = false
|
||||
reportUnknownMemberType = false
|
||||
reportUnknownVariableType = false
|
||||
reportPrivateUsage = false
|
||||
reportUnnecessaryIsInstance = false
|
||||
reportUnnecessaryComparison = false
|
||||
reportConstantRedefinition = false
|
||||
|
||||
[tool.codespell]
|
||||
ignore-words-list = "asend,shttp,te"
|
||||
|
|
|
|||
|
|
@ -1573,7 +1573,7 @@ class Client(Generic[ClientTransportT]):
|
|||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
# Send protocol request
|
||||
params = PaginatedRequestParams(cursor=cursor, limit=limit)
|
||||
params = PaginatedRequestParams(cursor=cursor, limit=limit) # type: ignore[call-arg] # Optional field in MCP SDK
|
||||
request = ListTasksRequest(params=params)
|
||||
server_response = await self.session.send_request(
|
||||
request=request, # type: ignore[invalid-argument-type]
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ def create_elicitation_callback(
|
|||
f"{result.content!r}"
|
||||
)
|
||||
return MCPElicitResult(
|
||||
_meta=result.meta,
|
||||
_meta=result.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
action=result.action,
|
||||
content=content,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -379,7 +379,7 @@ class ToolTask(Task["CallToolResult"]):
|
|||
mcp_result = mcp.types.CallToolResult(
|
||||
content=raw_result.content,
|
||||
structuredContent=raw_result.structured_content, # type: ignore[arg-type]
|
||||
_meta=raw_result.meta,
|
||||
_meta=raw_result.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
result = await self._client._parse_call_tool_result(
|
||||
self._tool_name, mcp_result, raise_on_error=True
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ class PromptResult(FastMCPBaseModel):
|
|||
return GetPromptResult(
|
||||
description=self.description,
|
||||
messages=self.messages,
|
||||
_meta=self.meta,
|
||||
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ class Prompt(FastMCPComponent):
|
|||
arguments=arguments,
|
||||
title=overrides.get("title", self.title),
|
||||
icons=overrides.get("icons", self.icons),
|
||||
_meta=overrides.get(
|
||||
_meta=overrides.get( # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -123,14 +123,14 @@ class ResourceContent(pydantic.BaseModel):
|
|||
uri=AnyUrl(uri) if isinstance(uri, str) else uri,
|
||||
text=self.content,
|
||||
mimeType=self.mime_type or "text/plain",
|
||||
_meta=self.meta,
|
||||
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
else:
|
||||
return mcp.types.BlobResourceContents(
|
||||
uri=AnyUrl(uri) if isinstance(uri, str) else uri,
|
||||
blob=base64.b64encode(self.content).decode(),
|
||||
mimeType=self.mime_type or "application/octet-stream",
|
||||
_meta=self.meta,
|
||||
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ class Resource(FastMCPComponent):
|
|||
title=overrides.get("title", self.title),
|
||||
icons=overrides.get("icons", self.icons),
|
||||
annotations=overrides.get("annotations", self.annotations),
|
||||
_meta=overrides.get(
|
||||
_meta=overrides.get( # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ class ResourceTemplate(FastMCPComponent):
|
|||
title=overrides.get("title", self.title),
|
||||
icons=overrides.get("icons", self.icons),
|
||||
annotations=overrides.get("annotations", self.annotations),
|
||||
_meta=overrides.get(
|
||||
_meta=overrides.get( # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1021,7 +1021,7 @@ class OAuthProxy(OAuthProvider):
|
|||
# Store transaction data for IdP callback processing
|
||||
if client.client_id is None:
|
||||
raise AuthorizeError(
|
||||
error="invalid_client", # type: ignore[arg-type]
|
||||
error="invalid_client", # type: ignore[arg-type] # "invalid_client" is valid OAuth error but not in Literal type
|
||||
error_description="Client ID is required",
|
||||
)
|
||||
transaction = OAuthTransaction(
|
||||
|
|
@ -1104,7 +1104,7 @@ class OAuthProxy(OAuthProvider):
|
|||
# Create authorization code object with PKCE challenge
|
||||
if client.client_id is None:
|
||||
raise AuthorizeError(
|
||||
error="invalid_client", # type: ignore[arg-type]
|
||||
error="invalid_client", # type: ignore[arg-type] # "invalid_client" is valid OAuth error but not in Literal type
|
||||
error_description="Client ID is required",
|
||||
)
|
||||
return AuthorizationCode(
|
||||
|
|
@ -1393,7 +1393,7 @@ class OAuthProxy(OAuthProvider):
|
|||
|
||||
try:
|
||||
logger.debug("Refreshing upstream token (jti=%s)", refresh_jti[:8])
|
||||
token_response: dict[str, Any] = await oauth_client.refresh_token( # type: ignore[misc]
|
||||
token_response: dict[str, Any] = await oauth_client.refresh_token( # type: ignore[assignment]
|
||||
url=self._upstream_token_endpoint,
|
||||
refresh_token=upstream_token_set.refresh_token,
|
||||
scope=" ".join(upstream_scopes) if upstream_scopes else None,
|
||||
|
|
@ -1802,7 +1802,7 @@ class OAuthProxy(OAuthProvider):
|
|||
|
||||
idp_tokens: dict[str, Any] = await oauth_client.fetch_token(
|
||||
**token_params
|
||||
) # type: ignore[misc]
|
||||
) # type: ignore[assignment]
|
||||
|
||||
logger.debug(
|
||||
f"Successfully exchanged IdP code for tokens (transaction: {txn_id}, PKCE: {bool(proxy_code_verifier)})"
|
||||
|
|
|
|||
|
|
@ -197,6 +197,10 @@ class AWSCognitoProvider(OIDCProxy):
|
|||
raise ValueError(
|
||||
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET"
|
||||
)
|
||||
if not settings.base_url:
|
||||
raise ValueError(
|
||||
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL"
|
||||
)
|
||||
|
||||
# Apply defaults
|
||||
required_scopes_final = settings.required_scopes or ["openid"]
|
||||
|
|
|
|||
|
|
@ -233,6 +233,11 @@ class AzureProvider(OAuthProxy):
|
|||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
if not settings.base_url:
|
||||
raise ValueError(
|
||||
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_BASE_URL"
|
||||
)
|
||||
|
||||
# Apply defaults
|
||||
self.identifier_uri = settings.identifier_uri or f"api://{settings.client_id}"
|
||||
self.additional_authorize_scopes = settings.additional_authorize_scopes or []
|
||||
|
|
|
|||
|
|
@ -267,6 +267,10 @@ class DiscordProvider(OAuthProxy):
|
|||
raise ValueError(
|
||||
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET"
|
||||
)
|
||||
if not settings.base_url:
|
||||
raise ValueError(
|
||||
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_DISCORD_BASE_URL"
|
||||
)
|
||||
|
||||
# Apply defaults
|
||||
timeout_seconds_final = settings.timeout_seconds or 10
|
||||
|
|
|
|||
|
|
@ -262,6 +262,10 @@ class GitHubProvider(OAuthProxy):
|
|||
raise ValueError(
|
||||
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET"
|
||||
)
|
||||
if not settings.base_url:
|
||||
raise ValueError(
|
||||
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_BASE_URL"
|
||||
)
|
||||
|
||||
# Apply defaults
|
||||
|
||||
|
|
|
|||
|
|
@ -286,6 +286,10 @@ class GoogleProvider(OAuthProxy):
|
|||
raise ValueError(
|
||||
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET"
|
||||
)
|
||||
if not settings.base_url:
|
||||
raise ValueError(
|
||||
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL"
|
||||
)
|
||||
|
||||
# Apply defaults
|
||||
timeout_seconds_final = settings.timeout_seconds or 10
|
||||
|
|
|
|||
|
|
@ -234,6 +234,10 @@ class WorkOSProvider(OAuthProxy):
|
|||
raise ValueError(
|
||||
"authkit_domain is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN"
|
||||
)
|
||||
if not settings.base_url:
|
||||
raise ValueError(
|
||||
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_BASE_URL"
|
||||
)
|
||||
|
||||
# Apply defaults and ensure authkit_domain is a full URL
|
||||
authkit_domain_str = settings.authkit_domain
|
||||
|
|
|
|||
|
|
@ -680,7 +680,7 @@ class Context:
|
|||
if mask_error_details is not None
|
||||
else settings.mask_error_details
|
||||
)
|
||||
tool_results = await run_sampling_tools(
|
||||
tool_results: list[SamplingMessageContentBlock] = await run_sampling_tools( # type: ignore[assignment]
|
||||
step_tool_calls, tool_map, mask_error_details=effective_mask
|
||||
)
|
||||
|
||||
|
|
@ -688,7 +688,7 @@ class Context:
|
|||
current_messages.append(
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=tool_results, # type: ignore[arg-type]
|
||||
content=tool_results,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -912,10 +912,50 @@ class Context:
|
|||
"""When response_type is a list of strings, the accepted elicitation will
|
||||
contain the selected string response"""
|
||||
|
||||
@overload
|
||||
async def elicit(
|
||||
self,
|
||||
message: str,
|
||||
response_type: type[T] | list[str] | dict[str, dict[str, str]] | None = None,
|
||||
response_type: dict[str, dict[str, str]],
|
||||
) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation: ...
|
||||
|
||||
"""When response_type is a dict mapping keys to title dicts, the accepted
|
||||
elicitation will contain the selected key"""
|
||||
|
||||
@overload
|
||||
async def elicit(
|
||||
self,
|
||||
message: str,
|
||||
response_type: list[list[str]],
|
||||
) -> (
|
||||
AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
|
||||
): ...
|
||||
|
||||
"""When response_type is a list containing a list of strings (multi-select),
|
||||
the accepted elicitation will contain a list of selected strings"""
|
||||
|
||||
@overload
|
||||
async def elicit(
|
||||
self,
|
||||
message: str,
|
||||
response_type: list[dict[str, dict[str, str]]],
|
||||
) -> (
|
||||
AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
|
||||
): ...
|
||||
|
||||
"""When response_type is a list containing a dict mapping keys to title dicts
|
||||
(multi-select with titles), the accepted elicitation will contain a list of
|
||||
selected keys"""
|
||||
|
||||
async def elicit(
|
||||
self,
|
||||
message: str,
|
||||
response_type: type[T]
|
||||
| list[str]
|
||||
| dict[str, dict[str, str]]
|
||||
| list[list[str]]
|
||||
| list[dict[str, dict[str, str]]]
|
||||
| None = None,
|
||||
) -> (
|
||||
AcceptedElicitation[T]
|
||||
| AcceptedElicitation[dict[str, Any]]
|
||||
|
|
|
|||
|
|
@ -637,7 +637,7 @@ def get_access_token() -> AccessToken | None:
|
|||
scopes=access_token_as_dict["scopes"],
|
||||
# Optional fields
|
||||
expires_at=access_token_as_dict.get("expires_at"),
|
||||
resource_owner=access_token_as_dict.get("resource_owner"),
|
||||
resource_owner=access_token_as_dict.get("resource_owner"), # type: ignore[call-arg] # Optional field in MCP SDK
|
||||
claims=access_token_as_dict.get("claims"),
|
||||
)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -194,21 +194,23 @@ class ResponseCachingMiddleware(Middleware):
|
|||
call_tool_settings or CallToolSettings()
|
||||
)
|
||||
|
||||
# PydanticAdapter type signature will be fixed to accept generic aliases
|
||||
# See: https://github.com/strawgate/py-key-value/pull/250
|
||||
self._list_tools_cache: PydanticAdapter[list[Tool]] = PydanticAdapter(
|
||||
key_value=self._stats,
|
||||
pydantic_model=list[Tool],
|
||||
pydantic_model=list[Tool], # type: ignore[arg-type]
|
||||
default_collection="tools/list",
|
||||
)
|
||||
|
||||
self._list_resources_cache: PydanticAdapter[list[Resource]] = PydanticAdapter(
|
||||
key_value=self._stats,
|
||||
pydantic_model=list[Resource],
|
||||
pydantic_model=list[Resource], # type: ignore[arg-type]
|
||||
default_collection="resources/list",
|
||||
)
|
||||
|
||||
self._list_prompts_cache: PydanticAdapter[list[Prompt]] = PydanticAdapter(
|
||||
key_value=self._stats,
|
||||
pydantic_model=list[Prompt],
|
||||
pydantic_model=list[Prompt], # type: ignore[arg-type]
|
||||
default_collection="prompts/list",
|
||||
)
|
||||
|
||||
|
|
@ -216,19 +218,19 @@ class ResponseCachingMiddleware(Middleware):
|
|||
list[CachableReadResourceContents]
|
||||
] = PydanticAdapter(
|
||||
key_value=self._stats,
|
||||
pydantic_model=list[CachableReadResourceContents],
|
||||
pydantic_model=list[CachableReadResourceContents], # type: ignore[arg-type]
|
||||
default_collection="resources/read",
|
||||
)
|
||||
|
||||
self._get_prompt_cache: PydanticAdapter[PromptResult] = PydanticAdapter(
|
||||
key_value=self._stats,
|
||||
pydantic_model=PromptResult,
|
||||
pydantic_model=PromptResult, # type: ignore[arg-type]
|
||||
default_collection="prompts/get",
|
||||
)
|
||||
|
||||
self._call_tool_cache: PydanticAdapter[CachableToolResult] = PydanticAdapter(
|
||||
key_value=self._stats,
|
||||
pydantic_model=CachableToolResult,
|
||||
pydantic_model=CachableToolResult, # type: ignore[arg-type]
|
||||
default_collection="tools/call",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -246,11 +246,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
# if auth is `NotSet`, try to create a provider from the environment
|
||||
if auth is NotSet:
|
||||
if fastmcp.settings.server_auth is not None:
|
||||
# server_auth_class returns the class itself
|
||||
auth = fastmcp.settings.server_auth_class()
|
||||
# server_auth_class returns the class itself, not an instance
|
||||
auth_class = cast(
|
||||
type[AuthProvider], fastmcp.settings.server_auth_class
|
||||
)
|
||||
auth = auth_class()
|
||||
else:
|
||||
auth = None
|
||||
self.auth: AuthProvider | None = cast(AuthProvider | None, auth)
|
||||
self.auth: AuthProvider | None = auth
|
||||
|
||||
if tools:
|
||||
for tool in tools:
|
||||
|
|
@ -1859,7 +1862,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
|
||||
) -> (
|
||||
Callable[[AnyFunction], FunctionTool]
|
||||
| FunctionTool
|
||||
| partial[Callable[[AnyFunction], FunctionTool] | FunctionTool]
|
||||
):
|
||||
"""Decorator to register a tool.
|
||||
|
||||
Tools can optionally request a Context object by adding a parameter with the
|
||||
|
|
@ -2241,7 +2248,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
enabled: bool | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
|
||||
) -> (
|
||||
Callable[[AnyFunction], FunctionPrompt]
|
||||
| FunctionPrompt
|
||||
| partial[Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt]
|
||||
):
|
||||
"""Decorator to register a prompt.
|
||||
|
||||
Prompts can optionally request a Context object by adding a parameter with the
|
||||
|
|
@ -2490,7 +2501,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
async with self._lifespan_manager():
|
||||
config = uvicorn.Config(app, host=host, port=port, **config_kwargs)
|
||||
server = uvicorn.Server(config)
|
||||
path = app.state.path.lstrip("/") # type: ignore
|
||||
path = getattr(app.state, "path", "").lstrip("/")
|
||||
logger.info(
|
||||
f"Starting MCP server {self.name!r} with transport {transport!r} on http://{host}:{port}/{path}"
|
||||
)
|
||||
|
|
@ -2897,7 +2908,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
|
||||
client_factory = fresh_client_factory
|
||||
else:
|
||||
base_client = ProxyClient(backend) # type: ignore
|
||||
# backend is not a Client, so it's compatible with ProxyClient.__init__
|
||||
base_client = ProxyClient(cast(Any, backend))
|
||||
|
||||
# Fresh client created from transport - use fresh sessions per request
|
||||
def proxy_client_factory():
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ async def submit_to_docket(
|
|||
jsonrpc="2.0",
|
||||
method="notifications/tasks/created",
|
||||
params={}, # Empty params per spec
|
||||
_meta={ # taskId in _meta per spec
|
||||
_meta={ # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
"modelcontextprotocol.io/related-task": {
|
||||
"taskId": server_task_id,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ These handlers query and manage existing tasks (contrast with handlers.py which
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
import mcp.types
|
||||
from docket.execution import ExecutionState
|
||||
|
|
@ -145,7 +145,9 @@ async def tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskR
|
|||
await execution.sync()
|
||||
|
||||
# Map Docket state to MCP state
|
||||
mcp_state = DOCKET_TO_MCP_STATE.get(execution.state, "failed")
|
||||
mcp_state: Literal[
|
||||
"working", "input_required", "completed", "failed", "cancelled"
|
||||
] = DOCKET_TO_MCP_STATE.get(execution.state, "failed") # type: ignore[assignment]
|
||||
|
||||
# Build response (use default ttl since we don't track per-task values)
|
||||
# createdAt is REQUIRED per SEP-1686 final spec (line 430)
|
||||
|
|
@ -163,10 +165,22 @@ async def tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskR
|
|||
# Extract progress message from Docket if available (spec line 403)
|
||||
status_message = execution.progress.message
|
||||
|
||||
# createdAt is required per spec, but can be None from Redis
|
||||
# Parse ISO string to datetime, or use current time as fallback
|
||||
if created_at:
|
||||
try:
|
||||
created_at_dt = datetime.fromisoformat(
|
||||
created_at.replace("Z", "+00:00")
|
||||
)
|
||||
except (ValueError, AttributeError):
|
||||
created_at_dt = datetime.now(timezone.utc)
|
||||
else:
|
||||
created_at_dt = datetime.now(timezone.utc)
|
||||
|
||||
return GetTaskResult(
|
||||
taskId=client_task_id,
|
||||
status=mcp_state, # type: ignore[arg-type]
|
||||
createdAt=created_at, # type: ignore[arg-type]
|
||||
status=mcp_state,
|
||||
createdAt=created_at_dt,
|
||||
lastUpdatedAt=datetime.now(timezone.utc),
|
||||
ttl=DEFAULT_TTL_MS,
|
||||
pollInterval=poll_interval_ms,
|
||||
|
|
@ -255,7 +269,7 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
return mcp.types.CallToolResult(
|
||||
content=[mcp.types.TextContent(type="text", text=str(error))],
|
||||
isError=True,
|
||||
_meta={
|
||||
_meta={ # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
"modelcontextprotocol.io/related-task": {
|
||||
"taskId": client_task_id,
|
||||
}
|
||||
|
|
@ -287,11 +301,12 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
mcp_result = mcp.types.CallToolResult(
|
||||
content=content,
|
||||
structuredContent=structured_content,
|
||||
_meta=related_task_meta,
|
||||
_meta=related_task_meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
else:
|
||||
mcp_result = mcp.types.CallToolResult(
|
||||
content=mcp_result, _meta=related_task_meta
|
||||
content=mcp_result,
|
||||
_meta=related_task_meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
return mcp_result
|
||||
|
||||
|
|
@ -308,7 +323,7 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
mcp_content = resource_content.to_mcp_resource_contents(component_id)
|
||||
return mcp.types.ReadResourceResult(
|
||||
contents=[mcp_content],
|
||||
_meta=related_task_meta,
|
||||
_meta=related_task_meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
|
||||
elif task_type == "template":
|
||||
|
|
@ -317,7 +332,7 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
mcp_content = resource_content.to_mcp_resource_contents(component_id)
|
||||
return mcp.types.ReadResourceResult(
|
||||
contents=[mcp_content],
|
||||
_meta=related_task_meta,
|
||||
_meta=related_task_meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class ToolResult:
|
|||
return CallToolResult(
|
||||
structuredContent=self.structured_content,
|
||||
content=self.content,
|
||||
_meta=self.meta,
|
||||
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
if self.structured_content is None:
|
||||
return self.content
|
||||
|
|
@ -190,7 +190,7 @@ class Tool(FastMCPComponent):
|
|||
icons=overrides.get("icons", self.icons),
|
||||
annotations=overrides.get("annotations", self.annotations),
|
||||
execution=overrides.get("execution", self.execution),
|
||||
_meta=overrides.get(
|
||||
_meta=overrides.get( # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -805,7 +805,7 @@ async def test_list_list_multi_select_untitled():
|
|||
if result.action == "accept":
|
||||
assert isinstance(result, AcceptedElicitation)
|
||||
assert isinstance(result.data, list)
|
||||
return ",".join(result.data)
|
||||
return ",".join(result.data) # type: ignore[no-matching-overload]
|
||||
return "declined"
|
||||
|
||||
async def elicitation_handler(message, response_type, params, ctx):
|
||||
|
|
@ -843,7 +843,7 @@ async def test_list_dict_multi_select_titled():
|
|||
if result.action == "accept":
|
||||
assert isinstance(result, AcceptedElicitation)
|
||||
assert isinstance(result.data, list)
|
||||
return ",".join(result.data)
|
||||
return ",".join(result.data) # type: ignore[no-matching-overload]
|
||||
return "declined"
|
||||
|
||||
async def elicitation_handler(message, response_type, params, ctx):
|
||||
|
|
|
|||
|
|
@ -133,18 +133,30 @@ class TestAzureProvider:
|
|||
required_scopes=[],
|
||||
)
|
||||
|
||||
def test_init_missing_base_url_raises_error(self):
|
||||
"""Test that missing base_url raises ValueError."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
with pytest.raises(ValueError, match="base_url is required"):
|
||||
AzureProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
def test_init_defaults(self):
|
||||
"""Test that default values are applied correctly."""
|
||||
provider = AzureProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
# Check defaults
|
||||
assert provider.base_url is None
|
||||
assert provider._redirect_path == "/auth/callback"
|
||||
# Azure provider defaults are set but we can't easily verify them without accessing internals
|
||||
|
||||
|
|
@ -179,6 +191,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="organizations",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
|
@ -190,6 +203,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="consumers",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
|
@ -203,6 +217,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=[
|
||||
"read",
|
||||
"write",
|
||||
|
|
@ -227,6 +242,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
|
@ -240,6 +256,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="my-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=[".default"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -393,6 +410,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
|
@ -421,6 +439,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="gov-tenant-id",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=["read"],
|
||||
base_authority="login.microsoftonline.us",
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -452,6 +471,7 @@ class TestAzureProvider:
|
|||
"FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID": "env-client-id",
|
||||
"FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET": "env-secret",
|
||||
"FASTMCP_SERVER_AUTH_AZURE_TENANT_ID": "env-tenant-id",
|
||||
"FASTMCP_SERVER_AUTH_AZURE_BASE_URL": "https://myserver.com",
|
||||
"FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES": "read",
|
||||
"FASTMCP_SERVER_AUTH_AZURE_BASE_AUTHORITY": "login.microsoftonline.us",
|
||||
"FASTMCP_SERVER_AUTH_AZURE_JWT_SIGNING_KEY": "test-secret",
|
||||
|
|
@ -483,6 +503,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="organizations",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=["read"],
|
||||
base_authority="login.microsoftonline.us",
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -498,6 +519,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read", "write"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -516,6 +538,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -536,6 +559,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
additional_authorize_scopes=[
|
||||
|
|
@ -566,6 +590,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
additional_authorize_scopes=["User.Read", "openid"],
|
||||
|
|
@ -590,6 +615,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
additional_authorize_scopes=["User.Read"],
|
||||
|
|
@ -613,6 +639,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -634,6 +661,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
additional_authorize_scopes=["User.Read", "openid"],
|
||||
|
|
@ -653,6 +681,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -671,6 +700,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
additional_authorize_scopes=["User.Read", "openid"],
|
||||
|
|
@ -697,6 +727,7 @@ class TestAzureProvider:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -736,6 +767,7 @@ class TestOIDCScopeHandling:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -754,6 +786,7 @@ class TestOIDCScopeHandling:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -778,6 +811,7 @@ class TestOIDCScopeHandling:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -795,6 +829,7 @@ class TestOIDCScopeHandling:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -819,6 +854,7 @@ class TestOIDCScopeHandling:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read", "openid", "profile"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -833,6 +869,7 @@ class TestOIDCScopeHandling:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["openid", "profile"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -847,6 +884,7 @@ class TestOIDCScopeHandling:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read", "openid", "profile"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
@ -868,6 +906,7 @@ class TestOIDCScopeHandling:
|
|||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["read"],
|
||||
jwt_signing_key="test-secret",
|
||||
|
|
|
|||
|
|
@ -70,16 +70,26 @@ class TestDiscordProvider:
|
|||
with pytest.raises(ValueError, match="client_secret is required"):
|
||||
DiscordProvider(client_id="env_client_id")
|
||||
|
||||
def test_init_missing_base_url_raises_error(self):
|
||||
"""Test that missing base_url raises ValueError."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
with pytest.raises(ValueError, match="base_url is required"):
|
||||
DiscordProvider(
|
||||
client_id="env_client_id",
|
||||
client_secret="GOCSPX-test123",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
def test_init_defaults(self):
|
||||
"""Test that default values are applied correctly."""
|
||||
provider = DiscordProvider(
|
||||
client_id="env_client_id",
|
||||
client_secret="GOCSPX-test123",
|
||||
base_url="https://myserver.com",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
# Check defaults
|
||||
assert provider.base_url is None
|
||||
assert provider._redirect_path == "/auth/callback"
|
||||
|
||||
def test_oauth_endpoints_configured_correctly(self):
|
||||
|
|
@ -108,6 +118,7 @@ class TestDiscordProvider:
|
|||
provider = DiscordProvider(
|
||||
client_id="env_client_id",
|
||||
client_secret="GOCSPX-test123",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=[
|
||||
"identify",
|
||||
"email",
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ class TestGitHubProvider:
|
|||
{
|
||||
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
|
||||
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
|
||||
"FASTMCP_SERVER_AUTH_GITHUB_BASE_URL": "https://env-example.com",
|
||||
"FASTMCP_SERVER_AUTH_GITHUB_JWT_SIGNING_KEY": "test-secret",
|
||||
},
|
||||
):
|
||||
|
|
@ -147,16 +148,26 @@ class TestGitHubProvider:
|
|||
with pytest.raises(ValueError, match="client_secret is required"):
|
||||
GitHubProvider(client_id="test_client")
|
||||
|
||||
def test_init_missing_base_url_raises_error(self):
|
||||
"""Test that missing base_url raises ValueError."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
with pytest.raises(ValueError, match="base_url is required"):
|
||||
GitHubProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
def test_init_defaults(self):
|
||||
"""Test that default values are applied correctly."""
|
||||
provider = GitHubProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
base_url="https://example.com",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
# Check defaults
|
||||
assert provider.base_url is None
|
||||
assert provider._redirect_path == "/auth/callback"
|
||||
# The required_scopes should be passed to the token verifier
|
||||
assert provider._token_validator.required_scopes == ["user"]
|
||||
|
|
|
|||
|
|
@ -70,16 +70,26 @@ class TestGoogleProvider:
|
|||
with pytest.raises(ValueError, match="client_secret is required"):
|
||||
GoogleProvider(client_id="123456789.apps.googleusercontent.com")
|
||||
|
||||
def test_init_missing_base_url_raises_error(self):
|
||||
"""Test that missing base_url raises ValueError."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
with pytest.raises(ValueError, match="base_url is required"):
|
||||
GoogleProvider(
|
||||
client_id="123456789.apps.googleusercontent.com",
|
||||
client_secret="GOCSPX-test123",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
def test_init_defaults(self):
|
||||
"""Test that default values are applied correctly."""
|
||||
provider = GoogleProvider(
|
||||
client_id="123456789.apps.googleusercontent.com",
|
||||
client_secret="GOCSPX-test123",
|
||||
base_url="https://myserver.com",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
# Check defaults
|
||||
assert provider.base_url is None
|
||||
assert provider._redirect_path == "/auth/callback"
|
||||
# Google provider has ["openid"] as default but we can't easily verify without accessing internals
|
||||
|
||||
|
|
@ -109,6 +119,7 @@ class TestGoogleProvider:
|
|||
provider = GoogleProvider(
|
||||
client_id="123456789.apps.googleusercontent.com",
|
||||
client_secret="GOCSPX-test123",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=[
|
||||
"openid",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
|
|
@ -125,6 +136,7 @@ class TestGoogleProvider:
|
|||
provider = GoogleProvider(
|
||||
client_id="123456789.apps.googleusercontent.com",
|
||||
client_secret="GOCSPX-test123",
|
||||
base_url="https://myserver.com",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
|
|
@ -139,6 +151,7 @@ class TestGoogleProvider:
|
|||
provider = GoogleProvider(
|
||||
client_id="123456789.apps.googleusercontent.com",
|
||||
client_secret="GOCSPX-test123",
|
||||
base_url="https://myserver.com",
|
||||
jwt_signing_key="test-secret",
|
||||
extra_authorize_params={"prompt": "select_account"},
|
||||
)
|
||||
|
|
@ -153,6 +166,7 @@ class TestGoogleProvider:
|
|||
provider = GoogleProvider(
|
||||
client_id="123456789.apps.googleusercontent.com",
|
||||
client_secret="GOCSPX-test123",
|
||||
base_url="https://myserver.com",
|
||||
jwt_signing_key="test-secret",
|
||||
extra_authorize_params={"login_hint": "user@example.com"},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -126,17 +126,28 @@ class TestWorkOSProvider:
|
|||
assert parsed.netloc == "localhost:8080"
|
||||
assert parsed.path == "/oauth2/authorize"
|
||||
|
||||
def test_init_missing_base_url_raises_error(self):
|
||||
"""Test that missing base_url raises ValueError."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
with pytest.raises(ValueError, match="base_url is required"):
|
||||
WorkOSProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
authkit_domain="https://test.authkit.app",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
def test_init_defaults(self):
|
||||
"""Test that default values are applied correctly."""
|
||||
provider = WorkOSProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
authkit_domain="https://test.authkit.app",
|
||||
base_url="https://myserver.com",
|
||||
jwt_signing_key="test-secret",
|
||||
)
|
||||
|
||||
# Check defaults
|
||||
assert provider.base_url is None
|
||||
assert provider._redirect_path == "/auth/callback"
|
||||
# WorkOS provider has no default scopes but we can't easily verify without accessing internals
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@ async def _start_flow(
|
|||
redirect_uri_provided_explicitly=True,
|
||||
state="client-state-xyz",
|
||||
code_challenge="challenge",
|
||||
code_challenge_method="S256",
|
||||
scopes=["read"],
|
||||
)
|
||||
consent_url = await proxy.authorize(
|
||||
|
|
@ -155,7 +154,6 @@ class TestServerSideStorage:
|
|||
redirect_uri_provided_explicitly=True,
|
||||
state="client-state-123",
|
||||
code_challenge="challenge-abc",
|
||||
code_challenge_method="S256",
|
||||
scopes=["read", "write"],
|
||||
)
|
||||
|
||||
|
|
@ -208,7 +206,6 @@ class TestServerSideStorage:
|
|||
redirect_uri_provided_explicitly=True,
|
||||
state="client-state",
|
||||
code_challenge="challenge-xyz",
|
||||
code_challenge_method="S256",
|
||||
scopes=["read"],
|
||||
)
|
||||
|
||||
|
|
@ -288,7 +285,6 @@ class TestServerSideStorage:
|
|||
redirect_uri_provided_explicitly=True,
|
||||
state="test-state",
|
||||
code_challenge="test-challenge",
|
||||
code_challenge_method="S256",
|
||||
scopes=["read"],
|
||||
)
|
||||
|
||||
|
|
@ -485,7 +481,6 @@ class TestStoragePersistence:
|
|||
redirect_uri_provided_explicitly=True,
|
||||
state="persist-state",
|
||||
code_challenge="persist-challenge",
|
||||
code_challenge_method="S256",
|
||||
scopes=["read"],
|
||||
)
|
||||
|
||||
|
|
@ -523,7 +518,6 @@ class TestStoragePersistence:
|
|||
redirect_uri_provided_explicitly=True,
|
||||
state="pydantic-state",
|
||||
code_challenge="pydantic-challenge",
|
||||
code_challenge_method="S256",
|
||||
scopes=["read", "write"],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ class TestOAuthProxyAuthorization:
|
|||
client_id="test-client",
|
||||
client_secret="test-secret",
|
||||
redirect_uris=[AnyUrl("http://localhost:54321/callback")],
|
||||
jwt_signing_key="test-secret",
|
||||
jwt_signing_key="test-secret", # type: ignore[call-arg] # Optional field in MCP SDK
|
||||
)
|
||||
|
||||
# Register client first (required for consent flow)
|
||||
|
|
@ -462,7 +462,6 @@ class TestOAuthProxyAuthorization:
|
|||
redirect_uri_provided_explicitly=True,
|
||||
state="client-state-123",
|
||||
code_challenge="challenge-abc",
|
||||
code_challenge_method="S256",
|
||||
scopes=["read", "write"],
|
||||
)
|
||||
|
||||
|
|
@ -946,7 +945,6 @@ class TestOAuthProxyE2E:
|
|||
redirect_uri_provided_explicitly=True,
|
||||
state="client-state",
|
||||
code_challenge="client_challenge_value",
|
||||
code_challenge_method="S256",
|
||||
scopes=["read"],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ async def test_dependency_context_managers_cleaned_up_in_background():
|
|||
cleanup_called.append("exit")
|
||||
|
||||
@mcp.tool(task=True)
|
||||
async def use_connection(name: str, conn: str = Depends(tracked_connection)) -> str:
|
||||
async def use_connection(name: str, conn: str = Depends(tracked_connection)) -> str: # type: ignore[assignment]
|
||||
assert conn == "connection"
|
||||
assert "enter" in cleanup_called
|
||||
assert "exit" not in cleanup_called # Still open during execution
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ async def test_depends_with_async_function(mcp: FastMCP):
|
|||
return 42
|
||||
|
||||
@mcp.tool()
|
||||
async def greet_user(name: str, user_id: int = Depends(get_user_id)) -> str:
|
||||
async def greet_user(name: str, user_id: int = Depends(get_user_id)) -> str: # type: ignore[assignment]
|
||||
return f"Hello {name}, your ID is {user_id}"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
|
|
@ -93,7 +93,7 @@ async def test_depends_with_async_context_manager(mcp: FastMCP):
|
|||
cleanup_called = True
|
||||
|
||||
@mcp.tool()
|
||||
async def query_db(sql: str, db: str = Depends(get_database)) -> str:
|
||||
async def query_db(sql: str, db: str = Depends(get_database)) -> str: # type: ignore[assignment]
|
||||
return f"Executing '{sql}' on {db}"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
|
|
@ -202,7 +202,7 @@ async def test_sync_tool_with_async_dependency(mcp: FastMCP):
|
|||
return "loaded_config"
|
||||
|
||||
@mcp.tool()
|
||||
def process_data(value: int, config: str = Depends(fetch_config)) -> str:
|
||||
def process_data(value: int, config: str = Depends(fetch_config)) -> str: # type: ignore[assignment]
|
||||
return f"Processing {value} with {config}"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
|
|
@ -392,7 +392,8 @@ async def test_async_tool_context_manager_stays_open(mcp: FastMCP):
|
|||
|
||||
@mcp.tool()
|
||||
async def query_data(
|
||||
query: str, connection: Connection = Depends(get_connection)
|
||||
query: str,
|
||||
connection: Connection = Depends(get_connection), # type: ignore[assignment]
|
||||
) -> str:
|
||||
assert connection.is_open
|
||||
return f"open={connection.is_open}"
|
||||
|
|
@ -408,7 +409,7 @@ async def test_async_resource_context_manager_stays_open(mcp: FastMCP):
|
|||
"""Test that context manager dependencies stay open during async resource execution."""
|
||||
|
||||
@mcp.resource("data://config")
|
||||
async def load_config(connection: Connection = Depends(get_connection)) -> str:
|
||||
async def load_config(connection: Connection = Depends(get_connection)) -> str: # type: ignore[assignment]
|
||||
assert connection.is_open
|
||||
return f"open={connection.is_open}"
|
||||
|
||||
|
|
@ -424,7 +425,8 @@ async def test_async_resource_template_context_manager_stays_open(mcp: FastMCP):
|
|||
|
||||
@mcp.resource("user://{user_id}")
|
||||
async def get_user(
|
||||
user_id: str, connection: Connection = Depends(get_connection)
|
||||
user_id: str,
|
||||
connection: Connection = Depends(get_connection), # type: ignore[assignment]
|
||||
) -> str:
|
||||
assert connection.is_open
|
||||
return f"open={connection.is_open},user={user_id}"
|
||||
|
|
@ -441,7 +443,8 @@ async def test_async_prompt_context_manager_stays_open(mcp: FastMCP):
|
|||
|
||||
@mcp.prompt()
|
||||
async def research_prompt(
|
||||
topic: str, connection: Connection = Depends(get_connection)
|
||||
topic: str,
|
||||
connection: Connection = Depends(get_connection), # type: ignore[assignment]
|
||||
) -> str:
|
||||
assert connection.is_open
|
||||
return f"open={connection.is_open},topic={topic}"
|
||||
|
|
@ -484,7 +487,8 @@ async def test_connection_dependency_excluded_from_tool_schema(mcp: FastMCP):
|
|||
|
||||
@mcp.tool()
|
||||
async def with_connection(
|
||||
name: str, connection: Connection = Depends(get_connection)
|
||||
name: str,
|
||||
connection: Connection = Depends(get_connection), # type: ignore[assignment]
|
||||
) -> str:
|
||||
return name
|
||||
|
||||
|
|
@ -509,7 +513,8 @@ async def test_sync_tool_context_manager_stays_open(mcp: FastMCP):
|
|||
|
||||
@mcp.tool()
|
||||
async def query_sync(
|
||||
query: str, connection: Connection = Depends(get_sync_connection)
|
||||
query: str,
|
||||
connection: Connection = Depends(get_sync_connection), # type: ignore[assignment]
|
||||
) -> str:
|
||||
assert connection.is_open
|
||||
return f"open={connection.is_open}"
|
||||
|
|
@ -535,7 +540,7 @@ async def test_sync_resource_context_manager_stays_open(mcp: FastMCP):
|
|||
conn.is_open = False
|
||||
|
||||
@mcp.resource("data://sync")
|
||||
async def load_sync(connection: Connection = Depends(get_sync_connection)) -> str:
|
||||
async def load_sync(connection: Connection = Depends(get_sync_connection)) -> str: # type: ignore[assignment]
|
||||
assert connection.is_open
|
||||
return f"open={connection.is_open}"
|
||||
|
||||
|
|
@ -561,7 +566,8 @@ async def test_sync_resource_template_context_manager_stays_open(mcp: FastMCP):
|
|||
|
||||
@mcp.resource("item://{item_id}")
|
||||
async def get_item(
|
||||
item_id: str, connection: Connection = Depends(get_sync_connection)
|
||||
item_id: str,
|
||||
connection: Connection = Depends(get_sync_connection), # type: ignore[assignment]
|
||||
) -> str:
|
||||
assert connection.is_open
|
||||
return f"open={connection.is_open},item={item_id}"
|
||||
|
|
@ -588,7 +594,8 @@ async def test_sync_prompt_context_manager_stays_open(mcp: FastMCP):
|
|||
|
||||
@mcp.prompt()
|
||||
async def sync_prompt(
|
||||
topic: str, connection: Connection = Depends(get_sync_connection)
|
||||
topic: str,
|
||||
connection: Connection = Depends(get_sync_connection), # type: ignore[assignment]
|
||||
) -> str:
|
||||
assert connection.is_open
|
||||
return f"open={connection.is_open},topic={topic}"
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ class TestFastMCPComponent:
|
|||
def test_extra_fields_forbidden(self):
|
||||
"""Test that extra fields are not allowed."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
FastMCPComponent(name="test", unknown_field="value")
|
||||
FastMCPComponent(name="test", unknown_field="value") # type: ignore[call-arg] # Intentionally passing invalid field for test
|
||||
assert "Extra inputs are not permitted" in str(exc_info.value)
|
||||
|
||||
|
||||
|
|
|
|||
40
uv.lock
generated
40
uv.lock
generated
|
|
@ -784,7 +784,7 @@ dev = [
|
|||
{ name = "pytest-timeout", specifier = ">=2.4.0" },
|
||||
{ name = "pytest-xdist", specifier = ">=3.6.1" },
|
||||
{ name = "ruff", specifier = ">=0.12.8" },
|
||||
{ name = "ty", specifier = "==0.0.1a31" },
|
||||
{ name = "ty", specifier = "==0.0.5" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2521,27 +2521,27 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.1a31"
|
||||
version = "0.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/30/78/daa1e70377b8127e06db63063b7dd9694cb2bb611b4e3c2182b9ec5a02a1/ty-0.0.1a31.tar.gz", hash = "sha256:b878b04af63b1e716436897838ca6a107a672539155b6fc2051268cd85da9cd6", size = 4656004, upload-time = "2025-12-04T09:01:47.147Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/db/6299d478000f4f1c6f9bf2af749359381610ffc4cbe6713b66e436ecf6e7/ty-0.0.5.tar.gz", hash = "sha256:983da6330773ff71e2b249810a19c689f9a0372f6e21bbf7cde37839d05b4346", size = 4806218, upload-time = "2025-12-20T21:19:17.24Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/08/4c/1e91d6b22dee1435db1cdf55e54ec601497dba650684517b1cd5b4345e80/ty-0.0.1a31-py3-none-linux_armv6l.whl", hash = "sha256:662b9a3a3497da12416789e21fda9eb4e1ac66c5233867d89953916099ee44f5", size = 9620261, upload-time = "2025-12-04T09:01:35.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/8f/eb6ac56cc03a00d3258c3362c4fb5a58152d03e9fa207db0465e2dc717e2/ty-0.0.1a31-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:da05f73df587ff1362d487681370db47541123c005a0d1a60a5a048039e309cc", size = 9411370, upload-time = "2025-12-04T09:01:23.903Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/72/2cdbef5bd7ee7a58e71e67e845ae3f99dca695d0bca7561a3294fb8d723e/ty-0.0.1a31-py3-none-macosx_11_0_arm64.whl", hash = "sha256:74032bf207ce1eddc042f26aa9b6e0713373cf2c502174a53a41f9c469f02adb", size = 8925400, upload-time = "2025-12-04T09:01:59.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/4d/a10c3f2e8969e9e1efe3179d2c961236413c9765c9f95e84e8f515fb9b02/ty-0.0.1a31-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd383fd54872df15816a7853a8c824400c85f850916bad2052564bad8462f4f2", size = 9201615, upload-time = "2025-12-04T09:01:21.085Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/e5/bd26f0fc432459718b72a0bb41bd222fd1fad81c1d5f645a7eba94e14be6/ty-0.0.1a31-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6defaf175bce7c91cea9a168a1c30bb523269ed174941cd31f8edc2d77f8ec7", size = 9401110, upload-time = "2025-12-04T09:01:32.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/63/cbb3419f74ce38c0a2affbc269d4d27ec032cfbc3b011a8db5815c89f540/ty-0.0.1a31-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d41f7e68f05517177ef82d89bfe0bf8e787a6b72ad396c1e44a16ef353b95e2", size = 9779837, upload-time = "2025-12-04T09:01:37.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/fb/1d99243a0e005fe8d53671d4a25d5ddcf345a12fb3c683726bd597e42f23/ty-0.0.1a31-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:22f2298a0de1a8af24f50e498023770b05ea4fc0ccebb2c53deb40ff73dc76fc", size = 10444412, upload-time = "2025-12-04T09:01:56.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/7f/95242feb774356b7a93beb5278cd8c8bbb6a8b12d94977ff954929ed257e/ty-0.0.1a31-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38669b9aa53bd87160a2ee8447a3bf8d91dd14b7462f8aa98f1d2740b609589a", size = 10171070, upload-time = "2025-12-04T09:01:44.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/fa/4d8adeb9ff7fd32efcb9ebb05d5f61cd9ad4b4030390c76cd771fb38ac33/ty-0.0.1a31-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fbc75e8e848929155b7ba0e9a222b2561222a1135bb492a9c5b9ad945c80b18", size = 10188190, upload-time = "2025-12-04T09:01:42.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/40/295903716cc2e4fdb88d8cf8b974f0936e6c021f35d5a7f78b769c746bcc/ty-0.0.1a31-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915c0639dfb73f5f19cd69bbe89036ef18b8066ba88ce38d3e3cc0f39b32f99a", size = 9713419, upload-time = "2025-12-04T09:01:26.633Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/93/b622782ce78f0cbacf167c617b41f45e76de02e3d5d5898fc78ad7a47de7/ty-0.0.1a31-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:28d23f4e58e9b08fc8966ad8dac754b4cd5ccafed711e2a32a62f3d2cb6f44cb", size = 9170660, upload-time = "2025-12-04T09:02:01.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5d/2a04dfd412c87d1da220260a5cf8444d36fa356d1f993ee1db5ad820df93/ty-0.0.1a31-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cb2ebbc8065f4dd09e4b82f492becc55cad39068e842f82bfa1c9f7b9864b034", size = 9443773, upload-time = "2025-12-04T09:01:39.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/5e/18c0123b8dcd6a7e7f4a35d3eed127c6c0140377f5986df6bd01c9df5eb1/ty-0.0.1a31-py3-none-musllinux_1_2_i686.whl", hash = "sha256:966984a8a0e4f99d133e9b73bc778d4861b58467bdb85950805d67ff90e73e3e", size = 9532255, upload-time = "2025-12-04T09:01:29.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/c0/2570e4f891f33c3f9160f052d3759e9c7a3dee29bac5b93ad1f29ed42526/ty-0.0.1a31-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bc4fe23fddaa78c0b11637a1eff152b95988960e5d240d282b41f827d13b28f0", size = 9837753, upload-time = "2025-12-04T09:02:04.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/ad/6a231c11b95d3aa3f54824edfb7ab13ae13eea405bbcc6c80090551bd1b2/ty-0.0.1a31-py3-none-win32.whl", hash = "sha256:f82f4e051c40033ca9f10cffafc346fd86ea6541e786c2b1fcffa08c661efbaa", size = 9011568, upload-time = "2025-12-04T09:01:54.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/8b/c07438de84e3e9cbecedd2e8895dc25ca1b110847dd95b5e9f50124eb8d5/ty-0.0.1a31-py3-none-win_amd64.whl", hash = "sha256:12fae6138c4cbd143fe4b5c616056535353a2d0821062d8750132d3ea022fa8f", size = 9880831, upload-time = "2025-12-04T09:01:18.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/9c/ad589282e76e185eb54c3ce212182f7a28547ed20a5a08b51f9684dc2849/ty-0.0.1a31-py3-none-win_arm64.whl", hash = "sha256:4cc339de4dd4b8dd7167cfd1f826a25e303b3dec27da74596a0ce3ed83bcd293", size = 9380327, upload-time = "2025-12-04T09:01:49.651Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/98/c1f61ba378b4191e641bb36c07b7fcc70ff844d61be7a4bf2fea7472b4a9/ty-0.0.5-py3-none-linux_armv6l.whl", hash = "sha256:1594cd9bb68015eb2f5a3c68a040860f3c9306dc6667d7a0e5f4df9967b460e2", size = 9785554, upload-time = "2025-12-20T21:19:05.024Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/f9/b37b77c03396bd779c1397dae4279b7ad79315e005b3412feed8812a4256/ty-0.0.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7c0140ba980233d28699d9ddfe8f43d0b3535d6a3bbff9935df625a78332a3cf", size = 9603995, upload-time = "2025-12-20T21:19:15.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/70/4e75c11903b0e986c0203040472627cb61d6a709e1797fb08cdf9d565743/ty-0.0.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:15de414712cde92048ae4b1a77c4dc22920bd23653fe42acaf73028bad88f6b9", size = 9145815, upload-time = "2025-12-20T21:19:36.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/05/93983dfcf871a41dfe58e5511d28e6aa332a1f826cc67333f77ae41a2f8a/ty-0.0.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:438aa51ad6c5fae64191f8d58876266e26f9250cf09f6624b6af47a22fa88618", size = 9619849, upload-time = "2025-12-20T21:19:19.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/b6/896ab3aad59f846823f202e94be6016fb3f72434d999d2ae9bd0f28b3af9/ty-0.0.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b3d373fd96af1564380caf153600481c676f5002ee76ba8a7c3508cdff82ee0", size = 9606611, upload-time = "2025-12-20T21:19:24.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/ae/098e33fc92330285ed843e2750127e896140c4ebd2d73df7732ea496f588/ty-0.0.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8453692503212ad316cf8b99efbe85a91e5f63769c43be5345e435a1b16cba5a", size = 10029523, upload-time = "2025-12-20T21:19:07.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/5a/f4b4c33758b9295e9aca0de9645deca0f4addd21d38847228723a6e780fc/ty-0.0.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2e4c454139473abbd529767b0df7a795ed828f780aef8d0d4b144558c0dc4446", size = 10870892, upload-time = "2025-12-20T21:19:34.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c5/4e3e7e88389365aa1e631c99378711cf0c9d35a67478cb4720584314cf44/ty-0.0.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:426d4f3b82475b1ec75f3cc9ee5a667c8a4ae8441a09fcd8e823a53b706d00c7", size = 10599291, upload-time = "2025-12-20T21:19:26.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/5d/138f859ea87bd95e17b9818e386ae25a910e46521c41d516bf230ed83ffc/ty-0.0.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5710817b67c6b2e4c0224e4f319b7decdff550886e9020f6d46aa1ce8f89a609", size = 10413515, upload-time = "2025-12-20T21:19:11.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/21/1cbcd0d3b1182172f099e88218137943e0970603492fb10c7c9342369d9a/ty-0.0.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23c55ef08882c7c5ced1ccb90b4eeefa97f690aea254f58ac0987896c590f76", size = 10144992, upload-time = "2025-12-20T21:19:13.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/30/fdac06a5470c09ad2659a0806497b71f338b395d59e92611f71b623d05a0/ty-0.0.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b9e4c1a28a23b14cf8f4f793f4da396939f16c30bfa7323477c8cc234e352ac4", size = 9606408, upload-time = "2025-12-20T21:19:09.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/93/e99dcd7f53295192d03efd9cbcec089a916f49cad4935c0160ea9adbd53d/ty-0.0.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4e9ebb61529b9745af662e37c37a01ad743cdd2c95f0d1421705672874d806cd", size = 9630040, upload-time = "2025-12-20T21:19:38.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/f8/6d1e87186e4c35eb64f28000c1df8fd5f73167ce126c5e3dd21fd1204a23/ty-0.0.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5eb191a8e332f50f56dfe45391bdd7d43dd4ef6e60884710fd7ce84c5d8c1eb5", size = 9754016, upload-time = "2025-12-20T21:19:32.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/e6/20f989342cb3115852dda404f1d89a10a3ce93f14f42b23f095a3d1a00c9/ty-0.0.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:92ed7451a1e82ee134a2c24ca43b74dd31e946dff2b08e5c34473e6b051de542", size = 10252877, upload-time = "2025-12-20T21:19:20.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/9d/fc66fa557443233dfad9ae197ff3deb70ae0efcfb71d11b30ef62f5cdcc3/ty-0.0.5-py3-none-win32.whl", hash = "sha256:71f6707e4c1c010c158029a688a498220f28bb22fdb6707e5c20e09f11a5e4f2", size = 9212640, upload-time = "2025-12-20T21:19:30.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/b6/05c35f6dea29122e54af0e9f8dfedd0a100c721affc8cc801ebe2bc2ed13/ty-0.0.5-py3-none-win_amd64.whl", hash = "sha256:2b8b754a0d7191e94acdf0c322747fec34371a4d0669f5b4e89549aef28814ae", size = 10034701, upload-time = "2025-12-20T21:19:28.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/ca/4201ed5cb2af73912663d0c6ded927c28c28b3c921c9348aa8d2cfef4853/ty-0.0.5-py3-none-win_arm64.whl", hash = "sha256:83bea5a5296caac20d52b790ded2b830a7ff91c4ed9f36730fe1f393ceed6654", size = 9566474, upload-time = "2025-12-20T21:19:22.518Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue