From ff318e655dfc6cf68caaac509f97d5dd9ece0d1a Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Wed, 14 May 2025 13:42:20 -0400 Subject: [PATCH] Add reprs for OpenAPI objects --- src/fastmcp/server/openapi.py | 12 +++++ tests/server/test_openapi.py | 46 +++++++++++++++++++ .../utilities/openapi/test_openapi_fastapi.py | 17 +++++++ 3 files changed, 75 insertions(+) diff --git a/src/fastmcp/server/openapi.py b/src/fastmcp/server/openapi.py index fd46ac3cb..19ee1e42b 100644 --- a/src/fastmcp/server/openapi.py +++ b/src/fastmcp/server/openapi.py @@ -138,6 +138,10 @@ class OpenAPITool(Tool): self._route = route self._timeout = timeout + def __repr__(self) -> str: + """Custom representation to prevent recursion errors when printing.""" + return f"OpenAPITool(name={self.name!r}, method={self._route.method}, path={self._route.path})" + async def _execute_request(self, *args, **kwargs): """Execute the HTTP request based on the route configuration.""" context = kwargs.get("context") @@ -287,6 +291,10 @@ class OpenAPIResource(Resource): self._route = route self._timeout = timeout + def __repr__(self) -> str: + """Custom representation to prevent recursion errors when printing.""" + return f"OpenAPIResource(name={self.name!r}, uri={self.uri!r}, path={self._route.path})" + async def read(self) -> str | bytes: """Fetch the resource data by making an HTTP request.""" try: @@ -397,6 +405,10 @@ class OpenAPIResourceTemplate(ResourceTemplate): self._route = route self._timeout = timeout + def __repr__(self) -> str: + """Custom representation to prevent recursion errors when printing.""" + return f"OpenAPIResourceTemplate(name={self.name!r}, uri_template={self.uri_template!r}, path={self._route.path})" + async def create_resource( self, uri: str, diff --git a/tests/server/test_openapi.py b/tests/server/test_openapi.py index b7a6df16c..ee2c0f741 100644 --- a/tests/server/test_openapi.py +++ b/tests/server/test_openapi.py @@ -1771,3 +1771,49 @@ class TestFastAPIDescriptionPropagation: "name parameter missing from Tool schema in client API" ) # We don't test for the description field content as it may not be consistently propagated + + +class TestReprMethods: + """Tests for the custom __repr__ methods of OpenAPI objects.""" + + async def test_openapi_tool_repr(self, fastmcp_openapi_server: FastMCPOpenAPI): + """Test that OpenAPITool's __repr__ method works without recursion errors.""" + tools = fastmcp_openapi_server._tool_manager.list_tools() + tool = next(iter(tools)) + + # Verify repr doesn't cause recursion and contains expected elements + tool_repr = repr(tool) + assert "OpenAPITool" in tool_repr + assert f"name={tool.name!r}" in tool_repr + assert "method=" in tool_repr + assert "path=" in tool_repr + + async def test_openapi_resource_repr(self, fastmcp_openapi_server: FastMCPOpenAPI): + """Test that OpenAPIResource's __repr__ method works without recursion errors.""" + resources = list( + fastmcp_openapi_server._resource_manager.get_resources().values() + ) + resource = next(iter(resources)) + + # Verify repr doesn't cause recursion and contains expected elements + resource_repr = repr(resource) + assert "OpenAPIResource" in resource_repr + assert f"name={resource.name!r}" in resource_repr + assert "uri=" in resource_repr + assert "path=" in resource_repr + + async def test_openapi_resource_template_repr( + self, fastmcp_openapi_server: FastMCPOpenAPI + ): + """Test that OpenAPIResourceTemplate's __repr__ method works without recursion errors.""" + templates = list( + fastmcp_openapi_server._resource_manager.get_templates().values() + ) + template = next(iter(templates)) + + # Verify repr doesn't cause recursion and contains expected elements + template_repr = repr(template) + assert "OpenAPIResourceTemplate" in template_repr + assert f"name={template.name!r}" in template_repr + assert "uri_template=" in template_repr + assert "path=" in template_repr diff --git a/tests/utilities/openapi/test_openapi_fastapi.py b/tests/utilities/openapi/test_openapi_fastapi.py index 94d0afe6e..3f0ee8b41 100644 --- a/tests/utilities/openapi/test_openapi_fastapi.py +++ b/tests/utilities/openapi/test_openapi_fastapi.py @@ -520,3 +520,20 @@ def test_duplicate_tags_handling(fastapi_server): # We'll test both possibilities to be safe assert "duplicate" in test_route.tags, "Tag 'duplicate' should be present" assert "items" in test_route.tags, "Tag 'items' should be present" + + +def test_repr_http_routes(parsed_routes): + """Test that HTTPRoute objects can be represented without recursion errors.""" + # Test repr on all parsed routes + for route in parsed_routes: + route_repr = repr(route) + + # Verify repr contains essential information + assert route.method in route_repr, f"Method {route.method} missing from repr" + assert route.path in route_repr, f"Path {route.path} missing from repr" + + # If operation_id exists, it should be in the repr + if route.operation_id: + assert route.operation_id in route_repr, ( + f"Operation ID {route.operation_id} missing from repr" + )