Skip to content

Commit ede5e71

Browse files
committed
✨ Path params component (#171)
1 parent 1ba203d commit ede5e71

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3638
-2673
lines changed

flama/__init__.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from flama.applications import * # noqa
22
from flama.background import * # noqa
33
from flama.cli import * # noqa
4-
from flama.endpoints import * # noqa
5-
from flama.injection.components import Component # noqa
6-
from flama.modules import Module # noqa
7-
from flama.routing import * # noqa
4+
from flama.injection.components import * # noqa
5+
from flama.modules import * # noqa
86
from flama.serialize import * # noqa

flama/applications.py

+17-19
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
import threading
44
import typing as t
55

6-
from flama import asgi, exceptions, http, injection, types, url, validation, websockets
6+
from flama import asgi, exceptions, http, injection, routing, types, url, validation, websockets
77
from flama.ddd.components import WorkerComponent
88
from flama.events import Events
99
from flama.middleware import MiddlewareStack
1010
from flama.models.modules import ModelsModule
1111
from flama.modules import Modules
1212
from flama.pagination import paginator
1313
from flama.resources import ResourcesModule
14-
from flama.routing import BaseRoute, Router
1514
from flama.schemas.modules import SchemaModule
1615

1716
try:
@@ -23,7 +22,6 @@
2322
from flama.middleware import Middleware
2423
from flama.modules import Module
2524
from flama.pagination.types import PaginationType
26-
from flama.routing import Mount, Route, WebSocketRoute
2725

2826
__all__ = ["Flama"]
2927

@@ -33,7 +31,7 @@
3331
class Flama:
3432
def __init__(
3533
self,
36-
routes: t.Optional[t.Sequence[t.Union["BaseRoute", "Mount"]]] = None,
34+
routes: t.Optional[t.Sequence["routing.BaseRoute"]] = None,
3735
components: t.Optional[t.Union[t.Sequence[injection.Component], set[injection.Component]]] = None,
3836
modules: t.Optional[t.Union[t.Sequence["Module"], set["Module"]]] = None,
3937
middleware: t.Optional[t.Sequence["Middleware"]] = None,
@@ -75,8 +73,7 @@ def __init__(
7573
"send": types.Send,
7674
"exc": Exception,
7775
"app": Flama,
78-
"path_params": types.PathParams,
79-
"route": BaseRoute,
76+
"route": routing.BaseRoute,
8077
"request": http.Request,
8178
"response": http.Response,
8279
"websocket": websockets.WebSocket,
@@ -102,7 +99,7 @@ def __init__(
10299
self.modules = Modules(app=self, modules={*default_modules, *(modules or [])})
103100

104101
# Initialize router
105-
self.app = self.router = Router(
102+
self.app = self.router = routing.Router(
106103
routes=routes, components=[*default_components, *(components or [])], lifespan=lifespan
107104
)
108105

@@ -144,11 +141,12 @@ async def __call__(self, scope: types.Scope, receive: types.Receive, send: types
144141
:param receive: ASGI receive event.
145142
:param send: ASGI send event.
146143
"""
147-
if scope["type"] != "lifespan" and self.status in (types.AppStatus.NOT_STARTED, types.AppStatus.STARTING):
148-
raise exceptions.ApplicationError("Application is not ready to process requests yet.")
144+
if scope["type"] != "lifespan":
145+
if self.status in (types.AppStatus.NOT_STARTED, types.AppStatus.STARTING):
146+
raise exceptions.ApplicationError("Application is not ready to process requests yet.")
149147

150-
if scope["type"] != "lifespan" and self.status in (types.AppStatus.SHUT_DOWN, types.AppStatus.SHUTTING_DOWN):
151-
raise exceptions.ApplicationError("Application is already shut down.")
148+
elif self.status in (types.AppStatus.SHUT_DOWN, types.AppStatus.SHUTTING_DOWN):
149+
raise exceptions.ApplicationError("Application is already shut down.")
152150

153151
scope["app"] = self
154152
scope.setdefault("root_app", self)
@@ -182,7 +180,7 @@ def add_component(self, component: injection.Component):
182180
self.router.build(self)
183181

184182
@property
185-
def routes(self) -> list["BaseRoute"]:
183+
def routes(self) -> list["routing.BaseRoute"]:
186184
"""List of registered routes.
187185
188186
:return: Routes.
@@ -196,10 +194,10 @@ def add_route(
196194
methods: t.Optional[list[str]] = None,
197195
name: t.Optional[str] = None,
198196
include_in_schema: bool = True,
199-
route: t.Optional["Route"] = None,
197+
route: t.Optional["routing.Route"] = None,
200198
pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
201199
tags: t.Optional[dict[str, t.Any]] = None,
202-
) -> "Route":
200+
) -> "routing.Route":
203201
"""Register a new HTTP route or endpoint under given path.
204202
205203
:param path: URL path.
@@ -257,10 +255,10 @@ def add_websocket_route(
257255
path: t.Optional[str] = None,
258256
endpoint: t.Optional[types.WebSocketHandler] = None,
259257
name: t.Optional[str] = None,
260-
route: t.Optional["WebSocketRoute"] = None,
258+
route: t.Optional["routing.WebSocketRoute"] = None,
261259
pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
262260
tags: t.Optional[dict[str, t.Any]] = None,
263-
) -> "WebSocketRoute":
261+
) -> "routing.WebSocketRoute":
264262
"""Register a new websocket route or endpoint under given path.
265263
266264
:param path: URL path.
@@ -296,9 +294,9 @@ def mount(
296294
path: t.Optional[str] = None,
297295
app: t.Optional[types.App] = None,
298296
name: t.Optional[str] = None,
299-
mount: t.Optional["Mount"] = None,
297+
mount: t.Optional["routing.Mount"] = None,
300298
tags: t.Optional[dict[str, t.Any]] = None,
301-
) -> "Mount":
299+
) -> "routing.Mount":
302300
"""Register a new mount point containing an ASGI app in this router under given path.
303301
304302
:param path: URL path.
@@ -366,7 +364,7 @@ def resolve_url(self, name: str, **path_params: t.Any) -> url.URL:
366364
"""
367365
return self.router.resolve_url(name, **path_params)
368366

369-
def resolve_route(self, scope: types.Scope) -> tuple[BaseRoute, types.Scope]:
367+
def resolve_route(self, scope: types.Scope) -> tuple[routing.BaseRoute, types.Scope]:
370368
"""Look for a route that matches given ASGI scope.
371369
372370
:param scope: ASGI scope.

flama/asgi.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from http.cookies import SimpleCookie
22
from urllib.parse import parse_qsl
33

4-
from flama import http, types
4+
from flama import http, routing, types
55
from flama.injection.components import Component, Components
66

77
__all__ = [
@@ -11,6 +11,7 @@
1111
"HostComponent",
1212
"PortComponent",
1313
"PathComponent",
14+
"PathParamsComponent",
1415
"QueryStringComponent",
1516
"QueryParamsComponent",
1617
"HeadersComponent",
@@ -60,7 +61,12 @@ def resolve(self, scope: types.Scope) -> types.Port:
6061

6162
class PathComponent(Component):
6263
def resolve(self, scope: types.Scope) -> types.Path:
63-
return types.Path(scope.get("root_path", "") + scope["path"])
64+
return types.Path(scope.get("root_path", "")) / types.Path(scope["path"])
65+
66+
67+
class PathParamsComponent(Component):
68+
def resolve(self, scope: types.Scope, route: routing.BaseRoute) -> types.PathParams:
69+
return types.PathParams(route.path.match(scope["path"]).parameters or {})
6470

6571

6672
class QueryStringComponent(Component):
@@ -69,9 +75,8 @@ def resolve(self, scope: types.Scope) -> types.QueryString:
6975

7076

7177
class QueryParamsComponent(Component):
72-
def resolve(self, scope: types.Scope) -> types.QueryParams:
73-
query_string = scope["query_string"].decode()
74-
return types.QueryParams(parse_qsl(query_string))
78+
def resolve(self, query: types.QueryString) -> types.QueryParams:
79+
return types.QueryParams(parse_qsl(query))
7580

7681

7782
class HeadersComponent(Component):
@@ -113,6 +118,7 @@ async def resolve(self, receive: types.Receive) -> types.Body:
113118
HostComponent(),
114119
PortComponent(),
115120
PathComponent(),
121+
PathParamsComponent(),
116122
QueryStringComponent(),
117123
QueryParamsComponent(),
118124
HeadersComponent(),

flama/endpoints.py

-172
This file was deleted.

flama/endpoints/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from flama.endpoints.base import * # noqa
2+
from flama.endpoints.http import * # noqa
3+
from flama.endpoints.websocket import * # noqa

flama/endpoints/base.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import typing as t
2+
3+
from flama import types
4+
5+
__all__ = ["BaseEndpoint"]
6+
7+
8+
class BaseEndpoint(types.EndpointProtocol):
9+
def __init__(self, scope: "types.Scope", receive: "types.Receive", send: "types.Send") -> None:
10+
"""An endpoint.
11+
12+
:param scope: ASGI scope.
13+
:param receive: ASGI receive function.
14+
:param send: ASGI send function.
15+
"""
16+
app = scope["app"]
17+
scope["path"] = scope.get("root_path", "").rstrip("/") + scope["path"]
18+
scope["root_path"] = ""
19+
route, route_scope = app.router.resolve_route(scope)
20+
self.state = {
21+
"scope": route_scope,
22+
"receive": receive,
23+
"send": send,
24+
"exc": None,
25+
"app": app,
26+
"root_app": scope["root_app"],
27+
"path_params": route_scope.get("path_params", {}),
28+
"route": route,
29+
}
30+
31+
def __await__(self) -> t.Generator:
32+
return self.dispatch().__await__()

0 commit comments

Comments
 (0)