Merge branch 'main' into windows

This commit is contained in:
Jeremiah Lowin 2024-12-02 20:11:28 -05:00 committed by GitHub
commit f5bc5cea98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 32 additions and 37 deletions

View file

@ -12,12 +12,14 @@ on:
- "tests/**"
- "uv.lock"
- "pyproject.toml"
- ".github/workflows/**"
pull_request:
paths:
- "src/**"
- "tests/**"
- "uv.lock"
- "pyproject.toml"
- ".github/workflows/**"
workflow_dispatch:
@ -26,8 +28,13 @@ permissions:
jobs:
run_tests:
name: Run tests
runs-on: ubuntu-latest
name: "Run tests: 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,11 +42,11 @@ 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
run: uv sync --extra tests
- name: Run tests
run: uv run pytest -vv

View file

@ -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.

View file

@ -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"

View file

@ -1,34 +1,17 @@
"""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 +19,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 +31,15 @@ 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):
# AnyUrl doesn't support triple-slashes, but files do ("file:///absolute/path")
if uri.startswith("file://"):
return FileUrl(uri)
return AnyUrl(uri)
return uri
@field_validator("name", mode="before")
@classmethod
def set_default_name(cls, name: str | None, info: ValidationInfo) -> str: