From 70d5c0d652333bf4ed6c0b48992f425919f225ac Mon Sep 17 00:00:00 2001 From: Arya Rizky Date: Tue, 12 May 2026 16:14:57 +0700 Subject: [PATCH] fix: handle custom APIRoute classes without strict_content_type parameter Custom APIRoute subclasses that define an explicit __init__ matching the pre-strict_content_type signature (added in FastAPI 0.118+) crash with TypeError when APIRouter.add_api_route passes strict_content_type as a keyword argument. Fall back gracefully by catching TypeError and retrying without the strict_content_type parameter. This maintains backward compatibility with existing custom route classes that pinned the previously accepted constructor parameters. Fixes #15503 --- fastapi/routing.py | 12 ++++- tests/test_custom_route_class.py | 82 ++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 21a1385a27..ea5de7dba4 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1383,8 +1383,8 @@ class APIRouter(routing.Router): current_generate_unique_id = get_value_or_default( generate_unique_id_function, self.generate_unique_id_function ) - route = route_class( - self.prefix + path, + route_kwargs: dict[str, Any] = dict( + path=self.prefix + path, endpoint=endpoint, response_model=response_model, status_code=status_code, @@ -1414,6 +1414,14 @@ class APIRouter(routing.Router): strict_content_type, self.strict_content_type ), ) + # For custom route classes that define an explicit __init__ without + # strict_content_type (added in FastAPI 0.118+), fall back gracefully + # by omitting the parameter instead of raising TypeError. + try: + route = route_class(**route_kwargs) + except TypeError: + route_kwargs.pop("strict_content_type", None) + route = route_class(**route_kwargs) self.routes.append(route) def api_route( diff --git a/tests/test_custom_route_class.py b/tests/test_custom_route_class.py index 786c1efc31..311bea32d2 100644 --- a/tests/test_custom_route_class.py +++ b/tests/test_custom_route_class.py @@ -119,3 +119,85 @@ def test_openapi_schema(): }, } ) + +from typing import Any, Callable, Sequence + +class LegacyAPIRoute(APIRoute): + """Route subclass with explicit __init__ matching the pre-strict_content_type signature.""" + + def __init__( + self, + path: str, + endpoint: "Callable[..., Any]", + *, + response_model: Any = None, + status_code: "int | None" = None, + tags: "list[str] | None" = None, + dependencies: "Sequence[Any] | None" = None, + summary: "str | None" = None, + description: "str | None" = None, + response_description: str = "Successful Response", + responses: "dict[int | str, dict[str, Any]] | None" = None, + deprecated: "bool | None" = None, + methods: "set[str] | list[str] | None" = None, + operation_id: "str | None" = None, + response_model_include: "Any | None" = None, + response_model_exclude: "Any | None" = None, + response_model_by_alias: bool = True, + response_model_exclude_unset: bool = False, + response_model_exclude_defaults: bool = False, + response_model_exclude_none: bool = False, + include_in_schema: bool = True, + response_class: Any = None, + name: "str | None" = None, + dependency_overrides_provider: "Any | None" = None, + callbacks: "list[Any] | None" = None, + openapi_extra: "dict[str, Any] | None" = None, + generate_unique_id_function: Any = None, + ) -> None: + super().__init__( + path, + endpoint, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=methods, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + dependency_overrides_provider=dependency_overrides_provider, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + +def test_legacy_route_class_with_explicit_init() -> None: + """Custom APIRoute subclasses with explicit constructors (pre-strict_content_type) + should work with APIRouter.add_api_route without raising TypeError.""" + app = FastAPI() + router = APIRouter(route_class=LegacyAPIRoute) + + @router.get("/items") + def read_items(): + return {"items": []} + + app.include_router(router) + + client = TestClient(app) + response = client.get("/items") + assert response.status_code == 200 + assert response.json() == {"items": []}