diff --git a/.travis.yml b/.travis.yml index afac549a..6f2400f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ matrix: - env: TOX_ENV=check python: 3.6 install: pip install -U tox -script: tox -e $TOX_ENV +script: travis_retry tox -e $TOX_ENV deploy: provider: pypi user: channelcat diff --git a/docs/sanic/exceptions.md b/docs/sanic/exceptions.md index 4c0888f5..79108032 100644 --- a/docs/sanic/exceptions.md +++ b/docs/sanic/exceptions.md @@ -13,7 +13,7 @@ To throw an exception, simply `raise` the relevant exception from the from sanic.exceptions import ServerError @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) ``` @@ -24,7 +24,7 @@ from sanic.exceptions import abort from sanic.response import text @app.route('/youshallnotpass') -def no_no(request): +async def no_no(request): abort(401) # this won't happen text("OK") @@ -43,7 +43,7 @@ from sanic.response import text from sanic.exceptions import 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)) ``` diff --git a/docs/sanic/routing.md b/docs/sanic/routing.md index 98179e17..e9cb0aef 100644 --- a/docs/sanic/routing.md +++ b/docs/sanic/routing.md @@ -138,13 +138,14 @@ app.add_route(person_handler2, '/person/', 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: ```python +from sanic.response import redirect + @app.route('/') async def index(request): # generate a URL for the endpoint `post_handler` url = app.url_for('post_handler', post_id=5) # the URL is `/posts/5`, redirect to it - return redirect(url) - + return redirect(url) @app.route('/posts/') async def post_handler(request, post_id): diff --git a/sanic/app.py b/sanic/app.py index 6b83125d..87667825 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -220,7 +220,7 @@ class Sanic: name=name) 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 functions as a handler to the application url routes. @@ -233,9 +233,9 @@ class Sanic: :param strict_slashes: :param version: :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 """ - stream = False # Handle HTTPMethodView differently if hasattr(handler, 'view_class'): methods = set() diff --git a/sanic/testing.py b/sanic/testing.py index 873e43f7..a639e16e 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -1,7 +1,9 @@ import traceback from json import JSONDecodeError - from sanic.log import logger +from sanic.exceptions import MethodNotSupported +from sanic.response import text + HOST = '127.0.0.1' PORT = 42101 @@ -54,6 +56,15 @@ class SanicTestClient: results[0] = 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') async def _collect_response(sanic, loop): try: diff --git a/tests/test_keep_alive_timeout.py b/tests/test_keep_alive_timeout.py index c3de8462..4df00640 100644 --- a/tests/test_keep_alive_timeout.py +++ b/tests/test_keep_alive_timeout.py @@ -15,17 +15,30 @@ class ReuseableTCPConnector(TCPConnector): super(ReuseableTCPConnector, self).__init__(*args, **kwargs) self.old_proto = None - @asyncio.coroutine - def connect(self, req): - new_conn = yield from super(ReuseableTCPConnector, self)\ - .connect(req) - 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 + if aiohttp.__version__ >= '3.0': + + 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) + 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 class ReuseableSanicTestClient(SanicTestClient): @@ -168,7 +181,7 @@ class ReuseableSanicTestClient(SanicTestClient): response.body = await response.read() if do_kill_session: - session.close() + await session.close() self._session = None return response diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py index 113ffdd7..80dd7218 100644 --- a/tests/test_request_timeout.py +++ b/tests/test_request_timeout.py @@ -28,19 +28,18 @@ class DelayableTCPConnector(TCPConnector): acting_as = self._acting_as return getattr(acting_as, item) - @asyncio.coroutine - def start(self, connection, read_until_eof=False): + async def start(self, connection, read_until_eof=False): if self.send_task is None: 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.resp = resp self._acting_as = self.resp self.orig_start = getattr(resp, 'start') try: - ret = yield from self.orig_start(connection, - read_until_eof) + ret = await self.orig_start(connection, + read_until_eof) except Exception as e: raise e return ret @@ -51,12 +50,11 @@ class DelayableTCPConnector(TCPConnector): if self.send_task is not None: self.send_task.cancel() - @asyncio.coroutine - def delayed_send(self, *args, **kwargs): + async def delayed_send(self, *args, **kwargs): req = self.req if self.delay and self.delay > 0: #sync_sleep(self.delay) - _ = yield from asyncio.sleep(self.delay) + await asyncio.sleep(self.delay) t = req.loop.time() print("sending at {}".format(t), flush=True) conn = next(iter(args)) # first arg is connection @@ -80,18 +78,32 @@ class DelayableTCPConnector(TCPConnector): self._post_connect_delay = _post_connect_delay self._pre_request_delay = _pre_request_delay - @asyncio.coroutine - def connect(self, req): - d_req = DelayableTCPConnector.\ - RequestContextManager(req, self._pre_request_delay) - conn = yield from super(DelayableTCPConnector, self).connect(req) - if self._post_connect_delay and self._post_connect_delay > 0: - _ = yield from 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 + if aiohttp.__version__ >= '3.0': + + async def connect(self, req, traces=None): + d_req = DelayableTCPConnector.\ + RequestContextManager(req, self._pre_request_delay) + conn = await super(DelayableTCPConnector, self).connect(req, traces=traces) + if self._post_connect_delay and self._post_connect_delay > 0: + 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) + req.send = d_req.send + t = req.loop.time() + print("Connected at {}".format(t), flush=True) + return conn class DelayableSanicTestClient(SanicTestClient): diff --git a/tests/test_response.py b/tests/test_response.py index 57e01cb6..b5eb891c 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -45,6 +45,10 @@ def test_method_not_allowed(): request, response = app.test_client.head('/') assert response.headers['Allow'] == 'GET' + request, response = app.test_client.post('/') + assert response.headers['Allow'] == 'GET' + + @app.post('/') async def test(request): return response.json({'hello': 'world'}) @@ -54,6 +58,11 @@ def test_method_not_allowed(): assert set(response.headers['Allow'].split(', ')) == set(['GET', 'POST']) 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 def json_app(): diff --git a/tox.ini b/tox.ini index 88e31f22..71498013 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = pytest-sanic pytest-sugar pytest-xdist - aiohttp==1.3.5 + aiohttp>=2.3 chardet<=2.3.0 beautifulsoup4 gunicorn