mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-28 02:10:38 +02:00
Introduce ResourceResult as canonical resource return type (#2734)
This commit is contained in:
parent
04cf7bc458
commit
648684d2bb
36 changed files with 1190 additions and 555 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
from pydantic import AnyUrl, BaseModel
|
||||
|
||||
from fastmcp.resources.resource import FunctionResource, ResourceContent
|
||||
from fastmcp.resources.resource import FunctionResource, ResourceContent, ResourceResult
|
||||
|
||||
|
||||
class TestFunctionResource:
|
||||
|
|
@ -36,10 +36,16 @@ class TestFunctionResource:
|
|||
name="test",
|
||||
fn=get_data,
|
||||
)
|
||||
# read() returns raw value
|
||||
result = await resource.read()
|
||||
assert isinstance(result, ResourceContent)
|
||||
assert result.content == "Hello, world!"
|
||||
assert result.mime_type == "text/plain"
|
||||
assert result == "Hello, world!"
|
||||
|
||||
# _read() converts to ResourceResult
|
||||
result = await resource._read()
|
||||
assert isinstance(result, ResourceResult)
|
||||
assert len(result.contents) == 1
|
||||
assert result.contents[0].content == "Hello, world!"
|
||||
assert result.contents[0].mime_type == "text/plain"
|
||||
|
||||
async def test_read_binary(self):
|
||||
"""Test reading binary data from a FunctionResource."""
|
||||
|
|
@ -52,12 +58,17 @@ class TestFunctionResource:
|
|||
name="test",
|
||||
fn=get_data,
|
||||
)
|
||||
# read() returns raw value
|
||||
result = await resource.read()
|
||||
assert isinstance(result, ResourceContent)
|
||||
assert result.content == b"Hello, world!"
|
||||
assert result == b"Hello, world!"
|
||||
|
||||
async def test_json_conversion(self):
|
||||
"""Test automatic JSON conversion of non-string results."""
|
||||
# _read() converts to ResourceResult
|
||||
result = await resource._read()
|
||||
assert isinstance(result, ResourceResult)
|
||||
assert result.contents[0].content == b"Hello, world!"
|
||||
|
||||
async def test_dict_return_raises_type_error(self):
|
||||
"""Returning dict from read() raises TypeError - use ResourceResult."""
|
||||
|
||||
def get_data() -> dict:
|
||||
return {"key": "value"}
|
||||
|
|
@ -67,10 +78,13 @@ class TestFunctionResource:
|
|||
name="test",
|
||||
fn=get_data,
|
||||
)
|
||||
# read() returns raw value (no type checking at runtime)
|
||||
result = await resource.read()
|
||||
assert isinstance(result, ResourceContent)
|
||||
assert isinstance(result.content, str)
|
||||
assert '"key":"value"' in result.content
|
||||
assert result == {"key": "value"}
|
||||
|
||||
# _read() raises TypeError - must return str, bytes, or ResourceResult
|
||||
with pytest.raises(TypeError, match="must be str, bytes, or list"):
|
||||
await resource._read()
|
||||
|
||||
async def test_error_handling(self):
|
||||
"""Test error handling in FunctionResource."""
|
||||
|
|
@ -86,8 +100,8 @@ class TestFunctionResource:
|
|||
with pytest.raises(ValueError, match="Test error"):
|
||||
await resource.read()
|
||||
|
||||
async def test_basemodel_conversion(self):
|
||||
"""Test handling of BaseModel types."""
|
||||
async def test_basemodel_return_raises_type_error(self):
|
||||
"""Returning BaseModel from read() raises TypeError - use ResourceResult."""
|
||||
|
||||
class MyModel(BaseModel):
|
||||
name: str
|
||||
|
|
@ -97,12 +111,16 @@ class TestFunctionResource:
|
|||
name="test",
|
||||
fn=lambda: MyModel(name="test"),
|
||||
)
|
||||
result = await resource.read()
|
||||
assert isinstance(result, ResourceContent)
|
||||
assert result.content == '{"name":"test"}'
|
||||
# read() returns raw value (no type checking at runtime)
|
||||
raw_result = await resource.read()
|
||||
assert isinstance(raw_result, MyModel)
|
||||
|
||||
async def test_custom_type_conversion(self):
|
||||
"""Test handling of custom types."""
|
||||
# _read() raises TypeError - must return str, bytes, or ResourceResult
|
||||
with pytest.raises(TypeError, match="must be str, bytes, or list"):
|
||||
await resource._read()
|
||||
|
||||
async def test_custom_type_return_raises_type_error(self):
|
||||
"""Returning custom type from read() raises TypeError - use ResourceResult."""
|
||||
|
||||
class CustomData:
|
||||
def __str__(self) -> str:
|
||||
|
|
@ -116,9 +134,13 @@ class TestFunctionResource:
|
|||
name="test",
|
||||
fn=get_data,
|
||||
)
|
||||
result = await resource.read()
|
||||
assert isinstance(result, ResourceContent)
|
||||
assert isinstance(result.content, str)
|
||||
# read() returns raw value (no type checking at runtime)
|
||||
raw_result = await resource.read()
|
||||
assert isinstance(raw_result, CustomData)
|
||||
|
||||
# _read() raises TypeError - must return str, bytes, or ResourceResult
|
||||
with pytest.raises(TypeError, match="must be str, bytes, or list"):
|
||||
await resource._read()
|
||||
|
||||
async def test_async_read_text(self):
|
||||
"""Test reading text from async FunctionResource."""
|
||||
|
|
@ -131,10 +153,15 @@ class TestFunctionResource:
|
|||
name="test",
|
||||
fn=get_data,
|
||||
)
|
||||
# read() returns raw value
|
||||
result = await resource.read()
|
||||
assert isinstance(result, ResourceContent)
|
||||
assert result.content == "Hello, world!"
|
||||
assert result.mime_type == "text/plain"
|
||||
assert result == "Hello, world!"
|
||||
|
||||
# _read() converts to ResourceResult
|
||||
result = await resource._read()
|
||||
assert isinstance(result, ResourceResult)
|
||||
assert result.contents[0].content == "Hello, world!"
|
||||
assert result.contents[0].mime_type == "text/plain"
|
||||
|
||||
async def test_resource_content_text(self):
|
||||
"""Test returning ResourceContent with text content."""
|
||||
|
|
@ -178,21 +205,11 @@ class TestFunctionResource:
|
|||
assert result.meta is None
|
||||
|
||||
async def test_resource_content_without_meta(self):
|
||||
"""Test returning ResourceContent without meta."""
|
||||
|
||||
def get_data() -> ResourceContent:
|
||||
return ResourceContent(content="plain text")
|
||||
|
||||
resource = FunctionResource(
|
||||
uri=AnyUrl("function://test"),
|
||||
name="test",
|
||||
fn=get_data,
|
||||
)
|
||||
result = await resource.read()
|
||||
assert isinstance(result, ResourceContent)
|
||||
assert result.content == "plain text"
|
||||
assert result.mime_type is None
|
||||
assert result.meta is None
|
||||
"""Test that ResourceContent auto-sets mime_type defaults."""
|
||||
content = ResourceContent(content="plain text")
|
||||
assert content.content == "plain text"
|
||||
assert content.mime_type == "text/plain" # Auto-set for string content
|
||||
assert content.meta is None
|
||||
|
||||
async def test_async_resource_content(self):
|
||||
"""Test async function returning ResourceContent."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue