Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions litestar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
patch,
post,
put,
query,
route,
websocket,
websocket_listener,
Expand Down Expand Up @@ -39,6 +40,7 @@
"patch",
"post",
"put",
"query",
"route",
"websocket",
"websocket_listener",
Expand Down
13 changes: 7 additions & 6 deletions litestar/_openapi/typescript_converter/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@
TypeScriptType,
TypeScriptUnion,
)
from litestar.enums import HttpMethod, ParamType
from litestar.enums import ParamType
from litestar.openapi.spec import (
Components,
OpenAPI,
Operation,
Parameter,
PathItem,
Reference,
RequestBody,
Responses,
Schema,
)
from litestar.openapi.spec.base import BaseSchemaObject

__all__ = (
"convert_openapi_to_typescript",
Expand All @@ -40,7 +42,8 @@
"resolve_ref",
)

from litestar.openapi.spec.base import BaseSchemaObject
# OpenAPI 3.x path-item method fields — derived from the PathItem spec's Operation-typed fields.
_PATH_ITEM_METHODS = tuple(f.name for f in fields(PathItem) if "Operation" in str(f.type) and "None" in str(f.type))

T = TypeVar("T")

Expand Down Expand Up @@ -275,10 +278,8 @@ def convert_openapi_to_typescript(openapi_schema: OpenAPI, namespace: str = "API
shared_params = [
get_openapi_type(p, components=openapi_schema.components) for p in (path_item.parameters or [])
]
for method in HttpMethod:
if (
operation := cast("Operation | None", getattr(path_item, method.lower(), "None"))
) and operation.operation_id:
for method in _PATH_ITEM_METHODS:
if (operation := cast("Operation | None", getattr(path_item, method, None))) and operation.operation_id:
params = parse_params(
[
*(
Expand Down
2 changes: 1 addition & 1 deletion litestar/config/csrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class CSRFConfig:
"""The value to set in the ``SameSite`` attribute of the cookie."""
cookie_domain: str | None = field(default=None)
"""Specifies which hosts can receive the cookie."""
safe_methods: set[Method] = field(default_factory=lambda: {"GET", "HEAD", "OPTIONS"})
safe_methods: set[Method] = field(default_factory=lambda: {"GET", "HEAD", "OPTIONS", "QUERY"})
"""A set of "safe methods" that can set the cookie."""
exclude: str | list[str] | None = field(default=None)
"""A pattern or list of patterns to skip in the CSRF middleware."""
Expand Down
1 change: 1 addition & 0 deletions litestar/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class HttpMethod(StrEnum):
PATCH = "PATCH"
POST = "POST"
PUT = "PUT"
QUERY = "QUERY"
TRACE = "TRACE"


Expand Down
3 changes: 2 additions & 1 deletion litestar/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .asgi_handlers import ASGIRouteHandler, asgi
from .base import BaseRouteHandler
from .http_handlers import HTTPRouteHandler, delete, get, head, patch, post, put, route
from .http_handlers import HTTPRouteHandler, delete, get, head, patch, post, put, query, route
from .websocket_handlers import (
WebsocketListener,
WebsocketListenerRouteHandler,
Expand All @@ -25,6 +25,7 @@
"patch",
"post",
"put",
"query",
"route",
"send_websocket_stream",
"websocket",
Expand Down
3 changes: 2 additions & 1 deletion litestar/handlers/http_handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from .base import HTTPRouteHandler
from .decorators import delete, get, head, patch, post, put, route
from .decorators import delete, get, head, patch, post, put, query, route

__all__ = (
"HTTPRouteHandler",
Expand All @@ -11,5 +11,6 @@
"patch",
"post",
"put",
"query",
"route",
)
173 changes: 172 additions & 1 deletion litestar/handlers/http_handlers/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
)
from litestar.types.callable_types import AnyCallable, OperationIDCreator

__all__ = ("delete", "get", "head", "patch", "post", "put", "route")
__all__ = ("delete", "get", "head", "patch", "post", "put", "query", "route")


def route(
Expand Down Expand Up @@ -1086,6 +1086,177 @@ def decorator(fn: AnyCallable) -> HTTPRouteHandler:
return decorator


def query(
path: str | None | Sequence[str] = None,
*,
after_request: AfterRequestHookHandler | None = None,
after_response: AfterResponseHookHandler | None = None,
background: BackgroundTask | BackgroundTasks | None = None,
before_request: BeforeRequestHookHandler | None = None,
cache: bool | int | type[CACHE_FOREVER] = False,
cache_control: CacheControlHeader | None = None,
cache_key_builder: CacheKeyBuilder | None = None,
dependencies: Dependencies | None = None,
dto: type[AbstractDTO] | None | EmptyType = Empty,
etag: ETag | None = None,
exception_handlers: ExceptionHandlersMap | None = None,
guards: Sequence[Guard] | None = None,
media_type: MediaType | str | None = None,
middleware: Sequence[Middleware] | None = None,
name: str | None = None,
opt: Mapping[str, Any] | None = None,
request_class: type[Request] | None = None,
request_max_body_size: int | None | EmptyType = Empty,
response_class: type[Response] | None = None,
response_cookies: ResponseCookies | None = None,
response_headers: ResponseHeaders | None = None,
return_dto: type[AbstractDTO] | None | EmptyType = Empty,
signature_namespace: Mapping[str, Any] | None = None,
status_code: int | None = None,
sync_to_thread: bool | None = None,
# OpenAPI related attributes
content_encoding: str | None = None,
content_media_type: str | None = None,
deprecated: bool = False,
description: str | None = None,
include_in_schema: bool | EmptyType = False,
operation_class: type[Operation] = Operation,
operation_id: str | OperationIDCreator | None = None,
raises: Sequence[type[HTTPException]] | None = None,
response_description: str | None = None,
responses: Mapping[int, ResponseSpec] | None = None,
security: Sequence[SecurityRequirement] | None = None,
summary: str | None = None,
tags: Sequence[str] | None = None,
type_decoders: TypeDecodersSequence | None = None,
type_encoders: TypeEncodersMap | None = None,
handler_class: type[HTTPRouteHandler] = HTTPRouteHandler,
**kwargs: Any,
) -> Callable[[AnyCallable], HTTPRouteHandler]:
"""Create an :class:`HTTPRouteHandler` with a ``QUERY`` method.

Args:
path: A path fragment for the route handler function or a sequence of path fragments.
If not given defaults to ``/``
after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed
to any route handler. If this function returns a value, the request will not reach the route handler,
and instead this value will be used.
after_response: A sync or async function called after the response has been awaited. It receives the
:class:`Request <.connection.Request>` object and should not return any values.
background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or
:class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished.
Defaults to ``None``.
before_request: A sync or async function called immediately before calling the route handler. Receives
the :class:`.connection.Request` instance and any non-``None`` return value is used for the response,
bypassing the route handler.
cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number
of seconds (e.g. ``120``) to cache the response.
cache_control: A ``cache-control`` header of type
:class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response.
cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization
of the cache key if caching is configured on the application level.
dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances.
dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and
validation of request data.
etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response.
exception_handlers: A mapping of status codes and/or exception types to handler functions.
guards: A sequence of :class:`Guard <.types.Guard>` callables.
media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a
valid IANA Media-Type.
middleware: A sequence of :class:`Middleware <.types.Middleware>`.
name: A string identifying the route handler.
opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or
wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`.
request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's
default request.
request_max_body_size: Maximum allowed size of the request body in bytes. If this size is exceeded,
a '413 - Request Entity Too Large' error response is returned.
response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's
default response.
response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances.
response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>`
instances.
responses: A mapping of additional status codes and a description of their expected content.
This information will be included in the OpenAPI schema
return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing
outbound response data.
signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling.
status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and
``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``.
sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the
main event loop. This has an effect only for sync handler functions. See using sync handler functions.
content_encoding: A string describing the encoding of the content, e.g. ``base64``.
content_media_type: A string designating the media-type of the content, e.g. ``image/png``.
deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema.
description: Text used for the route's schema description section.
include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema.
operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema.
operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId.
raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation.
This list should describe all exceptions raised within the route handler's function/method. The Litestar
ValidationException will be added automatically for the schema if any validation is involved.
response_description: Text used for the route's response schema description section.
security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint.
summary: Text used for the route's schema summary section.
tags: A sequence of string tags that will be appended to the OpenAPI schema.
type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec
hook for deserialization.
type_encoders: A mapping of types to callables that transform them into types supported for serialization.
handler_class: Route handler class instantiated by the decorator
**kwargs: Any additional kwarg - will be set in the opt dictionary.
"""

def decorator(fn: AnyCallable) -> HTTPRouteHandler:
return handler_class(
fn=fn,
after_request=after_request,
after_response=after_response,
background=background,
before_request=before_request,
cache=cache,
cache_control=cache_control,
cache_key_builder=cache_key_builder,
content_encoding=content_encoding,
content_media_type=content_media_type,
dependencies=dependencies,
deprecated=deprecated,
description=description,
dto=dto,
etag=etag,
exception_handlers=exception_handlers,
guards=guards,
http_method=HttpMethod.QUERY,
include_in_schema=include_in_schema,
media_type=media_type,
middleware=middleware,
name=name,
operation_class=operation_class,
operation_id=operation_id,
opt=opt,
path=path,
raises=raises,
request_class=request_class,
request_max_body_size=request_max_body_size,
response_class=response_class,
response_cookies=response_cookies,
response_description=response_description,
response_headers=response_headers,
responses=responses,
return_dto=return_dto,
security=security,
signature_namespace=signature_namespace,
status_code=status_code,
summary=summary,
sync_to_thread=sync_to_thread,
tags=tags,
type_decoders=type_decoders,
type_encoders=type_encoders,
**kwargs,
)

return decorator


def delete(
path: str | None | Sequence[str] = None,
*,
Expand Down
57 changes: 56 additions & 1 deletion litestar/testing/request_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from httpx._content import encode_json as httpx_encode_json
from httpx._content import encode_multipart_data, encode_urlencoded_data

from litestar import delete, patch, post, put
from litestar import delete, patch, post, put, query
from litestar.app import Litestar
from litestar.connection import Request
from litestar.enums import HttpMethod, ParamType, RequestEncodingType, ScopeType
Expand All @@ -33,6 +33,7 @@
HttpMethod.DELETE: delete,
HttpMethod.PATCH: patch,
HttpMethod.PUT: put,
HttpMethod.QUERY: query,
}


Expand Down Expand Up @@ -520,6 +521,60 @@ def patch(
route_handler=route_handler,
)

def query(
self,
path: str = "/",
headers: dict[str, str] | None = None,
cookies: list[Cookie] | str | None = None,
session: dict[str, Any] | None = None,
user: Any = None,
auth: Any = None,
request_media_type: RequestEncodingType = RequestEncodingType.JSON,
data: dict[str, Any] | DataContainerType | None = None,
query_params: dict[str, str | list[str]] | None = None,
state: dict[str, Any] | None = None,
path_params: dict[str, str] | None = None,
http_version: str | None = "1.1",
route_handler: RouteHandlerType | None = None,
) -> Request[Any, Any, Any]:
"""Create a QUERY :class:`Request <litestar.connection.Request>` instance.

Args:
path: The request's path.
headers: A dictionary of headers.
cookies: A string representing the cookie header or a list of "Cookie" instances.
This value can include multiple cookies.
session: A dictionary of session data.
user: A value for `request.scope["user"]`.
auth: A value for `request.scope["auth"]`.
request_media_type: The 'Content-Type' header of the request.
data: A value for the request's body. Can be any supported serializable type.
query_params: A dictionary of values from which the request's query will be generated.
state: Arbitrary request state.
path_params: A string keyed dictionary of path parameter values.
http_version: HTTP version. Defaults to "1.1".
route_handler: A route handler instance or method. If not provided a default handler is set.

Returns:
A :class:`Request <litestar.connection.Request>` instance
"""
return self._create_request_with_data(
auth=auth,
cookies=cookies,
data=data,
headers=headers,
http_method=HttpMethod.QUERY,
path=path,
query_params=query_params,
request_media_type=request_media_type,
session=session,
user=user,
state=state,
path_params=path_params,
http_version=http_version,
route_handler=route_handler,
)

def delete(
self,
path: str = "/",
Expand Down
2 changes: 1 addition & 1 deletion litestar/types/asgi_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
from .internal_types import RouteHandlerType
from .serialization import DataContainerType

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

Expand Down
Loading