mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 21:44:18 +02:00
Avoid propagating logs (#1042)
* Avoid propagating logs * Set up caplog for non-root loggers * Update tests
This commit is contained in:
parent
f7dfd5be9c
commit
228425e309
8 changed files with 65 additions and 30 deletions
|
|
@ -50,3 +50,6 @@ def configure_logging(
|
|||
logger.removeHandler(hdlr)
|
||||
|
||||
logger.addHandler(handler)
|
||||
|
||||
# Don't propagate to the root logger
|
||||
logger.propagate = False
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import multiprocessing
|
||||
import socket
|
||||
import time
|
||||
|
|
@ -129,3 +130,15 @@ def run_server_in_process(
|
|||
proc.join(timeout=2)
|
||||
if proc.is_alive():
|
||||
raise RuntimeError("Server process failed to terminate even after kill")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def caplog_for_fastmcp(caplog):
|
||||
"""Context manager to capture logs from FastMCP loggers even when propagation is disabled."""
|
||||
caplog.clear()
|
||||
logger = logging.getLogger("FastMCP")
|
||||
logger.addHandler(caplog.handler)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
logger.removeHandler(caplog.handler)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import pytest
|
|||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.settings import Settings
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
|
||||
# reset deprecation warnings for this module
|
||||
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
|
||||
|
|
@ -313,7 +314,8 @@ class TestDeprecatedEnvironmentVariables:
|
|||
try:
|
||||
os.environ[env_var_name] = "192.168.1.1"
|
||||
|
||||
settings = Settings()
|
||||
with caplog_for_fastmcp(caplog):
|
||||
settings = Settings()
|
||||
|
||||
# Check that a warning was logged
|
||||
assert any(
|
||||
|
|
@ -341,8 +343,9 @@ class TestDeprecatedSettingsProperty:
|
|||
"""Test that accessing fastmcp.settings.settings logs a deprecation warning."""
|
||||
from fastmcp import settings
|
||||
|
||||
# Access the deprecated property
|
||||
deprecated_settings = settings.settings
|
||||
with caplog_for_fastmcp(caplog):
|
||||
# Access the deprecated property
|
||||
deprecated_settings = settings.settings
|
||||
|
||||
# Check that a warning was logged
|
||||
assert any(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from fastmcp.exceptions import NotFoundError, PromptError
|
|||
from fastmcp.prompts import Prompt
|
||||
from fastmcp.prompts.prompt import FunctionPrompt, PromptMessage, TextContent
|
||||
from fastmcp.prompts.prompt_manager import PromptManager
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
|
||||
|
||||
class TestPromptManager:
|
||||
|
|
@ -31,7 +32,10 @@ class TestPromptManager:
|
|||
manager = PromptManager(duplicate_behavior="warn")
|
||||
prompt = Prompt.from_function(fn)
|
||||
first = manager.add_prompt(prompt)
|
||||
second = manager.add_prompt(prompt)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
second = manager.add_prompt(prompt)
|
||||
|
||||
assert first == second
|
||||
assert "Prompt already exists" in caplog.text
|
||||
|
||||
|
|
@ -58,7 +62,9 @@ class TestPromptManager:
|
|||
prompt = Prompt.from_function(test_fn, name="test_prompt")
|
||||
|
||||
manager.add_prompt(prompt)
|
||||
manager.add_prompt(prompt)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
manager.add_prompt(prompt)
|
||||
|
||||
assert "Prompt already exists: test_prompt" in caplog.text
|
||||
# Should have the prompt
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from fastmcp.resources import (
|
|||
ResourceTemplate,
|
||||
)
|
||||
from fastmcp.resources.resource import FunctionResource
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -78,7 +79,9 @@ class TestResourceManager:
|
|||
)
|
||||
|
||||
manager.add_resource(resource)
|
||||
manager.add_resource(resource)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
manager.add_resource(resource)
|
||||
|
||||
assert "Resource already exists" in caplog.text
|
||||
# Should have the resource
|
||||
|
|
@ -181,7 +184,9 @@ class TestResourceManager:
|
|||
)
|
||||
|
||||
manager.add_template(template)
|
||||
manager.add_template(template)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
manager.add_template(template)
|
||||
|
||||
assert "Template already exists" in caplog.text
|
||||
# Should have the template
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from fastmcp import FastMCP
|
|||
from fastmcp.client import Client
|
||||
from fastmcp.client.transports import FastMCPTransport, SSETransport
|
||||
from fastmcp.server.proxy import FastMCPProxy
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
|
||||
|
||||
class TestBasicMount:
|
||||
|
|
@ -290,25 +291,26 @@ class TestMultipleServerMount:
|
|||
main_app.mount(unreachable_proxy, "unreachable")
|
||||
|
||||
# All object types should work from working server despite unreachable proxy
|
||||
async with Client(main_app) as client:
|
||||
# Test tools
|
||||
tools = await client.list_tools()
|
||||
tool_names = [tool.name for tool in tools]
|
||||
assert "working_working_tool" in tool_names
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(main_app) as client:
|
||||
# Test tools
|
||||
tools = await client.list_tools()
|
||||
tool_names = [tool.name for tool in tools]
|
||||
assert "working_working_tool" in tool_names
|
||||
|
||||
# Test calling a tool
|
||||
result = await client.call_tool("working_working_tool", {})
|
||||
assert result.data == "Working tool"
|
||||
# Test calling a tool
|
||||
result = await client.call_tool("working_working_tool", {})
|
||||
assert result.data == "Working tool"
|
||||
|
||||
# Test resources
|
||||
resources = await client.list_resources()
|
||||
resource_uris = [str(resource.uri) for resource in resources]
|
||||
assert "working://working/data" in resource_uris
|
||||
# Test resources
|
||||
resources = await client.list_resources()
|
||||
resource_uris = [str(resource.uri) for resource in resources]
|
||||
assert "working://working/data" in resource_uris
|
||||
|
||||
# Test prompts
|
||||
prompts = await client.list_prompts()
|
||||
prompt_names = [prompt.name for prompt in prompts]
|
||||
assert "working_working_prompt" in prompt_names
|
||||
# Test prompts
|
||||
prompts = await client.list_prompts()
|
||||
prompt_names = [prompt.name for prompt in prompts]
|
||||
assert "working_working_prompt" in prompt_names
|
||||
|
||||
# Verify that warnings were logged for the unreachable server
|
||||
warning_messages = [
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from typing_extensions import TypedDict
|
|||
|
||||
from fastmcp.tools.tool import Tool, _convert_to_content
|
||||
from fastmcp.utilities.json_schema import compress_schema
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
from fastmcp.utilities.types import Audio, File, Image
|
||||
|
||||
|
||||
|
|
@ -1007,12 +1008,11 @@ class TestConvertResultToContent:
|
|||
|
||||
def test_custom_serializer_error_fallback(self, caplog):
|
||||
"""Test that if a custom serializer fails, it falls back to the default."""
|
||||
import logging
|
||||
|
||||
def custom_serializer_that_fails(data):
|
||||
raise ValueError("Serialization failed")
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = _convert_to_content(
|
||||
{"a": 1}, serializer=custom_serializer_that_fails
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from fastmcp import Context, FastMCP
|
|||
from fastmcp.exceptions import NotFoundError, ToolError
|
||||
from fastmcp.tools import FunctionTool, ToolManager
|
||||
from fastmcp.tools.tool import Tool
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
from fastmcp.utilities.types import Image
|
||||
|
||||
|
||||
|
|
@ -178,8 +179,10 @@ class TestAddTools:
|
|||
|
||||
tool1 = Tool.from_function(test_fn, name="test_tool")
|
||||
manager.add_tool(tool1)
|
||||
tool2 = Tool.from_function(test_fn, name="test_tool")
|
||||
manager.add_tool(tool2)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
tool2 = Tool.from_function(test_fn, name="test_tool")
|
||||
manager.add_tool(tool2)
|
||||
|
||||
assert "Tool already exists: test_tool" in caplog.text
|
||||
# Should have the tool
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue