diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 88ca4a00..ab0b3530 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -22,6 +22,7 @@ accessible via `from esmerald import Controller`. - Fix escaped " in TemplateResponse. - Fix TemplateResponse's auto-detection of the media-type when used directly. - Don't mangle strings by default for other media-types than json. +- Don't mangle returned responses. ## 3.6.2 diff --git a/esmerald/routing/base.py b/esmerald/routing/base.py index c5da8874..70ba00b1 100644 --- a/esmerald/routing/base.py +++ b/esmerald/routing/base.py @@ -275,7 +275,9 @@ def _get_response_container_handler( cookies: ResponseCookies, headers: Dict[str, Any], media_type: str, - ) -> Callable[[ResponseContainer, Type[Esmerald], Dict[str, Any]], LilyaResponse]: + ) -> Callable[ + [Union[ResponseContainer, LilyaResponse], Type[Esmerald], Dict[str, Any]], LilyaResponse + ]: """ Creates a handler for ResponseContainer types. @@ -290,22 +292,30 @@ def _get_response_container_handler( """ async def response_content( - data: ResponseContainer, app: Type["Esmerald"], **kwargs: Dict[str, Any] + data: Union[ResponseContainer, LilyaResponse], + app: Type["Esmerald"], + **kwargs: Dict[str, Any], ) -> LilyaResponse: _headers = {**self.get_headers(headers), **data.headers} _cookies = self.get_cookies(data.cookies, cookies) - response: Response = data.to_response( - app=app, - headers=_headers, - status_code=data.status_code or self.status_code, - media_type=media_type, - ) + if isinstance(data, LilyaResponse): + response: LilyaResponse = data + else: + response = data.to_response( + app=app, + headers=_headers, + status_code=data.status_code or self.status_code, + media_type=media_type, + ) for cookie in _cookies: response.set_cookie(**cookie) return response return cast( - Callable[[ResponseContainer, Type["Esmerald"], Dict[str, Any]], LilyaResponse], + Callable[ + [Union[ResponseContainer, LilyaResponse], Type["Esmerald"], Dict[str, Any]], + LilyaResponse, + ], response_content, ) @@ -435,7 +445,7 @@ def _get_default_handler( async def response_content(data: Any, **kwargs: Dict[str, Any]) -> LilyaResponse: data = await self.get_response_data(data=data) _cookies = self.get_cookies(cookies) - if isinstance(data, JSONResponse): + if isinstance(data, LilyaResponse): response = data response.status_code = self.status_code response.background = self.background diff --git a/esmerald/routing/router.py b/esmerald/routing/router.py index 1d30bcb8..fb205991 100644 --- a/esmerald/routing/router.py +++ b/esmerald/routing/router.py @@ -481,9 +481,9 @@ async def another(request: Request) -> str: path = "/" else: assert path.startswith("/"), "A path prefix must start with '/'" - assert not path.endswith( - "/" - ), "A path must not end with '/', as the routes will start with '/'" + assert not path.endswith("/"), ( + "A path must not end with '/', as the routes will start with '/'" + ) new_routes: list[Any] = [] for route in routes or []: @@ -511,9 +511,9 @@ async def another(request: Request) -> str: ) new_routes.append(route) - assert lifespan is None or ( - on_startup is None and on_shutdown is None - ), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both." + assert lifespan is None or (on_startup is None and on_shutdown is None), ( + "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both." + ) super().__init__( redirect_slashes=redirect_slashes, diff --git a/tests/routing/test_routing_data.py b/tests/routing/test_routing_data.py new file mode 100644 index 00000000..b18fcef0 --- /dev/null +++ b/tests/routing/test_routing_data.py @@ -0,0 +1,26 @@ +from typing import Optional + +from pydantic import BaseModel + +from esmerald import Esmerald, Form, Request +from esmerald.routing.gateways import Gateway +from esmerald.routing.handlers import route +from esmerald.testclient import EsmeraldTestClient + + +class Model(BaseModel): + id: str + + +def test_get_and_post(): + @route(methods=["GET", "POST"]) + async def start(request: Request, data: Optional[Model] = Form()) -> bytes: + return b"hello world" + + app = Esmerald( + debug=True, + routes=[Gateway("/", handler=start)], + ) + client = EsmeraldTestClient(app) + response = client.get("/") + assert response.status_code == 200 diff --git a/tests/test_direct_responses.py b/tests/test_direct_responses.py new file mode 100644 index 00000000..5e2ca25e --- /dev/null +++ b/tests/test_direct_responses.py @@ -0,0 +1,51 @@ +import os + +from esmerald import Esmerald, Redirect, Request, Template +from esmerald.config.template import TemplateConfig +from esmerald.responses.base import RedirectResponse +from esmerald.routing.gateways import Gateway +from esmerald.routing.handlers import get +from esmerald.testclient import EsmeraldTestClient + + +def test_return_response_container(template_dir): + path = os.path.join(template_dir, "start.html") + with open(path, "w") as file: + file.write("Hello, world") + + @get() + async def start(request: Request) -> Template: + return Redirect(path="/home", status_code=301) + + app = Esmerald( + debug=True, + routes=[Gateway("/", handler=start)], + template_config=TemplateConfig( + directory=template_dir, + ), + middleware=[], + ) + client = EsmeraldTestClient(app) + response = client.get("/", follow_redirects=False) + assert response.status_code == 301 + + +def test_return_response(template_dir): + path = os.path.join(template_dir, "start.html") + with open(path, "w") as file: + file.write("Hello, world") + + @get() + async def start(request: Request) -> Template: + return RedirectResponse(url="/home", status_code=301) + + app = Esmerald( + debug=True, + routes=[Gateway("/", handler=start)], + template_config=TemplateConfig( + directory=template_dir, + ), + ) + client = EsmeraldTestClient(app) + response = client.get("/", follow_redirects=False) + assert response.status_code == 301