Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(routing)!: Refactor routes and route handlers #3386

Merged
merged 24 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1ec91ea
docs!: update to v3 style (#3324)
JacobCoffee Apr 10, 2024
7578ec7
remove handler names
provinzkraut Apr 10, 2024
e80ca94
Remove option handler creation from HTTPRoute
provinzkraut Apr 12, 2024
161a764
Remove methods attribute from BaseRoute
provinzkraut Apr 12, 2024
f27764d
Move kwargs model to handlers and creation to on_registration
provinzkraut Apr 13, 2024
27fa3fb
Store kwargs model on handlers instead of routes
provinzkraut Apr 13, 2024
d65a31c
Simplify HTTPRoute route_handler_map creation
provinzkraut Apr 13, 2024
84f45b2
Simplify Router.route_handler_method_map
provinzkraut Apr 13, 2024
cf4dcfa
Relax typing of HTTPRoute
provinzkraut Apr 13, 2024
9369f28
Move handling logic to route handlers
provinzkraut Apr 13, 2024
1505bdc
Remove scope_type
provinzkraut Apr 13, 2024
54af8a5
Don't pass route to HTTPRouteHandler during handling
provinzkraut Apr 14, 2024
4677145
Don't pass scope to handle methods
provinzkraut Apr 14, 2024
5429aa3
Resolve and establish connections in routes; Only pass connections to…
provinzkraut Apr 14, 2024
1af6267
Fix docstring
provinzkraut Apr 14, 2024
2761ca0
Fix pyright error
provinzkraut Apr 14, 2024
626deb8
Fix missing reference
provinzkraut Apr 14, 2024
1168f81
Fix docs a bit more
provinzkraut Apr 14, 2024
4158e18
Fix merge issue
provinzkraut Apr 20, 2024
7167168
Update litestar/router.py
provinzkraut Apr 20, 2024
3ba1f5c
Apply suggestions from code review
provinzkraut Apr 20, 2024
af08c4a
formatting
provinzkraut Apr 21, 2024
39d8079
remove app parameter from CORSMiddleware
provinzkraut Apr 21, 2024
cbdeeee
Fix options handler
provinzkraut Apr 21, 2024
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
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@
(PY_RE, r"advanced_alchemy\.config.common\.EngineT"),
(PY_RE, r"advanced_alchemy\.config.common\.SessionT"),
(PY_RE, r".*R"),
(PY_RE, r".*ScopeT"),
]

# Warnings about missing references to those targets in the specified location will be ignored.
Expand Down
3 changes: 1 addition & 2 deletions litestar/_asgi/routing_trie/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,7 @@ def configure_node(
node.path_parameters = {}

if isinstance(route, HTTPRoute):
for method, handler_mapping in route.route_handler_map.items():
handler, _ = handler_mapping
for method, handler in route.route_handler_map.items():
node.asgi_handlers[method] = ASGIHandlerTuple(
asgi_app=build_route_middleware_stack(app=app, route=route, route_handler=handler),
handler=handler,
Expand Down
4 changes: 1 addition & 3 deletions litestar/_openapi/path_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ def create_path_item(self) -> PathItem:
Returns:
A PathItem instance.
"""
for http_method, handler_tuple in self.route.route_handler_map.items():
route_handler, _ = handler_tuple

for http_method, route_handler in self.route.route_handler_map.items():
if not route_handler.resolve_include_in_schema():
continue

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.resolve_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
11 changes: 2 additions & 9 deletions litestar/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
)
from litestar.plugins.base import CLIPlugin
from litestar.router import Router
from litestar.routes import ASGIRoute, HTTPRoute, WebSocketRoute
from litestar.stores.registry import StoreRegistry
from litestar.types import Empty, TypeDecodersSequence
from litestar.types.internal_types import PathParameterDefinition, TemplateConfigType
Expand All @@ -66,6 +65,7 @@
from litestar.openapi.spec import SecurityRequirement
from litestar.openapi.spec.open_api import OpenAPI
from litestar.response import Response
from litestar.routes import ASGIRoute, HTTPRoute, WebSocketRoute
from litestar.stores.base import Store
from litestar.types import (
AfterExceptionHookHandler,
Expand Down Expand Up @@ -648,14 +648,7 @@ def register(self, value: ControllerRouterHandler) -> None: # type: ignore[over
route_handlers = get_route_handlers(route)

for route_handler in route_handlers:
route_handler.on_registration(self)

if isinstance(route, HTTPRoute):
route.create_handler_map()

elif isinstance(route, WebSocketRoute):
handler = route.route_handler
route.handler_parameter_model = handler.create_kwargs_model(path_parameters=route.path_parameters)
route_handler.on_registration(self, route=route)

for plugin in self.plugins.receive_route:
plugin.receive_route(route)
Expand Down
18 changes: 18 additions & 0 deletions litestar/handlers/asgi_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@


if TYPE_CHECKING:
from litestar.connection import ASGIConnection
from litestar.types import (
ExceptionHandlersMap,
Guard,
Expand Down Expand Up @@ -82,5 +83,22 @@ def _validate_handler_function(self) -> None:
if not is_async_callable(self.fn):
raise ImproperlyConfiguredException("Functions decorated with 'asgi' must be async functions")

async def handle(self, connection: ASGIConnection[ASGIRouteHandler, Any, Any, Any]) -> None:
"""ASGI app that authorizes the connection and then awaits the handler function.

.. versionadded: 3.0

Args:
connection: The ASGI connection

Returns:
None
"""

if self.resolve_guards():
await self.authorize_connection(connection=connection)

await self.fn(scope=connection.scope, receive=connection.receive, send=connection.send)


asgi = ASGIRouteHandler
13 changes: 7 additions & 6 deletions litestar/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from copy import copy
from functools import partial
from typing import TYPE_CHECKING, Any, Callable, Mapping, Sequence, cast
from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping, Sequence, cast

from litestar._signature import SignatureModel
from litestar.di import Provide
Expand Down Expand Up @@ -34,9 +34,9 @@
from litestar.dto import AbstractDTO
from litestar.params import ParameterKwarg
from litestar.router import Router
from litestar.routes import BaseRoute
from litestar.types import AnyCallable, AsyncAnyCallable, ExceptionHandler
from litestar.types.empty import EmptyType
from litestar.types.internal_types import PathParameterDefinition

__all__ = ("BaseRouteHandler",)

Expand Down Expand Up @@ -526,11 +526,12 @@ def _validate_dependency_is_unique(dependencies: dict[str, Provide], key: str, p
f"If you wish to override a provider, it must have the same key."
)

def on_registration(self, app: Litestar) -> None:
def on_registration(self, app: Litestar, route: BaseRoute) -> None:
"""Called once per handler when the app object is instantiated.

Args:
app: The :class:`Litestar<.app.Litestar>` app object.
route: The route this handler is being registered on

Returns:
None
Expand Down Expand Up @@ -567,9 +568,9 @@ def __str__(self) -> str:
target = type(target)
return f"{target.__module__}.{target.__qualname__}"

def create_kwargs_model(
def _create_kwargs_model(
self,
path_parameters: dict[str, PathParameterDefinition],
path_parameters: Iterable[str],
) -> KwargsModel:
"""Create a `KwargsModel` for a given route handler."""
from litestar._kwargs import KwargsModel
Expand All @@ -578,6 +579,6 @@ def create_kwargs_model(
signature_model=self.signature_model,
parsed_signature=self.parsed_fn_signature,
dependencies=self.resolve_dependencies(),
path_parameters=set(path_parameters.keys()),
path_parameters=set(path_parameters),
layered_parameters=self.resolve_layered_parameters(),
)
40 changes: 40 additions & 0 deletions litestar/handlers/http_handlers/_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Iterable

from litestar.enums import HttpMethod, MediaType
from litestar.handlers import HTTPRouteHandler
from litestar.response import Response
from litestar.status_codes import HTTP_204_NO_CONTENT

if TYPE_CHECKING:
from litestar.types import Method


def create_options_handler(path: str, allow_methods: Iterable[Method]) -> HTTPRouteHandler:
"""Args:
path: The route path

Returns:
An HTTP route handler for OPTIONS requests.
"""

def options_handler() -> Response:
"""Handler function for OPTIONS requests.

Returns:
Response
"""
return Response(
content=None,
status_code=HTTP_204_NO_CONTENT,
headers={"Allow": ", ".join(sorted(allow_methods))}, # pyright: ignore
media_type=MediaType.TEXT,
)

return HTTPRouteHandler(
path=path,
http_method=[HttpMethod.OPTIONS],
include_in_schema=False,
sync_to_thread=False,
)(options_handler)
7 changes: 7 additions & 0 deletions litestar/handlers/http_handlers/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from inspect import isawaitable
from typing import TYPE_CHECKING, Any, Sequence, cast

from litestar.datastructures import UploadFile
from litestar.enums import HttpMethod
from litestar.exceptions import ValidationException
from litestar.response import Response
Expand Down Expand Up @@ -215,3 +216,9 @@ def is_empty_response_annotation(return_annotation: FieldDefinition) -> bool:


HTTP_METHOD_NAMES = {m.value for m in HttpMethod}


async def cleanup_temporary_files(form_data: dict[str, Any]) -> None:
for v in form_data.values():
if isinstance(v, UploadFile) and not v.file.closed:
await v.close()
Loading
Loading