sanic/sanic/blueprints.py

266 lines
10 KiB
Python
Raw Normal View History

from collections import defaultdict, namedtuple
from sanic.constants import HTTP_METHODS
from sanic.views import CompositionView
2017-03-24 01:37:06 +00:00
FutureRoute = namedtuple('Route',
2017-07-13 04:18:56 +01:00
['handler', 'uri', 'methods', 'host',
2017-08-21 11:05:34 +01:00
'strict_slashes', 'stream', 'version', 'name'])
FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host'])
FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs'])
FutureException = namedtuple('Route', ['handler', 'args', 'kwargs'])
2017-01-30 01:44:46 +00:00
FutureStatic = namedtuple('Route',
['uri', 'file_or_directory', 'args', 'kwargs'])
class Blueprint:
def __init__(self, name,
url_prefix=None,
host=None, version=None,
strict_slashes=False):
"""Create a new blueprint
:param name: unique name of the blueprint
:param url_prefix: URL to be prefixed before all route URLs
2017-08-21 08:28:01 +01:00
:param strict_slashes: strict to trailing slash
"""
2016-10-15 20:53:51 +01:00
self.name = name
self.url_prefix = url_prefix
2017-01-11 02:07:58 +00:00
self.host = host
2016-10-15 20:53:51 +01:00
self.routes = []
2017-02-21 06:41:53 +00:00
self.websocket_routes = []
self.exceptions = []
self.listeners = defaultdict(list)
self.middlewares = []
self.statics = []
2017-07-13 04:18:56 +01:00
self.version = version
self.strict_slashes = strict_slashes
2016-10-15 20:53:51 +01:00
def register(self, app, options):
"""Register the blueprint to the sanic app."""
url_prefix = options.get('url_prefix', self.url_prefix)
# Routes
for future in self.routes:
2017-02-02 17:21:14 +00:00
# attach the blueprint name to the handler so that it can be
# prefixed properly in the router
future.handler.__blueprintname__ = self.name
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
2017-07-13 04:18:56 +01:00
version = future.version or self.version
2017-08-21 11:05:34 +01:00
app.route(uri=uri[1:] if uri.startswith('//') else uri,
methods=future.methods,
host=future.host or self.host,
strict_slashes=future.strict_slashes,
stream=future.stream,
version=version,
name=future.name,
)(future.handler)
2017-02-21 06:41:53 +00:00
for future in self.websocket_routes:
# attach the blueprint name to the handler so that it can be
# prefixed properly in the router
future.handler.__blueprintname__ = self.name
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
2017-08-21 11:05:34 +01:00
app.websocket(uri=uri,
host=future.host or self.host,
strict_slashes=future.strict_slashes,
name=future.name,
)(future.handler)
2017-02-21 06:41:53 +00:00
# Middleware
for future in self.middlewares:
if future.args or future.kwargs:
app.register_middleware(future.middleware,
*future.args,
**future.kwargs)
else:
app.register_middleware(future.middleware)
# Exceptions
for future in self.exceptions:
app.exception(*future.args, **future.kwargs)(future.handler)
# Static Files
for future in self.statics:
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
2017-01-30 01:44:46 +00:00
app.static(uri, future.file_or_directory,
*future.args, **future.kwargs)
2016-10-15 20:53:51 +01:00
# Event listeners
for event, listeners in self.listeners.items():
for listener in listeners:
app.listener(event)(listener)
2017-03-24 01:37:06 +00:00
def route(self, uri, methods=frozenset({'GET'}), host=None,
2017-08-21 11:05:34 +01:00
strict_slashes=None, stream=False, version=None, name=None):
"""Create a blueprint route from a decorated function.
:param uri: endpoint at which the route will be accessible.
:param methods: list of acceptable HTTP methods.
2016-10-15 20:53:51 +01:00
"""
if strict_slashes is None:
strict_slashes = self.strict_slashes
2016-10-15 20:53:51 +01:00
def decorator(handler):
2017-05-05 12:09:32 +01:00
route = FutureRoute(
2017-08-21 11:05:34 +01:00
handler, uri, methods, host, strict_slashes, stream, version,
name)
self.routes.append(route)
2016-10-15 20:53:51 +01:00
return handler
return decorator
2017-03-24 01:37:06 +00:00
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
2017-08-21 11:05:34 +01:00
strict_slashes=None, version=None, name=None):
"""Create a blueprint route from a function.
:param handler: function for handling uri requests. Accepts function,
2017-02-14 08:20:39 +00:00
or class instance with a view_class method.
:param uri: endpoint at which the route will be accessible.
:param methods: list of acceptable HTTP methods.
2017-08-21 11:05:34 +01:00
:param host:
:param strict_slashes:
:param version:
:param name: user defined route name for url_for
:return: function or class instance
"""
# Handle HTTPMethodView differently
if hasattr(handler, 'view_class'):
methods = set()
for method in HTTP_METHODS:
if getattr(handler.view_class, method.lower(), None):
methods.add(method)
if strict_slashes is None:
strict_slashes = self.strict_slashes
# handle composition view differently
if isinstance(handler, CompositionView):
methods = handler.handlers.keys()
2017-03-24 01:37:06 +00:00
self.route(uri=uri, methods=methods, host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)(handler)
return handler
2017-08-21 11:05:34 +01:00
def websocket(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-02-21 06:41:53 +00:00
"""Create a blueprint websocket route from a decorated function.
:param uri: endpoint at which the route will be accessible.
"""
if strict_slashes is None:
strict_slashes = self.strict_slashes
2017-02-21 06:41:53 +00:00
def decorator(handler):
2017-07-13 04:18:56 +01:00
route = FutureRoute(handler, uri, [], host, strict_slashes,
2017-08-21 11:05:34 +01:00
False, version, name)
2017-02-21 06:41:53 +00:00
self.websocket_routes.append(route)
return handler
return decorator
2017-08-21 11:05:34 +01:00
def add_websocket_route(self, handler, uri, host=None, version=None,
name=None):
2017-02-21 06:41:53 +00:00
"""Create a blueprint websocket route from a function.
:param handler: function for handling uri requests. Accepts function,
or class instance with a view_class method.
:param uri: endpoint at which the route will be accessible.
:return: function or class instance
"""
2017-08-21 11:05:34 +01:00
self.websocket(uri=uri, host=host, version=version, name=name)(handler)
2017-02-21 06:41:53 +00:00
return handler
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 middleware(self, *args, **kwargs):
"""Create a blueprint middleware from a decorated function."""
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:
return register_middleware
def exception(self, *args, **kwargs):
"""Create a blueprint exception from a decorated function."""
def decorator(handler):
exception = FutureException(handler, args, kwargs)
self.exceptions.append(exception)
return handler
return decorator
2016-10-25 10:45:28 +01:00
def static(self, uri, file_or_directory, *args, **kwargs):
"""Create a blueprint static route from a decorated function.
:param uri: endpoint at which the route will be accessible.
:param file_or_directory: Static asset.
"""
static = FutureStatic(uri, file_or_directory, args, kwargs)
self.statics.append(static)
2017-01-30 07:20:38 +00:00
# Shorthand method decorators
2017-08-21 11:05:34 +01:00
def get(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-03-24 01:37:06 +00:00
return self.route(uri, methods=["GET"], host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)
2017-01-30 07:20:38 +00:00
def post(self, uri, host=None, strict_slashes=None, stream=False,
2017-08-21 11:05:34 +01:00
version=None, name=None):
2017-03-24 01:37:06 +00:00
return self.route(uri, methods=["POST"], host=host,
2017-07-13 04:18:56 +01:00
strict_slashes=strict_slashes, stream=stream,
2017-08-21 11:05:34 +01:00
version=version, name=name)
2017-01-30 07:20:38 +00:00
def put(self, uri, host=None, strict_slashes=None, stream=False,
2017-08-21 11:05:34 +01:00
version=None, name=None):
2017-03-24 01:37:06 +00:00
return self.route(uri, methods=["PUT"], host=host,
2017-07-13 04:18:56 +01:00
strict_slashes=strict_slashes, stream=stream,
2017-08-21 11:05:34 +01:00
version=version, name=name)
2017-01-30 07:20:38 +00:00
2017-08-21 11:05:34 +01:00
def head(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-03-24 01:37:06 +00:00
return self.route(uri, methods=["HEAD"], host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)
2017-01-30 07:20:38 +00:00
2017-08-21 11:05:34 +01:00
def options(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-03-24 01:37:06 +00:00
return self.route(uri, methods=["OPTIONS"], host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)
2017-01-30 07:20:38 +00:00
def patch(self, uri, host=None, strict_slashes=None, stream=False,
2017-08-21 11:05:34 +01:00
version=None, name=None):
2017-03-24 01:37:06 +00:00
return self.route(uri, methods=["PATCH"], host=host,
2017-07-13 04:18:56 +01:00
strict_slashes=strict_slashes, stream=stream,
2017-08-21 11:05:34 +01:00
version=version, name=name)
2017-01-30 07:20:38 +00:00
2017-08-21 11:05:34 +01:00
def delete(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-03-24 01:37:06 +00:00
return self.route(uri, methods=["DELETE"], host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)