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

Add route context #2302

Merged
merged 15 commits into from Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions sanic/app.py
Expand Up @@ -384,12 +384,16 @@ def _apply_route(self, route: FutureRoute) -> List[Route]:
websocket_handler.is_websocket = True # type: ignore
params["handler"] = websocket_handler

ctx = params.pop("route_context")

routes = self.router.add(**params)
if isinstance(routes, Route):
routes = [routes]

for r in routes:
r.ctx.websocket = websocket
r.ctx.static = params.get("static", False)
r.ctx.__dict__.update(ctx)

return routes

Expand Down
1 change: 1 addition & 0 deletions sanic/blueprints.py
Expand Up @@ -326,6 +326,7 @@ def register(self, app, options):
future.static,
version_prefix,
error_format,
future.route_context,
)

route = app._apply_route(apply_route)
Expand Down
48 changes: 48 additions & 0 deletions sanic/mixins/routes.py
Expand Up @@ -26,12 +26,21 @@
from sanic.models.futures import FutureRoute, FutureStatic
from sanic.models.handler_types import RouteHandler
from sanic.response import HTTPResponse, file, file_stream
from sanic.types import HashableDict
from sanic.views import CompositionView


RouteWrapper = Callable[
[RouteHandler], Union[RouteHandler, Tuple[Route, RouteHandler]]
]
RESTRICTED_ROUTE_CONTEXT = (
"ignore_body",
"stream",
"hosts",
"static",
"error_format",
"websocket",
)


class RouteMixin:
Expand Down Expand Up @@ -65,6 +74,7 @@ def route(
static: bool = False,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
ahopkins marked this conversation as resolved.
Show resolved Hide resolved
) -> RouteWrapper:
"""
Decorate a function to be registered as a route
Expand Down Expand Up @@ -94,6 +104,8 @@ def route(
if not methods and not websocket:
methods = frozenset({"GET"})

route_context = self._build_route_context(kwargs)

def decorator(handler):
nonlocal uri
nonlocal methods
Expand Down Expand Up @@ -152,6 +164,7 @@ def decorator(handler):
static,
version_prefix,
error_format,
route_context,
)

self._future_routes.add(route)
Expand Down Expand Up @@ -196,6 +209,7 @@ def add_route(
stream: bool = False,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
) -> RouteHandler:
"""A helper method to register class instance or
functions as a handler to the application url
Expand Down Expand Up @@ -247,6 +261,7 @@ def add_route(
name=name,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)(handler)
return handler

Expand All @@ -261,6 +276,7 @@ def get(
ignore_body: bool = True,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
) -> RouteWrapper:
"""
Add an API URL under the **GET** *HTTP* method
Expand All @@ -285,6 +301,7 @@ def get(
ignore_body=ignore_body,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)

def post(
Expand All @@ -297,6 +314,7 @@ def post(
name: Optional[str] = None,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
) -> RouteWrapper:
"""
Add an API URL under the **POST** *HTTP* method
Expand All @@ -321,6 +339,7 @@ def post(
name=name,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)

def put(
Expand All @@ -333,6 +352,7 @@ def put(
name: Optional[str] = None,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
) -> RouteWrapper:
"""
Add an API URL under the **PUT** *HTTP* method
Expand All @@ -357,6 +377,7 @@ def put(
name=name,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)

def head(
Expand All @@ -369,6 +390,7 @@ def head(
ignore_body: bool = True,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
) -> RouteWrapper:
"""
Add an API URL under the **HEAD** *HTTP* method
Expand Down Expand Up @@ -401,6 +423,7 @@ def head(
ignore_body=ignore_body,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)

def options(
Expand All @@ -413,6 +436,7 @@ def options(
ignore_body: bool = True,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
) -> RouteWrapper:
"""
Add an API URL under the **OPTIONS** *HTTP* method
Expand Down Expand Up @@ -445,6 +469,7 @@ def options(
ignore_body=ignore_body,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)

def patch(
Expand All @@ -457,6 +482,7 @@ def patch(
name: Optional[str] = None,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
) -> RouteWrapper:
"""
Add an API URL under the **PATCH** *HTTP* method
Expand Down Expand Up @@ -491,6 +517,7 @@ def patch(
name=name,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)

def delete(
Expand All @@ -503,6 +530,7 @@ def delete(
ignore_body: bool = True,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
) -> RouteWrapper:
"""
Add an API URL under the **DELETE** *HTTP* method
Expand All @@ -527,6 +555,7 @@ def delete(
ignore_body=ignore_body,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)

def websocket(
Expand All @@ -540,6 +569,7 @@ def websocket(
apply: bool = True,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
):
"""
Decorate a function to be registered as a websocket route
Expand Down Expand Up @@ -567,6 +597,7 @@ def websocket(
websocket=True,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)

def add_websocket_route(
Expand All @@ -580,6 +611,7 @@ def add_websocket_route(
name: Optional[str] = None,
version_prefix: str = "/v",
error_format: Optional[str] = None,
**kwargs,
):
"""
A helper method to register a function as a websocket route.
Expand Down Expand Up @@ -609,6 +641,7 @@ def add_websocket_route(
name=name,
version_prefix=version_prefix,
error_format=error_format,
**kwargs,
)(handler)

def static(
Expand Down Expand Up @@ -957,3 +990,18 @@ def visit_Return(self, node: Return) -> Any:
HttpResponseVisitor().visit(node)

return types

def _build_route_context(self, raw):
ctx_kwargs = {
key.replace("ctx_", ""): value
for key, value in raw.items()
if key.startswith("ctx_")
}
restricted = [
key for key in ctx_kwargs.keys() if key in RESTRICTED_ROUTE_CONTEXT
]
if restricted:
raise AttributeError(
f"Cannot use restricted route context: {restricted}."
)
return HashableDict(ctx_kwargs)
ahopkins marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 1 addition & 5 deletions sanic/mixins/signals.py
Expand Up @@ -3,11 +3,7 @@
from sanic.models.futures import FutureSignal
from sanic.models.handler_types import SignalHandler
from sanic.signals import Signal


class HashableDict(dict):
def __hash__(self):
return hash(tuple(sorted(self.items())))
from sanic.types import HashableDict


class SignalMixin:
Expand Down
2 changes: 2 additions & 0 deletions sanic/models/futures.py
Expand Up @@ -7,6 +7,7 @@
MiddlewareType,
SignalHandler,
)
from sanic.types import HashableDict


class FutureRoute(NamedTuple):
Expand All @@ -25,6 +26,7 @@ class FutureRoute(NamedTuple):
static: bool
version_prefix: str
error_format: Optional[str]
route_context: HashableDict


class FutureListener(NamedTuple):
Expand Down
4 changes: 4 additions & 0 deletions sanic/types/__init__.py
@@ -0,0 +1,4 @@
from .hashable_dict import HashableDict


__all__ = ("HashableDict",)
3 changes: 3 additions & 0 deletions sanic/types/hashable_dict.py
@@ -0,0 +1,3 @@
class HashableDict(dict):
def __hash__(self):
return hash(tuple(sorted(self.items())))