Skip to content

Commit

Permalink
feat!: static handler configuration (#3900)
Browse files Browse the repository at this point in the history
  • Loading branch information
provinzkraut authored Jan 16, 2025
1 parent a2afa51 commit d1fb342
Show file tree
Hide file tree
Showing 78 changed files with 1,447 additions and 1,258 deletions.
21 changes: 21 additions & 0 deletions docs/release-notes/whats-new-3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,24 @@ Change the media type of :attr:`~enums.MediaType.MESSAGEPACK` and
newly introduced official ``application/vnd.msgpack``.

https://www.iana.org/assignments/media-types/application/vnd.msgpack


Deprecated ``resolve_`` methods on route handlers
-------------------------------------------------

All ``resolve_`` methods on the route handlers
(e.g. :meth:`~litestar.handlers.HTTPRouteHandler.resolve_response_headers`) have been
deprecated and will be removed in ``4.0``. The attributes can now safely be accessed
directly (e.g. `HTTPRouteHandlers.response_headers`).


Moved routing related methods from ``Router`` to ``Litestar``
-------------------------------------------------------------

:class:`~litestar.router.Router` now only holds route handlers and configuration, while
the actual routing is done in :class:`~litestar.app.Litestar`. With this, several
methods and properties have been moved from ``Router`` to ``Litestar``:

- ``route_handler_method_map``
- ``get_route_handler_map``
- ``routes``
16 changes: 13 additions & 3 deletions litestar/_asgi/asgi_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class ASGIRouter:
"_plain_routes",
"_registered_routes",
"_static_routes",
"_trie_initialized",
"app",
"root_route_map_node",
"route_handler_index",
Expand All @@ -69,6 +70,7 @@ def __init__(self, app: Litestar) -> None:
self._mount_routes: dict[str, RouteTrieNode] = {}
self._plain_routes: set[str] = set()
self._registered_routes: set[HTTPRoute | WebSocketRoute | ASGIRoute] = set()
self._trie_initialized = False
self.app = app
self.root_route_map_node: RouteTrieNode = create_node()
self.route_handler_index: dict[str, RouteHandlerType] = {}
Expand All @@ -94,7 +96,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
ScopeState.from_scope(scope).exception_handlers = self._app_exception_handlers
raise
else:
ScopeState.from_scope(scope).exception_handlers = route_handler.resolve_exception_handlers()
ScopeState.from_scope(scope).exception_handlers = route_handler.exception_handlers
scope["route_handler"] = route_handler
scope["path_template"] = path_template
await asgi_app(scope, receive, send)
Expand Down Expand Up @@ -145,8 +147,16 @@ def construct_routing_trie(self) -> None:
This map is used in the asgi router to route requests.
"""
new_routes = [route for route in self.app.routes if route not in self._registered_routes]
for route in new_routes:
if self._trie_initialized: # pragma: no cover
self._mount_paths_regex = None
self._mount_routes = {}
self._plain_routes = set()
self._registered_routes = set()
self.root_route_map_node = create_node()
self.route_handler_index = {}
self.route_mapping = defaultdict(list)

for route in self.app.routes:
add_route_to_trie(
app=self.app,
mount_routes=self._mount_routes,
Expand Down
2 changes: 1 addition & 1 deletion litestar/_asgi/routing_trie/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def build_route_middleware_stack(
from litestar.routes import HTTPRoute

asgi_handler: ASGIApp = route.handle # type: ignore[assignment]
handler_middleware = route_handler.resolve_middleware()
handler_middleware = route_handler.middleware
has_cached_route = isinstance(route, HTTPRoute) and any(r.cache for r in route.route_handlers)
has_middleware = (
app.csrf_config or app.compression_config or has_cached_route or app.allowed_hosts or handler_middleware
Expand Down
2 changes: 1 addition & 1 deletion litestar/_kwargs/extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ async def _extract_multipart(
stream=connection.stream(),
boundary=connection.content_type[-1].get("boundary", "").encode(),
multipart_form_part_limit=multipart_form_part_limit,
type_decoders=connection.route_handler.resolve_type_decoders(),
type_decoders=connection.route_handler.type_decoders,
)
else:
form_values = scope_state.form
Expand Down
10 changes: 5 additions & 5 deletions litestar/_layers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@
from litestar.types.composite_types import ResponseCookies, ResponseHeaders


def narrow_response_headers(headers: ResponseHeaders | None) -> Sequence[ResponseHeader] | None:
"""Given :class:`.types.ResponseHeaders` as a :class:`typing.Mapping`, create a list of
def narrow_response_headers(headers: ResponseHeaders | None) -> Sequence[ResponseHeader]:
"""Given :class:`.types.ResponseHeaders` as a :class:`typing.Mapping`, create a tuple of
:class:`.datastructures.response_header.ResponseHeader` from it, otherwise return ``headers`` unchanged
"""
return (
tuple(ResponseHeader(name=name, value=value) for name, value in headers.items())
if isinstance(headers, Mapping)
else headers
)
) or ()


def narrow_response_cookies(cookies: ResponseCookies | None) -> Sequence[Cookie] | None:
def narrow_response_cookies(cookies: ResponseCookies | None) -> Sequence[Cookie]:
"""Given :class:`.types.ResponseCookies` as a :class:`typing.Mapping`, create a list of
:class:`.datastructures.cookie.Cookie` from it, otherwise return ``cookies`` unchanged
"""
return (
tuple(Cookie(key=key, value=value) for key, value in cookies.items())
if isinstance(cookies, Mapping)
else cookies
)
) or ()
2 changes: 1 addition & 1 deletion litestar/_openapi/datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,6 @@ def add_operation_id(self, operation_id: str) -> None:
if operation_id in self.operation_ids:
raise ImproperlyConfiguredException(
"operation_ids must be unique, "
f"please ensure the value of 'operation_id' is either not set or unique for {operation_id}"
f"please ensure the value of 'operation_id' is either not set or unique for {operation_id!r}"
)
self.operation_ids.add(operation_id)
4 changes: 2 additions & 2 deletions litestar/_openapi/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ def __init__(
self.schema_creator = SchemaCreator.from_openapi_context(self.context, prefer_alias=True)
self.route_handler = route_handler
self.parameters = ParameterCollection(route_handler)
self.dependency_providers = route_handler.resolve_dependencies()
self.layered_parameters = route_handler.resolve_layered_parameters()
self.dependency_providers = route_handler.dependencies
self.layered_parameters = route_handler.parameter_field_definitions
self.path_parameters = path_parameters

def create_parameter(self, field_definition: FieldDefinition, parameter_name: str) -> Parameter:
Expand Down
8 changes: 4 additions & 4 deletions litestar/_openapi/path_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def create_path_item(self) -> PathItem:
A PathItem instance.
"""
for http_method, route_handler in self.route.route_handler_map.items():
if not route_handler.resolve_include_in_schema():
if not route_handler.include_in_schema:
continue

operation = self.create_operation_for_handler_method(route_handler, HttpMethod(http_method))
Expand Down Expand Up @@ -64,7 +64,7 @@ def create_operation_for_handler_method(
request_body = None
if data_field := signature_fields.get("data"):
request_body = create_request_body(
self.context, route_handler.handler_id, route_handler.resolve_data_dto(), data_field
self.context, route_handler.handler_id, route_handler.data_dto, data_field
)

raises_validation_error = bool(data_field or self._path_item.parameters or parameters)
Expand All @@ -74,14 +74,14 @@ def create_operation_for_handler_method(

return route_handler.operation_class(
operation_id=operation_id,
tags=route_handler.resolve_tags() or None,
tags=sorted(route_handler.tags) if route_handler.tags else None,
summary=route_handler.summary or SEPARATORS_CLEANUP_PATTERN.sub("", route_handler.handler_name.title()),
description=self.create_description_for_handler(route_handler),
deprecated=route_handler.deprecated,
responses=responses,
request_body=request_body,
parameters=parameters or None, # type: ignore[arg-type]
security=route_handler.resolve_security() or None,
security=list(route_handler.security) if route_handler.security else None,
)

def create_operation_id(self, route_handler: HTTPRouteHandler, http_method: HttpMethod) -> str:
Expand Down
2 changes: 1 addition & 1 deletion litestar/_openapi/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def receive_route(self, route: BaseRoute) -> None:
if not isinstance(route, HTTPRoute):
return

if any(route_handler.resolve_include_in_schema() for route_handler in route.route_handler_map.values()):
if any(route_handler.include_in_schema for route_handler in route.route_handler_map.values()):
# Force recompute the schema if a new route is added
self._openapi = None
self.included_routes[route.path] = route
6 changes: 3 additions & 3 deletions litestar/_openapi/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def create_success_response(self) -> OpenAPIResponse:
else:
media_type = self.route_handler.media_type

if dto := self.route_handler.resolve_return_dto():
if dto := self.route_handler.return_dto:
result = dto.create_openapi_schema(
field_definition=self.field_definition,
handler_id=self.route_handler.handler_id,
Expand Down Expand Up @@ -209,7 +209,7 @@ def set_success_response_headers(self, response: OpenAPIResponse) -> None:
else:
schema_creator = SchemaCreator.from_openapi_context(self.context, generate_examples=False)

for response_header in self.route_handler.resolve_response_headers():
for response_header in self.route_handler.response_headers:
header = OpenAPIHeader()
for attribute_name, attribute_value in (
(k, v) for k, v in asdict(response_header).items() if v is not None
Expand All @@ -223,7 +223,7 @@ def set_success_response_headers(self, response: OpenAPIResponse) -> None:

response.headers[response_header.name] = header

if cookies := self.route_handler.resolve_response_cookies():
if cookies := self.route_handler.response_cookies:
response.headers["Set-Cookie"] = OpenAPIHeader(
schema=Schema(
all_of=[create_cookie_schema(cookie=cookie) for cookie in sorted(cookies, key=attrgetter("key"))]
Expand Down
Loading

0 comments on commit d1fb342

Please sign in to comment.