Skip to content

Commit

Permalink
feat(web): Add possibility to parse query params
Browse files Browse the repository at this point in the history
  • Loading branch information
paveldedik committed Dec 2, 2024
1 parent 1d0f1ab commit 3a710b4
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 2 deletions.
13 changes: 11 additions & 2 deletions ludic/web/responses.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import inspect
from collections.abc import Callable
from typing import Any, ParamSpec, TypeVar, get_origin
from types import NoneType, UnionType
from typing import Any, ParamSpec, TypeVar, get_args, get_origin

from starlette._utils import is_async_callable
from starlette.concurrency import run_in_threadpool
Expand Down Expand Up @@ -107,7 +108,7 @@ async def prepare_response(
return response


async def extract_from_request(
async def extract_from_request( # noqa
handler: Callable[..., Any],
request: Request | WebSocket,
) -> dict[str, Any]:
Expand All @@ -132,6 +133,14 @@ async def extract_from_request(
):
async with request.form() as form:
handler_kwargs[name] = param.annotation(form)
elif isinstance(param.annotation, UnionType):
if (args := get_args(param.annotation)) and (
len(args) != 2 or args[1] is not NoneType
):
raise TypeError(
f"Request handler has an invalid signature: {param.annotation!r}"
)
handler_kwargs[name] = request.query_params.get(name)
elif issubclass(param.annotation, FormData):
async with request.form() as form:
handler_kwargs[name] = form
Expand Down
53 changes: 53 additions & 0 deletions tests/web/test_routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pytest
from starlette.testclient import TestClient

from ludic.html import div
from ludic.web import LudicApp
from ludic.web.datastructures import Headers

app = LudicApp()


@app.get("/mandatory-param/{test}")
def mandatory_param(test: str) -> div:
return div(test)


@app.get("/extract-headers/")
def extract_headers(headers: Headers) -> div:
return div(headers["X-Foo"])


@app.get("/kw-only-params")
def kw_only_params(*, bar: str | None) -> div:
return div(bar or "nothing")


@app.get("/invalid-signature")
def invalid_signature(*, bar: str | int) -> div:
return div(bar or "nothing")


def test_mandatory_param() -> None:
with TestClient(app) as client:
response = client.get("/mandatory-param/value")
assert response.content.decode("utf-8") == "<div>value</div>"


def test_extract_headers() -> None:
with TestClient(app) as client:
response = client.get("/extract-headers", headers={"X-Foo": "x-foo-value"})
assert response.content.decode("utf-8") == "<div>x-foo-value</div>"


def test_kw_only_params() -> None:
with TestClient(app) as client:
response = client.get("/kw-only-params?bar=something")
assert response.content.decode("utf-8") == "<div>something</div>"
response = client.get("/kw-only-params")
assert response.content.decode("utf-8") == "<div>nothing</div>"


def test_invalid_signature() -> None:
with TestClient(app) as client, pytest.raises(TypeError):
client.get("/invalid-signature?bar=something")

0 comments on commit 3a710b4

Please sign in to comment.