Finish moving some more logic to mixins
This commit is contained in:
parent
dadf76ce72
commit
e9459792a4
48
sanic/app.py
48
sanic/app.py
|
@ -23,6 +23,7 @@ from sanic.blueprints import Blueprint
|
||||||
from sanic.config import BASE_LOGO, Config
|
from sanic.config import BASE_LOGO, Config
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.exceptions import (
|
from sanic.exceptions import (
|
||||||
|
InvalidUsage,
|
||||||
NotFound,
|
NotFound,
|
||||||
SanicException,
|
SanicException,
|
||||||
ServerError,
|
ServerError,
|
||||||
|
@ -31,9 +32,17 @@ from sanic.exceptions import (
|
||||||
from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType
|
from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType
|
||||||
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
||||||
from sanic.mixins.base import BaseMixin
|
from sanic.mixins.base import BaseMixin
|
||||||
|
from sanic.mixins.exceptions import ExceptionMixin
|
||||||
|
from sanic.mixins.listeners import ListenerEvent, ListenerMixin
|
||||||
from sanic.mixins.middleware import MiddlewareMixin
|
from sanic.mixins.middleware import MiddlewareMixin
|
||||||
from sanic.mixins.routes import RouteMixin
|
from sanic.mixins.routes import RouteMixin
|
||||||
from sanic.models.futures import FutureMiddleware, FutureRoute, FutureStatic
|
from sanic.models.futures import (
|
||||||
|
FutureException,
|
||||||
|
FutureListener,
|
||||||
|
FutureMiddleware,
|
||||||
|
FutureRoute,
|
||||||
|
FutureStatic,
|
||||||
|
)
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import BaseHTTPResponse, HTTPResponse
|
from sanic.response import BaseHTTPResponse, HTTPResponse
|
||||||
from sanic.router import Router
|
from sanic.router import Router
|
||||||
|
@ -49,7 +58,9 @@ from sanic.views import CompositionView
|
||||||
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
||||||
|
|
||||||
|
|
||||||
class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
class Sanic(
|
||||||
|
BaseMixin, RouteMixin, MiddlewareMixin, ListenerMixin, ExceptionMixin
|
||||||
|
):
|
||||||
_app_registry: Dict[str, "Sanic"] = {}
|
_app_registry: Dict[str, "Sanic"] = {}
|
||||||
test_mode = False
|
test_mode = False
|
||||||
|
|
||||||
|
@ -144,17 +155,8 @@ class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def listener(self, event):
|
def _apply_listener(self, listener: FutureListener):
|
||||||
"""Create a listener from a decorated function.
|
return self.register_listener(listener.listener, listener.event)
|
||||||
|
|
||||||
:param event: event to listen to
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(listener):
|
|
||||||
self.listeners[event].append(listener)
|
|
||||||
return listener
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def register_listener(self, listener, event):
|
def register_listener(self, listener, event):
|
||||||
"""
|
"""
|
||||||
|
@ -165,7 +167,14 @@ class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||||
:return: listener
|
:return: listener
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.listener(event)(listener)
|
try:
|
||||||
|
_event = ListenerEvent(event)
|
||||||
|
except ValueError:
|
||||||
|
valid = ", ".join(ListenerEvent.__members__.values())
|
||||||
|
raise InvalidUsage(f"Invalid event: {event}. Use one of: {valid}")
|
||||||
|
|
||||||
|
self.listeners[_event].append(listener)
|
||||||
|
return listener
|
||||||
|
|
||||||
def _apply_route(self, route: FutureRoute) -> Route:
|
def _apply_route(self, route: FutureRoute) -> Route:
|
||||||
return self.router.add(**route._asdict())
|
return self.router.add(**route._asdict())
|
||||||
|
@ -187,24 +196,21 @@ class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||||
self.websocket_enabled = enable
|
self.websocket_enabled = enable
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def exception(self, *exceptions):
|
def _apply_exception_handler(self, handler: FutureException):
|
||||||
"""Decorate a function to be registered as a handler for exceptions
|
"""Decorate a function to be registered as a handler for exceptions
|
||||||
|
|
||||||
:param exceptions: exceptions
|
:param exceptions: exceptions
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def response(handler):
|
for exception in handler.exceptions:
|
||||||
for exception in exceptions:
|
|
||||||
if isinstance(exception, (tuple, list)):
|
if isinstance(exception, (tuple, list)):
|
||||||
for e in exception:
|
for e in exception:
|
||||||
self.error_handler.add(e, handler)
|
self.error_handler.add(e, handler.handler)
|
||||||
else:
|
else:
|
||||||
self.error_handler.add(exception, handler)
|
self.error_handler.add(exception, handler.handler)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def register_middleware(self, middleware, attach_to="request"):
|
def register_middleware(self, middleware, attach_to="request"):
|
||||||
"""
|
"""
|
||||||
Register an application level middleware that will be attached
|
Register an application level middleware that will be attached
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict
|
||||||
|
|
||||||
from sanic.blueprint_group import BlueprintGroup
|
from sanic.blueprint_group import BlueprintGroup
|
||||||
from sanic.constants import HTTP_METHODS
|
|
||||||
from sanic.mixins.base import BaseMixin
|
from sanic.mixins.base import BaseMixin
|
||||||
|
from sanic.mixins.exceptions import ExceptionMixin
|
||||||
|
from sanic.mixins.listeners import ListenerMixin
|
||||||
from sanic.mixins.middleware import MiddlewareMixin
|
from sanic.mixins.middleware import MiddlewareMixin
|
||||||
from sanic.mixins.routes import RouteMixin
|
from sanic.mixins.routes import RouteMixin
|
||||||
from sanic.models.futures import (
|
from sanic.models.futures import FutureRoute, FutureStatic
|
||||||
FutureException,
|
|
||||||
FutureListener,
|
|
||||||
FutureMiddleware,
|
|
||||||
FutureRoute,
|
|
||||||
FutureStatic,
|
|
||||||
)
|
|
||||||
from sanic.views import CompositionView
|
|
||||||
|
|
||||||
|
|
||||||
class Blueprint(BaseMixin, RouteMixin, MiddlewareMixin):
|
class Blueprint(
|
||||||
|
BaseMixin, RouteMixin, MiddlewareMixin, ListenerMixin, ExceptionMixin
|
||||||
|
):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
|
@ -61,6 +57,14 @@ class Blueprint(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||||
kwargs["apply"] = False
|
kwargs["apply"] = False
|
||||||
return super().middleware(*args, **kwargs)
|
return super().middleware(*args, **kwargs)
|
||||||
|
|
||||||
|
def listener(self, *args, **kwargs):
|
||||||
|
kwargs["apply"] = False
|
||||||
|
return super().listener(*args, **kwargs)
|
||||||
|
|
||||||
|
def exception(self, *args, **kwargs):
|
||||||
|
kwargs["apply"] = False
|
||||||
|
return super().exception(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def group(*blueprints, url_prefix=""):
|
def group(*blueprints, url_prefix=""):
|
||||||
"""
|
"""
|
||||||
|
@ -103,9 +107,6 @@ class Blueprint(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||||
|
|
||||||
routes = []
|
routes = []
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# - Add BP name to handler name for all routes
|
|
||||||
|
|
||||||
# Routes
|
# Routes
|
||||||
for future in self._future_routes:
|
for future in self._future_routes:
|
||||||
# attach the blueprint name to the handler so that it can be
|
# attach the blueprint name to the handler so that it can be
|
||||||
|
@ -144,45 +145,12 @@ class Blueprint(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||||
app._apply_middleware(future, route_names)
|
app._apply_middleware(future, route_names)
|
||||||
|
|
||||||
# Exceptions
|
# Exceptions
|
||||||
for future in self.exceptions:
|
for future in self._future_exceptions:
|
||||||
app.exception(*future.args, **future.kwargs)(future.handler)
|
app._apply_exception_handler(future)
|
||||||
|
|
||||||
# Event listeners
|
# Event listeners
|
||||||
for event, listeners in self.listeners.items():
|
for listener in self._future_listeners:
|
||||||
for listener in listeners:
|
app._apply_listener(listener)
|
||||||
app.listener(event)(listener)
|
|
||||||
|
|
||||||
def listener(self, event):
|
|
||||||
"""Create a listener from a decorated function.
|
|
||||||
|
|
||||||
:param event: Event to listen to.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(listener):
|
|
||||||
self.listeners[event].append(listener)
|
|
||||||
return listener
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def exception(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
This method enables the process of creating a global exception
|
|
||||||
handler for the current blueprint under question.
|
|
||||||
|
|
||||||
:param args: List of Python exceptions to be caught by the handler
|
|
||||||
:param kwargs: Additional optional arguments to be passed to the
|
|
||||||
exception handler
|
|
||||||
|
|
||||||
:return a decorated method to handle global exceptions for any
|
|
||||||
route registered under this blueprint.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(handler):
|
|
||||||
exception = FutureException(handler, args, kwargs)
|
|
||||||
self.exceptions.append(exception)
|
|
||||||
return handler
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def _generate_name(self, handler, name: str) -> str:
|
def _generate_name(self, handler, name: str) -> str:
|
||||||
return f"{self.name}.{name or handler.__name__}"
|
return f"{self.name}.{name or handler.__name__}"
|
||||||
|
|
38
sanic/mixins/exceptions.py
Normal file
38
sanic/mixins/exceptions.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from enum import Enum, auto
|
||||||
|
from functools import partial
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from sanic.models.futures import FutureException
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionMixin:
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
self._future_exceptions: Set[FutureException] = set()
|
||||||
|
|
||||||
|
def _apply_exception_handler(self, handler: FutureException):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def exception(self, *exceptions, apply=True):
|
||||||
|
"""
|
||||||
|
This method enables the process of creating a global exception
|
||||||
|
handler for the current blueprint under question.
|
||||||
|
|
||||||
|
:param args: List of Python exceptions to be caught by the handler
|
||||||
|
:param kwargs: Additional optional arguments to be passed to the
|
||||||
|
exception handler
|
||||||
|
|
||||||
|
:return a decorated method to handle global exceptions for any
|
||||||
|
route registered under this blueprint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(handler):
|
||||||
|
nonlocal apply
|
||||||
|
nonlocal exceptions
|
||||||
|
|
||||||
|
future_exception = FutureException(handler, exceptions)
|
||||||
|
self._future_exceptions.add(future_exception)
|
||||||
|
if apply:
|
||||||
|
self._apply_exception_handler(future_exception)
|
||||||
|
return handler
|
||||||
|
|
||||||
|
return decorator
|
55
sanic/mixins/listeners.py
Normal file
55
sanic/mixins/listeners.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
from enum import Enum, auto
|
||||||
|
from functools import partial
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from sanic.models.futures import FutureListener
|
||||||
|
|
||||||
|
|
||||||
|
class ListenerEvent(str, Enum):
|
||||||
|
def _generate_next_value_(name: str, *args) -> str: # type: ignore
|
||||||
|
return name.lower()
|
||||||
|
|
||||||
|
BEFORE_SERVER_START = auto()
|
||||||
|
AFTER_SERVER_START = auto()
|
||||||
|
BEFORE_SERVER_STOP = auto()
|
||||||
|
AFTER_SERVER_STOP = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ListenerMixin:
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
self._future_listeners: Set[FutureListener] = set()
|
||||||
|
|
||||||
|
def _apply_listener(self, listener: FutureListener):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def listener(self, listener_or_event, event_or_none=None, apply=True):
|
||||||
|
"""Create a listener from a decorated function.
|
||||||
|
|
||||||
|
:param event: Event to listen to.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_listener(listener, event):
|
||||||
|
nonlocal apply
|
||||||
|
|
||||||
|
future_listener = FutureListener(listener, event)
|
||||||
|
self._future_listeners.add(future_listener)
|
||||||
|
if apply:
|
||||||
|
self._apply_listener(future_listener)
|
||||||
|
return listener
|
||||||
|
|
||||||
|
if callable(listener_or_event):
|
||||||
|
return register_listener(listener_or_event, event_or_none)
|
||||||
|
else:
|
||||||
|
return partial(register_listener, event=listener_or_event)
|
||||||
|
|
||||||
|
def before_server_start(self, listener):
|
||||||
|
return self.listener(listener, "before_server_start")
|
||||||
|
|
||||||
|
def after_server_start(self, listener):
|
||||||
|
return self.listener(listener, "after_server_start")
|
||||||
|
|
||||||
|
def before_server_stop(self, listener):
|
||||||
|
return self.listener(listener, "before_server_stop")
|
||||||
|
|
||||||
|
def after_server_stop(self, listener):
|
||||||
|
return self.listener(listener, "after_server_stop")
|
|
@ -23,12 +23,14 @@ class MiddlewareMixin:
|
||||||
identifying which type of middleware is being registered.
|
identifying which type of middleware is being registered.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def register_middleware(_middleware, attach_to="request"):
|
def register_middleware(middleware, attach_to="request"):
|
||||||
future_middleware = FutureMiddleware(_middleware, attach_to)
|
nonlocal apply
|
||||||
|
|
||||||
|
future_middleware = FutureMiddleware(middleware, attach_to)
|
||||||
self._future_middleware.add(future_middleware)
|
self._future_middleware.add(future_middleware)
|
||||||
if apply:
|
if apply:
|
||||||
self._apply_middleware(future_middleware)
|
self._apply_middleware(future_middleware)
|
||||||
return _middleware
|
return middleware
|
||||||
|
|
||||||
# Detect which way this was called, @middleware or @middleware('AT')
|
# Detect which way this was called, @middleware or @middleware('AT')
|
||||||
if callable(middleware_or_request):
|
if callable(middleware_or_request):
|
||||||
|
@ -39,3 +41,9 @@ class MiddlewareMixin:
|
||||||
return partial(
|
return partial(
|
||||||
register_middleware, attach_to=middleware_or_request
|
register_middleware, attach_to=middleware_or_request
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def on_request(self, middleware):
|
||||||
|
return self.middleware(middleware, "request")
|
||||||
|
|
||||||
|
def on_response(self, middleware):
|
||||||
|
return self.middleware(middleware, "response")
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import signature
|
from inspect import signature
|
||||||
from pathlib import PurePath
|
from pathlib import PurePath
|
||||||
from typing import List, Set, Union
|
from typing import Set, Union
|
||||||
|
|
||||||
import websockets
|
|
||||||
|
|
||||||
from sanic_routing.route import Route
|
from sanic_routing.route import Route
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,9 @@ FutureRoute = namedtuple(
|
||||||
"ignore_body",
|
"ignore_body",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
FutureListener = namedtuple(
|
FutureListener = namedtuple("FutureListener", ["listener", "event"])
|
||||||
"FutureListener", ["handler", "uri", "methods", "host"]
|
|
||||||
)
|
|
||||||
FutureMiddleware = namedtuple("FutureMiddleware", ["middleware", "attach_to"])
|
FutureMiddleware = namedtuple("FutureMiddleware", ["middleware", "attach_to"])
|
||||||
FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"])
|
FutureException = namedtuple("FutureException", ["handler", "exceptions"])
|
||||||
FutureStatic = namedtuple(
|
FutureStatic = namedtuple(
|
||||||
"FutureStatic",
|
"FutureStatic",
|
||||||
[
|
[
|
||||||
|
|
|
@ -4,7 +4,6 @@ from sanic_routing import BaseRouter
|
||||||
from sanic_routing.route import Route
|
from sanic_routing.route import Route
|
||||||
|
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.log import logger
|
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ from os import path
|
||||||
from pathlib import PurePath
|
from pathlib import PurePath
|
||||||
from re import sub
|
from re import sub
|
||||||
from time import gmtime, strftime
|
from time import gmtime, strftime
|
||||||
from typing import Union
|
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from sanic.compat import stat_async
|
from sanic.compat import stat_async
|
||||||
|
|
Loading…
Reference in New Issue
Block a user