Merge pull request #113 from jlowin/uv-sync

fix deprecation warnings
This commit is contained in:
Jeremiah Lowin 2025-04-11 10:58:26 -04:00 committed by GitHub
commit f9c8c071ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 77 additions and 78 deletions

View file

@ -113,7 +113,7 @@ For development, install with:
git clone https://github.com/jlowin/fastmcp.git
cd fastmcp
# Install with dev dependencies
uv sync --dev
uv sync
```
## Quickstart
@ -589,7 +589,7 @@ Contributions make the open-source community vibrant! We welcome improvements an
#### Setup
1. Clone: `git clone https://github.com/jlowin/fastmcp.git && cd fastmcp`
2. Install Env & Dependencies: `uv venv && uv sync --dev` (Activate the `.venv` after creation)
2. Install Env & Dependencies: `uv venv && uv sync` (Activate the `.venv` after creation)
#### Testing

View file

@ -9,7 +9,6 @@ the ToolManager's import_tools functionality. It shows how to:
"""
import asyncio
from typing import Dict, List
from fastmcp import FastMCP
@ -34,7 +33,7 @@ news_app = FastMCP("News App")
@news_app.tool()
def get_news_headlines() -> List[str]:
def get_news_headlines() -> list[str]:
"""Get the latest news headlines."""
return [
"Tech company launches new product",
@ -58,7 +57,7 @@ app = FastMCP("Main App")
@app.tool()
def check_app_status() -> Dict[str, str]:
def check_app_status() -> dict[str, str]:
"""Check the status of the main application."""
return {"status": "running", "version": "1.0.0", "uptime": "3h 24m"}

View file

@ -16,12 +16,6 @@ requires-python = ">=3.10"
readme = "README.md"
license = { text = "Apache-2.0" }
[project.scripts]
fastmcp = "fastmcp.cli:app"
[project.optional-dependencies]
[dependency-groups]
dev = [
"pre-commit",
@ -36,23 +30,32 @@ dev = [
"pdbpp>=0.10.3",
"dirty-equals>=0.9.0",
]
[project.scripts]
fastmcp = "fastmcp.cli:app"
[build-system]
requires = ["hatchling>=1.21.0", "hatch-vcs>=0.4.0"]
requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
build-backend = "hatchling.build"
[tool.hatch.version]
source = "uv-dynamic-versioning"
[tool.uv-dynamic-versioning]
vcs = "git"
style = "pep440"
bump = true
fallback-version = "0.0.0"
[tool.uv]
# no default groups
default-groups = []
# uncomment to omit `dev` default group
# default-groups = []
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
filterwarnings = [
"ignore:Accessing the 'model_fields' attribute on the instance is deprecated:DeprecationWarning",
]
filterwarnings = []
[tool.hatch.version]
source = "vcs"
[tool.pyright]
include = ["src", "tests"]
@ -67,7 +70,7 @@ venvPath = "."
venv = ".venv"
[tool.ruff.lint]
extend-select = ["I"]
extend-select = ["I", "UP"]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "I001", "RUF013"]

View file

@ -1,6 +1,7 @@
import datetime
from contextlib import AbstractAsyncContextManager
from pathlib import Path
from typing import Any, AsyncContextManager
from typing import Any
import mcp.types
from mcp import ClientSession
@ -48,7 +49,7 @@ class Client:
):
self.transport = infer_transport(transport)
self._session: ClientSession | None = None
self._session_cm: AsyncContextManager[ClientSession] | None = None
self._session_cm: AbstractAsyncContextManager[ClientSession] | None = None
# Store common kwargs to pass to transport.connect_session
if roots is not None and list_roots_callback is not None:

View file

@ -2,14 +2,10 @@ import abc
import contextlib
import datetime
import os
from collections.abc import AsyncIterator
from pathlib import Path
from typing import (
AsyncIterator,
Dict,
List,
Optional,
TypedDict,
Union,
)
from mcp import ClientSession, StdioServerParameters
@ -103,7 +99,7 @@ class WSTransport(ClientTransport):
class SSETransport(ClientTransport):
"""Transport implementation that connects to an MCP server via Server-Sent Events."""
def __init__(self, url: str | AnyUrl, headers: Optional[Dict[str, str]] = None):
def __init__(self, url: str | AnyUrl, headers: dict[str, str] | None = None):
if isinstance(url, AnyUrl):
url = str(url)
if not isinstance(url, str) or not url.startswith("http"):
@ -138,9 +134,9 @@ class StdioTransport(ClientTransport):
def __init__(
self,
command: str,
args: List[str],
env: Optional[Dict[str, str]] = None,
cwd: Optional[str] = None,
args: list[str],
env: dict[str, str] | None = None,
cwd: str | None = None,
):
"""
Initialize a Stdio transport.
@ -182,10 +178,10 @@ class PythonStdioTransport(StdioTransport):
def __init__(
self,
script_path: Union[str, Path],
args: Optional[List[str]] = None,
env: Optional[Dict[str, str]] = None,
cwd: Optional[str] = None,
script_path: str | Path,
args: list[str] | None = None,
env: dict[str, str] | None = None,
cwd: str | None = None,
python_cmd: str = "python",
):
"""
@ -217,10 +213,10 @@ class NodeStdioTransport(StdioTransport):
def __init__(
self,
script_path: Union[str, Path],
args: Optional[List[str]] = None,
env: Optional[Dict[str, str]] = None,
cwd: Optional[str] = None,
script_path: str | Path,
args: list[str] | None = None,
env: dict[str, str] | None = None,
cwd: str | None = None,
node_cmd: str = "node",
):
"""
@ -253,12 +249,12 @@ class UvxStdioTransport(StdioTransport):
def __init__(
self,
tool_name: str,
tool_args: Optional[List[str]] = None,
project_directory: Optional[str] = None,
python_version: Optional[str] = None,
with_packages: Optional[List[str]] = None,
from_package: Optional[str] = None,
env_vars: Optional[Dict[str, str]] = None,
tool_args: list[str] | None = None,
project_directory: str | None = None,
python_version: str | None = None,
with_packages: list[str] | None = None,
from_package: str | None = None,
env_vars: dict[str, str] | None = None,
):
"""
Initialize a Uvx transport.
@ -308,9 +304,9 @@ class NpxStdioTransport(StdioTransport):
def __init__(
self,
package: str,
args: Optional[List[str]] = None,
project_directory: Optional[str] = None,
env_vars: Optional[Dict[str, str]] = None,
args: list[str] | None = None,
project_directory: str | None = None,
env_vars: dict[str, str] | None = None,
use_package_lock: bool = True,
):
"""
@ -394,7 +390,7 @@ def infer_transport(
return FastMCPTransport(mcp=transport)
# the transport is a path to a script
elif isinstance(transport, (Path, str)) and Path(transport).exists():
elif isinstance(transport, Path | str) and Path(transport).exists():
if str(transport).endswith(".py"):
return PythonStdioTransport(script_path=transport)
elif str(transport).endswith(".js"):
@ -403,11 +399,11 @@ def infer_transport(
raise ValueError(f"Unsupported script type: {transport}")
# the transport is an http(s) URL
elif isinstance(transport, (AnyUrl, str)) and str(transport).startswith("http"):
elif isinstance(transport, AnyUrl | str) and str(transport).startswith("http"):
return SSETransport(url=transport)
# the transport is a websocket URL
elif isinstance(transport, (AnyUrl, str)) and str(transport).startswith("ws"):
elif isinstance(transport, AnyUrl | str) and str(transport).startswith("ws"):
return WSTransport(url=transport)
# the transport is an unknown type

View file

@ -4,7 +4,8 @@ import enum
import json
import re
from dataclasses import dataclass
from typing import Any, Literal, Pattern
from re import Pattern
from typing import Any, Literal
import httpx
from pydantic.networks import AnyUrl

View file

@ -99,7 +99,7 @@ class FastMCP(Generic[LifespanResultT]):
self.dependencies = self.settings.dependencies
# Setup for mounted apps
self._mounted_apps: dict[str, "FastMCP"] = {}
self._mounted_apps: dict[str, FastMCP] = {}
# Set up MCP protocol handlers
self._setup_handlers()
@ -640,7 +640,7 @@ def _convert_to_content(
other_content = []
for item in result:
if isinstance(item, (TextContent, ImageContent, EmbeddedResource, Image)):
if isinstance(item, TextContent | ImageContent | EmbeddedResource | Image):
mcp_types.append(_convert_to_content(item)[0])
else:
other_content.append(item)

View file

@ -62,7 +62,7 @@ class ToolManager:
return await tool.run(arguments, context=context)
def import_tools(
self, tool_manager: "ToolManager", prefix: str | None = None
self, tool_manager: ToolManager, prefix: str | None = None
) -> None:
"""
Import all tools from another ToolManager with prefixed names.

View file

@ -27,7 +27,7 @@ class ArgModelBase(BaseModel):
That is, sub-models etc are not dumped - they are kept as pydantic models.
"""
kwargs: dict[str, Any] = {}
for field_name in self.model_fields.keys():
for field_name in self.__class__.model_fields.keys():
kwargs[field_name] = getattr(self, field_name)
return kwargs

View file

@ -1,6 +1,6 @@
import json
import logging
from typing import Any, Literal, Union, cast
from typing import Any, Literal, cast
# Using the recommended library: openapi-pydantic
from openapi_pydantic import (
@ -92,7 +92,7 @@ __all__ = [
def _resolve_ref(
item: Union[Reference, Schema, Parameter, RequestBody, Any], openapi: OpenAPI
item: Reference | Schema | Parameter | RequestBody | Any, openapi: OpenAPI
) -> Any:
"""Resolves a potential Reference object to its target definition (no changes needed here)."""
if isinstance(item, Reference):
@ -110,7 +110,7 @@ def _resolve_ref(
elif isinstance(target, BaseModel):
# Use model_extra for fields not explicitly defined (like components types)
# Check class fields first, then model_extra
if part in target.model_fields: # Access class attribute here
if part in target.__class__.model_fields:
target = getattr(target, part, None)
elif target.model_extra and part in target.model_extra:
target = target.model_extra[part]
@ -141,7 +141,7 @@ def _resolve_ref(
def _extract_schema_as_dict(
schema_obj: Union[Schema, Reference], openapi: OpenAPI
schema_obj: Schema | Reference, openapi: OpenAPI
) -> JsonSchema:
"""Resolves a schema/reference and returns it as a dictionary."""
resolved_schema = _resolve_ref(schema_obj, openapi)
@ -177,8 +177,8 @@ def _convert_to_parameter_location(param_in: str) -> ParameterLocation:
def _extract_parameters(
operation_params: list[Union[Parameter, Reference]] | None,
path_item_params: list[Union[Parameter, Reference]] | None,
operation_params: list[Parameter | Reference] | None,
path_item_params: list[Parameter | Reference] | None,
openapi: OpenAPI,
) -> list[ParameterInfo]:
"""Extracts and resolves parameters using corrected attribute names."""

View file

@ -1,6 +1,6 @@
"""Tests for the OpenAPI parsing utilities."""
from typing import Any, Dict
from typing import Any
import pytest
from fastapi import Body, FastAPI, Path, Query
@ -12,7 +12,7 @@ from fastmcp.utilities.openapi import parse_openapi_to_http_routes
@pytest.fixture
def petstore_schema() -> Dict[str, Any]:
def petstore_schema() -> dict[str, Any]:
"""Fixture that returns a simple Pet Store API schema."""
return {
"openapi": "3.1.0",
@ -109,7 +109,7 @@ def parsed_petstore_routes(petstore_schema):
@pytest.fixture
def bookstore_schema() -> Dict[str, Any]:
def bookstore_schema() -> dict[str, Any]:
"""Fixture that returns a Book Store API schema with different parameter types."""
return {
"openapi": "3.1.0",
@ -292,7 +292,7 @@ def fastapi_app() -> FastAPI:
@pytest.fixture
def fastapi_openapi_schema(fastapi_app) -> Dict[str, Any]:
def fastapi_openapi_schema(fastapi_app) -> dict[str, Any]:
"""Fixture that returns the OpenAPI schema of the FastAPI app."""
return fastapi_app.openapi()

View file

@ -1,6 +1,6 @@
"""Tests for advanced features of the OpenAPI utilities."""
from typing import Any, Dict
from typing import Any
import pytest
@ -8,7 +8,7 @@ from fastmcp.utilities.openapi import parse_openapi_to_http_routes
@pytest.fixture
def complex_schema() -> Dict[str, Any]:
def complex_schema() -> dict[str, Any]:
"""Fixture that returns a complex OpenAPI schema with nested references."""
return {
"openapi": "3.1.0",
@ -167,7 +167,7 @@ def complex_route_map(parsed_complex_routes):
@pytest.fixture
def schema_with_invalid_reference() -> Dict[str, Any]:
def schema_with_invalid_reference() -> dict[str, Any]:
"""Fixture that returns a schema with an invalid reference."""
return {
"openapi": "3.1.0",
@ -191,7 +191,7 @@ def schema_with_invalid_reference() -> Dict[str, Any]:
@pytest.fixture
def schema_with_content_params() -> Dict[str, Any]:
def schema_with_content_params() -> dict[str, Any]:
"""Fixture that returns a schema with content-based parameters (complex parameters)."""
return {
"openapi": "3.1.0",
@ -236,7 +236,7 @@ def parsed_content_param_routes(schema_with_content_params):
@pytest.fixture
def schema_all_http_methods() -> Dict[str, Any]:
def schema_all_http_methods() -> dict[str, Any]:
"""Fixture that returns a schema with all HTTP methods."""
return {
"openapi": "3.1.0",

View file

@ -1,6 +1,6 @@
"""Tests for FastAPI integration with the OpenAPI utilities."""
from typing import Any, Dict
from typing import Any
import pytest
from fastapi import FastAPI
@ -12,7 +12,6 @@ from fastmcp.utilities.openapi import parse_openapi_to_http_routes
def fastapi_server() -> FastAPI:
"""Fixture that returns a FastAPI app for live OpenAPI schema testing."""
from enum import Enum
from typing import List
from fastapi import Body, Depends, Header, HTTPException, Path, Query
from pydantic import BaseModel, Field
@ -145,7 +144,7 @@ def fastapi_server() -> FastAPI:
)
async def update_item_tags(
item_id: int = Path(..., description="The ID of the item"),
tags: List[str] = Body(..., description="Updated tags"),
tags: list[str] = Body(..., description="Updated tags"),
):
"""Update just the tags of an item."""
return {"item_id": item_id, "tags": tags}
@ -229,7 +228,7 @@ def fastapi_server() -> FastAPI:
@pytest.fixture
def fastapi_openapi_schema(fastapi_server) -> Dict[str, Any]:
def fastapi_openapi_schema(fastapi_server) -> dict[str, Any]:
"""Fixture that returns the OpenAPI schema from a live FastAPI server."""
return fastapi_server.openapi()

6
uv.lock generated
View file

@ -1,4 +1,5 @@
version = 1
revision = 1
requires-python = ">=3.10"
[[package]]
@ -127,7 +128,7 @@ name = "click"
version = "8.1.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "platform_system == 'Windows'" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
wheels = [
@ -230,7 +231,7 @@ name = "fancycompleter"
version = "0.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyreadline", marker = "platform_system == 'Windows'" },
{ name = "pyreadline", marker = "sys_platform == 'win32'" },
{ name = "pyrepl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a9/95/649d135442d8ecf8af5c7e235550c628056423c96c4bc6787348bdae9248/fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272", size = 10866 }
@ -254,7 +255,6 @@ wheels = [
[[package]]
name = "fastmcp"
version = "0.4.2.dev40+g8720a78.d20250410"
source = { editable = "." }
dependencies = [
{ name = "dotenv" },