Finish moving some more logic to mixins

This commit is contained in:
Adam Hopkins 2021-01-27 15:57:21 +02:00
parent dadf76ce72
commit e9459792a4
9 changed files with 157 additions and 88 deletions

View File

@ -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

View File

@ -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__}"

View 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
View 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")

View File

@ -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")

View File

@ -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

View File

@ -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",
[ [

View File

@ -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

View File

@ -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