Merge pull request #20 from channelcat/master

merge upstream master branch
This commit is contained in:
7 2018-04-29 21:50:07 -07:00 committed by GitHub
commit 7928b9b3a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 127 additions and 183 deletions

View File

@ -1,5 +1,4 @@
sudo: false sudo: false
dist: precise
language: python language: python
cache: cache:
directories: directories:

53
docs/sanic/debug_mode.rst Normal file
View File

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

View File

@ -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. - [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. - [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. - [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. - [OpenAPI/Swagger](https://github.com/channelcat/sanic-openapi): OpenAPI support, plus a Swagger UI.
- [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support. - [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support.
- [Motor](https://github.com/lixxu/sanic-motor): Simple motor wrapper. - [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. - [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. - [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/)) - [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.

View File

@ -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 There are two types of middleware: request and response. Both are declared
using the `@app.middleware` decorator, with the decorator's parameter being a using the `@app.middleware` decorator, with the decorator's parameter being a
string representing its type: `'request'` or `'response'`. Response middleware string representing its type: `'request'` or `'response'`.
receives both the request and the response as arguments.
* 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: The simplest middleware doesn't modify the request or response at all:

View File

@ -1,7 +1,7 @@
import os import os
from sanic import Sanic from sanic import Sanic
from sanic.log import log from sanic.log import logger as log
from sanic import response from sanic import response
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
@ -66,7 +66,7 @@ def post_json(request):
@app.route("/form") @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')}) return response.json({"received": True, "form_data": request.form, "test": request.form.get('test')})

View File

@ -1,5 +1,5 @@
aiofiles aiofiles
aiohttp==1.3.5 aiohttp>=2.3.0
chardet<=2.3.0 chardet<=2.3.0
beautifulsoup4 beautifulsoup4
coverage coverage

View File

@ -649,7 +649,7 @@ class Sanic:
def run(self, host=None, port=None, debug=False, ssl=None, def run(self, host=None, port=None, debug=False, ssl=None,
sock=None, workers=1, protocol=None, sock=None, workers=1, protocol=None,
backlog=100, stop_event=None, register_sys_signals=True, 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 """Run the HTTP Server and listen until keyboard interrupt or term
signal. On termination, drain connections before closing. signal. On termination, drain connections before closing.
@ -667,6 +667,13 @@ class Sanic:
:param protocol: Subclass of asyncio protocol class :param protocol: Subclass of asyncio protocol class
:return: Nothing :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: if sock is None:
host, port = host or "127.0.0.1", port or 8000 host, port = host or "127.0.0.1", port or 8000

View File

@ -48,7 +48,7 @@ class Request(dict):
'app', 'headers', 'version', 'method', '_cookies', 'transport', 'app', 'headers', 'version', 'method', '_cookies', 'transport',
'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files', 'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
'_ip', '_parsed_url', 'uri_template', 'stream', '_remote_addr', '_ip', '_parsed_url', 'uri_template', 'stream', '_remote_addr',
'_socket', '_port' '_socket', '_port', '__weakref__'
) )
def __init__(self, url_bytes, headers, version, method, transport): def __init__(self, url_bytes, headers, version, method, transport):

View File

@ -45,7 +45,7 @@ class SanicTestClient:
def _sanic_endpoint_test( def _sanic_endpoint_test(
self, method='get', uri='/', gather_request=True, self, method='get', uri='/', gather_request=True,
debug=False, server_kwargs={}, debug=False, server_kwargs={"auto_reload": False},
*request_args, **request_kwargs): *request_args, **request_kwargs):
results = [None, None] results = [None, None]
exceptions = [] exceptions = []

View File

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

View File

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

View File

@ -7,7 +7,7 @@ from sanic.config import Config
from sanic import server from sanic import server
import aiohttp import aiohttp
from aiohttp import TCPConnector from aiohttp import TCPConnector
from sanic.testing import SanicTestClient, HOST from sanic.testing import SanicTestClient, HOST, PORT
class ReuseableTCPConnector(TCPConnector): class ReuseableTCPConnector(TCPConnector):
@ -43,7 +43,7 @@ class ReuseableTCPConnector(TCPConnector):
class ReuseableSanicTestClient(SanicTestClient): class ReuseableSanicTestClient(SanicTestClient):
def __init__(self, app, loop=None): def __init__(self, app, loop=None):
super().__init__(app, port=app.test_port) super(ReuseableSanicTestClient, self).__init__(app)
if loop is None: if loop is None:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
self._loop = loop self._loop = loop
@ -87,8 +87,7 @@ class ReuseableSanicTestClient(SanicTestClient):
_server = self._server _server = self._server
else: else:
_server_co = self.app.create_server(host=HOST, debug=debug, _server_co = self.app.create_server(host=HOST, debug=debug,
port=self.app.test_port, port=PORT, **server_kwargs)
**server_kwargs)
server.trigger_events( server.trigger_events(
self.app.listeners['before_server_start'], loop) self.app.listeners['before_server_start'], loop)
@ -280,4 +279,3 @@ def test_keep_alive_server_timeout():
assert isinstance(exception, ValueError) assert isinstance(exception, ValueError)
assert "Connection reset" in exception.args[0] or \ assert "Connection reset" in exception.args[0] or \
"got a new connection" in exception.args[0] "got a new connection" in exception.args[0]

View File

@ -3,7 +3,7 @@ import random
import signal import signal
from sanic import Sanic from sanic import Sanic
from sanic.testing import HOST from sanic.testing import HOST, PORT
def test_multiprocessing(): def test_multiprocessing():
@ -19,7 +19,7 @@ def test_multiprocessing():
process.terminate() process.terminate()
signal.signal(signal.SIGALRM, stop_on_alarm) signal.signal(signal.SIGALRM, stop_on_alarm)
signal.alarm(1) signal.alarm(3)
app.run(HOST, app.test_port, workers=num_workers) app.run(HOST, PORT, workers=num_workers)
assert len(process_list) == num_workers assert len(process_list) == num_workers

View File

@ -6,7 +6,7 @@ from sanic.response import text
from sanic.config import Config from sanic.config import Config
import aiohttp import aiohttp
from aiohttp import TCPConnector from aiohttp import TCPConnector
from sanic.testing import SanicTestClient, HOST from sanic.testing import SanicTestClient, HOST, PORT
class DelayableTCPConnector(TCPConnector): class DelayableTCPConnector(TCPConnector):
@ -58,18 +58,36 @@ class DelayableTCPConnector(TCPConnector):
t = req.loop.time() t = req.loop.time()
print("sending at {}".format(t), flush=True) print("sending at {}".format(t), flush=True)
conn = next(iter(args)) # first arg is connection conn = next(iter(args)) # first arg is connection
try: if aiohttp.__version__ >= "3.1.0":
delayed_resp = self.orig_send(*args, **kwargs) try:
except Exception as e: delayed_resp = await self.orig_send(*args, **kwargs)
return aiohttp.ClientResponse(req.method, req.url) 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 return delayed_resp
def send(self, *args, **kwargs): if aiohttp.__version__ >= "3.1.0":
gen = self.delayed_send(*args, **kwargs) # aiohttp changed the request.send method to async
task = self.req.loop.create_task(gen) async def send(self, *args, **kwargs):
self.send_task = task gen = self.delayed_send(*args, **kwargs)
self._acting_as = task task = self.req.loop.create_task(gen)
return self 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): def __init__(self, *args, **kwargs):
_post_connect_delay = kwargs.pop('post_connect_delay', 0) _post_connect_delay = kwargs.pop('post_connect_delay', 0)
@ -108,7 +126,7 @@ class DelayableTCPConnector(TCPConnector):
class DelayableSanicTestClient(SanicTestClient): class DelayableSanicTestClient(SanicTestClient):
def __init__(self, app, loop, request_delay=1): 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._request_delay = request_delay
self._loop = None self._loop = None

View File

@ -9,7 +9,7 @@ from sanic import Sanic
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
from sanic.response import json, text from sanic.response import json, text
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE 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) app.add_route(handler, path)
request, response = app.test_client.get(path + '?{}'.format(query)) 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) parsed = urlparse(request.url)
@ -371,9 +371,9 @@ def test_url_attributes_with_ssl(path, query, expected_url):
app.add_route(handler, path) app.add_route(handler, path)
request, response = app.test_client.get( 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}) 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) parsed = urlparse(request.url)

View File

@ -10,7 +10,7 @@ from random import choice
from sanic import Sanic from sanic import Sanic
from sanic.response import HTTPResponse, stream, StreamingHTTPResponse, file, file_stream, json 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 from unittest.mock import MagicMock
JSON_DATA = {'ok': True} JSON_DATA = {'ok': True}
@ -187,7 +187,7 @@ def test_stream_response_writes_correct_content_to_transport(streaming_app):
app.stop() app.stop()
streaming_app.run(host=HOST, port=streaming_app.test_port) streaming_app.run(host=HOST, port=PORT)
@pytest.fixture @pytest.fixture

View File

@ -6,7 +6,7 @@ import signal
import pytest import pytest
from sanic import Sanic from sanic import Sanic
from sanic.testing import HOST from sanic.testing import HOST, PORT
AVAILABLE_LISTENERS = [ AVAILABLE_LISTENERS = [
'before_server_start', 'before_server_start',
@ -31,7 +31,7 @@ def start_stop_app(random_name_app, **run_kwargs):
signal.signal(signal.SIGALRM, stop_on_alarm) signal.signal(signal.SIGALRM, stop_on_alarm)
signal.alarm(1) signal.alarm(1)
try: try:
random_name_app.run(HOST, random_name_app.test_port, **run_kwargs) random_name_app.run(HOST, PORT, **run_kwargs)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass

View File

@ -1,6 +1,6 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import HTTPResponse from sanic.response import HTTPResponse
from sanic.testing import HOST from sanic.testing import HOST, PORT
from unittest.mock import MagicMock from unittest.mock import MagicMock
import asyncio import asyncio
from queue import Queue from queue import Queue
@ -30,7 +30,7 @@ def test_register_system_signals():
app.listener('before_server_start')(set_loop) app.listener('before_server_start')(set_loop)
app.listener('after_server_stop')(after) app.listener('after_server_stop')(after)
app.run(HOST, app.test_port) app.run(HOST, PORT)
assert calledq.get() == True assert calledq.get() == True
@ -46,5 +46,5 @@ def test_dont_register_system_signals():
app.listener('before_server_start')(set_loop) app.listener('before_server_start')(set_loop)
app.listener('after_server_stop')(after) 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 assert calledq.get() == False

View File

@ -5,7 +5,7 @@ from sanic import Sanic
from sanic.response import text from sanic.response import text
from sanic.views import HTTPMethodView from sanic.views import HTTPMethodView
from sanic.blueprints import Blueprint 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 from sanic.exceptions import URLBuildError
import string 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_ARGS2 = dict(arg1=['v1', 'v2'], _anchor='anchor')
URL_FOR_VALUE2 = '/myurl?arg1=v1&arg1=v2#anchor' URL_FOR_VALUE2 = '/myurl?arg1=v1&arg1=v2#anchor'
URL_FOR_ARGS3 = dict(arg1='v1', _anchor='anchor', _scheme='http', URL_FOR_ARGS3 = dict(arg1='v1', _anchor='anchor', _scheme='http',
_server='{}:PORT_PLACEHOLDER'.format(test_host), _external=True) _server='{}:{}'.format(test_host, test_port), _external=True)
URL_FOR_VALUE3 = 'http://{}:PORT_PLACEHOLDER/myurl?arg1=v1#anchor'.format(test_host) URL_FOR_VALUE3 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port)
URL_FOR_ARGS4 = dict(arg1='v1', _anchor='anchor', _external=True, URL_FOR_ARGS4 = dict(arg1='v1', _anchor='anchor', _external=True,
_server='http://{}:PORT_PLACEHOLDER'.format(test_host),) _server='http://{}:{}'.format(test_host, test_port))
URL_FOR_VALUE4 = 'http://{}:PORT_PLACEHOLDER/myurl?arg1=v1#anchor'.format(test_host) URL_FOR_VALUE4 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port)
def _generate_handlers_from_names(app, l): 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): def passes(request):
return text('this should pass') 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) assert url == app.url_for('passes', **args)
request, response = app.test_client.get(url) request, response = app.test_client.get(url)
assert response.status == 200 assert response.status == 200

View File

@ -12,13 +12,12 @@ deps =
pytest-cov pytest-cov
pytest-sanic pytest-sanic
pytest-sugar pytest-sugar
pytest-xdist
aiohttp>=2.3 aiohttp>=2.3
chardet<=2.3.0 chardet<=2.3.0
beautifulsoup4 beautifulsoup4
gunicorn gunicorn
commands = commands =
pytest tests -n 4 --cov sanic --cov-report= {posargs} pytest tests --cov sanic --cov-report= {posargs}
- coverage combine --append - coverage combine --append
coverage report -m coverage report -m