fastmcp/tests/resources/test_resource_template.py
2024-11-30 16:53:18 -05:00

238 lines
7 KiB
Python

import pytest
from fastmcp.resources import ResourceTemplate, FunctionResource
class TestResourceTemplate:
"""Test ResourceTemplate functionality."""
def test_template_from_function(self):
"""Test creating a template from a function."""
def weather(city: str, units: str = "metric") -> str:
return f"Weather in {city} ({units})"
template = ResourceTemplate.from_function(
fn=weather,
uri_template="weather://{city}/current",
name="weather",
description="Get current weather",
)
assert template.name == "weather"
assert template.uri_template == "weather://{city}/current"
assert template.mime_type == "text/plain"
assert "city" in template.parameters["properties"]
def test_template_from_lambda_error(self):
"""Test error when creating template from lambda without name."""
with pytest.raises(
ValueError, match="You must provide a name for lambda functions"
):
ResourceTemplate.from_function(
fn=lambda x: x,
uri_template="test://{x}",
)
def test_template_matches(self):
"""Test URI matching against template."""
def dummy(x: str) -> str:
return x
template = ResourceTemplate.from_function(
fn=dummy,
uri_template="test://{x}/value",
name="test",
)
# Test matching URI
params = template.matches("test://hello/value")
assert params == {"x": "hello"}
# Test non-matching URI
params = template.matches("test://hello/wrong")
assert params is None
async def test_create_text_resource(self):
"""Test creating a text resource from template."""
def greet(name: str) -> str:
return f"Hello, {name}!"
template = ResourceTemplate.from_function(
fn=greet,
uri_template="greet://{name}",
name="greeter",
)
resource = await template.create_resource(
"greet://world",
{"name": "world"},
)
assert isinstance(resource, FunctionResource)
content = await resource.read()
assert content == "Hello, world!"
async def test_create_binary_resource(self):
"""Test creating a binary resource from template."""
def get_bytes(value: str) -> bytes:
return value.encode()
template = ResourceTemplate.from_function(
fn=get_bytes,
uri_template="bytes://{value}",
name="bytes",
)
resource = await template.create_resource(
"bytes://test",
{"value": "test"},
)
assert isinstance(resource, FunctionResource)
content = await resource.read()
assert content == b"test"
async def test_json_conversion(self):
"""Test automatic JSON conversion of non-string/bytes results."""
def get_data(key: str) -> dict:
return {"key": key, "value": 123}
template = ResourceTemplate.from_function(
fn=get_data,
uri_template="data://{key}",
name="data",
)
resource = await template.create_resource(
"data://test",
{"key": "test"},
)
assert isinstance(resource, FunctionResource)
content = await resource.read()
assert '"key": "test"' in content
assert '"value": 123' in content
async def test_template_error(self):
"""Test error handling in template resource creation."""
def failing_func(x: str) -> str:
raise ValueError("Test error")
template = ResourceTemplate.from_function(
fn=failing_func,
uri_template="fail://{x}",
name="fail",
)
with pytest.raises(ValueError, match="Error creating resource from template"):
await template.create_resource("fail://test", {"x": "test"})
async def test_async_text_resource(self):
"""Test creating a text resource from async function."""
async def greet(name: str) -> str:
return f"Hello, {name}!"
template = ResourceTemplate.from_function(
fn=greet,
uri_template="greet://{name}",
name="greeter",
)
resource = await template.create_resource(
"greet://world",
{"name": "world"},
)
assert isinstance(resource, FunctionResource)
content = await resource.read()
assert content == "Hello, world!"
async def test_async_binary_resource(self):
"""Test creating a binary resource from async function."""
async def get_bytes(value: str) -> bytes:
return value.encode()
template = ResourceTemplate.from_function(
fn=get_bytes,
uri_template="bytes://{value}",
name="bytes",
)
resource = await template.create_resource(
"bytes://test",
{"value": "test"},
)
assert isinstance(resource, FunctionResource)
content = await resource.read()
assert content == b"test"
async def test_async_json_conversion(self):
"""Test automatic JSON conversion of async results."""
async def get_data(key: str) -> dict:
return {"key": key, "value": 123}
template = ResourceTemplate.from_function(
fn=get_data,
uri_template="data://{key}",
name="data",
)
resource = await template.create_resource(
"data://test",
{"key": "test"},
)
assert isinstance(resource, FunctionResource)
content = await resource.read()
assert '"key": "test"' in content
assert '"value": 123' in content
async def test_async_error(self):
"""Test error handling in async template."""
async def failing_func(x: str) -> str:
raise ValueError("Test error")
template = ResourceTemplate.from_function(
fn=failing_func,
uri_template="fail://{x}",
name="fail",
)
with pytest.raises(
ValueError, match="Error creating resource from template: Test error"
):
await template.create_resource("fail://test", {"x": "test"})
async def test_sync_returning_coroutine(self):
"""Test sync function that returns a coroutine."""
async def async_helper(name: str) -> str:
return f"Hello, {name}!"
def get_greeting(name: str) -> str:
return async_helper(name) # Returns coroutine
template = ResourceTemplate.from_function(
fn=get_greeting,
uri_template="greet://{name}",
name="greeter",
)
resource = await template.create_resource(
"greet://world",
{"name": "world"},
)
assert isinstance(resource, FunctionResource)
content = await resource.read()
assert content == "Hello, world!"