fix merge conflicts

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

View File

@ -7,15 +7,6 @@ keyword arguments:
- `host` *(default `"127.0.0.1"`)*: Address to host the server on.
- `port` *(default `8000`)*: Port to host the server on.
- `debug` *(default `False`)*: Enables debug output (slows server).
- `before_start` *(default `None`)*: Function or list of functions to be executed
before the server starts accepting connections.
- `after_start` *(default `None`)*: Function or list of functions to be executed
after the server starts accepting connections.
- `before_stop` *(default `None`)*: Function or list of functions to be
executed when a stop signal is received before it is
respected.
- `after_stop` *(default `None`)*: Function or list of functions to be executed
when all requests are complete.
- `ssl` *(default `None`)*: `SSLContext` for SSL encryption of worker(s).
- `sock` *(default `None`)*: Socket for the server to accept connections from.
- `workers` *(default `1`)*: Number of worker processes to spawn.

View File

@ -7,3 +7,4 @@ A list of Sanic extensions created by the community.
- [CORS](https://github.com/ashleysommer/sanic-cors): A port of flask-cors.
- [Jinja2](https://github.com/lixxu/sanic-jinja2): Support for Jinja2 template.
- [OpenAPI/Swagger](https://github.com/channelcat/sanic-openapi): OpenAPI support, plus a Swagger UI.
- [Websockets](https://github.com/r0fls/sanic-websockets): Minimal wrapper to work with [Websockets](https://github.com/aaugustin/websockets)

View File

@ -1,6 +1,6 @@
"""
Example of caching using aiocache package. To run it you will need a Redis
instance running in localhost:6379.
instance running in localhost:6379. You can also try with SimpleMemoryCache.
Running this example you will see that the first call lasts 3 seconds and
the rest are instant because the value is retrieved from the Redis.
@ -20,8 +20,12 @@ from aiocache.serializers import JsonSerializer
app = Sanic(__name__)
def init_cache(sanic, loop):
aiocache.settings.set_defaults(
class_="aiocache.RedisCache"
class_="aiocache.RedisCache",
# class_="aiocache.SimpleMemoryCache",
loop=loop
)
@ -38,4 +42,4 @@ async def test(request):
return json(await expensive_call())
app.run(host="0.0.0.0", port=8000, loop=asyncio.get_event_loop())
app.run(host="0.0.0.0", port=8000, before_start=init_cache)

View File

@ -8,6 +8,7 @@ app = Sanic(__name__)
sem = None
@app.listener('before_server_start')
def init(sanic, loop):
global sem
CONCURRENCY_PER_WORKER = 4
@ -33,4 +34,4 @@ async def test(request):
return json(response)
app.run(host="0.0.0.0", port=8000, workers=2, before_start=init)
app.run(host="0.0.0.0", port=8000, workers=2)

View File

@ -26,6 +26,7 @@ async def get_pool():
app = Sanic(name=__name__)
@app.listener('before_server_start')
async def prepare_db(app, loop):
"""
Let's create some table and add some data
@ -61,5 +62,4 @@ async def handle(request):
if __name__ == '__main__':
app.run(host='0.0.0.0',
port=8000,
debug=True,
before_start=prepare_db)
debug=True)

View File

@ -32,7 +32,7 @@ polls = sa.Table('sanic_polls', metadata,
app = Sanic(name=__name__)
@app.listener('before_server_start')
async def prepare_db(app, loop):
""" Let's add some data
@ -58,9 +58,10 @@ async def handle(request):
async with engine.acquire() as conn:
result = []
async for row in conn.execute(polls.select()):
result.append({"question": row.question, "pub_date": row.pub_date})
result.append({"question": row.question,
"pub_date": row.pub_date})
return json({"polls": result})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, before_start=prepare_db)
app.run(host='0.0.0.0', port=8000)

View File

@ -27,6 +27,7 @@ def jsonify(records):
app = Sanic(__name__)
@app.listener('before_server_start')
async def create_db(app, loop):
"""
Create some table and add some data
@ -55,4 +56,4 @@ async def handler(request):
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, before_start=create_db)
app.run(host='0.0.0.0', port=8000)

View File

@ -14,14 +14,6 @@ from peewee_async import Manager, PostgresqlDatabase
# we instantiate a custom loop so we can pass it to our db manager
def setup(app, loop):
database = PostgresqlDatabase(database='test',
host='127.0.0.1',
user='postgres',
password='mysecretpassword')
objects = Manager(database, loop=loop)
## from peewee_async docs:
# Also theres no need to connect and re-connect before executing async queries
# with manager! Its all automatic. But you can run Manager.connect() or
@ -48,6 +40,15 @@ objects.database.allow_sync = False # this will raise AssertionError on ANY sync
app = Sanic('peewee_example')
@app.listener('before_server_start')
def setup(app, loop):
database = PostgresqlDatabase(database='test',
host='127.0.0.1',
user='postgres',
password='mysecretpassword')
objects = Manager(database, loop=loop)
@app.route('/post/<key>/<value>')
async def post(request, key, value):
"""
@ -75,4 +76,4 @@ async def get(request):
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8000, before_start=setup)
app.run(host='0.0.0.0', port=8000)

View File

@ -64,12 +64,14 @@ def query_string(request):
# Run Server
# ----------------------------------------------- #
@app.listener('after_server_start')
def after_start(app, loop):
log.info("OH OH OH OH OHHHHHHHH")
@app.listener('before_server_stop')
def before_stop(app, loop):
log.info("TRIED EVERYTHING")
app.run(host="0.0.0.0", port=8000, debug=True, after_start=after_start, before_stop=before_stop)
app.run(host="0.0.0.0", port=8000, debug=True)

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

View File

@ -5,6 +5,7 @@ from sanic import Sanic
def test_bad_request_response():
app = Sanic('test_bad_request_response')
lines = []
@app.listener('after_server_start')
async def _request(sanic, loop):
connect = asyncio.open_connection('127.0.0.1', 42101)
reader, writer = await connect
@ -15,6 +16,6 @@ def test_bad_request_response():
break
lines.append(line)
app.stop()
app.run(host='127.0.0.1', port=42101, debug=False, after_start=_request)
app.run(host='127.0.0.1', port=42101, debug=False)
assert lines[0] == b'HTTP/1.1 400 Bad Request\r\n'
assert lines[-1] == b'Error: Bad Request'

View File

@ -4,7 +4,7 @@ from sanic.response import text
from threading import Event
import asyncio
def test_ensure_future():
def test_create_task():
e = Event()
async def coro():
await asyncio.sleep(0.05)

View File

@ -9,10 +9,10 @@ from sanic import Sanic
from sanic.utils import HOST, PORT
AVAILABLE_LISTENERS = [
'before_start',
'after_start',
'before_stop',
'after_stop'
'before_server_start',
'after_server_start',
'before_server_stop',
'after_server_stop'
]
@ -42,9 +42,10 @@ def test_single_listener(listener_name):
random_name_app = Sanic(''.join(
[choice(ascii_letters) for _ in range(choice(range(5, 10)))]))
output = list()
start_stop_app(
random_name_app,
**{listener_name: create_listener(listener_name, output)})
# Register listener
random_name_app.listener(listener_name)(
create_listener(listener_name, output))
start_stop_app(random_name_app)
assert random_name_app.name + listener_name == output.pop()
@ -52,9 +53,9 @@ def test_all_listeners():
random_name_app = Sanic(''.join(
[choice(ascii_letters) for _ in range(choice(range(5, 10)))]))
output = list()
start_stop_app(
random_name_app,
**{listener_name: create_listener(listener_name, output)
for listener_name in AVAILABLE_LISTENERS})
for listener_name in AVAILABLE_LISTENERS:
listener = create_listener(listener_name, output)
random_name_app.listener(listener_name)(listener)
start_stop_app(random_name_app)
for listener_name in AVAILABLE_LISTENERS:
assert random_name_app.name + listener_name == output.pop()

View File

@ -27,10 +27,11 @@ def test_register_system_signals():
async def hello_route(request):
return HTTPResponse()
app.run(HOST, PORT,
before_start=set_loop,
after_start=stop,
after_stop=after)
app.listener('after_server_start')(stop)
app.listener('before_server_start')(set_loop)
app.listener('after_server_stop')(after)
app.run(HOST, PORT)
assert calledq.get() == True
@ -42,9 +43,9 @@ def test_dont_register_system_signals():
async def hello_route(request):
return HTTPResponse()
app.run(HOST, PORT,
before_start=set_loop,
after_start=stop,
after_stop=after,
register_sys_signals=False)
app.listener('after_server_start')(stop)
app.listener('before_server_start')(set_loop)
app.listener('after_server_stop')(after)
app.run(HOST, PORT, register_sys_signals=False)
assert calledq.get() == False

View File

@ -1,8 +1,9 @@
import pytest as pytest
from sanic import Sanic
from sanic.exceptions import InvalidUsage
from sanic.response import text, HTTPResponse
from sanic.views import HTTPMethodView
from sanic.views import HTTPMethodView, CompositionView
from sanic.blueprints import Blueprint
from sanic.request import Request
from sanic.utils import sanic_endpoint_test
@ -196,3 +197,65 @@ def test_with_decorator():
request, response = sanic_endpoint_test(app, method="get")
assert response.text == 'I am get method'
assert results[0] == 1
def test_composition_view_rejects_incorrect_methods():
def foo(request):
return text('Foo')
view = CompositionView()
with pytest.raises(InvalidUsage) as e:
view.add(['GET', 'FOO'], foo)
assert str(e.value) == 'FOO is not a valid HTTP method.'
def test_composition_view_rejects_duplicate_methods():
def foo(request):
return text('Foo')
view = CompositionView()
with pytest.raises(InvalidUsage) as e:
view.add(['GET', 'POST', 'GET'], foo)
assert str(e.value) == 'Method GET is already registered.'
@pytest.mark.parametrize('method', HTTP_METHODS)
def test_composition_view_runs_methods_as_expected(method):
app = Sanic('test_composition_view')
view = CompositionView()
view.add(['GET', 'POST', 'PUT'], lambda x: text('first method'))
view.add(['DELETE', 'PATCH'], lambda x: text('second method'))
app.add_route(view, '/')
if method in ['GET', 'POST', 'PUT']:
request, response = sanic_endpoint_test(app, uri='/', method=method)
assert response.text == 'first method'
if method in ['DELETE', 'PATCH']:
request, response = sanic_endpoint_test(app, uri='/', method=method)
assert response.text == 'second method'
@pytest.mark.parametrize('method', HTTP_METHODS)
def test_composition_view_rejects_invalid_methods(method):
app = Sanic('test_composition_view')
view = CompositionView()
view.add(['GET', 'POST', 'PUT'], lambda x: text('first method'))
app.add_route(view, '/')
if method in ['GET', 'POST', 'PUT']:
request, response = sanic_endpoint_test(app, uri='/', method=method)
assert response.status == 200
assert response.text == 'first method'
if method in ['DELETE', 'PATCH']:
request, response = sanic_endpoint_test(app, uri='/', method=method)
assert response.status == 405