mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 14:04:18 +02:00
Refactor Environment to support multiple runtime types (#1673)
This commit is contained in:
parent
6d46192659
commit
ea47d232bc
20 changed files with 480 additions and 407 deletions
|
|
@ -9,9 +9,9 @@ from pydantic import ValidationError
|
|||
|
||||
from fastmcp.utilities.mcp_server_config import (
|
||||
Deployment,
|
||||
Environment,
|
||||
MCPServerConfig,
|
||||
)
|
||||
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
||||
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
|
||||
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ class TestEnvironment:
|
|||
},
|
||||
)
|
||||
|
||||
cmd = config.environment.build_uv_run_command(["fastmcp", "run", "server.py"])
|
||||
cmd = config.environment.build_command(["fastmcp", "run", "server.py"])
|
||||
|
||||
assert cmd[0] == "uv"
|
||||
assert cmd[1] == "run"
|
||||
|
|
@ -263,7 +263,7 @@ class TestMCPServerConfig:
|
|||
assert config.source.path == "server.py"
|
||||
assert config.source.entrypoint is None
|
||||
# Environment and deployment are now always present but empty
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert isinstance(config.environment, UVEnvironment)
|
||||
assert isinstance(config.deployment, Deployment)
|
||||
# Check they have no values set
|
||||
assert not config.environment.needs_uv()
|
||||
|
|
@ -289,7 +289,7 @@ class TestMCPServerConfig:
|
|||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "server.py"
|
||||
assert config.source.entrypoint is None
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert isinstance(config.environment, UVEnvironment)
|
||||
assert isinstance(config.deployment, Deployment)
|
||||
|
||||
def test_from_file(self, tmp_path):
|
||||
|
|
@ -416,7 +416,7 @@ class TestMCPServerConfig:
|
|||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "server.py"
|
||||
# Environment and deployment are now always present but may be empty
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert isinstance(config.environment, UVEnvironment)
|
||||
assert isinstance(config.deployment, Deployment)
|
||||
|
||||
# Only environment with values
|
||||
|
|
@ -434,9 +434,9 @@ class TestMCPServerConfig:
|
|||
config = MCPServerConfig(
|
||||
source={"path": "server.py"}, deployment={"transport": "http"}
|
||||
)
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert isinstance(config.environment, UVEnvironment)
|
||||
assert all(
|
||||
getattr(config.environment, field, None) is None
|
||||
for field in Environment.model_fields
|
||||
for field in UVEnvironment.model_fields
|
||||
)
|
||||
assert config.deployment.transport == "http"
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ class TestPathResolution:
|
|||
|
||||
# Build UV command
|
||||
assert config.environment is not None
|
||||
uv_cmd = config.environment.build_uv_run_command(["fastmcp", "run"])
|
||||
uv_cmd = config.environment.build_command(["fastmcp", "run"])
|
||||
|
||||
# Should include requirements file
|
||||
assert "--with-requirements" in uv_cmd
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from fastmcp.utilities.mcp_server_config import Environment, MCPServerConfig
|
||||
from fastmcp.utilities.mcp_server_config import MCPServerConfig
|
||||
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
||||
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
|
||||
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ class TestMCPServerConfigPrepare:
|
|||
"""Test that prepare() calls both prepare_environment and prepare_source."""
|
||||
config = MCPServerConfig(
|
||||
source=FileSystemSource(path="server.py"),
|
||||
environment=Environment(python="3.10"),
|
||||
environment=UVEnvironment(python="3.10"),
|
||||
)
|
||||
|
||||
await config.prepare()
|
||||
|
|
@ -45,7 +46,7 @@ class TestMCPServerConfigPrepare:
|
|||
"""Test that prepare() with output_dir calls prepare_environment with it."""
|
||||
config = MCPServerConfig(
|
||||
source=FileSystemSource(path="server.py"),
|
||||
environment=Environment(python="3.10"),
|
||||
environment=UVEnvironment(python="3.10"),
|
||||
)
|
||||
|
||||
output_path = Path("/tmp/test-env")
|
||||
|
|
@ -66,7 +67,7 @@ class TestMCPServerConfigPrepare:
|
|||
"""Test that prepare() skips source when skip_source=True."""
|
||||
config = MCPServerConfig(
|
||||
source=FileSystemSource(path="server.py"),
|
||||
environment=Environment(python="3.10"),
|
||||
environment=UVEnvironment(python="3.10"),
|
||||
)
|
||||
|
||||
await config.prepare(skip_source=True)
|
||||
|
|
@ -79,7 +80,7 @@ class TestMCPServerConfigPrepare:
|
|||
new_callable=AsyncMock,
|
||||
)
|
||||
@patch(
|
||||
"fastmcp.utilities.mcp_server_config.v1.mcp_server_config.Environment.prepare",
|
||||
"fastmcp.utilities.mcp_server_config.v1.environments.uv.UVEnvironment.prepare",
|
||||
new_callable=AsyncMock,
|
||||
)
|
||||
async def test_prepare_no_environment_settings(self, mock_env_prepare, mock_src):
|
||||
|
|
@ -104,7 +105,7 @@ class TestEnvironmentPrepare:
|
|||
"""Test that prepare() raises error when uv is not installed."""
|
||||
mock_which.return_value = None
|
||||
|
||||
env = Environment(python="3.10")
|
||||
env = UVEnvironment(python="3.10")
|
||||
|
||||
with pytest.raises(RuntimeError, match="uv is not installed"):
|
||||
await env.prepare(tmp_path / "test-env")
|
||||
|
|
@ -115,7 +116,7 @@ class TestEnvironmentPrepare:
|
|||
"""Test that prepare() does nothing when no settings are configured."""
|
||||
mock_which.return_value = "/usr/bin/uv"
|
||||
|
||||
env = Environment() # No settings
|
||||
env = UVEnvironment() # No settings
|
||||
|
||||
await env.prepare(tmp_path / "test-env")
|
||||
|
||||
|
|
@ -131,7 +132,7 @@ class TestEnvironmentPrepare:
|
|||
returncode=0, stdout="Environment cached", stderr=""
|
||||
)
|
||||
|
||||
env = Environment(python="3.10")
|
||||
env = UVEnvironment(python="3.10")
|
||||
|
||||
await env.prepare(tmp_path / "test-env")
|
||||
|
||||
|
|
@ -150,7 +151,7 @@ class TestEnvironmentPrepare:
|
|||
mock_which.return_value = "/usr/bin/uv"
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
||||
|
||||
env = Environment(dependencies=["numpy", "pandas"])
|
||||
env = UVEnvironment(dependencies=["numpy", "pandas"])
|
||||
|
||||
await env.prepare(tmp_path / "test-env")
|
||||
|
||||
|
|
@ -179,7 +180,7 @@ class TestEnvironmentPrepare:
|
|||
1, ["uv"], stderr="Package not found"
|
||||
)
|
||||
|
||||
env = Environment(python="3.10")
|
||||
env = UVEnvironment(python="3.10")
|
||||
|
||||
with pytest.raises(RuntimeError, match="Failed to initialize project"):
|
||||
await env.prepare(tmp_path / "test-env")
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import pytest
|
|||
from fastmcp.cli.run import load_mcp_server_config
|
||||
from fastmcp.utilities.mcp_server_config import (
|
||||
Deployment,
|
||||
Environment,
|
||||
MCPServerConfig,
|
||||
)
|
||||
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
||||
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
|
||||
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ def test_load_mcp_server_config(sample_config, monkeypatch):
|
|||
assert isinstance(config, MCPServerConfig)
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert isinstance(config.deployment, Deployment)
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert isinstance(config.environment, UVEnvironment)
|
||||
|
||||
# Check source - path is not resolved yet, only during load_server
|
||||
assert config.source.path == "server.py"
|
||||
|
|
@ -262,7 +262,7 @@ def test_environment_config_path_resolution(tmp_path):
|
|||
config = load_mcp_server_config(config_file)
|
||||
|
||||
# Check that UV command is built with resolved paths
|
||||
uv_cmd = config.environment.build_uv_run_command(["fastmcp", "run", "server.py"])
|
||||
uv_cmd = config.environment.build_command(["fastmcp", "run", "server.py"])
|
||||
|
||||
assert "--with-requirements" in uv_cmd
|
||||
assert "--project" in uv_cmd
|
||||
|
|
|
|||
|
|
@ -27,9 +27,8 @@ class TestRunWithUv:
|
|||
cmd = mock_run.call_args[0][0]
|
||||
env = mock_run.call_args.kwargs.get("env", {})
|
||||
|
||||
# With no environment config, the command should be returned unchanged
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -150,9 +149,8 @@ class TestRunWithUv:
|
|||
assert exc_info.value.code == 0
|
||||
|
||||
cmd = mock_run.call_args[0][0]
|
||||
# With no environment config, no uv run prefix
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue