Spaces:
Running
Running
Merge branch 'main' into windows2
Browse files- .github/workflows/run-tests.yml +12 -5
- README.md +5 -8
- pyproject.toml +3 -5
- src/fastmcp/resources/base.py +12 -19
.github/workflows/run-tests.yml
CHANGED
|
@@ -12,12 +12,14 @@ on:
|
|
| 12 |
- "tests/**"
|
| 13 |
- "uv.lock"
|
| 14 |
- "pyproject.toml"
|
|
|
|
| 15 |
pull_request:
|
| 16 |
paths:
|
| 17 |
- "src/**"
|
| 18 |
- "tests/**"
|
| 19 |
- "uv.lock"
|
| 20 |
- "pyproject.toml"
|
|
|
|
| 21 |
|
| 22 |
workflow_dispatch:
|
| 23 |
|
|
@@ -26,8 +28,13 @@ permissions:
|
|
| 26 |
|
| 27 |
jobs:
|
| 28 |
run_tests:
|
| 29 |
-
name: Run tests
|
| 30 |
-
runs-on:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
steps:
|
| 33 |
- uses: actions/checkout@v4
|
|
@@ -35,11 +42,11 @@ jobs:
|
|
| 35 |
- name: Install uv
|
| 36 |
uses: astral-sh/setup-uv@v4
|
| 37 |
|
| 38 |
-
- name: Set up Python
|
| 39 |
-
run: uv python install
|
| 40 |
|
| 41 |
- name: Install FastMCP
|
| 42 |
-
run: uv sync --extra
|
| 43 |
|
| 44 |
- name: Run tests
|
| 45 |
run: uv run pytest -vv
|
|
|
|
| 12 |
- "tests/**"
|
| 13 |
- "uv.lock"
|
| 14 |
- "pyproject.toml"
|
| 15 |
+
- ".github/workflows/**"
|
| 16 |
pull_request:
|
| 17 |
paths:
|
| 18 |
- "src/**"
|
| 19 |
- "tests/**"
|
| 20 |
- "uv.lock"
|
| 21 |
- "pyproject.toml"
|
| 22 |
+
- ".github/workflows/**"
|
| 23 |
|
| 24 |
workflow_dispatch:
|
| 25 |
|
|
|
|
| 28 |
|
| 29 |
jobs:
|
| 30 |
run_tests:
|
| 31 |
+
name: "Run tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}"
|
| 32 |
+
runs-on: ${{ matrix.os }}
|
| 33 |
+
strategy:
|
| 34 |
+
matrix:
|
| 35 |
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
| 36 |
+
python-version: ["3.10"]
|
| 37 |
+
fail-fast: false
|
| 38 |
|
| 39 |
steps:
|
| 40 |
- uses: actions/checkout@v4
|
|
|
|
| 42 |
- name: Install uv
|
| 43 |
uses: astral-sh/setup-uv@v4
|
| 44 |
|
| 45 |
+
- name: Set up Python ${{ matrix.python-version }}
|
| 46 |
+
run: uv python install ${{ matrix.python-version }}
|
| 47 |
|
| 48 |
- name: Install FastMCP
|
| 49 |
+
run: uv sync --extra tests
|
| 50 |
|
| 51 |
- name: Run tests
|
| 52 |
run: uv run pytest -vv
|
README.md
CHANGED
|
@@ -464,23 +464,20 @@ FastMCP requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
|
|
| 464 |
|
| 465 |
### Installation
|
| 466 |
|
| 467 |
-
|
| 468 |
|
| 469 |
```bash
|
| 470 |
-
git clone https://github.com/
|
| 471 |
cd fastmcp
|
|
|
|
| 472 |
```
|
| 473 |
|
| 474 |
-
|
| 475 |
|
| 476 |
```bash
|
| 477 |
-
uv
|
| 478 |
-
source .venv/bin/activate
|
| 479 |
-
uv sync --frozen --all-extras --dev
|
| 480 |
```
|
| 481 |
|
| 482 |
-
|
| 483 |
-
|
| 484 |
### Testing
|
| 485 |
|
| 486 |
Please make sure to test any new functionality. Your tests should be simple and atomic and anticipate change rather than cement complex patterns.
|
|
|
|
| 464 |
|
| 465 |
### Installation
|
| 466 |
|
| 467 |
+
For development, we recommend installing FastMCP with development dependencies, which includes various utilities the maintainers find useful.
|
| 468 |
|
| 469 |
```bash
|
| 470 |
+
git clone https://github.com/jlowin/fastmcp.git
|
| 471 |
cd fastmcp
|
| 472 |
+
uv sync --frozen --extra dev
|
| 473 |
```
|
| 474 |
|
| 475 |
+
For running tests only (e.g., in CI), you only need the testing dependencies:
|
| 476 |
|
| 477 |
```bash
|
| 478 |
+
uv sync --frozen --extra tests
|
|
|
|
|
|
|
| 479 |
```
|
| 480 |
|
|
|
|
|
|
|
| 481 |
### Testing
|
| 482 |
|
| 483 |
Please make sure to test any new functionality. Your tests should be simple and atomic and anticipate change rather than cement complex patterns.
|
pyproject.toml
CHANGED
|
@@ -23,16 +23,14 @@ requires = ["hatchling>=1.21.0", "hatch-vcs>=0.4.0"]
|
|
| 23 |
build-backend = "hatchling.build"
|
| 24 |
|
| 25 |
[project.optional-dependencies]
|
| 26 |
-
|
| 27 |
-
"copychat>=0.5.2",
|
| 28 |
-
"ipython>=8.12.3",
|
| 29 |
-
"pdbpp>=0.10.3",
|
| 30 |
"pre-commit",
|
| 31 |
-
"pytest-xdist>=3.6.1",
|
| 32 |
"pytest>=8.3.3",
|
| 33 |
"pytest-asyncio>=0.23.5",
|
|
|
|
| 34 |
"ruff",
|
| 35 |
]
|
|
|
|
| 36 |
|
| 37 |
[tool.pytest.ini_options]
|
| 38 |
asyncio_mode = "auto"
|
|
|
|
| 23 |
build-backend = "hatchling.build"
|
| 24 |
|
| 25 |
[project.optional-dependencies]
|
| 26 |
+
tests = [
|
|
|
|
|
|
|
|
|
|
| 27 |
"pre-commit",
|
|
|
|
| 28 |
"pytest>=8.3.3",
|
| 29 |
"pytest-asyncio>=0.23.5",
|
| 30 |
+
"pytest-xdist>=3.6.1",
|
| 31 |
"ruff",
|
| 32 |
]
|
| 33 |
+
dev = ["fastmcp[tests]", "copychat>=0.5.2", "ipython>=8.12.3", "pdbpp>=0.10.3"]
|
| 34 |
|
| 35 |
[tool.pytest.ini_options]
|
| 36 |
asyncio_mode = "auto"
|
src/fastmcp/resources/base.py
CHANGED
|
@@ -1,34 +1,17 @@
|
|
| 1 |
"""Base classes and interfaces for FastMCP resources."""
|
| 2 |
|
| 3 |
import abc
|
| 4 |
-
from typing import
|
| 5 |
|
| 6 |
from pydantic import (
|
| 7 |
AnyUrl,
|
| 8 |
BaseModel,
|
| 9 |
-
BeforeValidator,
|
| 10 |
ConfigDict,
|
| 11 |
Field,
|
| 12 |
FileUrl,
|
| 13 |
ValidationInfo,
|
| 14 |
field_validator,
|
| 15 |
)
|
| 16 |
-
from pydantic.networks import _BaseUrl # TODO: remove this once pydantic is updated
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
def maybe_cast_str_to_any_url(x) -> AnyUrl:
|
| 20 |
-
if isinstance(x, FileUrl):
|
| 21 |
-
return x
|
| 22 |
-
elif isinstance(x, AnyUrl):
|
| 23 |
-
return x
|
| 24 |
-
elif isinstance(x, str):
|
| 25 |
-
if x.startswith("file://"):
|
| 26 |
-
return FileUrl(x)
|
| 27 |
-
return AnyUrl(x)
|
| 28 |
-
raise ValueError(f"Expected str or AnyUrl, got {type(x)}")
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
LaxAnyUrl = Annotated[_BaseUrl | str, BeforeValidator(maybe_cast_str_to_any_url)]
|
| 32 |
|
| 33 |
|
| 34 |
class Resource(BaseModel, abc.ABC):
|
|
@@ -36,7 +19,8 @@ class Resource(BaseModel, abc.ABC):
|
|
| 36 |
|
| 37 |
model_config = ConfigDict(validate_default=True)
|
| 38 |
|
| 39 |
-
uri:
|
|
|
|
| 40 |
name: str | None = Field(description="Name of the resource", default=None)
|
| 41 |
description: str | None = Field(
|
| 42 |
description="Description of the resource", default=None
|
|
@@ -47,6 +31,15 @@ class Resource(BaseModel, abc.ABC):
|
|
| 47 |
pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
|
| 48 |
)
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
@field_validator("name", mode="before")
|
| 51 |
@classmethod
|
| 52 |
def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
|
|
|
|
| 1 |
"""Base classes and interfaces for FastMCP resources."""
|
| 2 |
|
| 3 |
import abc
|
| 4 |
+
from typing import Union
|
| 5 |
|
| 6 |
from pydantic import (
|
| 7 |
AnyUrl,
|
| 8 |
BaseModel,
|
|
|
|
| 9 |
ConfigDict,
|
| 10 |
Field,
|
| 11 |
FileUrl,
|
| 12 |
ValidationInfo,
|
| 13 |
field_validator,
|
| 14 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
class Resource(BaseModel, abc.ABC):
|
|
|
|
| 19 |
|
| 20 |
model_config = ConfigDict(validate_default=True)
|
| 21 |
|
| 22 |
+
# uri: Annotated[AnyUrl, BeforeValidator(maybe_cast_str_to_any_url)] = Field(
|
| 23 |
+
uri: AnyUrl = Field(default=..., description="URI of the resource")
|
| 24 |
name: str | None = Field(description="Name of the resource", default=None)
|
| 25 |
description: str | None = Field(
|
| 26 |
description="Description of the resource", default=None
|
|
|
|
| 31 |
pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
|
| 32 |
)
|
| 33 |
|
| 34 |
+
@field_validator("uri", mode="before")
|
| 35 |
+
def validate_uri(cls, uri: AnyUrl | str) -> AnyUrl:
|
| 36 |
+
if isinstance(uri, str):
|
| 37 |
+
# AnyUrl doesn't support triple-slashes, but files do ("file:///absolute/path")
|
| 38 |
+
if uri.startswith("file://"):
|
| 39 |
+
return FileUrl(uri)
|
| 40 |
+
return AnyUrl(uri)
|
| 41 |
+
return uri
|
| 42 |
+
|
| 43 |
@field_validator("name", mode="before")
|
| 44 |
@classmethod
|
| 45 |
def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
|