Add SanicBase
This commit is contained in:
parent
e9459792a4
commit
e04f206c50
170
sanic/app.py
170
sanic/app.py
|
@ -7,7 +7,7 @@ from asyncio import CancelledError, Protocol, ensure_future, get_event_loop
|
||||||
from asyncio.futures import Future
|
from asyncio.futures import Future
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import isawaitable, signature
|
from inspect import isawaitable
|
||||||
from socket import socket
|
from socket import socket
|
||||||
from ssl import Purpose, SSLContext, create_default_context
|
from ssl import Purpose, SSLContext, create_default_context
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
@ -18,10 +18,10 @@ from sanic_routing.route import Route
|
||||||
|
|
||||||
from sanic import reloader_helpers
|
from sanic import reloader_helpers
|
||||||
from sanic.asgi import ASGIApp
|
from sanic.asgi import ASGIApp
|
||||||
|
from sanic.base import BaseSanic
|
||||||
from sanic.blueprint_group import BlueprintGroup
|
from sanic.blueprint_group import BlueprintGroup
|
||||||
from sanic.blueprints import Blueprint
|
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.exceptions import (
|
from sanic.exceptions import (
|
||||||
InvalidUsage,
|
InvalidUsage,
|
||||||
NotFound,
|
NotFound,
|
||||||
|
@ -31,11 +31,7 @@ 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.listeners import ListenerEvent
|
||||||
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 (
|
from sanic.models.futures import (
|
||||||
FutureException,
|
FutureException,
|
||||||
FutureListener,
|
FutureListener,
|
||||||
|
@ -54,13 +50,10 @@ from sanic.server import (
|
||||||
serve_multiple,
|
serve_multiple,
|
||||||
)
|
)
|
||||||
from sanic.static import register as static_register
|
from sanic.static import register as static_register
|
||||||
from sanic.views import CompositionView
|
|
||||||
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
||||||
|
|
||||||
|
|
||||||
class Sanic(
|
class Sanic(BaseSanic):
|
||||||
BaseMixin, RouteMixin, MiddlewareMixin, ListenerMixin, ExceptionMixin
|
|
||||||
):
|
|
||||||
_app_registry: Dict[str, "Sanic"] = {}
|
_app_registry: Dict[str, "Sanic"] = {}
|
||||||
test_mode = False
|
test_mode = False
|
||||||
|
|
||||||
|
@ -154,10 +147,6 @@ class Sanic(
|
||||||
partial(self._loop_add_task, task)
|
partial(self._loop_add_task, task)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Decorator
|
|
||||||
def _apply_listener(self, listener: FutureListener):
|
|
||||||
return self.register_listener(listener.listener, listener.event)
|
|
||||||
|
|
||||||
def register_listener(self, listener, event):
|
def register_listener(self, listener, event):
|
||||||
"""
|
"""
|
||||||
Register the listener for a given event.
|
Register the listener for a given event.
|
||||||
|
@ -176,41 +165,6 @@ class Sanic(
|
||||||
self.listeners[_event].append(listener)
|
self.listeners[_event].append(listener)
|
||||||
return listener
|
return listener
|
||||||
|
|
||||||
def _apply_route(self, route: FutureRoute) -> Route:
|
|
||||||
return self.router.add(**route._asdict())
|
|
||||||
|
|
||||||
def _apply_static(self, static: FutureStatic) -> Route:
|
|
||||||
return static_register(self, static)
|
|
||||||
|
|
||||||
def enable_websocket(self, enable=True):
|
|
||||||
"""Enable or disable the support for websocket.
|
|
||||||
|
|
||||||
Websocket is enabled automatically if websocket routes are
|
|
||||||
added to the application.
|
|
||||||
"""
|
|
||||||
if not self.websocket_enabled:
|
|
||||||
# if the server is stopped, we want to cancel any ongoing
|
|
||||||
# websocket tasks, to allow the server to exit promptly
|
|
||||||
self.listener("before_server_stop")(self._cancel_websocket_tasks)
|
|
||||||
|
|
||||||
self.websocket_enabled = enable
|
|
||||||
|
|
||||||
# Decorator
|
|
||||||
def _apply_exception_handler(self, handler: FutureException):
|
|
||||||
"""Decorate a function to be registered as a handler for exceptions
|
|
||||||
|
|
||||||
:param exceptions: exceptions
|
|
||||||
:return: decorated function
|
|
||||||
"""
|
|
||||||
|
|
||||||
for exception in handler.exceptions:
|
|
||||||
if isinstance(exception, (tuple, list)):
|
|
||||||
for e in exception:
|
|
||||||
self.error_handler.add(e, handler.handler)
|
|
||||||
else:
|
|
||||||
self.error_handler.add(exception, handler.handler)
|
|
||||||
return handler
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -251,7 +205,30 @@ class Sanic(
|
||||||
if middleware not in self.named_response_middleware[_rn]:
|
if middleware not in self.named_response_middleware[_rn]:
|
||||||
self.named_response_middleware[_rn].appendleft(middleware)
|
self.named_response_middleware[_rn].appendleft(middleware)
|
||||||
|
|
||||||
# Decorator
|
def _apply_exception_handler(self, handler: FutureException):
|
||||||
|
"""Decorate a function to be registered as a handler for exceptions
|
||||||
|
|
||||||
|
:param exceptions: exceptions
|
||||||
|
:return: decorated function
|
||||||
|
"""
|
||||||
|
|
||||||
|
for exception in handler.exceptions:
|
||||||
|
if isinstance(exception, (tuple, list)):
|
||||||
|
for e in exception:
|
||||||
|
self.error_handler.add(e, handler.handler)
|
||||||
|
else:
|
||||||
|
self.error_handler.add(exception, handler.handler)
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def _apply_listener(self, listener: FutureListener):
|
||||||
|
return self.register_listener(listener.listener, listener.event)
|
||||||
|
|
||||||
|
def _apply_route(self, route: FutureRoute) -> Route:
|
||||||
|
return self.router.add(**route._asdict())
|
||||||
|
|
||||||
|
def _apply_static(self, static: FutureStatic) -> Route:
|
||||||
|
return static_register(self, static)
|
||||||
|
|
||||||
def _apply_middleware(
|
def _apply_middleware(
|
||||||
self,
|
self,
|
||||||
middleware: FutureMiddleware,
|
middleware: FutureMiddleware,
|
||||||
|
@ -267,6 +244,19 @@ class Sanic(
|
||||||
middleware.middleware, middleware.attach_to
|
middleware.middleware, middleware.attach_to
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def enable_websocket(self, enable=True):
|
||||||
|
"""Enable or disable the support for websocket.
|
||||||
|
|
||||||
|
Websocket is enabled automatically if websocket routes are
|
||||||
|
added to the application.
|
||||||
|
"""
|
||||||
|
if not self.websocket_enabled:
|
||||||
|
# if the server is stopped, we want to cancel any ongoing
|
||||||
|
# websocket tasks, to allow the server to exit promptly
|
||||||
|
self.listener("before_server_stop")(self._cancel_websocket_tasks)
|
||||||
|
|
||||||
|
self.websocket_enabled = enable
|
||||||
|
|
||||||
def blueprint(self, blueprint, **options):
|
def blueprint(self, blueprint, **options):
|
||||||
"""Register a blueprint on the application.
|
"""Register a blueprint on the application.
|
||||||
|
|
||||||
|
@ -426,12 +416,6 @@ class Sanic(
|
||||||
# Request Handling
|
# Request Handling
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
def converted_response_type(self, response):
|
|
||||||
"""
|
|
||||||
No implementation provided.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def handle_exception(self, request, exception):
|
async def handle_exception(self, request, exception):
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
# Request Middleware
|
# Request Middleware
|
||||||
|
@ -563,11 +547,43 @@ class Sanic(
|
||||||
except CancelledError:
|
except CancelledError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# -------------------------------------------- #
|
|
||||||
# Response Generation Failed
|
# Response Generation Failed
|
||||||
# -------------------------------------------- #
|
|
||||||
await self.handle_exception(request, e)
|
await self.handle_exception(request, e)
|
||||||
|
|
||||||
|
async def _websocket_handler(
|
||||||
|
self, handler, request, *args, subprotocols=None, **kwargs
|
||||||
|
):
|
||||||
|
request.app = self
|
||||||
|
if not getattr(handler, "__blueprintname__", False):
|
||||||
|
request.endpoint = handler.__name__
|
||||||
|
else:
|
||||||
|
request.endpoint = (
|
||||||
|
getattr(handler, "__blueprintname__", "") + handler.__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.asgi:
|
||||||
|
ws = request.transport.get_websocket_connection()
|
||||||
|
else:
|
||||||
|
protocol = request.transport.get_protocol()
|
||||||
|
protocol.app = self
|
||||||
|
|
||||||
|
ws = await protocol.websocket_handshake(request, subprotocols)
|
||||||
|
|
||||||
|
# schedule the application handler
|
||||||
|
# its future is kept in self.websocket_tasks in case it
|
||||||
|
# needs to be cancelled due to the server being stopped
|
||||||
|
fut = ensure_future(handler(request, ws, *args, **kwargs))
|
||||||
|
self.websocket_tasks.add(fut)
|
||||||
|
try:
|
||||||
|
await fut
|
||||||
|
except (CancelledError, ConnectionClosed):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.websocket_tasks.remove(fut)
|
||||||
|
await ws.close()
|
||||||
|
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
# Testing
|
# Testing
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
@ -898,9 +914,7 @@ class Sanic(
|
||||||
"backlog": backlog,
|
"backlog": backlog,
|
||||||
}
|
}
|
||||||
|
|
||||||
# -------------------------------------------- #
|
|
||||||
# Register start/stop events
|
# Register start/stop events
|
||||||
# -------------------------------------------- #
|
|
||||||
|
|
||||||
for event_name, settings_name, reverse in (
|
for event_name, settings_name, reverse in (
|
||||||
("before_server_start", "before_start", False),
|
("before_server_start", "before_start", False),
|
||||||
|
@ -962,40 +976,6 @@ class Sanic(
|
||||||
for task in app.websocket_tasks:
|
for task in app.websocket_tasks:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
async def _websocket_handler(
|
|
||||||
self, handler, request, *args, subprotocols=None, **kwargs
|
|
||||||
):
|
|
||||||
request.app = self
|
|
||||||
if not getattr(handler, "__blueprintname__", False):
|
|
||||||
request.endpoint = handler.__name__
|
|
||||||
else:
|
|
||||||
request.endpoint = (
|
|
||||||
getattr(handler, "__blueprintname__", "") + handler.__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.asgi:
|
|
||||||
ws = request.transport.get_websocket_connection()
|
|
||||||
else:
|
|
||||||
protocol = request.transport.get_protocol()
|
|
||||||
protocol.app = self
|
|
||||||
|
|
||||||
ws = await protocol.websocket_handshake(request, subprotocols)
|
|
||||||
|
|
||||||
# schedule the application handler
|
|
||||||
# its future is kept in self.websocket_tasks in case it
|
|
||||||
# needs to be cancelled due to the server being stopped
|
|
||||||
fut = ensure_future(handler(request, ws, *args, **kwargs))
|
|
||||||
self.websocket_tasks.add(fut)
|
|
||||||
try:
|
|
||||||
await fut
|
|
||||||
except (CancelledError, ConnectionClosed):
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
self.websocket_tasks.remove(fut)
|
|
||||||
await ws.close()
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
# ASGI
|
# ASGI
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
|
@ -131,6 +131,7 @@ class Lifespan:
|
||||||
in sequence since the ASGI lifespan protocol only supports a single
|
in sequence since the ASGI lifespan protocol only supports a single
|
||||||
startup event.
|
startup event.
|
||||||
"""
|
"""
|
||||||
|
self.asgi_app.sanic_app.router.finalize()
|
||||||
listeners = self.asgi_app.sanic_app.listeners.get(
|
listeners = self.asgi_app.sanic_app.listeners.get(
|
||||||
"before_server_start", []
|
"before_server_start", []
|
||||||
) + self.asgi_app.sanic_app.listeners.get("after_server_start", [])
|
) + self.asgi_app.sanic_app.listeners.get("after_server_start", [])
|
||||||
|
|
36
sanic/base.py
Normal file
36
sanic/base.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from sanic.mixins.exceptions import ExceptionMixin
|
||||||
|
from sanic.mixins.listeners import ListenerMixin
|
||||||
|
from sanic.mixins.middleware import MiddlewareMixin
|
||||||
|
from sanic.mixins.routes import RouteMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Base(type):
|
||||||
|
def __new__(cls, name, bases, attrs):
|
||||||
|
init = attrs.get("__init__")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
nonlocal init
|
||||||
|
nonlocal name
|
||||||
|
|
||||||
|
bases = [
|
||||||
|
b for base in type(self).__bases__ for b in base.__bases__
|
||||||
|
]
|
||||||
|
|
||||||
|
for base in bases:
|
||||||
|
base.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
if init:
|
||||||
|
init(self, *args, **kwargs)
|
||||||
|
|
||||||
|
attrs["__init__"] = __init__
|
||||||
|
return type.__new__(cls, name, bases, attrs)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSanic(
|
||||||
|
RouteMixin,
|
||||||
|
MiddlewareMixin,
|
||||||
|
ListenerMixin,
|
||||||
|
ExceptionMixin,
|
||||||
|
metaclass=Base,
|
||||||
|
):
|
||||||
|
...
|
|
@ -1,17 +1,11 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from sanic.base import BaseSanic
|
||||||
from sanic.blueprint_group import BlueprintGroup
|
from sanic.blueprint_group import BlueprintGroup
|
||||||
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 FutureRoute, FutureStatic
|
from sanic.models.futures import FutureRoute, FutureStatic
|
||||||
|
|
||||||
|
|
||||||
class Blueprint(
|
class Blueprint(BaseSanic):
|
||||||
BaseMixin, RouteMixin, MiddlewareMixin, ListenerMixin, ExceptionMixin
|
|
||||||
):
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
class Base(type):
|
|
||||||
def __new__(cls, name, bases, attrs):
|
|
||||||
init = attrs.get("__init__")
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
nonlocal init
|
|
||||||
for base in type(self).__bases__:
|
|
||||||
if base.__name__ != "BaseMixin":
|
|
||||||
base.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
if init:
|
|
||||||
init(self, *args, **kwargs)
|
|
||||||
|
|
||||||
attrs["__init__"] = __init__
|
|
||||||
return type.__new__(cls, name, bases, attrs)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMixin(metaclass=Base):
|
|
||||||
...
|
|
|
@ -9,7 +9,9 @@ import pytest
|
||||||
from sanic_testing import TestManager
|
from sanic_testing import TestManager
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.router import RouteExists, Router
|
|
||||||
|
|
||||||
|
# from sanic.router import RouteExists, Router
|
||||||
|
|
||||||
|
|
||||||
random.seed("Pack my box with five dozen liquor jugs.")
|
random.seed("Pack my box with five dozen liquor jugs.")
|
||||||
|
@ -104,24 +106,25 @@ class RouteStringGenerator:
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def sanic_router(app):
|
def sanic_router(app):
|
||||||
# noinspection PyProtectedMember
|
...
|
||||||
def _setup(route_details: tuple) -> (Router, tuple):
|
# # noinspection PyProtectedMember
|
||||||
router = Router(app)
|
# def _setup(route_details: tuple) -> (Router, tuple):
|
||||||
added_router = []
|
# router = Router(app)
|
||||||
for method, route in route_details:
|
# added_router = []
|
||||||
try:
|
# for method, route in route_details:
|
||||||
router._add(
|
# try:
|
||||||
uri=f"/{route}",
|
# router._add(
|
||||||
methods=frozenset({method}),
|
# uri=f"/{route}",
|
||||||
host="localhost",
|
# methods=frozenset({method}),
|
||||||
handler=_handler,
|
# host="localhost",
|
||||||
)
|
# handler=_handler,
|
||||||
added_router.append((method, route))
|
# )
|
||||||
except RouteExists:
|
# added_router.append((method, route))
|
||||||
pass
|
# except RouteExists:
|
||||||
return router, added_router
|
# pass
|
||||||
|
# return router, added_router
|
||||||
|
|
||||||
return _setup
|
# return _setup
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
import pytest
|
# import pytest
|
||||||
|
|
||||||
from sanic.response import text
|
# from sanic.response import text
|
||||||
from sanic.router import RouteExists
|
# from sanic.router import RouteExists
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
# @pytest.mark.parametrize(
|
||||||
"method,attr, expected",
|
# "method,attr, expected",
|
||||||
[
|
# [
|
||||||
("get", "text", "OK1 test"),
|
# ("get", "text", "OK1 test"),
|
||||||
("post", "text", "OK2 test"),
|
# ("post", "text", "OK2 test"),
|
||||||
("put", "text", "OK2 test"),
|
# ("put", "text", "OK2 test"),
|
||||||
("delete", "status", 405),
|
# ("delete", "status", 405),
|
||||||
],
|
# ],
|
||||||
)
|
# )
|
||||||
def test_overload_dynamic_routes(app, method, attr, expected):
|
# def test_overload_dynamic_routes(app, method, attr, expected):
|
||||||
@app.route("/overload/<param>", methods=["GET"])
|
# @app.route("/overload/<param>", methods=["GET"])
|
||||||
async def handler1(request, param):
|
# async def handler1(request, param):
|
||||||
return text("OK1 " + param)
|
# return text("OK1 " + param)
|
||||||
|
|
||||||
@app.route("/overload/<param>", methods=["POST", "PUT"])
|
# @app.route("/overload/<param>", methods=["POST", "PUT"])
|
||||||
async def handler2(request, param):
|
# async def handler2(request, param):
|
||||||
return text("OK2 " + param)
|
# return text("OK2 " + param)
|
||||||
|
|
||||||
request, response = getattr(app.test_client, method)("/overload/test")
|
# request, response = getattr(app.test_client, method)("/overload/test")
|
||||||
assert getattr(response, attr) == expected
|
# assert getattr(response, attr) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_overload_dynamic_routes_exist(app):
|
# def test_overload_dynamic_routes_exist(app):
|
||||||
@app.route("/overload/<param>", methods=["GET"])
|
# @app.route("/overload/<param>", methods=["GET"])
|
||||||
async def handler1(request, param):
|
# async def handler1(request, param):
|
||||||
return text("OK1 " + param)
|
# return text("OK1 " + param)
|
||||||
|
|
||||||
@app.route("/overload/<param>", methods=["POST", "PUT"])
|
# @app.route("/overload/<param>", methods=["POST", "PUT"])
|
||||||
async def handler2(request, param):
|
# async def handler2(request, param):
|
||||||
return text("OK2 " + param)
|
# return text("OK2 " + param)
|
||||||
|
|
||||||
# if this doesn't raise an error, than at least the below should happen:
|
# # if this doesn't raise an error, than at least the below should happen:
|
||||||
# assert response.text == 'Duplicated'
|
# # assert response.text == 'Duplicated'
|
||||||
with pytest.raises(RouteExists):
|
# with pytest.raises(RouteExists):
|
||||||
|
|
||||||
@app.route("/overload/<param>", methods=["PUT", "DELETE"])
|
# @app.route("/overload/<param>", methods=["PUT", "DELETE"])
|
||||||
async def handler3(request, param):
|
# async def handler3(request, param):
|
||||||
return text("Duplicated")
|
# return text("Duplicated")
|
||||||
|
|
1200
tests/test_routes.py
1200
tests/test_routes.py
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user