From b5e50ecb75afab6ffdffe0deab486b6c91cd37c6 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Sat, 11 Feb 2017 14:30:17 +0000 Subject: [PATCH 1/5] Use app decorator instead of run arguments for before_start Mirror listener of blueprints --- docs/sanic/deploying.md | 9 --- examples/limit_concurrency.py | 3 +- examples/sanic_aiopg_example.py | 4 +- examples/sanic_aiopg_sqlalchemy_example.py | 7 +- examples/sanic_asyncpg_example.py | 3 +- examples/sanic_peewee.py | 19 ++--- examples/try_everything.py | 4 +- sanic/blueprints.py | 5 ++ sanic/sanic.py | 83 ++++++++++------------ sanic/utils.py | 5 +- tests/test_bad_request.py | 3 +- tests/test_server_events.py | 15 ++-- tests/test_signal_handlers.py | 19 ++--- 13 files changed, 90 insertions(+), 89 deletions(-) diff --git a/docs/sanic/deploying.md b/docs/sanic/deploying.md index 0c526efe..d5f3ad06 100644 --- a/docs/sanic/deploying.md +++ b/docs/sanic/deploying.md @@ -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. diff --git a/examples/limit_concurrency.py b/examples/limit_concurrency.py index 16307462..8d38a813 100644 --- a/examples/limit_concurrency.py +++ b/examples/limit_concurrency.py @@ -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) diff --git a/examples/sanic_aiopg_example.py b/examples/sanic_aiopg_example.py index fb1ea100..16bde35f 100644 --- a/examples/sanic_aiopg_example.py +++ b/examples/sanic_aiopg_example.py @@ -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) diff --git a/examples/sanic_aiopg_sqlalchemy_example.py b/examples/sanic_aiopg_sqlalchemy_example.py index 802d4fbe..34bda28a 100644 --- a/examples/sanic_aiopg_sqlalchemy_example.py +++ b/examples/sanic_aiopg_sqlalchemy_example.py @@ -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) diff --git a/examples/sanic_asyncpg_example.py b/examples/sanic_asyncpg_example.py index 9bb7d9c1..633fc50a 100644 --- a/examples/sanic_asyncpg_example.py +++ b/examples/sanic_asyncpg_example.py @@ -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) diff --git a/examples/sanic_peewee.py b/examples/sanic_peewee.py index f3981b36..8db8ddff 100644 --- a/examples/sanic_peewee.py +++ b/examples/sanic_peewee.py @@ -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 there’s no need to connect and re-connect before executing async queries # with manager! It’s 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//') 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) diff --git a/examples/try_everything.py b/examples/try_everything.py index f386fb03..e94ffc6e 100644 --- a/examples/try_everything.py +++ b/examples/try_everything.py @@ -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) diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 0c14f4bc..64a37da0 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -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. diff --git a/sanic/sanic.py b/sanic/sanic.py index 951632be..1d7233bf 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -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,8 +10,8 @@ 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 @@ -20,6 +20,7 @@ from .static import register as static_register class Sanic: + def __init__(self, name=None, router=None, error_handler=None): # Only set up a default log handler if the @@ -45,6 +46,7 @@ class Sanic: self.debug = None self.sock = None self.processes = None + self.listeners = defaultdict(list) # Register alternative method names self.go_fast = self.run @@ -53,6 +55,18 @@ class Sanic: # Registration # -------------------------------------------------------------------- # + # 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): """ @@ -367,8 +381,7 @@ class Sanic: # Execution # -------------------------------------------------------------------- # - def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None, - after_start=None, before_stop=None, after_stop=None, ssl=None, + def run(self, host="127.0.0.1", port=8000, debug=False, ssl=None, sock=None, workers=1, loop=None, protocol=HttpProtocol, backlog=100, stop_event=None, register_sys_signals=True): """ @@ -378,14 +391,6 @@ class Sanic: :param host: Address to host on :param port: Port to host on :param debug: Enables debug output (slows server) - :param before_start: Functions to be executed before the server starts - accepting connections - :param after_start: Functions to be executed after the server starts - accepting connections - :param before_stop: Functions to be executed when a stop signal is - received before it is respected - :param after_stop: Functions to be executed when all requests are - complete :param ssl: SSLContext for SSL encryption of worker(s) :param sock: Socket for the server to accept connections from :param workers: Number of processes @@ -398,11 +403,11 @@ class Sanic: :return: Nothing """ 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) + host=host, port=port, debug=debug, 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) @@ -418,19 +423,16 @@ class Sanic: get_event_loop().stop() async def create_server(self, host="127.0.0.1", port=8000, debug=False, - before_start=None, after_start=None, - before_stop=None, after_stop=None, ssl=None, - sock=None, loop=None, protocol=HttpProtocol, - backlog=100, stop_event=None): + ssl=None, sock=None, loop=None, + protocol=HttpProtocol, backlog=100, + stop_event=None): """ Asynchronous version of `run`. """ - 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, loop=loop, - protocol=protocol, backlog=backlog, stop_event=stop_event, - run_async=True) + server_settings = self._helper(host=host, port=port, debug=debug, + ssl=ssl, sock=sock, loop=loop, + protocol=protocol, backlog=backlog, + stop_event=stop_event, run_async=True) # Serve proto = "http" @@ -440,11 +442,10 @@ class Sanic: return await serve(**server_settings) - def _helper(self, host="127.0.0.1", port=8000, debug=False, - before_start=None, after_start=None, before_stop=None, - after_stop=None, ssl=None, sock=None, workers=1, loop=None, - protocol=HttpProtocol, backlog=100, stop_event=None, - register_sys_signals=True, run_async=False): + def _helper(self, host="127.0.0.1", port=8000, debug=False, ssl=None, + sock=None, workers=1, loop=None, protocol=HttpProtocol, + backlog=100, stop_event=None, register_sys_signals=True, + run_async=False): """ Helper function used by `run` and `create_server`. """ @@ -481,19 +482,13 @@ 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 in ( + ("before_server_start", "before_start", False), + ("after_server_start", "after_start", False), + ("before_server_stop", "before_stop", True), + ("after_server_stop", "after_stop", True), ): - listeners = [] - for blueprint in self.blueprints.values(): - listeners += blueprint.listeners[event_name] - if args: - if callable(args): - args = [args] - listeners += args + listeners = self.listeners[event_name].copy() if reverse: listeners.reverse() # Prepend sanic to the arguments when listeners are triggered diff --git a/sanic/utils.py b/sanic/utils.py index 8e8f8124..c7520151 100644 --- a/sanic/utils.py +++ b/sanic/utils.py @@ -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') # TODO undo this 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)) diff --git a/tests/test_bad_request.py b/tests/test_bad_request.py index 095f4ab1..bf595085 100644 --- a/tests/test_bad_request.py +++ b/tests/test_bad_request.py @@ -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' diff --git a/tests/test_server_events.py b/tests/test_server_events.py index fce98440..05484b8c 100644 --- a/tests/test_server_events.py +++ b/tests/test_server_events.py @@ -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() diff --git a/tests/test_signal_handlers.py b/tests/test_signal_handlers.py index 756df828..3654e57c 100644 --- a/tests/test_signal_handlers.py +++ b/tests/test_signal_handlers.py @@ -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 From 2d5fd2fe1c763c48ec41290c8656b83003397b99 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Sat, 11 Feb 2017 14:35:44 +0000 Subject: [PATCH 2/5] fix test --- tests/test_server_events.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_server_events.py b/tests/test_server_events.py index 05484b8c..3335489e 100644 --- a/tests/test_server_events.py +++ b/tests/test_server_events.py @@ -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' ] From 8b08a370c54f490b04d45d129e901e4d58fb9d89 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Sat, 11 Feb 2017 14:39:32 +0000 Subject: [PATCH 3/5] Remove todo --- sanic/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/utils.py b/sanic/utils.py index c7520151..272ec8a8 100644 --- a/sanic/utils.py +++ b/sanic/utils.py @@ -28,7 +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') # TODO undo this + @app.listener('after_server_start') async def _collect_response(sanic, loop): try: response = await local_request(method, uri, *request_args, From ee6ff0cc60e838488db8bd8b7d37f0c75d62f2cf Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Sun, 12 Feb 2017 12:28:02 +0000 Subject: [PATCH 4/5] Add deprecation and old API --- sanic/sanic.py | 68 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/sanic/sanic.py b/sanic/sanic.py index 1d7233bf..dec6d10b 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -381,7 +381,8 @@ class Sanic: # Execution # -------------------------------------------------------------------- # - def run(self, host="127.0.0.1", port=8000, debug=False, ssl=None, + def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None, + after_start=None, before_stop=None, after_stop=None, ssl=None, sock=None, workers=1, loop=None, protocol=HttpProtocol, backlog=100, stop_event=None, register_sys_signals=True): """ @@ -391,6 +392,14 @@ class Sanic: :param host: Address to host on :param port: Port to host on :param debug: Enables debug output (slows server) + :param before_start: Functions to be executed before the server starts + accepting connections + :param after_start: Functions to be executed after the server starts + accepting connections + :param before_stop: Functions to be executed when a stop signal is + received before it is respected + :param after_stop: Functions to be executed when all requests are + complete :param ssl: SSLContext for SSL encryption of worker(s) :param sock: Socket for the server to accept connections from :param workers: Number of processes @@ -403,10 +412,11 @@ class Sanic: :return: Nothing """ server_settings = self._helper( - host=host, port=port, debug=debug, ssl=ssl, sock=sock, - workers=workers, loop=loop, protocol=protocol, backlog=backlog, - stop_event=stop_event, register_sys_signals=register_sys_signals - ) + 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: @@ -423,16 +433,19 @@ class Sanic: get_event_loop().stop() async def create_server(self, host="127.0.0.1", port=8000, debug=False, - ssl=None, sock=None, loop=None, - protocol=HttpProtocol, backlog=100, - stop_event=None): + before_start=None, after_start=None, + before_stop=None, after_stop=None, ssl=None, + sock=None, loop=None, protocol=HttpProtocol, + backlog=100, stop_event=None): """ Asynchronous version of `run`. """ - server_settings = self._helper(host=host, port=port, debug=debug, - ssl=ssl, sock=sock, loop=loop, - protocol=protocol, backlog=backlog, - stop_event=stop_event, run_async=True) + 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, loop=loop, + protocol=protocol, backlog=backlog, stop_event=stop_event, + run_async=True) # Serve proto = "http" @@ -442,10 +455,11 @@ class Sanic: return await serve(**server_settings) - def _helper(self, host="127.0.0.1", port=8000, debug=False, ssl=None, - sock=None, workers=1, loop=None, protocol=HttpProtocol, - backlog=100, stop_event=None, register_sys_signals=True, - run_async=False): + def _helper(self, host="127.0.0.1", port=8000, debug=False, + before_start=None, after_start=None, before_stop=None, + after_stop=None, ssl=None, sock=None, workers=1, loop=None, + protocol=HttpProtocol, backlog=100, stop_event=None, + register_sys_signals=True, run_async=False): """ Helper function used by `run` and `create_server`. """ @@ -458,6 +472,16 @@ 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 version" + " 0.4.0", + DeprecationWarning) + self.error_handler.debug = debug self.debug = debug self.loop = loop = get_event_loop() @@ -482,13 +506,15 @@ class Sanic: # Register start/stop events # -------------------------------------------- # - for event_name, settings_name, reverse in ( - ("before_server_start", "before_start", False), - ("after_server_start", "after_start", False), - ("before_server_stop", "before_stop", True), - ("after_server_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 = self.listeners[event_name].copy() + if args: + listeners.extend(args) if reverse: listeners.reverse() # Prepend sanic to the arguments when listeners are triggered From 2340910b4632ca48f5c968907226e6c1095b2e2b Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Sun, 12 Feb 2017 18:15:14 +0000 Subject: [PATCH 5/5] Update deprecation message Fix bug with single callbacks --- sanic/sanic.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sanic/sanic.py b/sanic/sanic.py index dec6d10b..1f2a6921 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -478,8 +478,8 @@ class Sanic: if debug: warnings.simplefilter('default') warnings.warn("Passing a before_start, before_stop, after_start or" - "after_stop callback will be deprecated in version" - " 0.4.0", + "after_stop callback will be deprecated in next " + "major version after 0.4.0", DeprecationWarning) self.error_handler.debug = debug @@ -514,7 +514,10 @@ class Sanic: ): listeners = self.listeners[event_name].copy() if args: - listeners.extend(args) + if callable(args): + listeners.append(args) + else: + listeners.extend(args) if reverse: listeners.reverse() # Prepend sanic to the arguments when listeners are triggered