diff --git a/.travis.yml b/.travis.yml index 6f2400f4..1fa1df57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ sudo: false -dist: precise language: python cache: directories: diff --git a/docs/sanic/debug_mode.rst b/docs/sanic/debug_mode.rst new file mode 100644 index 00000000..14356855 --- /dev/null +++ b/docs/sanic/debug_mode.rst @@ -0,0 +1,53 @@ +Debug Mode +============= + +When enabling Sanic's debug mode, Sanic will provide a more verbose logging output +and by default will enable the Auto Reload feature. + +.. warning:: + + Sanic's debug more will slow down the server's performance + and is therefore advised to enable it only in development environments. + + +Setting the debug mode +---------------------- + +By setting the ``debug`` mode a more verbose output from Sanic will be outputed +and the Automatic Reloader will be activated. + +.. code-block:: python + + from sanic import Sanic + from sanic.response import json + + app = Sanic() + + @app.route('/') + async def hello_world(request): + return json({"hello": "world"}) + + if __name__ == '__main__': + app.run(host="0.0.0.0", port=8000, debug=True) + + + +Manually setting auto reload +---------------------------- + +Sanic offers a way to enable or disable the Automatic Reloader manually, +the ``auto_reload`` argument will activate or deactivate the Automatic Reloader. + +.. code-block:: python + + from sanic import Sanic + from sanic.response import json + + app = Sanic() + + @app.route('/') + async def hello_world(request): + return json({"hello": "world"}) + + if __name__ == '__main__': + app.run(host="0.0.0.0", port=8000, auto_reload=True) \ No newline at end of file diff --git a/docs/sanic/extensions.md b/docs/sanic/extensions.md index d719a28f..01c89f95 100644 --- a/docs/sanic/extensions.md +++ b/docs/sanic/extensions.md @@ -7,7 +7,7 @@ A list of Sanic extensions created by the community. - [CORS](https://github.com/ashleysommer/sanic-cors): A port of flask-cors. - [Compress](https://github.com/subyraman/sanic_compress): Allows you to easily gzip Sanic responses. A port of Flask-Compress. - [Jinja2](https://github.com/lixxu/sanic-jinja2): Support for Jinja2 template. -- [JWT](https://github.com/ahopkins/sanic-jwt): Authentication extension for JSON Web Tokens (JWT). +- [Sanic JWT](https://github.com/ahopkins/sanic-jwt): Authentication, JWT, and permission scoping for Sanic. - [OpenAPI/Swagger](https://github.com/channelcat/sanic-openapi): OpenAPI support, plus a Swagger UI. - [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support. - [Motor](https://github.com/lixxu/sanic-motor): Simple motor wrapper. @@ -27,3 +27,7 @@ A list of Sanic extensions created by the community. - [sanic-transmute](https://github.com/yunstanford/sanic-transmute): A Sanic extension that generates APIs from python function and classes, and also generates Swagger UI/documentation automatically. - [pytest-sanic](https://github.com/yunstanford/pytest-sanic): A pytest plugin for Sanic. It helps you to test your code asynchronously. - [jinja2-sanic](https://github.com/yunstanford/jinja2-sanic): a jinja2 template renderer for Sanic.([Documentation](http://jinja2-sanic.readthedocs.io/en/latest/)) +- [GINO](https://github.com/fantix/gino): An asyncio ORM on top of SQLAlchemy core, delivered with a Sanic extension. ([Documentation](https://python-gino.readthedocs.io/)) +- [Sanic-Auth](https://github.com/pyx/sanic-auth): A minimal backend agnostic session-based user authentication mechanism for Sanic. +- [Sanic-CookieSession](https://github.com/pyx/sanic-cookiesession): A client-side only, cookie-based session, similar to the built-in session in Flask. +- [Sanic-WTF](https://github.com/pyx/sanic-wtf): Sanic-WTF makes using WTForms with Sanic and CSRF (Cross-Site Request Forgery) protection a little bit easier. diff --git a/docs/sanic/middleware.md b/docs/sanic/middleware.md index eafe3c6f..e041d452 100644 --- a/docs/sanic/middleware.md +++ b/docs/sanic/middleware.md @@ -10,9 +10,10 @@ Additionally, Sanic provides listeners which allow you to run code at various po There are two types of middleware: request and response. Both are declared using the `@app.middleware` decorator, with the decorator's parameter being a -string representing its type: `'request'` or `'response'`. Response middleware -receives both the request and the response as arguments. +string representing its type: `'request'` or `'response'`. +* Request middleware receives only the `request` as argument. +* Response middleware receives both the `request` and `response`. The simplest middleware doesn't modify the request or response at all: diff --git a/examples/try_everything.py b/examples/try_everything.py index 76967be1..a775704d 100644 --- a/examples/try_everything.py +++ b/examples/try_everything.py @@ -1,7 +1,7 @@ import os from sanic import Sanic -from sanic.log import log +from sanic.log import logger as log from sanic import response from sanic.exceptions import ServerError @@ -66,7 +66,7 @@ def post_json(request): @app.route("/form") -def post_json(request): +def post_form_json(request): return response.json({"received": True, "form_data": request.form, "test": request.form.get('test')}) diff --git a/requirements-dev.txt b/requirements-dev.txt index d46aee12..674ef91d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ aiofiles -aiohttp==1.3.5 +aiohttp>=2.3.0 chardet<=2.3.0 beautifulsoup4 coverage diff --git a/sanic/app.py b/sanic/app.py index 87667825..06540088 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -649,7 +649,7 @@ class Sanic: def run(self, host=None, port=None, debug=False, ssl=None, sock=None, workers=1, protocol=None, backlog=100, stop_event=None, register_sys_signals=True, - access_log=True, auto_reload=False): + access_log=True, **kwargs): """Run the HTTP Server and listen until keyboard interrupt or term signal. On termination, drain connections before closing. @@ -667,6 +667,13 @@ class Sanic: :param protocol: Subclass of asyncio protocol class :return: Nothing """ + # Default auto_reload to false + auto_reload = False + # If debug is set, default it to true + if debug: + auto_reload = True + # Allow for overriding either of the defaults + auto_reload = kwargs.get("auto_reload", auto_reload) if sock is None: host, port = host or "127.0.0.1", port or 8000 diff --git a/sanic/request.py b/sanic/request.py index cd7071d7..22d74a64 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -48,7 +48,7 @@ class Request(dict): 'app', 'headers', 'version', 'method', '_cookies', 'transport', 'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files', '_ip', '_parsed_url', 'uri_template', 'stream', '_remote_addr', - '_socket', '_port' + '_socket', '_port', '__weakref__' ) def __init__(self, url_bytes, headers, version, method, transport): diff --git a/sanic/testing.py b/sanic/testing.py index a639e16e..3a1d15c5 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -45,7 +45,7 @@ class SanicTestClient: def _sanic_endpoint_test( self, method='get', uri='/', gather_request=True, - debug=False, server_kwargs={}, + debug=False, server_kwargs={"auto_reload": False}, *request_args, **request_kwargs): results = [None, None] exceptions = [] diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index dd3dc6d7..00000000 --- a/tests/conftest.py +++ /dev/null @@ -1,23 +0,0 @@ -import re -import sanic - - -def pytest_collection_modifyitems(session, config, items): - base_port = sanic.testing.PORT - - worker_id = getattr(config, 'slaveinput', {}).get('slaveid', 'master') - m = re.search(r'[0-9]+', worker_id) - if m: - num_id = int(m.group(0)) + 1 - else: - num_id = 0 - new_port = base_port + num_id - - def new_test_client(app, port=new_port): - return sanic.testing.SanicTestClient(app, port) - - sanic.Sanic.test_port = new_port - sanic.Sanic.test_client = property(new_test_client) - - app = sanic.Sanic() - assert app.test_client.port == new_port diff --git a/tests/test_auto_reload.py b/tests/test_auto_reload.py deleted file mode 100644 index 104f823b..00000000 --- a/tests/test_auto_reload.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys -import subprocess -import signal -from threading import Thread -from time import sleep -from json.decoder import JSONDecodeError -import aiohttp -import asyncio -import async_timeout - -sanic_project_content_one = ''' -from sanic import Sanic -from sanic import response - -app = Sanic(__name__) - - -@app.route("/") -async def test(request): - return response.json({"test": 1}) - - -if __name__ == '__main__': - app.run(host="127.0.0.1", port=8000, auto_reload=True) -''' - -sanic_project_content_two = ''' -from sanic import Sanic -from sanic import response - -app = Sanic(__name__) - - -@app.route("/") -async def test(request): - return response.json({"test": 2}) - - -if __name__ == '__main__': - app.run(host="127.0.0.1", port=8000, auto_reload=True) -''' - -process_id = None - - -def execute_cmd(command): - process = subprocess.Popen(command, shell=True) - global process_id - process_id = process.pid - process.communicate() - - -class TestAutoReloading: - - def check_response(self, url, response): - """Send http request and tries to take it's response as json. - Returns a dictionary. - """ - async def req(url, excepted_response): - async with aiohttp.ClientSession() as session: - with async_timeout.timeout(10): - async with session.get(url) as response: - try: - result = await response.json() - except JSONDecodeError: - result = {} - return result == excepted_response - - loop = asyncio.get_event_loop() - return loop.run_until_complete(req(url, response)) - - def test_reloading_after_change_file(self, capsys): - if os.name != 'posix': - return - - with capsys.disabled(): - pass - sanic_app_file_path = "simple_sanic_app.py" - with open(sanic_app_file_path, "w") as _file: - _file.write(sanic_project_content_one) - - cmd = ' '.join([sys.executable, sanic_app_file_path]) - thread = Thread(target=execute_cmd, args=(cmd,)) - thread.start() - - sleep(2) # wait for completing server start process - assert self.check_response("http://127.0.0.1:8000/", {"test": 1}) - - with open(sanic_app_file_path, "w") as _file: - _file.write(sanic_project_content_two) - - sleep(2) # wait for completing server start process - assert self.check_response("http://127.0.0.1:8000/", {"test": 2}) - - thread.join(1) - os.remove(sanic_app_file_path) - - def teardown_method(self, method): - if process_id: - root_proc_path = \ - "/proc/{pid}/task/{pid}/children".format(pid=process_id) - if not os.path.isfile(root_proc_path): - return - with open(root_proc_path) as children_list_file: - children_list_pid = children_list_file.read().split() - for child_pid in children_list_pid: - os.kill(int(child_pid), signal.SIGTERM) diff --git a/tests/test_keep_alive_timeout.py b/tests/test_keep_alive_timeout.py index 4df00640..2a9e93a2 100644 --- a/tests/test_keep_alive_timeout.py +++ b/tests/test_keep_alive_timeout.py @@ -7,7 +7,7 @@ from sanic.config import Config from sanic import server import aiohttp from aiohttp import TCPConnector -from sanic.testing import SanicTestClient, HOST +from sanic.testing import SanicTestClient, HOST, PORT class ReuseableTCPConnector(TCPConnector): @@ -43,7 +43,7 @@ class ReuseableTCPConnector(TCPConnector): class ReuseableSanicTestClient(SanicTestClient): def __init__(self, app, loop=None): - super().__init__(app, port=app.test_port) + super(ReuseableSanicTestClient, self).__init__(app) if loop is None: loop = asyncio.get_event_loop() self._loop = loop @@ -87,8 +87,7 @@ class ReuseableSanicTestClient(SanicTestClient): _server = self._server else: _server_co = self.app.create_server(host=HOST, debug=debug, - port=self.app.test_port, - **server_kwargs) + port=PORT, **server_kwargs) server.trigger_events( self.app.listeners['before_server_start'], loop) @@ -280,4 +279,3 @@ def test_keep_alive_server_timeout(): assert isinstance(exception, ValueError) assert "Connection reset" in exception.args[0] or \ "got a new connection" in exception.args[0] - diff --git a/tests/test_multiprocessing.py b/tests/test_multiprocessing.py index cdbd91a8..7d94a972 100644 --- a/tests/test_multiprocessing.py +++ b/tests/test_multiprocessing.py @@ -3,7 +3,7 @@ import random import signal from sanic import Sanic -from sanic.testing import HOST +from sanic.testing import HOST, PORT def test_multiprocessing(): @@ -19,7 +19,7 @@ def test_multiprocessing(): process.terminate() signal.signal(signal.SIGALRM, stop_on_alarm) - signal.alarm(1) - app.run(HOST, app.test_port, workers=num_workers) + signal.alarm(3) + app.run(HOST, PORT, workers=num_workers) assert len(process_list) == num_workers diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py index 80dd7218..b3eb78aa 100644 --- a/tests/test_request_timeout.py +++ b/tests/test_request_timeout.py @@ -6,7 +6,7 @@ from sanic.response import text from sanic.config import Config import aiohttp from aiohttp import TCPConnector -from sanic.testing import SanicTestClient, HOST +from sanic.testing import SanicTestClient, HOST, PORT class DelayableTCPConnector(TCPConnector): @@ -58,18 +58,36 @@ class DelayableTCPConnector(TCPConnector): t = req.loop.time() print("sending at {}".format(t), flush=True) conn = next(iter(args)) # first arg is connection - try: - delayed_resp = self.orig_send(*args, **kwargs) - except Exception as e: - return aiohttp.ClientResponse(req.method, req.url) + if aiohttp.__version__ >= "3.1.0": + try: + delayed_resp = await self.orig_send(*args, **kwargs) + except Exception as e: + return aiohttp.ClientResponse(req.method, req.url, + writer=None, continue100=None, timer=None, + request_info=None, auto_decompress=None, traces=[], + loop=req.loop, session=None) + else: + try: + delayed_resp = self.orig_send(*args, **kwargs) + except Exception as e: + return aiohttp.ClientResponse(req.method, req.url) return delayed_resp - def send(self, *args, **kwargs): - gen = self.delayed_send(*args, **kwargs) - task = self.req.loop.create_task(gen) - self.send_task = task - self._acting_as = task - return self + if aiohttp.__version__ >= "3.1.0": + # aiohttp changed the request.send method to async + async def send(self, *args, **kwargs): + gen = self.delayed_send(*args, **kwargs) + task = self.req.loop.create_task(gen) + self.send_task = task + self._acting_as = task + return self + else: + def send(self, *args, **kwargs): + gen = self.delayed_send(*args, **kwargs) + task = self.req.loop.create_task(gen) + self.send_task = task + self._acting_as = task + return self def __init__(self, *args, **kwargs): _post_connect_delay = kwargs.pop('post_connect_delay', 0) @@ -108,7 +126,7 @@ class DelayableTCPConnector(TCPConnector): class DelayableSanicTestClient(SanicTestClient): def __init__(self, app, loop, request_delay=1): - super(DelayableSanicTestClient, self).__init__(app, port=app.test_port) + super(DelayableSanicTestClient, self).__init__(app) self._request_delay = request_delay self._loop = None diff --git a/tests/test_requests.py b/tests/test_requests.py index 14da4b0b..2a91fb9b 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -9,7 +9,7 @@ from sanic import Sanic from sanic.exceptions import ServerError from sanic.response import json, text from sanic.request import DEFAULT_HTTP_CONTENT_TYPE -from sanic.testing import HOST +from sanic.testing import HOST, PORT # ------------------------------------------------------------ # @@ -340,7 +340,7 @@ def test_url_attributes_no_ssl(path, query, expected_url): app.add_route(handler, path) request, response = app.test_client.get(path + '?{}'.format(query)) - assert request.url == expected_url.format(HOST, app.test_port) + assert request.url == expected_url.format(HOST, PORT) parsed = urlparse(request.url) @@ -371,9 +371,9 @@ def test_url_attributes_with_ssl(path, query, expected_url): app.add_route(handler, path) request, response = app.test_client.get( - 'https://{}:{}'.format(HOST, app.test_port) + path + '?{}'.format(query), + 'https://{}:{}'.format(HOST, PORT) + path + '?{}'.format(query), server_kwargs={'ssl': context}) - assert request.url == expected_url.format(HOST, app.test_port) + assert request.url == expected_url.format(HOST, PORT) parsed = urlparse(request.url) diff --git a/tests/test_response.py b/tests/test_response.py index b5eb891c..5b3f76de 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -10,7 +10,7 @@ from random import choice from sanic import Sanic from sanic.response import HTTPResponse, stream, StreamingHTTPResponse, file, file_stream, json -from sanic.testing import HOST +from sanic.testing import HOST, PORT from unittest.mock import MagicMock JSON_DATA = {'ok': True} @@ -187,7 +187,7 @@ def test_stream_response_writes_correct_content_to_transport(streaming_app): app.stop() - streaming_app.run(host=HOST, port=streaming_app.test_port) + streaming_app.run(host=HOST, port=PORT) @pytest.fixture diff --git a/tests/test_server_events.py b/tests/test_server_events.py index 9c0d99eb..c2ccc7dc 100644 --- a/tests/test_server_events.py +++ b/tests/test_server_events.py @@ -6,7 +6,7 @@ import signal import pytest from sanic import Sanic -from sanic.testing import HOST +from sanic.testing import HOST, PORT AVAILABLE_LISTENERS = [ 'before_server_start', @@ -31,7 +31,7 @@ def start_stop_app(random_name_app, **run_kwargs): signal.signal(signal.SIGALRM, stop_on_alarm) signal.alarm(1) try: - random_name_app.run(HOST, random_name_app.test_port, **run_kwargs) + random_name_app.run(HOST, PORT, **run_kwargs) except KeyboardInterrupt: pass diff --git a/tests/test_signal_handlers.py b/tests/test_signal_handlers.py index 967e903f..bee4f8e7 100644 --- a/tests/test_signal_handlers.py +++ b/tests/test_signal_handlers.py @@ -1,6 +1,6 @@ from sanic import Sanic from sanic.response import HTTPResponse -from sanic.testing import HOST +from sanic.testing import HOST, PORT from unittest.mock import MagicMock import asyncio from queue import Queue @@ -30,7 +30,7 @@ def test_register_system_signals(): app.listener('before_server_start')(set_loop) app.listener('after_server_stop')(after) - app.run(HOST, app.test_port) + app.run(HOST, PORT) assert calledq.get() == True @@ -46,5 +46,5 @@ def test_dont_register_system_signals(): app.listener('before_server_start')(set_loop) app.listener('after_server_stop')(after) - app.run(HOST, app.test_port, register_sys_signals=False) + app.run(HOST, PORT, register_sys_signals=False) assert calledq.get() == False diff --git a/tests/test_url_building.py b/tests/test_url_building.py index 670cafa5..98bbc20a 100644 --- a/tests/test_url_building.py +++ b/tests/test_url_building.py @@ -5,7 +5,7 @@ from sanic import Sanic from sanic.response import text from sanic.views import HTTPMethodView from sanic.blueprints import Blueprint -from sanic.testing import HOST as test_host +from sanic.testing import PORT as test_port, HOST as test_host from sanic.exceptions import URLBuildError import string @@ -15,11 +15,11 @@ URL_FOR_VALUE1 = '/myurl?arg1=v1&arg1=v2' URL_FOR_ARGS2 = dict(arg1=['v1', 'v2'], _anchor='anchor') URL_FOR_VALUE2 = '/myurl?arg1=v1&arg1=v2#anchor' URL_FOR_ARGS3 = dict(arg1='v1', _anchor='anchor', _scheme='http', - _server='{}:PORT_PLACEHOLDER'.format(test_host), _external=True) -URL_FOR_VALUE3 = 'http://{}:PORT_PLACEHOLDER/myurl?arg1=v1#anchor'.format(test_host) + _server='{}:{}'.format(test_host, test_port), _external=True) +URL_FOR_VALUE3 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port) URL_FOR_ARGS4 = dict(arg1='v1', _anchor='anchor', _external=True, - _server='http://{}:PORT_PLACEHOLDER'.format(test_host),) -URL_FOR_VALUE4 = 'http://{}:PORT_PLACEHOLDER/myurl?arg1=v1#anchor'.format(test_host) + _server='http://{}:{}'.format(test_host, test_port)) +URL_FOR_VALUE4 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port) def _generate_handlers_from_names(app, l): @@ -61,10 +61,6 @@ def test_simple_url_for_getting_with_more_params(args, url): def passes(request): return text('this should pass') - if '_server' in args: - args['_server'] = args['_server'].replace( - 'PORT_PLACEHOLDER', str(app.test_port)) - url = url.replace('PORT_PLACEHOLDER', str(app.test_port)) assert url == app.url_for('passes', **args) request, response = app.test_client.get(url) assert response.status == 200 diff --git a/tox.ini b/tox.ini index 71498013..be984b4e 100644 --- a/tox.ini +++ b/tox.ini @@ -12,13 +12,12 @@ deps = pytest-cov pytest-sanic pytest-sugar - pytest-xdist aiohttp>=2.3 chardet<=2.3.0 beautifulsoup4 gunicorn commands = - pytest tests -n 4 --cov sanic --cov-report= {posargs} + pytest tests --cov sanic --cov-report= {posargs} - coverage combine --append coverage report -m