Skip to content

Commit 6cc9559

Browse files
authored
Bump ty to ==0.0.1a25 (#2350)
* Bump ty to >=0.0.1a25 with type fixes Follow-up to #2295. Updates ty and fixes compatibility issues with alpha 25, including: - Updated ignore comment syntax (possibly-unbound-attribute → possibly-missing-attribute) - Fixed async generator type handling with anext() - Fixed type narrowing for timeout parameters - Converted base_url assignments to AnyHttpUrl after string manipulation - Added CallToolResult to return type annotations - Removed redundant type casts - Fixed test form data to use strings instead of bytes ty alpha 25 has limitations with isinstance() narrowing on unions (see pyproject.toml for details), requiring some targeted type ignores. * Pin ty to ==0.0.1a25 Alpha releases can have breaking changes, so pin to the tested version.
1 parent 0e97a26 commit 6cc9559

File tree

27 files changed

+113
-88
lines changed

27 files changed

+113
-88
lines changed

pyproject.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ dev = [
7171
"pytest-timeout>=2.4.0",
7272
"pytest-xdist>=3.6.1",
7373
"ruff>=0.12.8",
74-
"ty==0.0.1a19",
74+
"ty==0.0.1a25",
7575
"prek>=0.2.12",
7676
]
7777

@@ -136,12 +136,17 @@ python-version = "3.10"
136136

137137
[tool.ty.rules]
138138
# Rules with too many errors to fix right now (40+ each)
139-
no-matching-overload = "ignore" # 126 errors
139+
no-matching-overload = "ignore" # 126 errors
140140
unknown-argument = "ignore" # 61 errors
141141

142142
# Rules with moderate errors that need more investigation
143143
call-non-callable = "ignore" # 7 errors
144144

145+
# NOTE: ty currently doesn't support type narrowing with isinstance() on unions
146+
# See: https://github.com/astral-sh/ty/issues/122 and https://github.com/astral-sh/ty/issues/1113
147+
# Some code uses `# ty: ignore[invalid-argument-type]` for this limitation.
148+
# TODO: Remove these ignores once ty supports union narrowing
149+
145150
[tool.ruff.lint]
146151
fixable = ["ALL"]
147152
ignore = [

src/fastmcp/client/auth/oauth.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ async def async_auth_flow(
297297
response = None
298298
while True:
299299
try:
300-
yielded_request = await gen.asend(response)
300+
# First iteration sends None, subsequent iterations send response
301+
yielded_request = await gen.asend(response) # ty: ignore[invalid-argument-type]
301302
response = yield yielded_request
302303
except StopAsyncIteration:
303304
break
@@ -306,16 +307,16 @@ async def async_auth_flow(
306307
logger.debug(
307308
"OAuth client not found on server, clearing cache and retrying..."
308309
)
309-
310310
# Clear cached state and retry once
311311
self._initialized = False
312312
await self.token_storage_adapter.clear()
313313

314+
# Retry with fresh registration
314315
gen = super().async_auth_flow(request)
315316
response = None
316317
while True:
317318
try:
318-
yielded_request = await gen.asend(response)
319+
yielded_request = await gen.asend(response) # ty: ignore[invalid-argument-type]
319320
response = yield yielded_request
320321
except StopAsyncIteration:
321322
break

src/fastmcp/client/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ def __init__(
240240

241241
self._progress_handler = progress_handler
242242

243+
# Convert timeout to timedelta if needed
243244
if isinstance(timeout, int | float):
244245
timeout = datetime.timedelta(seconds=float(timeout))
245246

@@ -259,7 +260,7 @@ def __init__(
259260
"list_roots_callback": None,
260261
"logging_callback": create_log_callback(log_handler),
261262
"message_handler": message_handler,
262-
"read_timeout_seconds": timeout,
263+
"read_timeout_seconds": timeout, # ty: ignore[invalid-argument-type]
263264
"client_info": client_info,
264265
}
265266

@@ -852,12 +853,13 @@ async def call_tool_mcp(
852853
"""
853854
logger.debug(f"[{self.name}] called call_tool: {name}")
854855

856+
# Convert timeout to timedelta if needed
855857
if isinstance(timeout, int | float):
856858
timeout = datetime.timedelta(seconds=float(timeout))
857859
result = await self.session.call_tool(
858860
name=name,
859861
arguments=arguments,
860-
read_timeout_seconds=timeout,
862+
read_timeout_seconds=timeout, # ty: ignore[invalid-argument-type]
861863
progress_callback=progress_handler or self._progress_handler,
862864
)
863865
return result

src/fastmcp/experimental/utilities/openapi/director.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,27 @@ def build(
5454
url = self._build_url(route.path, path_params, base_url)
5555

5656
# Step 3: Prepare request data
57-
request_data = {
58-
"method": route.method.upper(),
59-
"url": url,
60-
"params": query_params if query_params else None,
61-
"headers": header_params if header_params else None,
62-
}
57+
method: str = route.method.upper()
58+
params = query_params if query_params else None
59+
headers = header_params if header_params else None
60+
json_body: dict[str, Any] | list[Any] | None = None
61+
content: str | bytes | None = None
6362

6463
# Step 4: Handle request body
6564
if body is not None:
6665
if isinstance(body, dict | list):
67-
request_data["json"] = body
66+
json_body = body
6867
else:
69-
request_data["content"] = body
68+
content = body
7069

7170
# Step 5: Create httpx.Request
7271
return httpx.Request(
73-
method=request_data["method"],
74-
url=request_data["url"],
75-
params=request_data.get("params"),
76-
headers=request_data.get("headers"),
77-
json=request_data.get("json"),
78-
content=request_data.get("content"),
72+
method=method,
73+
url=url,
74+
params=params,
75+
headers=headers,
76+
json=json_body,
77+
content=content,
7978
)
8079

8180
def _unflatten_arguments(

src/fastmcp/mcp_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def _to_server_and_underlying_transport(
101101
ClientTransport, # pyright: ignore[reportUnusedImport]
102102
)
103103

104-
transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
104+
transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType] # ty: ignore[unresolved-attribute]
105105
transport = cast(ClientTransport, transport)
106106

107107
client: Client[ClientTransport] = Client(transport=transport, name=client_name)

src/fastmcp/server/auth/handlers/authorize.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from __future__ import annotations
1515

16+
import json
1617
from typing import TYPE_CHECKING
1718

1819
from mcp.server.auth.handlers.authorize import (
@@ -211,22 +212,23 @@ async def handle(self, request: Request) -> Response:
211212
# Check if this is a client not found error
212213
if response.status_code == 400:
213214
# Try to extract client_id from request for enhanced error
214-
client_id = None
215+
client_id: str | None = None
215216
if request.method == "GET":
216217
client_id = request.query_params.get("client_id")
217218
else:
218219
form = await request.form()
219-
client_id = form.get("client_id")
220+
client_id_value = form.get("client_id")
221+
# Ensure client_id is a string, not UploadFile
222+
if isinstance(client_id_value, str):
223+
client_id = client_id_value
220224

221225
# If we have a client_id and the error is about it not being found,
222226
# enhance the response
223227
if client_id:
224228
try:
225229
# Check if response body contains "not found" error
226230
if hasattr(response, "body"):
227-
import json
228-
229-
body = json.loads(response.body)
231+
body = json.loads(bytes(response.body))
230232
if (
231233
body.get("error") == "invalid_request"
232234
and "not found" in body.get("error_description", "").lower()

src/fastmcp/server/auth/oauth_proxy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1605,9 +1605,10 @@ def get_routes(
16051605
):
16061606
authorize_route_found = True
16071607
# Replace with our enhanced authorization handler
1608+
# Note: self.base_url is guaranteed to be set in parent __init__
16081609
authorize_handler = AuthorizationHandler(
16091610
provider=self,
1610-
base_url=self.base_url,
1611+
base_url=self.base_url, # ty: ignore[invalid-argument-type]
16111612
server_name=None, # Could be extended to pass server metadata
16121613
server_icon_url=None,
16131614
)

src/fastmcp/server/auth/oidc_proxy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ def __init__(
340340
init_kwargs["extra_authorize_params"] = extra_params
341341
init_kwargs["extra_token_params"] = extra_params
342342

343-
super().__init__(**init_kwargs)
343+
super().__init__(**init_kwargs) # ty: ignore[invalid-argument-type]
344344

345345
def get_oidc_configuration(
346346
self,

src/fastmcp/server/auth/providers/descope.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def __init__(
102102
)
103103

104104
self.project_id = settings.project_id
105-
self.base_url = str(settings.base_url).rstrip("/")
105+
self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
106106
self.descope_base_url = str(settings.descope_base_url).rstrip("/")
107107

108108
# Create default JWT verifier if none provided

src/fastmcp/server/auth/providers/supabase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def __init__(
107107
)
108108

109109
self.project_url = str(settings.project_url).rstrip("/")
110-
self.base_url = str(settings.base_url).rstrip("/")
110+
self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
111111

112112
# Create default JWT verifier if none provided
113113
if token_verifier is None:

0 commit comments

Comments
 (0)