Skip to content

Commit 4ee6036

Browse files
committed
feat: add HTTP QUERY method support
Signed-off-by: Prateek Anand <bizprat@gmail.com>
1 parent 149a4ae commit 4ee6036

13 files changed

Lines changed: 337 additions & 14 deletions

File tree

litestar/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
patch,
1111
post,
1212
put,
13+
query,
1314
route,
1415
websocket,
1516
websocket_listener,
@@ -39,6 +40,7 @@
3940
"patch",
4041
"post",
4142
"put",
43+
"query",
4244
"route",
4345
"websocket",
4446
"websocket_listener",

litestar/_openapi/typescript_converter/converter.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
TypeScriptType,
1919
TypeScriptUnion,
2020
)
21-
from litestar.enums import HttpMethod, ParamType
21+
from litestar.enums import ParamType
2222
from litestar.openapi.spec import (
2323
Components,
2424
OpenAPI,
@@ -29,6 +29,7 @@
2929
Responses,
3030
Schema,
3131
)
32+
from litestar.openapi.spec.base import BaseSchemaObject
3233

3334
__all__ = (
3435
"convert_openapi_to_typescript",
@@ -40,7 +41,7 @@
4041
"resolve_ref",
4142
)
4243

43-
from litestar.openapi.spec.base import BaseSchemaObject
44+
OPENAPI_PATH_ITEM_METHODS = ("get", "put", "post", "delete", "options", "head", "patch", "trace")
4445

4546
T = TypeVar("T")
4647

@@ -275,10 +276,8 @@ def convert_openapi_to_typescript(openapi_schema: OpenAPI, namespace: str = "API
275276
shared_params = [
276277
get_openapi_type(p, components=openapi_schema.components) for p in (path_item.parameters or [])
277278
]
278-
for method in HttpMethod:
279-
if (
280-
operation := cast("Operation | None", getattr(path_item, method.lower(), "None"))
281-
) and operation.operation_id:
279+
for method in OPENAPI_PATH_ITEM_METHODS:
280+
if (operation := cast("Operation | None", getattr(path_item, method, None))) and operation.operation_id:
282281
params = parse_params(
283282
[
284283
*(

litestar/config/csrf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class CSRFConfig:
3434
"""The value to set in the ``SameSite`` attribute of the cookie."""
3535
cookie_domain: str | None = field(default=None)
3636
"""Specifies which hosts can receive the cookie."""
37-
safe_methods: set[Method] = field(default_factory=lambda: {"GET", "HEAD", "OPTIONS"})
37+
safe_methods: set[Method] = field(default_factory=lambda: {"GET", "HEAD", "OPTIONS", "QUERY"})
3838
"""A set of "safe methods" that can set the cookie."""
3939
exclude: str | list[str] | None = field(default=None)
4040
"""A pattern or list of patterns to skip in the CSRF middleware."""

litestar/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class HttpMethod(StrEnum):
2121
PATCH = "PATCH"
2222
POST = "POST"
2323
PUT = "PUT"
24+
QUERY = "QUERY"
2425
TRACE = "TRACE"
2526

2627

litestar/handlers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .asgi_handlers import ASGIRouteHandler, asgi
22
from .base import BaseRouteHandler
3-
from .http_handlers import HTTPRouteHandler, delete, get, head, patch, post, put, route
3+
from .http_handlers import HTTPRouteHandler, delete, get, head, patch, post, put, query, route
44
from .websocket_handlers import (
55
WebsocketListener,
66
WebsocketListenerRouteHandler,
@@ -25,6 +25,7 @@
2525
"patch",
2626
"post",
2727
"put",
28+
"query",
2829
"route",
2930
"send_websocket_stream",
3031
"websocket",

litestar/handlers/http_handlers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from .base import HTTPRouteHandler
4-
from .decorators import delete, get, head, patch, post, put, route
4+
from .decorators import delete, get, head, patch, post, put, query, route
55

66
__all__ = (
77
"HTTPRouteHandler",
@@ -11,5 +11,6 @@
1111
"patch",
1212
"post",
1313
"put",
14+
"query",
1415
"route",
1516
)

litestar/handlers/http_handlers/decorators.py

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
)
5757
from litestar.types.callable_types import AnyCallable, OperationIDCreator
5858

59-
__all__ = ("delete", "get", "head", "patch", "post", "put", "route")
59+
__all__ = ("delete", "get", "head", "patch", "post", "put", "query", "route")
6060

6161

6262
def route(
@@ -1086,6 +1086,177 @@ def decorator(fn: AnyCallable) -> HTTPRouteHandler:
10861086
return decorator
10871087

10881088

1089+
def query(
1090+
path: str | None | Sequence[str] = None,
1091+
*,
1092+
after_request: AfterRequestHookHandler | None = None,
1093+
after_response: AfterResponseHookHandler | None = None,
1094+
background: BackgroundTask | BackgroundTasks | None = None,
1095+
before_request: BeforeRequestHookHandler | None = None,
1096+
cache: bool | int | type[CACHE_FOREVER] = False,
1097+
cache_control: CacheControlHeader | None = None,
1098+
cache_key_builder: CacheKeyBuilder | None = None,
1099+
dependencies: Dependencies | None = None,
1100+
dto: type[AbstractDTO] | None | EmptyType = Empty,
1101+
etag: ETag | None = None,
1102+
exception_handlers: ExceptionHandlersMap | None = None,
1103+
guards: Sequence[Guard] | None = None,
1104+
media_type: MediaType | str | None = None,
1105+
middleware: Sequence[Middleware] | None = None,
1106+
name: str | None = None,
1107+
opt: Mapping[str, Any] | None = None,
1108+
request_class: type[Request] | None = None,
1109+
request_max_body_size: int | None | EmptyType = Empty,
1110+
response_class: type[Response] | None = None,
1111+
response_cookies: ResponseCookies | None = None,
1112+
response_headers: ResponseHeaders | None = None,
1113+
return_dto: type[AbstractDTO] | None | EmptyType = Empty,
1114+
signature_namespace: Mapping[str, Any] | None = None,
1115+
status_code: int | None = None,
1116+
sync_to_thread: bool | None = None,
1117+
# OpenAPI related attributes
1118+
content_encoding: str | None = None,
1119+
content_media_type: str | None = None,
1120+
deprecated: bool = False,
1121+
description: str | None = None,
1122+
include_in_schema: bool | EmptyType = False,
1123+
operation_class: type[Operation] = Operation,
1124+
operation_id: str | OperationIDCreator | None = None,
1125+
raises: Sequence[type[HTTPException]] | None = None,
1126+
response_description: str | None = None,
1127+
responses: Mapping[int, ResponseSpec] | None = None,
1128+
security: Sequence[SecurityRequirement] | None = None,
1129+
summary: str | None = None,
1130+
tags: Sequence[str] | None = None,
1131+
type_decoders: TypeDecodersSequence | None = None,
1132+
type_encoders: TypeEncodersMap | None = None,
1133+
handler_class: type[HTTPRouteHandler] = HTTPRouteHandler,
1134+
**kwargs: Any,
1135+
) -> Callable[[AnyCallable], HTTPRouteHandler]:
1136+
"""Create an :class:`HTTPRouteHandler` with a ``QUERY`` method.
1137+
1138+
Args:
1139+
path: A path fragment for the route handler function or a sequence of path fragments.
1140+
If not given defaults to ``/``
1141+
after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed
1142+
to any route handler. If this function returns a value, the request will not reach the route handler,
1143+
and instead this value will be used.
1144+
after_response: A sync or async function called after the response has been awaited. It receives the
1145+
:class:`Request <.connection.Request>` object and should not return any values.
1146+
background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or
1147+
:class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished.
1148+
Defaults to ``None``.
1149+
before_request: A sync or async function called immediately before calling the route handler. Receives
1150+
the :class:`.connection.Request` instance and any non-``None`` return value is used for the response,
1151+
bypassing the route handler.
1152+
cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number
1153+
of seconds (e.g. ``120``) to cache the response.
1154+
cache_control: A ``cache-control`` header of type
1155+
:class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response.
1156+
cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization
1157+
of the cache key if caching is configured on the application level.
1158+
dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances.
1159+
dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and
1160+
validation of request data.
1161+
etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response.
1162+
exception_handlers: A mapping of status codes and/or exception types to handler functions.
1163+
guards: A sequence of :class:`Guard <.types.Guard>` callables.
1164+
media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a
1165+
valid IANA Media-Type.
1166+
middleware: A sequence of :class:`Middleware <.types.Middleware>`.
1167+
name: A string identifying the route handler.
1168+
opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or
1169+
wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`.
1170+
request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's
1171+
default request.
1172+
request_max_body_size: Maximum allowed size of the request body in bytes. If this size is exceeded,
1173+
a '413 - Request Entity Too Large' error response is returned.
1174+
response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's
1175+
default response.
1176+
response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances.
1177+
response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>`
1178+
instances.
1179+
responses: A mapping of additional status codes and a description of their expected content.
1180+
This information will be included in the OpenAPI schema
1181+
return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing
1182+
outbound response data.
1183+
signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling.
1184+
status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and
1185+
``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``.
1186+
sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the
1187+
main event loop. This has an effect only for sync handler functions. See using sync handler functions.
1188+
content_encoding: A string describing the encoding of the content, e.g. ``base64``.
1189+
content_media_type: A string designating the media-type of the content, e.g. ``image/png``.
1190+
deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema.
1191+
description: Text used for the route's schema description section.
1192+
include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema.
1193+
operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema.
1194+
operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId.
1195+
raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation.
1196+
This list should describe all exceptions raised within the route handler's function/method. The Litestar
1197+
ValidationException will be added automatically for the schema if any validation is involved.
1198+
response_description: Text used for the route's response schema description section.
1199+
security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint.
1200+
summary: Text used for the route's schema summary section.
1201+
tags: A sequence of string tags that will be appended to the OpenAPI schema.
1202+
type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec
1203+
hook for deserialization.
1204+
type_encoders: A mapping of types to callables that transform them into types supported for serialization.
1205+
handler_class: Route handler class instantiated by the decorator
1206+
**kwargs: Any additional kwarg - will be set in the opt dictionary.
1207+
"""
1208+
1209+
def decorator(fn: AnyCallable) -> HTTPRouteHandler:
1210+
return handler_class(
1211+
fn=fn,
1212+
after_request=after_request,
1213+
after_response=after_response,
1214+
background=background,
1215+
before_request=before_request,
1216+
cache=cache,
1217+
cache_control=cache_control,
1218+
cache_key_builder=cache_key_builder,
1219+
content_encoding=content_encoding,
1220+
content_media_type=content_media_type,
1221+
dependencies=dependencies,
1222+
deprecated=deprecated,
1223+
description=description,
1224+
dto=dto,
1225+
etag=etag,
1226+
exception_handlers=exception_handlers,
1227+
guards=guards,
1228+
http_method=HttpMethod.QUERY,
1229+
include_in_schema=include_in_schema,
1230+
media_type=media_type,
1231+
middleware=middleware,
1232+
name=name,
1233+
operation_class=operation_class,
1234+
operation_id=operation_id,
1235+
opt=opt,
1236+
path=path,
1237+
raises=raises,
1238+
request_class=request_class,
1239+
request_max_body_size=request_max_body_size,
1240+
response_class=response_class,
1241+
response_cookies=response_cookies,
1242+
response_description=response_description,
1243+
response_headers=response_headers,
1244+
responses=responses,
1245+
return_dto=return_dto,
1246+
security=security,
1247+
signature_namespace=signature_namespace,
1248+
status_code=status_code,
1249+
summary=summary,
1250+
sync_to_thread=sync_to_thread,
1251+
tags=tags,
1252+
type_decoders=type_decoders,
1253+
type_encoders=type_encoders,
1254+
**kwargs,
1255+
)
1256+
1257+
return decorator
1258+
1259+
10891260
def delete(
10901261
path: str | None | Sequence[str] = None,
10911262
*,

litestar/testing/request_factory.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from httpx._content import encode_json as httpx_encode_json
1111
from httpx._content import encode_multipart_data, encode_urlencoded_data
1212

13-
from litestar import delete, patch, post, put
13+
from litestar import delete, patch, post, put, query
1414
from litestar.app import Litestar
1515
from litestar.connection import Request
1616
from litestar.enums import HttpMethod, ParamType, RequestEncodingType, ScopeType
@@ -33,6 +33,7 @@
3333
HttpMethod.DELETE: delete,
3434
HttpMethod.PATCH: patch,
3535
HttpMethod.PUT: put,
36+
HttpMethod.QUERY: query,
3637
}
3738

3839

@@ -520,6 +521,60 @@ def patch(
520521
route_handler=route_handler,
521522
)
522523

524+
def query(
525+
self,
526+
path: str = "/",
527+
headers: dict[str, str] | None = None,
528+
cookies: list[Cookie] | str | None = None,
529+
session: dict[str, Any] | None = None,
530+
user: Any = None,
531+
auth: Any = None,
532+
request_media_type: RequestEncodingType = RequestEncodingType.JSON,
533+
data: dict[str, Any] | DataContainerType | None = None,
534+
query_params: dict[str, str | list[str]] | None = None,
535+
state: dict[str, Any] | None = None,
536+
path_params: dict[str, str] | None = None,
537+
http_version: str | None = "1.1",
538+
route_handler: RouteHandlerType | None = None,
539+
) -> Request[Any, Any, Any]:
540+
"""Create a QUERY :class:`Request <litestar.connection.Request>` instance.
541+
542+
Args:
543+
path: The request's path.
544+
headers: A dictionary of headers.
545+
cookies: A string representing the cookie header or a list of "Cookie" instances.
546+
This value can include multiple cookies.
547+
session: A dictionary of session data.
548+
user: A value for `request.scope["user"]`.
549+
auth: A value for `request.scope["auth"]`.
550+
request_media_type: The 'Content-Type' header of the request.
551+
data: A value for the request's body. Can be any supported serializable type.
552+
query_params: A dictionary of values from which the request's query will be generated.
553+
state: Arbitrary request state.
554+
path_params: A string keyed dictionary of path parameter values.
555+
http_version: HTTP version. Defaults to "1.1".
556+
route_handler: A route handler instance or method. If not provided a default handler is set.
557+
558+
Returns:
559+
A :class:`Request <litestar.connection.Request>` instance
560+
"""
561+
return self._create_request_with_data(
562+
auth=auth,
563+
cookies=cookies,
564+
data=data,
565+
headers=headers,
566+
http_method=HttpMethod.QUERY,
567+
path=path,
568+
query_params=query_params,
569+
request_media_type=request_media_type,
570+
session=session,
571+
user=user,
572+
state=state,
573+
path_params=path_params,
574+
http_version=http_version,
575+
route_handler=route_handler,
576+
)
577+
523578
def delete(
524579
self,
525580
path: str = "/",

litestar/types/asgi_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
from .internal_types import RouteHandlerType
101101
from .serialization import DataContainerType
102102

103-
HttpMethodName: TypeAlias = Literal["GET", "POST", "DELETE", "PATCH", "PUT", "HEAD", "TRACE", "OPTIONS"]
103+
HttpMethodName: TypeAlias = Literal["GET", "POST", "DELETE", "PATCH", "PUT", "QUERY", "HEAD", "TRACE", "OPTIONS"]
104104
Method: TypeAlias = Union[HttpMethodName, HttpMethod]
105105
ScopeSession: TypeAlias = "EmptyType | dict[str, Any] | DataContainerType | None"
106106

0 commit comments

Comments
 (0)