Merge pull request #19 from channelcat/master

merge upstream master branch
This commit is contained in:
7 2018-03-13 22:11:40 -07:00 committed by GitHub
commit 31cf83f10b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 88 additions and 42 deletions

View File

@ -19,7 +19,7 @@ matrix:
- env: TOX_ENV=check - env: TOX_ENV=check
python: 3.6 python: 3.6
install: pip install -U tox install: pip install -U tox
script: tox -e $TOX_ENV script: travis_retry tox -e $TOX_ENV
deploy: deploy:
provider: pypi provider: pypi
user: channelcat user: channelcat

View File

@ -13,7 +13,7 @@ To throw an exception, simply `raise` the relevant exception from the
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
@app.route('/killme') @app.route('/killme')
def i_am_ready_to_die(request): async def i_am_ready_to_die(request):
raise ServerError("Something bad happened", status_code=500) raise ServerError("Something bad happened", status_code=500)
``` ```
@ -24,7 +24,7 @@ from sanic.exceptions import abort
from sanic.response import text from sanic.response import text
@app.route('/youshallnotpass') @app.route('/youshallnotpass')
def no_no(request): async def no_no(request):
abort(401) abort(401)
# this won't happen # this won't happen
text("OK") text("OK")
@ -43,7 +43,7 @@ from sanic.response import text
from sanic.exceptions import NotFound from sanic.exceptions import NotFound
@app.exception(NotFound) @app.exception(NotFound)
def ignore_404s(request, exception): async def ignore_404s(request, exception):
return text("Yep, I totally found the page: {}".format(request.url)) return text("Yep, I totally found the page: {}".format(request.url))
``` ```

View File

@ -138,6 +138,8 @@ app.add_route(person_handler2, '/person/<name:[A-z]>', methods=['GET'])
Sanic provides a `url_for` method, to generate URLs based on the handler method name. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name. For example: Sanic provides a `url_for` method, to generate URLs based on the handler method name. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name. For example:
```python ```python
from sanic.response import redirect
@app.route('/') @app.route('/')
async def index(request): async def index(request):
# generate a URL for the endpoint `post_handler` # generate a URL for the endpoint `post_handler`
@ -145,7 +147,6 @@ async def index(request):
# the URL is `/posts/5`, redirect to it # the URL is `/posts/5`, redirect to it
return redirect(url) return redirect(url)
@app.route('/posts/<post_id>') @app.route('/posts/<post_id>')
async def post_handler(request, post_id): async def post_handler(request, post_id):
return text('Post - {}'.format(post_id)) return text('Post - {}'.format(post_id))

View File

@ -220,7 +220,7 @@ class Sanic:
name=name) name=name)
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None, def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=None, version=None, name=None): strict_slashes=None, version=None, name=None, stream=False):
"""A helper method to register class instance or """A helper method to register class instance or
functions as a handler to the application url functions as a handler to the application url
routes. routes.
@ -233,9 +233,9 @@ class Sanic:
:param strict_slashes: :param strict_slashes:
:param version: :param version:
:param name: user defined route name for url_for :param name: user defined route name for url_for
:param stream: boolean specifying if the handler is a stream handler
:return: function or class instance :return: function or class instance
""" """
stream = False
# Handle HTTPMethodView differently # Handle HTTPMethodView differently
if hasattr(handler, 'view_class'): if hasattr(handler, 'view_class'):
methods = set() methods = set()

View File

@ -1,7 +1,9 @@
import traceback import traceback
from json import JSONDecodeError from json import JSONDecodeError
from sanic.log import logger from sanic.log import logger
from sanic.exceptions import MethodNotSupported
from sanic.response import text
HOST = '127.0.0.1' HOST = '127.0.0.1'
PORT = 42101 PORT = 42101
@ -54,6 +56,15 @@ class SanicTestClient:
results[0] = request results[0] = request
self.app.request_middleware.appendleft(_collect_request) self.app.request_middleware.appendleft(_collect_request)
@self.app.exception(MethodNotSupported)
async def error_handler(request, exception):
if request.method in ['HEAD', 'PATCH', 'PUT', 'DELETE']:
return text(
'', exception.status_code, headers=exception.headers
)
else:
return self.app.error_handler.default(request, exception)
@self.app.listener('after_server_start') @self.app.listener('after_server_start')
async def _collect_response(sanic, loop): async def _collect_response(sanic, loop):
try: try:

View File

@ -15,9 +15,22 @@ class ReuseableTCPConnector(TCPConnector):
super(ReuseableTCPConnector, self).__init__(*args, **kwargs) super(ReuseableTCPConnector, self).__init__(*args, **kwargs)
self.old_proto = None self.old_proto = None
@asyncio.coroutine if aiohttp.__version__ >= '3.0':
def connect(self, req):
new_conn = yield from super(ReuseableTCPConnector, self)\ async def connect(self, req, traces=None):
new_conn = await super(ReuseableTCPConnector, self)\
.connect(req, traces=traces)
if self.old_proto is not None:
if self.old_proto != new_conn._protocol:
raise RuntimeError(
"We got a new connection, wanted the same one!")
print(new_conn.__dict__)
self.old_proto = new_conn._protocol
return new_conn
else:
async def connect(self, req):
new_conn = await super(ReuseableTCPConnector, self)\
.connect(req) .connect(req)
if self.old_proto is not None: if self.old_proto is not None:
if self.old_proto != new_conn._protocol: if self.old_proto != new_conn._protocol:
@ -168,7 +181,7 @@ class ReuseableSanicTestClient(SanicTestClient):
response.body = await response.read() response.body = await response.read()
if do_kill_session: if do_kill_session:
session.close() await session.close()
self._session = None self._session = None
return response return response

View File

@ -28,18 +28,17 @@ class DelayableTCPConnector(TCPConnector):
acting_as = self._acting_as acting_as = self._acting_as
return getattr(acting_as, item) return getattr(acting_as, item)
@asyncio.coroutine async def start(self, connection, read_until_eof=False):
def start(self, connection, read_until_eof=False):
if self.send_task is None: if self.send_task is None:
raise RuntimeError("do a send() before you do a start()") raise RuntimeError("do a send() before you do a start()")
resp = yield from self.send_task resp = await self.send_task
self.send_task = None self.send_task = None
self.resp = resp self.resp = resp
self._acting_as = self.resp self._acting_as = self.resp
self.orig_start = getattr(resp, 'start') self.orig_start = getattr(resp, 'start')
try: try:
ret = yield from self.orig_start(connection, ret = await self.orig_start(connection,
read_until_eof) read_until_eof)
except Exception as e: except Exception as e:
raise e raise e
@ -51,12 +50,11 @@ class DelayableTCPConnector(TCPConnector):
if self.send_task is not None: if self.send_task is not None:
self.send_task.cancel() self.send_task.cancel()
@asyncio.coroutine async def delayed_send(self, *args, **kwargs):
def delayed_send(self, *args, **kwargs):
req = self.req req = self.req
if self.delay and self.delay > 0: if self.delay and self.delay > 0:
#sync_sleep(self.delay) #sync_sleep(self.delay)
_ = yield from asyncio.sleep(self.delay) await asyncio.sleep(self.delay)
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
@ -80,13 +78,27 @@ class DelayableTCPConnector(TCPConnector):
self._post_connect_delay = _post_connect_delay self._post_connect_delay = _post_connect_delay
self._pre_request_delay = _pre_request_delay self._pre_request_delay = _pre_request_delay
@asyncio.coroutine if aiohttp.__version__ >= '3.0':
def connect(self, req):
async def connect(self, req, traces=None):
d_req = DelayableTCPConnector.\ d_req = DelayableTCPConnector.\
RequestContextManager(req, self._pre_request_delay) RequestContextManager(req, self._pre_request_delay)
conn = yield from super(DelayableTCPConnector, self).connect(req) conn = await super(DelayableTCPConnector, self).connect(req, traces=traces)
if self._post_connect_delay and self._post_connect_delay > 0: if self._post_connect_delay and self._post_connect_delay > 0:
_ = yield from asyncio.sleep(self._post_connect_delay, await asyncio.sleep(self._post_connect_delay,
loop=self._loop)
req.send = d_req.send
t = req.loop.time()
print("Connected at {}".format(t), flush=True)
return conn
else:
async def connect(self, req):
d_req = DelayableTCPConnector.\
RequestContextManager(req, self._pre_request_delay)
conn = await super(DelayableTCPConnector, self).connect(req)
if self._post_connect_delay and self._post_connect_delay > 0:
await asyncio.sleep(self._post_connect_delay,
loop=self._loop) loop=self._loop)
req.send = d_req.send req.send = d_req.send
t = req.loop.time() t = req.loop.time()

View File

@ -45,6 +45,10 @@ def test_method_not_allowed():
request, response = app.test_client.head('/') request, response = app.test_client.head('/')
assert response.headers['Allow'] == 'GET' assert response.headers['Allow'] == 'GET'
request, response = app.test_client.post('/')
assert response.headers['Allow'] == 'GET'
@app.post('/') @app.post('/')
async def test(request): async def test(request):
return response.json({'hello': 'world'}) return response.json({'hello': 'world'})
@ -54,6 +58,11 @@ def test_method_not_allowed():
assert set(response.headers['Allow'].split(', ')) == set(['GET', 'POST']) assert set(response.headers['Allow'].split(', ')) == set(['GET', 'POST'])
assert response.headers['Content-Length'] == '0' assert response.headers['Content-Length'] == '0'
request, response = app.test_client.patch('/')
assert response.status == 405
assert set(response.headers['Allow'].split(', ')) == set(['GET', 'POST'])
assert response.headers['Content-Length'] == '0'
@pytest.fixture @pytest.fixture
def json_app(): def json_app():

View File

@ -13,7 +13,7 @@ deps =
pytest-sanic pytest-sanic
pytest-sugar pytest-sugar
pytest-xdist pytest-xdist
aiohttp==1.3.5 aiohttp>=2.3
chardet<=2.3.0 chardet<=2.3.0
beautifulsoup4 beautifulsoup4
gunicorn gunicorn