fix merge conflicts

This commit is contained in:
Raphael Deem
2017-02-13 14:17:58 -08:00
20 changed files with 220 additions and 112 deletions

View File

@@ -65,6 +65,11 @@ class Blueprint:
app.static(uri, future.file_or_directory,
*future.args, **future.kwargs)
# Event listeners
for event, listeners in self.listeners.items():
for listener in listeners:
app.listener(event)(listener)
def route(self, uri, methods=frozenset({'GET'}), host=None):
"""
Creates a blueprint route from a decorated function.

View File

@@ -1,4 +1,3 @@
from collections import ChainMap
from mimetypes import guess_type
from os import path
from ujson import dumps as json_dumps
@@ -98,17 +97,15 @@ class HTTPResponse:
def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None):
# This is all returned in a kind-of funky way
# We tried to make this as fast as possible in pure python
default_header = dict()
if keep_alive:
if keep_alive_timeout:
default_header['Keep-Alive'] = keep_alive_timeout
default_header['Connection'] = 'keep-alive'
else:
default_header['Connection'] = 'close'
default_header['Content-Length'] = len(self.body)
default_header['Content-Type'] = self.content_type
timeout_header = b''
if keep_alive and keep_alive_timeout is not None:
timeout_header = b'Keep-Alive: %d\r\n' % keep_alive_timeout
self.headers['Content-Length'] = self.headers.get(
'Content-Length', len(self.body))
self.headers['Content-Type'] = self.headers.get(
'Content-Type', self.content_type)
headers = b''
for name, value in ChainMap(self.headers, default_header).items():
for name, value in self.headers.items():
try:
headers += (
b'%b: %b\r\n' % (
@@ -117,6 +114,7 @@ class HTTPResponse:
headers += (
b'%b: %b\r\n' % (
str(name).encode(), str(value).encode('utf-8')))
# Try to pull from the common codes first
# Speeds up response rate 6% over pulling from all
status = COMMON_STATUS_CODES.get(self.status)
@@ -124,11 +122,15 @@ class HTTPResponse:
status = ALL_STATUS_CODES.get(self.status)
return (b'HTTP/%b %d %b\r\n'
b'Connection: %b\r\n'
b'%b'
b'%b\r\n'
b'%b') % (
version.encode(),
self.status,
status,
b'keep-alive' if keep_alive else b'close',
timeout_header,
headers,
self.body
)

View File

@@ -2,7 +2,7 @@ import logging
import re
import warnings
from asyncio import get_event_loop
from collections import deque
from collections import deque, defaultdict
from functools import partial
from inspect import isawaitable, stack, getmodulename
from traceback import format_exc
@@ -10,16 +10,18 @@ from urllib.parse import urlencode, urlunparse
from .config import Config
from .constants import HTTP_METHODS
from .handlers import ErrorHandler
from .exceptions import ServerError, URLBuildError
from .handlers import ErrorHandler
from .log import log
from .response import HTTPResponse
from .router import Router
from .server import serve, serve_multiple, HttpProtocol
from .static import register as static_register
from .views import CompositionView
class Sanic:
def __init__(self, name=None, router=None,
error_handler=None):
# Only set up a default log handler if the
@@ -44,11 +46,18 @@ class Sanic:
self._blueprint_order = []
self.debug = None
self.sock = None
self.before_start = []
self.listeners = defaultdict(list)
# Register alternative method names
self.go_fast = self.run
@property
def loop(self):
"""
Synonymous with asyncio.get_event_loop()
"""
return get_event_loop()
# -------------------------------------------------------------------- #
# Registration
# -------------------------------------------------------------------- #
@@ -62,13 +71,24 @@ class Sanic:
:param task: A future, couroutine or awaitable.
"""
@self.listener('before_server_start')
def run(app, loop):
if callable(task):
loop.create_task(task())
else:
loop.create_task(task)
self.before_start.append(run)
# 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
# Decorator
def route(self, uri, methods=frozenset({'GET'}), host=None):
@@ -130,7 +150,16 @@ class Sanic:
"""
# Handle HTTPMethodView differently
if hasattr(handler, 'view_class'):
methods = frozenset(HTTP_METHODS)
methods = set()
for method in HTTP_METHODS:
if getattr(handler.view_class, method.lower(), None):
methods.add(method)
# handle composition view differently
if isinstance(handler, CompositionView):
methods = handler.handlers.keys()
self.route(uri=uri, methods=methods, host=host)(handler)
return handler
@@ -154,14 +183,12 @@ class Sanic:
return response
# Decorator
def middleware(self, *args, **kwargs):
def middleware(self, middleware_or_request):
"""
Decorates and registers middleware to be called before a request
can either be called as @app.middleware or @app.middleware('request')
"""
attach_to = 'request'
def register_middleware(middleware):
def register_middleware(middleware, attach_to='request'):
if attach_to == 'request':
self.request_middleware.append(middleware)
if attach_to == 'response':
@@ -169,11 +196,12 @@ class Sanic:
return middleware
# Detect which way this was called, @middleware or @middleware('AT')
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
return register_middleware(args[0])
if callable(middleware_or_request):
return register_middleware(middleware_or_request)
else:
attach_to = args[0]
return register_middleware
return partial(register_middleware,
attach_to=middleware_or_request)
# Static Files
def static(self, uri, file_or_directory, pattern='.+',
@@ -414,19 +442,13 @@ class Sanic:
:param protocol: Subclass of asyncio protocol class
:return: Nothing
"""
if before_start is not None:
if not isinstance(before_start, list):
before_start = [before_start]
before_start.extend(self.before_start)
else:
before_start = self.before_start
server_settings = self._helper(
host=host, port=port, debug=debug, before_start=before_start,
after_start=after_start, before_stop=before_stop,
after_stop=after_stop, ssl=ssl, sock=sock, workers=workers,
loop=loop, protocol=protocol, backlog=backlog,
stop_event=stop_event, register_sys_signals=register_sys_signals)
try:
if workers == 1:
serve(**server_settings)
@@ -481,9 +503,19 @@ class Sanic:
"pull/335 has more information.",
DeprecationWarning)
# Deprecate this
if any(arg is not None for arg in (after_stop, after_start,
before_start, before_stop)):
if debug:
warnings.simplefilter('default')
warnings.warn("Passing a before_start, before_stop, after_start or"
"after_stop callback will be deprecated in next "
"major version after 0.4.0",
DeprecationWarning)
self.error_handler.debug = debug
self.debug = debug
self.loop = loop = get_event_loop()
loop = self.loop
server_settings = {
'protocol': protocol,
@@ -505,19 +537,18 @@ class Sanic:
# Register start/stop events
# -------------------------------------------- #
for event_name, settings_name, args, reverse in (
("before_server_start", "before_start", before_start, False),
("after_server_start", "after_start", after_start, False),
("before_server_stop", "before_stop", before_stop, True),
("after_server_stop", "after_stop", after_stop, True),
for event_name, settings_name, reverse, args in (
("before_server_start", "before_start", False, before_start),
("after_server_start", "after_start", False, after_start),
("before_server_stop", "before_stop", True, before_stop),
("after_server_stop", "after_stop", True, after_stop),
):
listeners = []
for blueprint in self.blueprints.values():
listeners += blueprint.listeners[event_name]
listeners = self.listeners[event_name].copy()
if args:
if callable(args):
args = [args]
listeners += args
listeners.append(args)
else:
listeners.extend(args)
if reverse:
listeners.reverse()
# Prepend sanic to the arguments when listeners are triggered

View File

@@ -346,7 +346,11 @@ def serve(host, port, request_handler, error_handler, before_start=None,
# Register signals for graceful termination
if register_sys_signals:
for _signal in (SIGINT, SIGTERM):
loop.add_signal_handler(_signal, loop.stop)
try:
loop.add_signal_handler(_signal, loop.stop)
except NotImplementedError:
log.warn(('Sanic tried to use loop.add_signal_handler')
('but it is not implemented on this platform.'))
pid = os.getpid()
try:

View File

@@ -28,6 +28,7 @@ def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
results[0] = request
app.request_middleware.appendleft(_collect_request)
@app.listener('after_server_start')
async def _collect_response(sanic, loop):
try:
response = await local_request(method, uri, *request_args,
@@ -37,8 +38,8 @@ def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
exceptions.append(e)
app.stop()
app.run(host=HOST, debug=debug, port=PORT,
after_start=_collect_response, **server_kwargs)
app.run(host=HOST, debug=debug, port=PORT, **server_kwargs)
app.listeners['after_server_start'].pop()
if exceptions:
raise ValueError("Exception during request: {}".format(exceptions))

View File

@@ -1,4 +1,5 @@
from .exceptions import InvalidUsage
from .constants import HTTP_METHODS
class HTTPMethodView:
@@ -40,11 +41,7 @@ class HTTPMethodView:
def dispatch_request(self, request, *args, **kwargs):
handler = getattr(self, request.method.lower(), None)
if handler:
return handler(request, *args, **kwargs)
raise InvalidUsage(
'Method {} not allowed for URL {}'.format(
request.method, request.url), status_code=405)
return handler(request, *args, **kwargs)
@classmethod
def as_view(cls, *class_args, **class_kwargs):
@@ -89,15 +86,15 @@ class CompositionView:
def add(self, methods, handler):
for method in methods:
if method not in HTTP_METHODS:
raise InvalidUsage(
'{} is not a valid HTTP method.'.format(method))
if method in self.handlers:
raise KeyError(
'Method {} already is registered.'.format(method))
raise InvalidUsage(
'Method {} is already registered.'.format(method))
self.handlers[method] = handler
def __call__(self, request, *args, **kwargs):
handler = self.handlers.get(request.method.upper(), None)
if handler is None:
raise InvalidUsage(
'Method {} not allowed for URL {}'.format(
request.method, request.url), status_code=405)
handler = self.handlers[request.method.upper()]
return handler(request, *args, **kwargs)