diff --git a/README.md b/README.md index 85528a148e..54ccf93df2 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ While supporting function based route handlers, Starlite also supports and promo controllers: ```python title="my_app/controllers/user.py" -from typing import List, Optional +from typing import List, Optional, NoReturn from pydantic import UUID4 from starlite import Controller, Partial, get, post, put, patch, delete @@ -140,7 +140,7 @@ class UserController(Controller): ... @delete(path="/{user_id:uuid}") - async def delete_user(self, user_id: UUID4) -> User: + async def delete_user(self, user_id: UUID4) -> NoReturn: ... ``` diff --git a/docs/index.md b/docs/index.md index b09cdfbd20..7f27b95671 100644 --- a/docs/index.md +++ b/docs/index.md @@ -102,7 +102,7 @@ class User: **Define a Controller** for your data model: ```python title="my_app/controllers/user.py" -from typing import List +from typing import List, NoReturn from pydantic import UUID4 from starlite import Controller, Partial, get, post, put, patch, delete @@ -134,7 +134,7 @@ class UserController(Controller): ... @delete(path="/{user_id:uuid}") - async def delete_user(self, user_id: UUID4) -> User: + async def delete_user(self, user_id: UUID4) -> NoReturn: ... ``` diff --git a/docs/usage/1-routing/3-controllers.md b/docs/usage/1-routing/3-controllers.md index 81e7855639..db915c1b6a 100644 --- a/docs/usage/1-routing/3-controllers.md +++ b/docs/usage/1-routing/3-controllers.md @@ -9,6 +9,7 @@ from pydantic import BaseModel, UUID4 from starlite.controller import Controller from starlite.handlers import get, post, patch, delete from starlite.types import Partial +from typing import NoReturn class UserOrder(BaseModel): @@ -34,7 +35,7 @@ class UserOrderController(Controller): ... @delete(path="/{order_id:uuid}") - async def delete_user_order(self, order_id: UUID4) -> UserOrder: + async def delete_user_order(self, order_id: UUID4) -> NoReturn: ... ``` diff --git a/docs/usage/5-responses/2-status-codes.md b/docs/usage/5-responses/2-status-codes.md index b4a7c78694..8938dac733 100644 --- a/docs/usage/5-responses/2-status-codes.md +++ b/docs/usage/5-responses/2-status-codes.md @@ -24,6 +24,11 @@ If `status_code` is not set by the user, the following defaults are used: - DELETE: 204 (No Content) - GET, PATCH, PUT: 200 (Ok) + +!!! important + For status codes < 100 or 204, 304 statuses, no response body is allowed. If you specify a return annotation other + than `typing.NoReturn` or `None` in those cases, an `ImproperlyConfiguredException` will be raised. + !!! note When using the `route` decorator with multiple http methods, the default status code is `200`. diff --git a/poetry.lock b/poetry.lock index d8340deef8..aa3f097cc4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -479,7 +479,7 @@ uvloop = ["uvloop (>=0.12.0)"] [[package]] name = "picologging" -version = "0.7.0" +version = "0.7.1" description = "A fast and lightweight logging library for Python" category = "main" optional = false @@ -557,7 +557,7 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pydantic-factories" -version = "1.5.1" +version = "1.5.2" description = "Mock data generation for pydantic based models" category = "main" optional = false @@ -1394,36 +1394,36 @@ piccolo = [ {file = "piccolo-0.82.0.tar.gz", hash = "sha256:1a61bda0c291b98b79f2a05706dbf533a155edc80647dde1f9fc5d0634191a5c"}, ] picologging = [ - {file = "picologging-0.7.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:c6732fb1acd1356feaa3f61f6558526d02c70c6f1be4fc7d3d75ecc3677fb053"}, - {file = "picologging-0.7.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1590228a4c18847d1a1026e6eb4c8bafc9c8c20af3968b15cb5114d760e22d8d"}, - {file = "picologging-0.7.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fb651541702b28a00f207f2b9d4531aab54076d57534511b31a8e32a61f2708"}, - {file = "picologging-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f494d85ea7dc7512a0f93b6b70fd13681b384fcaa2be447314a9d01eaf04d03e"}, - {file = "picologging-0.7.0-cp310-cp310-win32.whl", hash = "sha256:aaae7e796ee2c6a936944a1e99b03a1d316d0a0b01acdf768db0c6d76a4d7344"}, - {file = "picologging-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:212bd677da13cf6dc7febd9819d9672156987f699cc0bdbf5bd1a1ecceaca657"}, - {file = "picologging-0.7.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:066b3c47443753b44e724aefa9a34dced9187c4f5ca24feddac20c31cee97e2b"}, - {file = "picologging-0.7.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:5c3ed93961c57c7bf9b608ddb3c7fb13ea02d4c63df1cec4ee8d6a2539405ed9"}, - {file = "picologging-0.7.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8f4c2d05a0c0a6a90399620a4d53247e143329547421878fa09d789ba1f1916"}, - {file = "picologging-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6992df9b8bf6a98fb59a98cdf4a646f2f4fb42f172e964a884fc1ba4d5432d"}, - {file = "picologging-0.7.0-cp311-cp311-win32.whl", hash = "sha256:c81d29e9561c234a00eb1b64945a7ac1f3fbddfa0783ff3d617d0b9003a12566"}, - {file = "picologging-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:6c985811ff162b429ac8c3d7e5eff20cde67c6bd621a37156f9ede67c331a63d"}, - {file = "picologging-0.7.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d0cee81f1824a5b228a74726805184963f555e4318894720530f2eb01563b6b1"}, - {file = "picologging-0.7.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:638a3201640e232181366da7fbf5fdc67114751f15212d4ea5a8d517d5d91a66"}, - {file = "picologging-0.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d22a34cfe13486ed57d98b14ee203c4e65afeccc2dedd36de833d4ddac88eadc"}, - {file = "picologging-0.7.0-cp37-cp37m-win32.whl", hash = "sha256:dd338fcf7f280344fd83e58268821bf0e93b7f9ec7b9ef1d08866d7eff974427"}, - {file = "picologging-0.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3fe7f359316d18d23f061b00c35faf599942fbeb4eb9097d605d9358c1153210"}, - {file = "picologging-0.7.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:279d17d11d8978ac9221b678882c551bfd93071e9d5724a0e0c054229ae21d92"}, - {file = "picologging-0.7.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:cd9ae49f3f0367488d030f6d7c063354e780118401983f4ec556e91ce18f1668"}, - {file = "picologging-0.7.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfedef2ea209222a5072e2fb329f86625eb8b91b52287104fa098a056634acd3"}, - {file = "picologging-0.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:144806fed6318fd346299b972a4856f8f0fcd22912b3bab24bba38e30e9eabd9"}, - {file = "picologging-0.7.0-cp38-cp38-win32.whl", hash = "sha256:19ff4d07a6e563708594e93fdec0646e9e2006d81c5559b1c2b14c71e33d4d6f"}, - {file = "picologging-0.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:016376455062cf40d58e85f9547bb5771a222b76ac915c7beeaa00d94f1f6b95"}, - {file = "picologging-0.7.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:80817075d712061d5be2f4a9d4ec2eafd2a0c34f2bf64b2a3dd57bdc10b2f8de"}, - {file = "picologging-0.7.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:64a3710964d65b9347d54ff3c517ce6cc7bffb1707d9f0296f8631ff764efe73"}, - {file = "picologging-0.7.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29c9408846eec7562a5ce8364ce89a746d0b09ed527499b7919715a0202fedae"}, - {file = "picologging-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:503d97747543840063537caa6047b1fd994f05544802a7d736a0873fb1c48348"}, - {file = "picologging-0.7.0-cp39-cp39-win32.whl", hash = "sha256:15fdf28a75b652f850c1089b3463a4a092c46180de2a5ef114e07b9dfea680fc"}, - {file = "picologging-0.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae20265fb7fb952bfb783cdc7ec1438d078cbbf4d3915ce81d1cda2a515a8620"}, - {file = "picologging-0.7.0.tar.gz", hash = "sha256:720788043b677a49aea47fae8013988bbc1af2cd0df05e884b3e285f71423e18"}, + {file = "picologging-0.7.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:0a55e7c464aa23713d9045f5607c6d2409b90c4ea72faa050d5eba50d23a1505"}, + {file = "picologging-0.7.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:73217f9086d98df881cbeb70cadbee4a7b635ad6b7176c80b4506fbc02618807"}, + {file = "picologging-0.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c28f420d9a00e623309ac5f7b678509559db2392cb755649fd06254e86e65197"}, + {file = "picologging-0.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a462d1dd74409086da5276125ffb6c202d851f613b1c152e8a03ea01b01b8f86"}, + {file = "picologging-0.7.1-cp310-cp310-win32.whl", hash = "sha256:4bc135b5b5db62f1c9e7076e3b6c56cd90682e96f91425dd9fd5a1b9c29a41d8"}, + {file = "picologging-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:d6e9afcd4ffb982ff7edd7e17af9111f26784c23b6189e62c7cc6716fd322c18"}, + {file = "picologging-0.7.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:b77e8ca63ee17bb2f2089716b5a21165fe62288729318970076e93cbc4ef75b6"}, + {file = "picologging-0.7.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:aba55969b5daf5bfacc423444f452a9b1a371107c0b99537c3b3d94eafb14c66"}, + {file = "picologging-0.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f067374edfbbca696e1c8c7a7ec07f62e9b24544f8c6904d5a77848f1a50bab7"}, + {file = "picologging-0.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea56636a09684fa1cd704ec259593c17053cb35b916d2e4034fba5d3c113dfd4"}, + {file = "picologging-0.7.1-cp311-cp311-win32.whl", hash = "sha256:bf7275eca5d12a26246dde013577f43a1a10f728005531c59f534f5417203514"}, + {file = "picologging-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:b84479bf0a15e1bdff49e5ce2b31a4e0854d8b00d3511496411b17670f4cd140"}, + {file = "picologging-0.7.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:9107037d30bf21735e74347f82f7cd2d2881d006462a59ef719481cc3cc1af1b"}, + {file = "picologging-0.7.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5125448869bf63c2ae5dc145adca535d027795be6269de805b6546351c4668dd"}, + {file = "picologging-0.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e77855d8e1ff936e7155649412705184ee268f11d91dfa7a297e527d6c2f1a"}, + {file = "picologging-0.7.1-cp37-cp37m-win32.whl", hash = "sha256:c2c9faf90b93637f1330bdf7b68eacb76756c3eb89cc21f88d5f10c98167db81"}, + {file = "picologging-0.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:71df49376ec8beeec6b1d1daabe7d1e3019e62a8328d8fb6029b03b0c392a0fe"}, + {file = "picologging-0.7.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:ba5d219cd7513ce2070b5f849f01800be3fe5a433377e419298ae4faf34b3dcc"}, + {file = "picologging-0.7.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:39a7c98cf2ae044f89ed071796aa96f5759f466cace935d74099d936e9fc9b93"}, + {file = "picologging-0.7.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae8b90cb128a9c701e5662a41ae0ea8a98f6de8015bb61ba667cc0bdb6b4da65"}, + {file = "picologging-0.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77122684a6ad2122ef78f61b5ad26d8046a70cc3ac939b90215c3c93b8bf3ee0"}, + {file = "picologging-0.7.1-cp38-cp38-win32.whl", hash = "sha256:11f169f76f4b1a51201b15496e7655fea20d6f82ab7699452f0c38132ac3dc86"}, + {file = "picologging-0.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:556f2e65ec43841d6483b116e7d4ba1c8b38de72b7f1b396e80483cbf561e81e"}, + {file = "picologging-0.7.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:608c7a5ee632f915d3a1e4f77ce9abb61a951066ff737ff3ba1e554b1294f851"}, + {file = "picologging-0.7.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:f052d63433fa024db0371e891c532f68b63ea5a68b53edcd9d96f45342a571c6"}, + {file = "picologging-0.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53cd306767655ed17b31deac0e8cb601a1768c3dd92eae1cf2d48e651dba18a6"}, + {file = "picologging-0.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00a3db4fcdb50be7563c4562ad464de753d25b17a73ed4ae66cd91dd1399908e"}, + {file = "picologging-0.7.1-cp39-cp39-win32.whl", hash = "sha256:20c78f9def1c027eaf86b6eef52606667f7494bd06a7e73cfe8bac5c59c3ef3c"}, + {file = "picologging-0.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:c909cec6159aa55654809d89bae63f1b4f9d66188f0ba9ae0910c745ee8d2d1b"}, + {file = "picologging-0.7.1.tar.gz", hash = "sha256:e20951804501fca398f13cb0185c3a7be328228fffa6cd8c81f9585d11b3d588"}, ] platformdirs = [ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, @@ -1479,8 +1479,8 @@ pydantic = [ {file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"}, ] pydantic-factories = [ - {file = "pydantic-factories-1.5.1.tar.gz", hash = "sha256:16e5aaff78e751d1c560c40af7d01f85281d2fa7abf22b62fdfa1417a4faa501"}, - {file = "pydantic_factories-1.5.1-py3-none-any.whl", hash = "sha256:9f03f71ff8be3a168578feccb2f239f54b6cca396545197884859a8ff4f3cf71"}, + {file = "pydantic-factories-1.5.2.tar.gz", hash = "sha256:6a1430722fed03f2dee8d808107c9edf401b81964ea2ed9e3ffc3281c0f8b52f"}, + {file = "pydantic_factories-1.5.2-py3-none-any.whl", hash = "sha256:5e04e1c447d65e090f2a937683133905d7fc63a016e43b7ccf84f40e5abbc079"}, ] pydantic-openapi-schema = [ {file = "pydantic-openapi-schema-1.0.0.tar.gz", hash = "sha256:28cb8b52e18c66e04215ac3e150dd6f9ea8d5075069f08aaff8e1a1290e17f89"}, diff --git a/starlite/handlers/http.py b/starlite/handlers/http.py index 7122aa47c7..6915547f9f 100644 --- a/starlite/handlers/http.py +++ b/starlite/handlers/http.py @@ -2,11 +2,16 @@ from contextlib import suppress from enum import Enum from inspect import Signature, isawaitable, isclass -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, NoReturn, Optional, Type, Union, cast from pydantic import validate_arguments from starlette.responses import Response as StarletteResponse -from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT +from starlette.status import ( + HTTP_200_OK, + HTTP_201_CREATED, + HTTP_204_NO_CONTENT, + HTTP_304_NOT_MODIFIED, +) from starlite.constants import REDIRECT_STATUS_CODES from starlite.datastructures import ( @@ -407,9 +412,16 @@ def _validate_handler_function(self) -> None: signature = Signature.from_callable(cast("AnyCallable", self.fn)) return_annotation = signature.return_annotation if return_annotation is Signature.empty: - raise ValidationException( + raise ImproperlyConfiguredException( "A return value of a route handler function should be type annotated." - "If your function doesn't return a value or returns None, annotate it as returning None." + "If your function doesn't return a value or returns None, annotate it as returning 'NoReturn' or 'None' respectively." + ) + if ( + self.status_code < 200 or self.status_code in {HTTP_204_NO_CONTENT, HTTP_304_NOT_MODIFIED} + ) and return_annotation not in [NoReturn, None]: + raise ImproperlyConfiguredException( + "A status code 204, 304 or in the range below 200 does not support a response body." + "If the function should return a value, change the route handler status code to an appropriate value.", ) if isclass(return_annotation): with suppress(TypeError): diff --git a/starlite/response.py b/starlite/response.py index 6d28b7c0e0..7095b27672 100644 --- a/starlite/response.py +++ b/starlite/response.py @@ -4,6 +4,7 @@ Dict, Generic, List, + NoReturn, Optional, TypeVar, Union, @@ -15,7 +16,7 @@ from pydantic import BaseModel from pydantic_openapi_schema.v3_1_0.open_api import OpenAPI from starlette.responses import Response as StarletteResponse -from starlette.status import HTTP_204_NO_CONTENT +from starlette.status import HTTP_204_NO_CONTENT, HTTP_304_NOT_MODIFIED from starlite.enums import MediaType, OpenAPIMediaType from starlite.exceptions import ImproperlyConfiguredException @@ -87,7 +88,11 @@ def render(self, content: Any) -> bytes: An encoded bytes string """ try: - if content is None and self.status_code == HTTP_204_NO_CONTENT: + if ( + content is None + or content is NoReturn + and (self.status_code < 100 or self.status_code in {HTTP_204_NO_CONTENT, HTTP_304_NOT_MODIFIED}) + ): return b"" if self.media_type == MediaType.JSON: return dumps(content, default=self.serializer, option=OPT_SERIALIZE_NUMPY | OPT_OMIT_MICROSECONDS) diff --git a/tests/handlers/http/test_delete.py b/tests/handlers/http/test_delete.py index 09de5c3995..0d4d50807e 100644 --- a/tests/handlers/http/test_delete.py +++ b/tests/handlers/http/test_delete.py @@ -1,10 +1,15 @@ +from typing import Any, NoReturn + +import pytest + from starlite import delete from starlite.testing import create_test_client -def test_handler_return_none_and_204_status_response_empty() -> None: +@pytest.mark.parametrize("return_annotation", (None, NoReturn)) +def test_handler_return_none_and_204_status_response_empty(return_annotation: Any) -> None: @delete(path="/") - async def route() -> None: + async def route() -> return_annotation: # type: ignore[valid-type] return None with create_test_client(route_handlers=[route]) as client: diff --git a/tests/handlers/http/test_validations.py b/tests/handlers/http/test_validations.py index 774b6c73d5..1164bf234f 100644 --- a/tests/handlers/http/test_validations.py +++ b/tests/handlers/http/test_validations.py @@ -1,6 +1,11 @@ import pytest from pydantic import ValidationError -from starlette.status import HTTP_200_OK, HTTP_307_TEMPORARY_REDIRECT +from starlette.status import ( + HTTP_100_CONTINUE, + HTTP_200_OK, + HTTP_304_NOT_MODIFIED, + HTTP_307_TEMPORARY_REDIRECT, +) from starlite import ( File, @@ -9,6 +14,7 @@ Redirect, Response, WebSocket, + delete, get, route, ) @@ -49,7 +55,7 @@ class SpecialResponse(Response): @pytest.mark.asyncio() async def test_function_validation() -> None: - with pytest.raises(ValidationException): + with pytest.raises(ImproperlyConfiguredException): @get(path="/") def method_with_no_annotation(): # type: ignore @@ -61,6 +67,24 @@ def method_with_no_annotation(): # type: ignore def redirect_method_without_proper_status() -> Redirect: pass + with pytest.raises(ImproperlyConfiguredException): + + @delete(path="/") + def method_with_no_content() -> dict: + pass + + with pytest.raises(ImproperlyConfiguredException): + + @get(path="/", status_code=HTTP_304_NOT_MODIFIED) + def method_with_not_modified() -> dict: + pass + + with pytest.raises(ImproperlyConfiguredException): + + @get(path="/", status_code=HTTP_100_CONTINUE) + def method_with_status_lower_than_200() -> dict: + pass + @get(path="/", status_code=HTTP_307_TEMPORARY_REDIRECT) def redirect_method() -> Redirect: return Redirect("/test") # type: ignore diff --git a/tests/test_controller.py b/tests/test_controller.py index 6dfc38c7f4..3e2cf860aa 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -1,4 +1,4 @@ -from typing import Type, Union +from typing import Any, NoReturn, Type, Union import pytest from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT @@ -10,19 +10,20 @@ @pytest.mark.parametrize( - "decorator, http_method, expected_status_code", + "decorator, http_method, expected_status_code, return_annotation", [ - (get, HttpMethod.GET, HTTP_200_OK), - (post, HttpMethod.POST, HTTP_201_CREATED), - (put, HttpMethod.PUT, HTTP_200_OK), - (patch, HttpMethod.PATCH, HTTP_200_OK), - (delete, HttpMethod.DELETE, HTTP_204_NO_CONTENT), + (get, HttpMethod.GET, HTTP_200_OK, Person), + (post, HttpMethod.POST, HTTP_201_CREATED, Person), + (put, HttpMethod.PUT, HTTP_200_OK, Person), + (patch, HttpMethod.PATCH, HTTP_200_OK, Person), + (delete, HttpMethod.DELETE, HTTP_204_NO_CONTENT, NoReturn), ], ) def test_controller_http_method( decorator: Union[Type[get], Type[post], Type[put], Type[patch], Type[delete]], http_method: HttpMethod, expected_status_code: int, + return_annotation: Any, ) -> None: test_path = "/person" person_instance = PersonFactory.build() @@ -31,7 +32,7 @@ class MyController(Controller): path = test_path @decorator() - def test_method(self) -> Person: + def test_method(self) -> return_annotation: # type: ignore[valid-type] return person_instance with create_test_client(MyController) as client: