Move logic into mixins
This commit is contained in:
parent
33d7f4da6b
commit
dadf76ce72
126
sanic/app.py
126
sanic/app.py
|
@ -30,8 +30,10 @@ 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.middleware import MiddlewareMixin
|
||||||
from sanic.mixins.routes import RouteMixin
|
from sanic.mixins.routes import RouteMixin
|
||||||
from sanic.models.futures import FutureRoute
|
from sanic.models.futures import 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
|
||||||
|
@ -47,7 +49,7 @@ from sanic.views import CompositionView
|
||||||
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
||||||
|
|
||||||
|
|
||||||
class Sanic(RouteMixin):
|
class Sanic(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||||
_app_registry: Dict[str, "Sanic"] = {}
|
_app_registry: Dict[str, "Sanic"] = {}
|
||||||
test_mode = False
|
test_mode = False
|
||||||
|
|
||||||
|
@ -65,7 +67,6 @@ class Sanic(RouteMixin):
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
# Get name from previous stack frame
|
|
||||||
if name is None:
|
if name is None:
|
||||||
raise SanicException(
|
raise SanicException(
|
||||||
"Sanic instance cannot be unnamed. "
|
"Sanic instance cannot be unnamed. "
|
||||||
|
@ -169,44 +170,8 @@ class Sanic(RouteMixin):
|
||||||
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())
|
||||||
|
|
||||||
def add_websocket_route(
|
def _apply_static(self, static: FutureStatic) -> Route:
|
||||||
self,
|
return static_register(self, static)
|
||||||
handler,
|
|
||||||
uri,
|
|
||||||
host=None,
|
|
||||||
strict_slashes=None,
|
|
||||||
subprotocols=None,
|
|
||||||
version=None,
|
|
||||||
name=None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
A helper method to register a function as a websocket route.
|
|
||||||
|
|
||||||
:param handler: a callable function or instance of a class
|
|
||||||
that can handle the websocket request
|
|
||||||
:param host: Host IP or FQDN details
|
|
||||||
:param uri: URL path that will be mapped to the websocket
|
|
||||||
handler
|
|
||||||
handler
|
|
||||||
:param strict_slashes: If the API endpoint needs to terminate
|
|
||||||
with a "/" or not
|
|
||||||
:param subprotocols: Subprotocols to be used with websocket
|
|
||||||
handshake
|
|
||||||
:param name: A unique name assigned to the URL so that it can
|
|
||||||
be used with :func:`url_for`
|
|
||||||
:return: Objected decorated by :func:`websocket`
|
|
||||||
"""
|
|
||||||
if strict_slashes is None:
|
|
||||||
strict_slashes = self.strict_slashes
|
|
||||||
|
|
||||||
return self.websocket(
|
|
||||||
uri,
|
|
||||||
host=host,
|
|
||||||
strict_slashes=strict_slashes,
|
|
||||||
subprotocols=subprotocols,
|
|
||||||
version=version,
|
|
||||||
name=name,
|
|
||||||
)(handler)
|
|
||||||
|
|
||||||
def enable_websocket(self, enable=True):
|
def enable_websocket(self, enable=True):
|
||||||
"""Enable or disable the support for websocket.
|
"""Enable or disable the support for websocket.
|
||||||
|
@ -281,76 +246,19 @@ class Sanic(RouteMixin):
|
||||||
self.named_response_middleware[_rn].appendleft(middleware)
|
self.named_response_middleware[_rn].appendleft(middleware)
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def middleware(self, middleware_or_request):
|
def _apply_middleware(
|
||||||
"""
|
|
||||||
Decorate and register middleware to be called before a request.
|
|
||||||
Can either be called as *@app.middleware* or
|
|
||||||
*@app.middleware('request')*
|
|
||||||
|
|
||||||
:param: middleware_or_request: Optional parameter to use for
|
|
||||||
identifying which type of middleware is being registered.
|
|
||||||
"""
|
|
||||||
# Detect which way this was called, @middleware or @middleware('AT')
|
|
||||||
if callable(middleware_or_request):
|
|
||||||
return self.register_middleware(middleware_or_request)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return partial(
|
|
||||||
self.register_middleware, attach_to=middleware_or_request
|
|
||||||
)
|
|
||||||
|
|
||||||
# Static Files
|
|
||||||
def static(
|
|
||||||
self,
|
self,
|
||||||
uri,
|
middleware: FutureMiddleware,
|
||||||
file_or_directory,
|
route_names: Optional[List[str]] = None,
|
||||||
pattern=r"/?.+",
|
|
||||||
use_modified_since=True,
|
|
||||||
use_content_range=False,
|
|
||||||
stream_large_files=False,
|
|
||||||
name="static",
|
|
||||||
host=None,
|
|
||||||
strict_slashes=None,
|
|
||||||
content_type=None,
|
|
||||||
):
|
):
|
||||||
"""
|
print(f"{middleware=}")
|
||||||
Register a root to serve files from. The input can either be a
|
if route_names:
|
||||||
file or a directory. This method will enable an easy and simple way
|
return self.register_named_middleware(
|
||||||
to setup the :class:`Route` necessary to serve the static files.
|
middleware.middleware, route_names, middleware.attach_to
|
||||||
|
)
|
||||||
:param uri: URL path to be used for serving static content
|
else:
|
||||||
:param file_or_directory: Path for the Static file/directory with
|
return self.register_middleware(
|
||||||
static files
|
middleware.middleware, middleware.attach_to
|
||||||
:param pattern: Regex Pattern identifying the valid static files
|
|
||||||
:param use_modified_since: If true, send file modified time, and return
|
|
||||||
not modified if the browser's matches the server's
|
|
||||||
:param use_content_range: If true, process header for range requests
|
|
||||||
and sends the file part that is requested
|
|
||||||
:param stream_large_files: If true, use the
|
|
||||||
:func:`StreamingHTTPResponse.file_stream` handler rather
|
|
||||||
than the :func:`HTTPResponse.file` handler to send the file.
|
|
||||||
If this is an integer, this represents the threshold size to
|
|
||||||
switch to :func:`StreamingHTTPResponse.file_stream`
|
|
||||||
:param name: user defined name used for url_for
|
|
||||||
:param host: Host IP or FQDN for the service to use
|
|
||||||
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
|
||||||
URLs need to terminate with a */*
|
|
||||||
:param content_type: user defined content type for header
|
|
||||||
:return: routes registered on the router
|
|
||||||
:rtype: List[sanic.router.Route]
|
|
||||||
"""
|
|
||||||
return static_register(
|
|
||||||
self,
|
|
||||||
uri,
|
|
||||||
file_or_directory,
|
|
||||||
pattern,
|
|
||||||
use_modified_since,
|
|
||||||
use_content_range,
|
|
||||||
stream_large_files,
|
|
||||||
name,
|
|
||||||
host,
|
|
||||||
strict_slashes,
|
|
||||||
content_type,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def blueprint(self, blueprint, **options):
|
def blueprint(self, blueprint, **options):
|
||||||
|
|
|
@ -112,10 +112,13 @@ class BlueprintGroup(MutableSequence):
|
||||||
:param kwargs: Optional Keyword arg to use with Middleware
|
:param kwargs: Optional Keyword arg to use with Middleware
|
||||||
:return: Partial function to apply the middleware
|
:return: Partial function to apply the middleware
|
||||||
"""
|
"""
|
||||||
kwargs["bp_group"] = True
|
|
||||||
|
|
||||||
def register_middleware_for_blueprints(fn):
|
def register_middleware_for_blueprints(fn):
|
||||||
for blueprint in self.blueprints:
|
for blueprint in self.blueprints:
|
||||||
blueprint.middleware(fn, *args, **kwargs)
|
blueprint.middleware(fn, *args, **kwargs)
|
||||||
|
|
||||||
|
if args and callable(args[0]):
|
||||||
|
fn = args[0]
|
||||||
|
args = list(args)[1:]
|
||||||
|
return register_middleware_for_blueprints(fn)
|
||||||
return register_middleware_for_blueprints
|
return register_middleware_for_blueprints
|
||||||
|
|
|
@ -2,6 +2,8 @@ from collections import defaultdict, namedtuple
|
||||||
|
|
||||||
from sanic.blueprint_group import BlueprintGroup
|
from sanic.blueprint_group import BlueprintGroup
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
|
from sanic.mixins.base import BaseMixin
|
||||||
|
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 (
|
||||||
FutureException,
|
FutureException,
|
||||||
|
@ -13,7 +15,7 @@ from sanic.models.futures import (
|
||||||
from sanic.views import CompositionView
|
from sanic.views import CompositionView
|
||||||
|
|
||||||
|
|
||||||
class Blueprint(RouteMixin):
|
class Blueprint(BaseMixin, RouteMixin, MiddlewareMixin):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
|
@ -34,8 +36,6 @@ class Blueprint(RouteMixin):
|
||||||
:param strict_slashes: Enforce the API urls are requested with a
|
:param strict_slashes: Enforce the API urls are requested with a
|
||||||
training */*
|
training */*
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.url_prefix = url_prefix
|
self.url_prefix = url_prefix
|
||||||
self.host = host
|
self.host = host
|
||||||
|
@ -53,6 +53,14 @@ class Blueprint(RouteMixin):
|
||||||
kwargs["apply"] = False
|
kwargs["apply"] = False
|
||||||
return super().route(*args, **kwargs)
|
return super().route(*args, **kwargs)
|
||||||
|
|
||||||
|
def static(self, *args, **kwargs):
|
||||||
|
kwargs["apply"] = False
|
||||||
|
return super().static(*args, **kwargs)
|
||||||
|
|
||||||
|
def middleware(self, *args, **kwargs):
|
||||||
|
kwargs["apply"] = False
|
||||||
|
return super().middleware(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def group(*blueprints, url_prefix=""):
|
def group(*blueprints, url_prefix=""):
|
||||||
"""
|
"""
|
||||||
|
@ -118,51 +126,26 @@ class Blueprint(RouteMixin):
|
||||||
future.ignore_body,
|
future.ignore_body,
|
||||||
)
|
)
|
||||||
|
|
||||||
_route = app._apply_route(apply_route)
|
route = app._apply_route(apply_route)
|
||||||
|
routes.append(route)
|
||||||
|
|
||||||
# TODO:
|
# Static Files
|
||||||
# for future in self.websocket_routes:
|
for future in self._future_statics:
|
||||||
# # attach the blueprint name to the handler so that it can be
|
# Prepend the blueprint URI prefix if available
|
||||||
# # prefixed properly in the router
|
uri = url_prefix + future.uri if url_prefix else future.uri
|
||||||
# future.handler.__blueprintname__ = self.name
|
apply_route = FutureStatic(uri, *future[1:])
|
||||||
# # Prepend the blueprint URI prefix if available
|
route = app._apply_static(apply_route)
|
||||||
# uri = url_prefix + future.uri if url_prefix else future.uri
|
routes.append(route)
|
||||||
# _routes, _ = app.websocket(
|
|
||||||
# uri=uri,
|
|
||||||
# host=future.host or self.host,
|
|
||||||
# strict_slashes=future.strict_slashes,
|
|
||||||
# name=future.name,
|
|
||||||
# )(future.handler)
|
|
||||||
# if _routes:
|
|
||||||
# routes += _routes
|
|
||||||
|
|
||||||
# # Static Files
|
route_names = [route.name for route in routes if route]
|
||||||
# for future in self.statics:
|
|
||||||
# # Prepend the blueprint URI prefix if available
|
|
||||||
# uri = url_prefix + future.uri if url_prefix else future.uri
|
|
||||||
# _routes = app.static(
|
|
||||||
# uri, future.file_or_directory, *future.args, **future.kwargs
|
|
||||||
# )
|
|
||||||
# if _routes:
|
|
||||||
# routes += _routes
|
|
||||||
|
|
||||||
# route_names = [route.name for route in routes if route]
|
# Middleware
|
||||||
|
for future in self._future_middleware:
|
||||||
|
app._apply_middleware(future, route_names)
|
||||||
|
|
||||||
# # Middleware
|
# Exceptions
|
||||||
# for future in self.middlewares:
|
for future in self.exceptions:
|
||||||
# if future.args or future.kwargs:
|
app.exception(*future.args, **future.kwargs)(future.handler)
|
||||||
# app.register_named_middleware(
|
|
||||||
# future.middleware,
|
|
||||||
# route_names,
|
|
||||||
# *future.args,
|
|
||||||
# **future.kwargs,
|
|
||||||
# )
|
|
||||||
# else:
|
|
||||||
# app.register_named_middleware(future.middleware, route_names)
|
|
||||||
|
|
||||||
# # Exceptions
|
|
||||||
# for future in self.exceptions:
|
|
||||||
# app.exception(*future.args, **future.kwargs)(future.handler)
|
|
||||||
|
|
||||||
# Event listeners
|
# Event listeners
|
||||||
for event, listeners in self.listeners.items():
|
for event, listeners in self.listeners.items():
|
||||||
|
@ -181,35 +164,6 @@ class Blueprint(RouteMixin):
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def middleware(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Create a blueprint middleware from a decorated function.
|
|
||||||
|
|
||||||
:param args: Positional arguments to be used while invoking the
|
|
||||||
middleware
|
|
||||||
:param kwargs: optional keyword args that can be used with the
|
|
||||||
middleware.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def register_middleware(_middleware):
|
|
||||||
future_middleware = FutureMiddleware(_middleware, args, kwargs)
|
|
||||||
self.middlewares.append(future_middleware)
|
|
||||||
return _middleware
|
|
||||||
|
|
||||||
# Detect which way this was called, @middleware or @middleware('AT')
|
|
||||||
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
|
|
||||||
middleware = args[0]
|
|
||||||
args = []
|
|
||||||
return register_middleware(middleware)
|
|
||||||
else:
|
|
||||||
if kwargs.get("bp_group") and callable(args[0]):
|
|
||||||
middleware = args[0]
|
|
||||||
args = args[1:]
|
|
||||||
kwargs.pop("bp_group")
|
|
||||||
return register_middleware(middleware)
|
|
||||||
else:
|
|
||||||
return register_middleware
|
|
||||||
|
|
||||||
def exception(self, *args, **kwargs):
|
def exception(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
This method enables the process of creating a global exception
|
This method enables the process of creating a global exception
|
||||||
|
@ -230,20 +184,5 @@ class Blueprint(RouteMixin):
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def static(self, uri, file_or_directory, *args, **kwargs):
|
def _generate_name(self, handler, name: str) -> str:
|
||||||
"""Create a blueprint static route from a decorated function.
|
return f"{self.name}.{name or handler.__name__}"
|
||||||
|
|
||||||
:param uri: endpoint at which the route will be accessible.
|
|
||||||
:param file_or_directory: Static asset.
|
|
||||||
"""
|
|
||||||
name = kwargs.pop("name", "static")
|
|
||||||
if not name.startswith(self.name + "."):
|
|
||||||
name = f"{self.name}.{name}"
|
|
||||||
kwargs.update(name=name)
|
|
||||||
|
|
||||||
strict_slashes = kwargs.get("strict_slashes")
|
|
||||||
if strict_slashes is None and self.strict_slashes is not None:
|
|
||||||
kwargs.update(strict_slashes=self.strict_slashes)
|
|
||||||
|
|
||||||
static = FutureStatic(uri, file_or_directory, args, kwargs)
|
|
||||||
self.statics.append(static)
|
|
||||||
|
|
19
sanic/mixins/base.py
Normal file
19
sanic/mixins/base.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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):
|
||||||
|
...
|
41
sanic/mixins/middleware.py
Normal file
41
sanic/mixins/middleware.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from functools import partial
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from sanic.models.futures import FutureMiddleware
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareMixin:
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
self._future_middleware: Set[FutureMiddleware] = set()
|
||||||
|
|
||||||
|
def _apply_middleware(self, middleware: FutureMiddleware):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def middleware(
|
||||||
|
self, middleware_or_request, attach_to="request", apply=True
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Decorate and register middleware to be called before a request.
|
||||||
|
Can either be called as *@app.middleware* or
|
||||||
|
*@app.middleware('request')*
|
||||||
|
|
||||||
|
:param: middleware_or_request: Optional parameter to use for
|
||||||
|
identifying which type of middleware is being registered.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_middleware(_middleware, attach_to="request"):
|
||||||
|
future_middleware = FutureMiddleware(_middleware, attach_to)
|
||||||
|
self._future_middleware.add(future_middleware)
|
||||||
|
if apply:
|
||||||
|
self._apply_middleware(future_middleware)
|
||||||
|
return _middleware
|
||||||
|
|
||||||
|
# Detect which way this was called, @middleware or @middleware('AT')
|
||||||
|
if callable(middleware_or_request):
|
||||||
|
return register_middleware(
|
||||||
|
middleware_or_request, attach_to=attach_to
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return partial(
|
||||||
|
register_middleware, attach_to=middleware_or_request
|
||||||
|
)
|
|
@ -1,25 +1,31 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import signature
|
from inspect import signature
|
||||||
from typing import List, Set
|
from pathlib import PurePath
|
||||||
|
from typing import List, Set, Union
|
||||||
|
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
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.models.futures import FutureRoute
|
from sanic.models.futures import FutureRoute, FutureStatic
|
||||||
from sanic.views import CompositionView
|
from sanic.views import CompositionView
|
||||||
|
|
||||||
|
|
||||||
class RouteMixin:
|
class RouteMixin:
|
||||||
def __init__(self) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
self._future_routes: Set[Route] = set()
|
self._future_routes: Set[FutureRoute] = set()
|
||||||
self._future_websocket_routes: Set[Route] = set()
|
self._future_statics: Set[FutureStatic] = set()
|
||||||
|
self.name = ""
|
||||||
|
self.strict_slashes = False
|
||||||
|
|
||||||
def _apply_route(self, route: FutureRoute) -> Route:
|
def _apply_route(self, route: FutureRoute) -> Route:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _route(
|
def _apply_static(self, static: FutureStatic) -> Route:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def route(
|
||||||
self,
|
self,
|
||||||
uri,
|
uri,
|
||||||
methods=frozenset({"GET"}),
|
methods=frozenset({"GET"}),
|
||||||
|
@ -133,30 +139,6 @@ class RouteMixin:
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def route(
|
|
||||||
self,
|
|
||||||
uri,
|
|
||||||
methods=frozenset({"GET"}),
|
|
||||||
host=None,
|
|
||||||
strict_slashes=None,
|
|
||||||
stream=False,
|
|
||||||
version=None,
|
|
||||||
name=None,
|
|
||||||
ignore_body=False,
|
|
||||||
apply=True,
|
|
||||||
):
|
|
||||||
return self._route(
|
|
||||||
uri=uri,
|
|
||||||
methods=methods,
|
|
||||||
host=host,
|
|
||||||
strict_slashes=strict_slashes,
|
|
||||||
stream=stream,
|
|
||||||
version=version,
|
|
||||||
name=name,
|
|
||||||
ignore_body=ignore_body,
|
|
||||||
apply=apply,
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_route(
|
def add_route(
|
||||||
self,
|
self,
|
||||||
handler,
|
handler,
|
||||||
|
@ -435,7 +417,7 @@ class RouteMixin:
|
||||||
:param version: Blueprint Version
|
:param version: Blueprint Version
|
||||||
:param name: Unique name to identify the Websocket Route
|
:param name: Unique name to identify the Websocket Route
|
||||||
"""
|
"""
|
||||||
return self._route(
|
return self.route(
|
||||||
uri=uri,
|
uri=uri,
|
||||||
host=host,
|
host=host,
|
||||||
methods=None,
|
methods=None,
|
||||||
|
@ -474,11 +456,8 @@ class RouteMixin:
|
||||||
be used with :func:`url_for`
|
be used with :func:`url_for`
|
||||||
:return: Objected decorated by :func:`websocket`
|
:return: Objected decorated by :func:`websocket`
|
||||||
"""
|
"""
|
||||||
if strict_slashes is None:
|
|
||||||
strict_slashes = self.strict_slashes
|
|
||||||
|
|
||||||
return self.websocket(
|
return self.websocket(
|
||||||
uri,
|
uri=uri,
|
||||||
host=host,
|
host=host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
subprotocols=subprotocols,
|
subprotocols=subprotocols,
|
||||||
|
@ -486,5 +465,69 @@ class RouteMixin:
|
||||||
name=name,
|
name=name,
|
||||||
)(handler)
|
)(handler)
|
||||||
|
|
||||||
|
def static(
|
||||||
|
self,
|
||||||
|
uri,
|
||||||
|
file_or_directory: Union[str, bytes, PurePath],
|
||||||
|
pattern=r"/?.+",
|
||||||
|
use_modified_since=True,
|
||||||
|
use_content_range=False,
|
||||||
|
stream_large_files=False,
|
||||||
|
name="static",
|
||||||
|
host=None,
|
||||||
|
strict_slashes=None,
|
||||||
|
content_type=None,
|
||||||
|
apply=True,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Register a root to serve files from. The input can either be a
|
||||||
|
file or a directory. This method will enable an easy and simple way
|
||||||
|
to setup the :class:`Route` necessary to serve the static files.
|
||||||
|
|
||||||
|
:param uri: URL path to be used for serving static content
|
||||||
|
:param file_or_directory: Path for the Static file/directory with
|
||||||
|
static files
|
||||||
|
:param pattern: Regex Pattern identifying the valid static files
|
||||||
|
:param use_modified_since: If true, send file modified time, and return
|
||||||
|
not modified if the browser's matches the server's
|
||||||
|
:param use_content_range: If true, process header for range requests
|
||||||
|
and sends the file part that is requested
|
||||||
|
:param stream_large_files: If true, use the
|
||||||
|
:func:`StreamingHTTPResponse.file_stream` handler rather
|
||||||
|
than the :func:`HTTPResponse.file` handler to send the file.
|
||||||
|
If this is an integer, this represents the threshold size to
|
||||||
|
switch to :func:`StreamingHTTPResponse.file_stream`
|
||||||
|
:param name: user defined name used for url_for
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||||
|
URLs need to terminate with a */*
|
||||||
|
:param content_type: user defined content type for header
|
||||||
|
:return: routes registered on the router
|
||||||
|
:rtype: List[sanic.router.Route]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not name.startswith(self.name + "."):
|
||||||
|
name = f"{self.name}.{name}"
|
||||||
|
|
||||||
|
if strict_slashes is None and self.strict_slashes is not None:
|
||||||
|
strict_slashes = self.strict_slashes
|
||||||
|
|
||||||
|
static = FutureStatic(
|
||||||
|
uri,
|
||||||
|
file_or_directory,
|
||||||
|
pattern,
|
||||||
|
use_modified_since,
|
||||||
|
use_content_range,
|
||||||
|
stream_large_files,
|
||||||
|
name,
|
||||||
|
host,
|
||||||
|
strict_slashes,
|
||||||
|
content_type,
|
||||||
|
)
|
||||||
|
self._future_statics.add(static)
|
||||||
|
|
||||||
|
if apply:
|
||||||
|
self._apply_static(static)
|
||||||
|
|
||||||
def _generate_name(self, handler, name: str) -> str:
|
def _generate_name(self, handler, name: str) -> str:
|
||||||
return name or handler.__name__
|
return name or handler.__name__
|
||||||
|
|
|
@ -18,10 +18,20 @@ FutureRoute = namedtuple(
|
||||||
FutureListener = namedtuple(
|
FutureListener = namedtuple(
|
||||||
"FutureListener", ["handler", "uri", "methods", "host"]
|
"FutureListener", ["handler", "uri", "methods", "host"]
|
||||||
)
|
)
|
||||||
FutureMiddleware = namedtuple(
|
FutureMiddleware = namedtuple("FutureMiddleware", ["middleware", "attach_to"])
|
||||||
"FutureMiddleware", ["middleware", "args", "kwargs"]
|
|
||||||
)
|
|
||||||
FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"])
|
FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"])
|
||||||
FutureStatic = namedtuple(
|
FutureStatic = namedtuple(
|
||||||
"FutureStatic", ["uri", "file_or_directory", "args", "kwargs"]
|
"FutureStatic",
|
||||||
|
[
|
||||||
|
"uri",
|
||||||
|
"file_or_directory",
|
||||||
|
"pattern",
|
||||||
|
"use_modified_since",
|
||||||
|
"use_content_range",
|
||||||
|
"stream_large_files",
|
||||||
|
"name",
|
||||||
|
"host",
|
||||||
|
"strict_slashes",
|
||||||
|
"content_type",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,7 @@ from sanic.exceptions import (
|
||||||
)
|
)
|
||||||
from sanic.handlers import ContentRangeHandler
|
from sanic.handlers import ContentRangeHandler
|
||||||
from sanic.log import error_logger
|
from sanic.log import error_logger
|
||||||
|
from sanic.models.futures import FutureStatic
|
||||||
from sanic.response import HTTPResponse, file, file_stream
|
from sanic.response import HTTPResponse, file, file_stream
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,16 +113,7 @@ async def _static_request_handler(
|
||||||
|
|
||||||
def register(
|
def register(
|
||||||
app,
|
app,
|
||||||
uri: str,
|
static: FutureStatic,
|
||||||
file_or_directory: Union[str, bytes, PurePath],
|
|
||||||
pattern,
|
|
||||||
use_modified_since,
|
|
||||||
use_content_range,
|
|
||||||
stream_large_files,
|
|
||||||
name: str = "static",
|
|
||||||
host=None,
|
|
||||||
strict_slashes=None,
|
|
||||||
content_type=None,
|
|
||||||
):
|
):
|
||||||
# TODO: Though sanic is not a file server, I feel like we should at least
|
# TODO: Though sanic is not a file server, I feel like we should at least
|
||||||
# make a good effort here. Modified-since is nice, but we could
|
# make a good effort here. Modified-since is nice, but we could
|
||||||
|
@ -152,38 +144,42 @@ def register(
|
||||||
:rtype: List[sanic.router.Route]
|
:rtype: List[sanic.router.Route]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(file_or_directory, bytes):
|
if isinstance(static.file_or_directory, bytes):
|
||||||
file_or_directory = file_or_directory.decode("utf-8")
|
file_or_directory = static.file_or_directory.decode("utf-8")
|
||||||
elif isinstance(file_or_directory, PurePath):
|
elif isinstance(static.file_or_directory, PurePath):
|
||||||
file_or_directory = str(file_or_directory)
|
file_or_directory = str(static.file_or_directory)
|
||||||
elif not isinstance(file_or_directory, str):
|
elif not isinstance(static.file_or_directory, str):
|
||||||
raise ValueError("Invalid file path string.")
|
raise ValueError("Invalid file path string.")
|
||||||
|
else:
|
||||||
|
file_or_directory = static.file_or_directory
|
||||||
|
|
||||||
|
uri = static.uri
|
||||||
|
name = static.name
|
||||||
# If we're not trying to match a file directly,
|
# If we're not trying to match a file directly,
|
||||||
# serve from the folder
|
# serve from the folder
|
||||||
if not path.isfile(file_or_directory):
|
if not path.isfile(file_or_directory):
|
||||||
uri += "<file_uri:" + pattern + ">"
|
uri += "<file_uri:" + static.pattern + ">"
|
||||||
|
|
||||||
# special prefix for static files
|
# special prefix for static files
|
||||||
if not name.startswith("_static_"):
|
if not static.name.startswith("_static_"):
|
||||||
name = f"_static_{name}"
|
name = f"_static_{static.name}"
|
||||||
|
|
||||||
_handler = wraps(_static_request_handler)(
|
_handler = wraps(_static_request_handler)(
|
||||||
partial(
|
partial(
|
||||||
_static_request_handler,
|
_static_request_handler,
|
||||||
file_or_directory,
|
file_or_directory,
|
||||||
use_modified_since,
|
static.use_modified_since,
|
||||||
use_content_range,
|
static.use_content_range,
|
||||||
stream_large_files,
|
static.stream_large_files,
|
||||||
content_type=content_type,
|
content_type=static.content_type,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
_routes, _ = app.route(
|
_routes, _ = app.route(
|
||||||
uri,
|
uri=uri,
|
||||||
methods=["GET", "HEAD"],
|
methods=["GET", "HEAD"],
|
||||||
name=name,
|
name=name,
|
||||||
host=host,
|
host=static.host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=static.strict_slashes,
|
||||||
)(_handler)
|
)(_handler)
|
||||||
return _routes
|
return _routes
|
||||||
|
|
Loading…
Reference in New Issue
Block a user