From ec74c0293d6cfb40d7f8ee37fdc16455bd68a0e1 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:47:36 -0500 Subject: [PATCH 1/7] Remove BaseURL reference and use AnyURL --- src/fastmcp/resources/base.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/fastmcp/resources/base.py b/src/fastmcp/resources/base.py index bde9b3820..11f9029cb 100644 --- a/src/fastmcp/resources/base.py +++ b/src/fastmcp/resources/base.py @@ -1,34 +1,16 @@ """Base classes and interfaces for FastMCP resources.""" import abc -from typing import Annotated, Union +from typing import Union from pydantic import ( AnyUrl, BaseModel, - BeforeValidator, ConfigDict, Field, - FileUrl, ValidationInfo, field_validator, ) -from pydantic.networks import _BaseUrl # TODO: remove this once pydantic is updated - - -def maybe_cast_str_to_any_url(x) -> AnyUrl: - if isinstance(x, FileUrl): - return x - elif isinstance(x, AnyUrl): - return x - elif isinstance(x, str): - if x.startswith("file://"): - return FileUrl(x) - return AnyUrl(x) - raise ValueError(f"Expected str or AnyUrl, got {type(x)}") - - -LaxAnyUrl = Annotated[_BaseUrl | str, BeforeValidator(maybe_cast_str_to_any_url)] class Resource(BaseModel, abc.ABC): @@ -36,7 +18,8 @@ class Resource(BaseModel, abc.ABC): model_config = ConfigDict(validate_default=True) - uri: LaxAnyUrl = Field(default=..., description="URI of the resource") + # uri: Annotated[AnyUrl, BeforeValidator(maybe_cast_str_to_any_url)] = Field( + uri: AnyUrl = Field(default=..., description="URI of the resource") name: str | None = Field(description="Name of the resource", default=None) description: str | None = Field( description="Description of the resource", default=None @@ -47,6 +30,12 @@ class Resource(BaseModel, abc.ABC): pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$", ) + @field_validator("uri", mode="before") + def validate_uri(cls, uri: AnyUrl | str) -> AnyUrl: + if isinstance(uri, str): + return AnyUrl(uri) + return uri + @field_validator("name", mode="before") @classmethod def set_default_name(cls, name: str | None, info: ValidationInfo) -> str: From 73b06f81d3d0de3e4f81c0f62a08aca5f13b11d8 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:55:20 -0500 Subject: [PATCH 2/7] Use FileURL where possible --- src/fastmcp/resources/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/fastmcp/resources/base.py b/src/fastmcp/resources/base.py index 11f9029cb..28b0834d5 100644 --- a/src/fastmcp/resources/base.py +++ b/src/fastmcp/resources/base.py @@ -8,6 +8,7 @@ from pydantic import ( BaseModel, ConfigDict, Field, + FileUrl, ValidationInfo, field_validator, ) @@ -33,6 +34,9 @@ class Resource(BaseModel, abc.ABC): @field_validator("uri", mode="before") def validate_uri(cls, uri: AnyUrl | str) -> AnyUrl: if isinstance(uri, str): + # AnyUrl doesn't support triple-slashes, but files do ("file:///absolute/path") + if uri.startswith("file://"): + return FileUrl(uri) return AnyUrl(uri) return uri From 10e2db7c0ce183df20a016d3a772b61d667376bf Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:43:12 -0500 Subject: [PATCH 3/7] Set up multiple os tests --- .github/workflows/run-tests.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 452fc63d1..bc766999c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -26,8 +26,13 @@ permissions: jobs: run_tests: - name: Run tests - runs-on: ubuntu-latest + name: Python ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.10"] + fail-fast: false steps: - uses: actions/checkout@v4 @@ -35,8 +40,8 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v4 - - name: Set up Python - run: uv python install 3.11 + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} - name: Install FastMCP run: uv sync --extra dev From f7902c5690c8da61aed7c13256d451e16e973e28 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:45:22 -0500 Subject: [PATCH 4/7] Update run-tests.yml --- .github/workflows/run-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index bc766999c..6e394bf03 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,12 +12,14 @@ on: - "tests/**" - "uv.lock" - "pyproject.toml" + - ".github/workflows/run-tests.yml" pull_request: paths: - "src/**" - "tests/**" - "uv.lock" - "pyproject.toml" + - ".github/workflows/run-tests.yml" workflow_dispatch: From 7700a03280607004691ee5ce6a137f9d4de66ece Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:46:06 -0500 Subject: [PATCH 5/7] Update run-tests.yml --- .github/workflows/run-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6e394bf03..2a6542a09 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,14 +12,14 @@ on: - "tests/**" - "uv.lock" - "pyproject.toml" - - ".github/workflows/run-tests.yml" + - ".github/workflows/**" pull_request: paths: - "src/**" - "tests/**" - "uv.lock" - "pyproject.toml" - - ".github/workflows/run-tests.yml" + - ".github/workflows/**" workflow_dispatch: From fc256b63734874b3027c969116e1d12aa44b9eb0 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:47:02 -0500 Subject: [PATCH 6/7] Update run-tests.yml --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 2a6542a09..fe4c67fdb 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -28,7 +28,7 @@ permissions: jobs: run_tests: - name: Python ${{ matrix.python-version }} on ${{ matrix.os }} + name: "Run tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}" runs-on: ${{ matrix.os }} strategy: matrix: From 4a9f3659165735376cb5dffe39dbe6084dd3b73e Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:55:28 -0500 Subject: [PATCH 7/7] Update dependencies --- .github/workflows/run-tests.yml | 2 +- README.md | 13 +++++-------- pyproject.toml | 8 +++----- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index fe4c67fdb..8f83af7ae 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -46,7 +46,7 @@ jobs: run: uv python install ${{ matrix.python-version }} - name: Install FastMCP - run: uv sync --extra dev + run: uv sync --extra tests - name: Run tests run: uv run pytest -vv diff --git a/README.md b/README.md index 760de3f83..8e25a9ea8 100644 --- a/README.md +++ b/README.md @@ -464,23 +464,20 @@ FastMCP requires Python 3.10+ and [uv](https://docs.astral.sh/uv/). ### Installation -Create a fork of this repository, then clone it: +For development, we recommend installing FastMCP with development dependencies, which includes various utilities the maintainers find useful. ```bash -git clone https://github.com/YouFancyUserYou/fastmcp.git +git clone https://github.com/jlowin/fastmcp.git cd fastmcp +uv sync --frozen --extra dev ``` -Next, create a virtual environment and install FastMCP: +For running tests only (e.g., in CI), you only need the testing dependencies: ```bash -uv venv -source .venv/bin/activate -uv sync --frozen --all-extras --dev +uv sync --frozen --extra tests ``` - - ### Testing Please make sure to test any new functionality. Your tests should be simple and atomic and anticipate change rather than cement complex patterns. diff --git a/pyproject.toml b/pyproject.toml index 4ceca0072..8c3c4152d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,16 +23,14 @@ requires = ["hatchling>=1.21.0", "hatch-vcs>=0.4.0"] build-backend = "hatchling.build" [project.optional-dependencies] -dev = [ - "copychat>=0.5.2", - "ipython>=8.12.3", - "pdbpp>=0.10.3", +tests = [ "pre-commit", - "pytest-xdist>=3.6.1", "pytest>=8.3.3", "pytest-asyncio>=0.23.5", + "pytest-xdist>=3.6.1", "ruff", ] +dev = ["fastmcp[tests]", "copychat>=0.5.2", "ipython>=8.12.3", "pdbpp>=0.10.3"] [tool.pytest.ini_options] asyncio_mode = "auto"