Skip to content

Commit

Permalink
Changes:
Browse files Browse the repository at this point in the history
- fix `data` and `payload` special keyword arguments so they are allowed
  when another method than one without a body (HEAD, GET) is available,
  • Loading branch information
devkral committed Dec 28, 2024
1 parent 1b8dae9 commit 0deedb1
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/en/docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ hide:

### Fixed

- `data` and `payload` special kwargs are now allowed when a not-bodyless method is available for the handler. They default to None.
- `bytes` won't be encoded as json when returned from a handler. This would unexpectly lead to a base64 encoding.

## 3.6.1
Expand Down
17 changes: 13 additions & 4 deletions esmerald/routing/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,9 @@ def wrapper(func: Callable) -> Callable:
return wrapper


_body_less_methods = frozenset({"GET", "HEAD"})


class HTTPHandler(Dispatcher, OpenAPIFieldInfoMixin, LilyaPath):
__slots__ = (
"path",
Expand Down Expand Up @@ -2222,12 +2225,18 @@ def validate_reserved_kwargs(self) -> None: # pragma: no cover
"""
Validates if special words are in the signature.
"""
if DATA in self.handler_signature.parameters and "GET" in self.methods:
raise ImproperlyConfigured("'data' argument is unsupported for 'GET' request handlers")
if DATA in self.handler_signature.parameters and _body_less_methods.issuperset(
self.methods
):
raise ImproperlyConfigured(
"'data' argument is unsupported for 'GET' and 'HEAD' request handlers"
)

if PAYLOAD in self.handler_signature.parameters and "GET" in self.methods:
if PAYLOAD in self.handler_signature.parameters and _body_less_methods.issuperset(
self.methods
):
raise ImproperlyConfigured(
"'payload' argument is unsupported for 'GET' request handlers"
"'payload' argument is unsupported for 'GET' and 'HEAD' request handlers"
)

if SOCKET in self.handler_signature.parameters:
Expand Down
24 changes: 24 additions & 0 deletions tests/forms/test_forms_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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, form: Model | None = 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
55 changes: 54 additions & 1 deletion tests/routing/test_routing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import contextlib
import uuid
from dataclasses import dataclass
from typing import Optional

import pytest
from lilya.responses import JSONResponse, PlainText, Response as LilyaResponse
Expand All @@ -10,13 +11,14 @@

from esmerald.applications import Esmerald
from esmerald.enums import MediaType
from esmerald.exceptions import ImproperlyConfigured
from esmerald.permissions import AllowAny, DenyAll
from esmerald.requests import Request
from esmerald.responses import Response
from esmerald.responses.encoders import UJSONResponse
from esmerald.routing.apis.views import APIView
from esmerald.routing.gateways import Gateway, WebSocketGateway
from esmerald.routing.handlers import get, post, put, websocket
from esmerald.routing.handlers import get, post, put, route, websocket
from esmerald.routing.router import Include, Router
from esmerald.testclient import create_client

Expand Down Expand Up @@ -1020,3 +1022,54 @@ def another_user(data: UserOut) -> UserOut:

assert response.status_code == 200
assert response.json() == {"name": "test", "email": "[email protected]"}


def test_get_and_post_data(test_app_client_factory):
@route(path="/another-user", status_code=200, methods=["GET", "POST"])
def another_user(data: Optional[UserOut]) -> Optional[UserOut]:
return data

data = {"name": "test", "email": "[email protected]"}
app = Esmerald(routes=[Gateway(handler=another_user)])
client = test_app_client_factory(app)
response = client.get("/another-user")
assert response.status_code == 200
assert response.text == ""
response = client.post("/another-user", json=data)

assert response.status_code == 200
assert response.json() == {"name": "test", "email": "[email protected]"}


def test_get_and_post_payload(test_app_client_factory):
@route(path="/another-user", status_code=200, methods=["GET", "POST"])
def another_user(payload: Optional[UserOut]) -> Optional[UserOut]:
return payload

data = {"name": "test", "email": "[email protected]"}
app = Esmerald(routes=[Gateway(handler=another_user)])

client = test_app_client_factory(app)
response = client.get("/another-user")
assert response.status_code == 200
assert response.text == ""
response = client.post("/another-user", json=data)

assert response.status_code == 200
assert response.json() == {"name": "test", "email": "[email protected]"}


def test_get_and_head_data():
with pytest.raises(ImproperlyConfigured):

@route(path="/another-user", status_code=200, methods=["GET", "HEAD"])
def another_user(data: UserOut) -> UserOut:
return data


def test_get_and_head_payload():
with pytest.raises(ImproperlyConfigured):

@route(path="/another-user", status_code=200, methods=["GET", "HEAD"])
def another_user(payload: UserOut) -> UserOut:
return payload

0 comments on commit 0deedb1

Please sign in to comment.