From ffaf5616b19f6f0f4128209c8b49dbcb41568aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20Nouvertn=C3=A9?= Date: Tue, 23 Jul 2024 09:49:46 +0200 Subject: [PATCH] fix(handlers): Allow returning `Response[None]` from head route handlers (#3641) Allow returning Response[None] from head route handlers --- litestar/handlers/http_handlers/decorators.py | 10 ++++---- .../test_http_handlers/test_head.py | 23 +++++++++++++++++-- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/litestar/handlers/http_handlers/decorators.py b/litestar/handlers/http_handlers/decorators.py index 513674cfd7..593a1a7d19 100644 --- a/litestar/handlers/http_handlers/decorators.py +++ b/litestar/handlers/http_handlers/decorators.py @@ -8,9 +8,9 @@ from litestar.openapi.spec import Operation from litestar.response.file import ASGIFileResponse, File from litestar.types import Empty, TypeDecodersSequence -from litestar.types.builtin_types import NoneType from litestar.utils import is_class_and_subclass +from ._utils import is_empty_response_annotation from .base import HTTPRouteHandler if TYPE_CHECKING: @@ -590,11 +590,11 @@ def _validate_handler_function(self) -> None: super()._validate_handler_function() # we allow here File and File because these have special setting for head responses - return_annotation = self.parsed_fn_signature.return_type.annotation + field_definition = self.parsed_fn_signature.return_type if not ( - return_annotation in {NoneType, None} - or is_class_and_subclass(return_annotation, File) - or is_class_and_subclass(return_annotation, ASGIFileResponse) + is_empty_response_annotation(field_definition) + or is_class_and_subclass(field_definition.annotation, File) + or is_class_and_subclass(field_definition.annotation, ASGIFileResponse) ): raise ImproperlyConfiguredException( f"{self}: Handlers for 'HEAD' requests must not return a value. Either return 'None' or a response type without a body." diff --git a/tests/unit/test_handlers/test_http_handlers/test_head.py b/tests/unit/test_handlers/test_http_handlers/test_head.py index b6c97fa8d5..4f8983c6d6 100644 --- a/tests/unit/test_handlers/test_http_handlers/test_head.py +++ b/tests/unit/test_handlers/test_http_handlers/test_head.py @@ -1,8 +1,9 @@ from pathlib import Path +from typing import Generic, TypeVar import pytest -from litestar import HttpMethod, Litestar, head +from litestar import HttpMethod, Litestar, Response, head from litestar.exceptions import ImproperlyConfiguredException from litestar.response.file import ASGIFileResponse, File from litestar.status_codes import HTTP_200_OK @@ -26,7 +27,25 @@ def test_head_decorator_raises_validation_error_if_body_is_declared() -> None: def handler() -> dict: return {} - handler.on_registration(Litestar()) + Litestar(route_handlers=[handler]) + + +def test_head_decorator_none_response_return_value_allowed() -> None: + # https://github.com/litestar-org/litestar/issues/3640 + T = TypeVar("T") + + class MyResponse(Generic[T], Response[T]): + pass + + @head("/1") + def handler() -> Response[None]: + return Response(None) + + @head("/2") + def handler_subclass() -> MyResponse[None]: + return MyResponse(None) + + Litestar(route_handlers=[handler, handler_subclass]) def test_head_decorator_raises_validation_error_if_method_is_passed() -> None: