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.constants import HTTP_METHODS
|
||||
from sanic.exceptions import (
|
||||
InvalidUsage,
|
||||
NotFound,
|
||||
SanicException,
|
||||
ServerError,
|
||||
|
@ -31,9 +32,17 @@ from sanic.exceptions import (
|
|||
from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType
|
||||
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
||||
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.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.response import BaseHTTPResponse, HTTPResponse
|
||||
from sanic.router import Router
|
||||
|
@ -49,7 +58,9 @@ from sanic.views import CompositionView
|
|||
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
||||
|
||||
|
||||
class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||
class Sanic(
|
||||
BaseMixin, RouteMixin, MiddlewareMixin, ListenerMixin, ExceptionMixin
|
||||
):
|
||||
_app_registry: Dict[str, "Sanic"] = {}
|
||||
test_mode = False
|
||||
|
||||
|
@ -144,17 +155,8 @@ class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
|||
)
|
||||
|
||||
# Decorator
|
||||
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 _apply_listener(self, listener: FutureListener):
|
||||
return self.register_listener(listener.listener, listener.event)
|
||||
|
||||
def register_listener(self, listener, event):
|
||||
"""
|
||||
|
@ -165,7 +167,14 @@ class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
|||
: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:
|
||||
return self.router.add(**route._asdict())
|
||||
|
@ -187,24 +196,21 @@ class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
|||
self.websocket_enabled = enable
|
||||
|
||||
# Decorator
|
||||
def exception(self, *exceptions):
|
||||
def _apply_exception_handler(self, handler: FutureException):
|
||||
"""Decorate a function to be registered as a handler for exceptions
|
||||
|
||||
:param exceptions: exceptions
|
||||
:return: decorated function
|
||||
"""
|
||||
|
||||
def response(handler):
|
||||
for exception in exceptions:
|
||||
for exception in handler.exceptions:
|
||||
if isinstance(exception, (tuple, list)):
|
||||
for e in exception:
|
||||
self.error_handler.add(e, handler)
|
||||
self.error_handler.add(e, handler.handler)
|
||||
else:
|
||||
self.error_handler.add(exception, handler)
|
||||
self.error_handler.add(exception, handler.handler)
|
||||
return handler
|
||||
|
||||
return response
|
||||
|
||||
def register_middleware(self, middleware, attach_to="request"):
|
||||
"""
|
||||
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.constants import HTTP_METHODS
|
||||
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.routes import RouteMixin
|
||||
from sanic.models.futures import (
|
||||
FutureException,
|
||||
FutureListener,
|
||||
FutureMiddleware,
|
||||
FutureRoute,
|
||||
FutureStatic,
|
||||
)
|
||||
from sanic.views import CompositionView
|
||||
from sanic.models.futures import FutureRoute, FutureStatic
|
||||
|
||||
|
||||
class Blueprint(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||
class Blueprint(
|
||||
BaseMixin, RouteMixin, MiddlewareMixin, ListenerMixin, ExceptionMixin
|
||||
):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
|
@ -61,6 +57,14 @@ class Blueprint(BaseMixin, RouteMixin, MiddlewareMixin):
|
|||
kwargs["apply"] = False
|
||||
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
|
||||
def group(*blueprints, url_prefix=""):
|
||||
"""
|
||||
|
@ -103,9 +107,6 @@ class Blueprint(BaseMixin, RouteMixin, MiddlewareMixin):
|
|||
|
||||
routes = []
|
||||
|
||||
# TODO:
|
||||
# - Add BP name to handler name for all routes
|
||||
|
||||
# Routes
|
||||
for future in self._future_routes:
|
||||
# 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)
|
||||
|
||||
# Exceptions
|
||||
for future in self.exceptions:
|
||||
app.exception(*future.args, **future.kwargs)(future.handler)
|
||||
for future in self._future_exceptions:
|
||||
app._apply_exception_handler(future)
|
||||
|
||||
# Event listeners
|
||||
for event, listeners in self.listeners.items():
|
||||
for listener in listeners:
|
||||
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
|
||||
for listener in self._future_listeners:
|
||||
app._apply_listener(listener)
|
||||
|
||||
def _generate_name(self, handler, name: str) -> str:
|
||||
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.
|
||||
"""
|
||||
|
||||
def register_middleware(_middleware, attach_to="request"):
|
||||
future_middleware = FutureMiddleware(_middleware, attach_to)
|
||||
def register_middleware(middleware, attach_to="request"):
|
||||
nonlocal apply
|
||||
|
||||
future_middleware = FutureMiddleware(middleware, attach_to)
|
||||
self._future_middleware.add(future_middleware)
|
||||
if apply:
|
||||
self._apply_middleware(future_middleware)
|
||||
return _middleware
|
||||
return middleware
|
||||
|
||||
# Detect which way this was called, @middleware or @middleware('AT')
|
||||
if callable(middleware_or_request):
|
||||
|
@ -39,3 +41,9 @@ class MiddlewareMixin:
|
|||
return partial(
|
||||
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 inspect import signature
|
||||
from pathlib import PurePath
|
||||
from typing import List, Set, Union
|
||||
|
||||
import websockets
|
||||
from typing import Set, Union
|
||||
|
||||
from sanic_routing.route import Route
|
||||
|
||||
|
|
|
@ -15,11 +15,9 @@ FutureRoute = namedtuple(
|
|||
"ignore_body",
|
||||
],
|
||||
)
|
||||
FutureListener = namedtuple(
|
||||
"FutureListener", ["handler", "uri", "methods", "host"]
|
||||
)
|
||||
FutureListener = namedtuple("FutureListener", ["listener", "event"])
|
||||
FutureMiddleware = namedtuple("FutureMiddleware", ["middleware", "attach_to"])
|
||||
FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"])
|
||||
FutureException = namedtuple("FutureException", ["handler", "exceptions"])
|
||||
FutureStatic = namedtuple(
|
||||
"FutureStatic",
|
||||
[
|
||||
|
|
|
@ -4,7 +4,6 @@ from sanic_routing import BaseRouter
|
|||
from sanic_routing.route import Route
|
||||
|
||||
from sanic.constants import HTTP_METHODS
|
||||
from sanic.log import logger
|
||||
from sanic.request import Request
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ from os import path
|
|||
from pathlib import PurePath
|
||||
from re import sub
|
||||
from time import gmtime, strftime
|
||||
from typing import Union
|
||||
from urllib.parse import unquote
|
||||
|
||||
from sanic.compat import stat_async
|
||||
|
|
Loading…
Reference in New Issue
Block a user