From 3f7c9ea3f533af529aae2b4ef6d78432d8776fc2 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Thu, 27 Aug 2020 00:22:02 -0700 Subject: [PATCH 1/7] feat: fixes exception due to unread bytes in stream (#1897) * feat: fixes exception due to unread bytes in stream * feat: additonal unit tests to cover changes * fix: automated changes by `make fix-import` * fix: additonal changes by `make fix-import` Co-authored-by: Adam Hopkins --- sanic/response.py | 7 +++++-- sanic/router.py | 2 +- sanic/server.py | 13 +++++++------ sanic/testing.py | 4 +++- sanic/worker.py | 2 +- tests/test_keep_alive_timeout.py | 4 ++-- tests/test_request_data.py | 7 +++---- tests/test_request_stream.py | 18 ++++++++++++++++++ 8 files changed, 40 insertions(+), 17 deletions(-) diff --git a/sanic/response.py b/sanic/response.py index 2ebf0046..24033336 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -42,7 +42,7 @@ class BaseHTTPResponse: body=b"", ): """.. deprecated:: 20.3: - This function is not public API and will be removed.""" + This function is not public API and will be removed.""" # self.headers get priority over content_type if self.content_type and "Content-Type" not in self.headers: @@ -249,7 +249,10 @@ def raw( :param content_type: the content type (string) of the response. """ return HTTPResponse( - body=body, status=status, headers=headers, content_type=content_type, + body=body, + status=status, + headers=headers, + content_type=content_type, ) diff --git a/sanic/router.py b/sanic/router.py index ab6e3cef..a608f1a2 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -452,7 +452,7 @@ class Router: return route_handler, [], kwargs, route.uri, route.name def is_stream_handler(self, request): - """ Handler for request is stream or not. + """Handler for request is stream or not. :param request: Request object :return: bool """ diff --git a/sanic/server.py b/sanic/server.py index c4e08e76..2e27d80b 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -418,12 +418,13 @@ class HttpProtocol(asyncio.Protocol): async def stream_append(self): while self._body_chunks: body = self._body_chunks.popleft() - if self.request.stream.is_full(): - self.transport.pause_reading() - await self.request.stream.put(body) - self.transport.resume_reading() - else: - await self.request.stream.put(body) + if self.request: + if self.request.stream.is_full(): + self.transport.pause_reading() + await self.request.stream.put(body) + self.transport.resume_reading() + else: + await self.request.stream.put(body) def on_message_complete(self): # Entire request (headers and whole body) is received. diff --git a/sanic/testing.py b/sanic/testing.py index faabdfd1..020b3c11 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -103,7 +103,9 @@ class SanicTestClient: if self.port: server_kwargs = dict( - host=host or self.host, port=self.port, **server_kwargs, + host=host or self.host, + port=self.port, + **server_kwargs, ) host, port = host or self.host, self.port else: diff --git a/sanic/worker.py b/sanic/worker.py index b217b992..765f26f7 100644 --- a/sanic/worker.py +++ b/sanic/worker.py @@ -174,7 +174,7 @@ class GunicornWorker(base.Worker): @staticmethod def _create_ssl_context(cfg): - """ Creates SSLContext instance for usage in asyncio.create_server. + """Creates SSLContext instance for usage in asyncio.create_server. See ssl.SSLSocket.__init__ for more details. """ ctx = ssl.SSLContext(cfg.ssl_version) diff --git a/tests/test_keep_alive_timeout.py b/tests/test_keep_alive_timeout.py index 58385bec..4cc24f53 100644 --- a/tests/test_keep_alive_timeout.py +++ b/tests/test_keep_alive_timeout.py @@ -244,8 +244,8 @@ async def handler3(request): def test_keep_alive_timeout_reuse(): """If the server keep-alive timeout and client keep-alive timeout are - both longer than the delay, the client _and_ server will successfully - reuse the existing connection.""" + both longer than the delay, the client _and_ server will successfully + reuse the existing connection.""" try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) diff --git a/tests/test_request_data.py b/tests/test_request_data.py index d9a6351f..f5bfabda 100644 --- a/tests/test_request_data.py +++ b/tests/test_request_data.py @@ -46,8 +46,8 @@ def test_custom_context(app): invalid = str(e) j = loads(response.body) - j['response_mw_valid'] = user - j['response_mw_invalid'] = invalid + j["response_mw_valid"] = user + j["response_mw_invalid"] = invalid return json(j) request, response = app.test_client.get("/") @@ -59,8 +59,7 @@ def test_custom_context(app): "has_missing": False, "invalid": "'types.SimpleNamespace' object has no attribute 'missing'", "response_mw_valid": "sanic", - "response_mw_invalid": - "'types.SimpleNamespace' object has no attribute 'missing'" + "response_mw_invalid": "'types.SimpleNamespace' object has no attribute 'missing'", } diff --git a/tests/test_request_stream.py b/tests/test_request_stream.py index 972b2e1a..ff298868 100644 --- a/tests/test_request_stream.py +++ b/tests/test_request_stream.py @@ -1,4 +1,5 @@ import pytest +import asyncio from sanic.blueprints import Blueprint from sanic.exceptions import HeaderExpectationFailed @@ -6,6 +7,7 @@ from sanic.request import StreamBuffer from sanic.response import json, stream, text from sanic.views import CompositionView, HTTPMethodView from sanic.views import stream as stream_decorator +from sanic.server import HttpProtocol data = "abc" * 1_000_000 @@ -337,6 +339,22 @@ def test_request_stream_handle_exception(app): assert "Method GET not allowed for URL /post/random_id" in response.text +@pytest.mark.asyncio +async def test_request_stream_unread(app): + """ensure no error is raised when leaving unread bytes in byte-buffer""" + + err = None + protocol = HttpProtocol(loop=asyncio.get_event_loop(), app=app) + try: + protocol.request = None + protocol._body_chunks.append("this is a test") + await protocol.stream_append() + except AttributeError as e: + err = e + + assert err is None and not protocol._body_chunks + + def test_request_stream_blueprint(app): """for self.is_request_stream = True""" bp = Blueprint("test_blueprint_request_stream_blueprint") From 875be11ae5f04d7c0cc95caa0935d86308183895 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 27 Aug 2020 10:28:56 +0300 Subject: [PATCH 2/7] Update README.rst (#1917) --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index cbd53bb5..2ace1c5c 100644 --- a/README.rst +++ b/README.rst @@ -104,7 +104,7 @@ Hello World Example if __name__ == '__main__': app.run(host='0.0.0.0', port=8000) -Sanic can now be easily run using ``python3 hello.py``. +Sanic can now be easily run using ``sanic hello.app``. .. code:: From 58e15134fdc934ff423a0c710dad76d45368d493 Mon Sep 17 00:00:00 2001 From: raphaelauv Date: Wed, 2 Sep 2020 22:22:02 +0200 Subject: [PATCH 3/7] Add explicit ASGI compliance to the README (#1922) --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 2ace1c5c..cfdde631 100644 --- a/README.rst +++ b/README.rst @@ -58,6 +58,8 @@ Sanic | Build fast. Run fast. Sanic is a **Python 3.6+** web server and web framework that's written to go fast. It allows the usage of the ``async/await`` syntax added in Python 3.5, which makes your code non-blocking and speedy. +Sanic is also ASGI compliant, so you can deploy it with an `alternative ASGI webserver `_. + `Source code on GitHub `_ | `Help and discussion board `_. The project is maintained by the community, for the community. **Contributions are welcome!** From 9a8e49751d4f8f66c8b79f1d911e8fc1112588fc Mon Sep 17 00:00:00 2001 From: tomaszdrozdz Date: Tue, 8 Sep 2020 13:08:49 +0200 Subject: [PATCH 4/7] Adding --strict-markers for pytest --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 09cbc0dd..399cc2ec 100644 --- a/tox.ini +++ b/tox.ini @@ -55,6 +55,7 @@ commands = [pytest] filterwarnings = ignore:.*async with lock.* instead:DeprecationWarning +addopts = --strict-markers [testenv:security] deps = From eb8df1fc183910c7ff4ad96b70d14fb8bfb412ce Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 27 Sep 2020 02:58:36 +0300 Subject: [PATCH 5/7] Upgrade httpx --- sanic/response.py | 5 +- sanic/testing.py | 17 ++--- setup.py | 2 +- tests/test_app.py | 2 +- tests/test_keep_alive_timeout.py | 112 +++++++++---------------------- tests/test_request_stream.py | 5 +- tests/test_request_timeout.py | 68 ++++++++----------- tests/test_requests.py | 24 +++++-- tests/test_unix_socket.py | 7 +- tox.ini | 2 +- 10 files changed, 94 insertions(+), 150 deletions(-) diff --git a/sanic/response.py b/sanic/response.py index 24033336..d4dd39c1 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -249,10 +249,7 @@ def raw( :param content_type: the content type (string) of the response. """ return HTTPResponse( - body=body, - status=status, - headers=headers, - content_type=content_type, + body=body, status=status, headers=headers, content_type=content_type, ) diff --git a/sanic/testing.py b/sanic/testing.py index 020b3c11..1d13970b 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -11,6 +11,8 @@ from sanic.response import text ASGI_HOST = "mockserver" +ASGI_PORT = 1234 +ASGI_BASE_URL = f"http://{ASGI_HOST}:{ASGI_PORT}" HOST = "127.0.0.1" PORT = None @@ -103,9 +105,7 @@ class SanicTestClient: if self.port: server_kwargs = dict( - host=host or self.host, - port=self.port, - **server_kwargs, + host=host or self.host, port=self.port, **server_kwargs, ) host, port = host or self.host, self.port else: @@ -195,24 +195,19 @@ async def app_call_with_return(self, scope, receive, send): return await asgi_app() -class SanicASGIDispatch(httpx.ASGIDispatch): - pass - - class SanicASGITestClient(httpx.AsyncClient): def __init__( self, app, - base_url: str = f"http://{ASGI_HOST}", + base_url: str = ASGI_BASE_URL, suppress_exceptions: bool = False, ) -> None: app.__class__.__call__ = app_call_with_return app.asgi = True self.app = app - - dispatch = SanicASGIDispatch(app=app, client=(ASGI_HOST, PORT or 0)) - super().__init__(dispatch=dispatch, base_url=base_url) + transport = httpx.ASGITransport(app=app, client=(ASGI_HOST, ASGI_PORT)) + super().__init__(transport=transport, base_url=base_url) self.last_request = None diff --git a/setup.py b/setup.py index db788803..e4ff74ee 100644 --- a/setup.py +++ b/setup.py @@ -81,7 +81,7 @@ requirements = [ "aiofiles>=0.3.0", "websockets>=8.1,<9.0", "multidict>=4.0,<5.0", - "httpx==0.11.1", + "httpx==0.15.4", ] tests_require = [ diff --git a/tests/test_app.py b/tests/test_app.py index 6e1d28ff..5a7afa59 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,9 +1,9 @@ import asyncio import logging import sys -from unittest.mock import patch from inspect import isawaitable +from unittest.mock import patch import pytest diff --git a/tests/test_keep_alive_timeout.py b/tests/test_keep_alive_timeout.py index 4cc24f53..a6d5e2c6 100644 --- a/tests/test_keep_alive_timeout.py +++ b/tests/test_keep_alive_timeout.py @@ -3,6 +3,7 @@ import asyncio from asyncio import sleep as aio_sleep from json import JSONDecodeError +import httpcore import httpx from sanic import Sanic, server @@ -12,67 +13,26 @@ from sanic.testing import HOST, SanicTestClient CONFIG_FOR_TESTS = {"KEEP_ALIVE_TIMEOUT": 2, "KEEP_ALIVE": True} -old_conn = None PORT = 42101 # test_keep_alive_timeout_reuse doesn't work with random port +from httpcore._async.base import ConnectionState +from httpcore._async.connection import AsyncHTTPConnection +from httpcore._types import Origin -class ReusableSanicConnectionPool( - httpx.dispatch.connection_pool.ConnectionPool -): - @property - def cert(self): - return self.ssl.cert - @property - def verify(self): - return self.ssl.verify +class ReusableSanicConnectionPool(httpcore.AsyncConnectionPool): + last_reused_connection = None - @property - def trust_env(self): - return self.ssl.trust_env - - @property - def http2(self): - return self.ssl.http2 - - async def acquire_connection(self, origin, timeout): - global old_conn - connection = self.pop_connection(origin) - - if connection is None: - pool_timeout = None if timeout is None else timeout.pool_timeout - - await self.max_connections.acquire(timeout=pool_timeout) - ssl_config = httpx.config.SSLConfig( - cert=self.cert, - verify=self.verify, - trust_env=self.trust_env, - http2=self.http2, - ) - connection = httpx.dispatch.connection.HTTPConnection( - origin, - ssl=ssl_config, - backend=self.backend, - release_func=self.release_connection, - uds=self.uds, - ) - - self.active_connections.add(connection) - - if old_conn is not None: - if old_conn != connection: - raise RuntimeError( - "We got a new connection, wanted the same one!" - ) - old_conn = connection - - return connection + async def _get_connection_from_pool(self, *args, **kwargs): + conn = await super()._get_connection_from_pool(*args, **kwargs) + self.__class__.last_reused_connection = conn + return conn class ResusableSanicSession(httpx.AsyncClient): def __init__(self, *args, **kwargs) -> None: - dispatch = ReusableSanicConnectionPool() - super().__init__(dispatch=dispatch, *args, **kwargs) + transport = ReusableSanicConnectionPool() + super().__init__(transport=transport, *args, **kwargs) class ReuseableSanicTestClient(SanicTestClient): @@ -258,6 +218,7 @@ def test_keep_alive_timeout_reuse(): request, response = client.get("/1") assert response.status == 200 assert response.text == "OK" + assert ReusableSanicConnectionPool.last_reused_connection finally: client.kill_server() @@ -270,20 +231,15 @@ def test_keep_alive_client_timeout(): asyncio.set_event_loop(loop) client = ReuseableSanicTestClient(keep_alive_app_client_timeout, loop) headers = {"Connection": "keep-alive"} - try: - request, response = client.get( - "/1", headers=headers, request_keepalive=1 - ) - assert response.status == 200 - assert response.text == "OK" - loop.run_until_complete(aio_sleep(2)) - exception = None - request, response = client.get("/1", request_keepalive=1) - except ValueError as e: - exception = e - assert exception is not None - assert isinstance(exception, ValueError) - assert "got a new connection" in exception.args[0] + request, response = client.get( + "/1", headers=headers, request_keepalive=1 + ) + assert response.status == 200 + assert response.text == "OK" + loop.run_until_complete(aio_sleep(2)) + exception = None + request, response = client.get("/1", request_keepalive=1) + assert ReusableSanicConnectionPool.last_reused_connection is None finally: client.kill_server() @@ -298,22 +254,14 @@ def test_keep_alive_server_timeout(): asyncio.set_event_loop(loop) client = ReuseableSanicTestClient(keep_alive_app_server_timeout, loop) headers = {"Connection": "keep-alive"} - try: - request, response = client.get( - "/1", headers=headers, request_keepalive=60 - ) - assert response.status == 200 - assert response.text == "OK" - loop.run_until_complete(aio_sleep(3)) - exception = None - request, response = client.get("/1", request_keepalive=60) - except ValueError as e: - exception = e - assert exception is not None - assert isinstance(exception, ValueError) - assert ( - "Connection reset" in exception.args[0] - or "got a new connection" in exception.args[0] + request, response = client.get( + "/1", headers=headers, request_keepalive=60 ) + assert response.status == 200 + assert response.text == "OK" + loop.run_until_complete(aio_sleep(3)) + exception = None + request, response = client.get("/1", request_keepalive=60) + assert ReusableSanicConnectionPool.last_reused_connection is None finally: client.kill_server() diff --git a/tests/test_request_stream.py b/tests/test_request_stream.py index ff298868..d3354251 100644 --- a/tests/test_request_stream.py +++ b/tests/test_request_stream.py @@ -1,13 +1,14 @@ -import pytest import asyncio +import pytest + from sanic.blueprints import Blueprint from sanic.exceptions import HeaderExpectationFailed from sanic.request import StreamBuffer from sanic.response import json, stream, text +from sanic.server import HttpProtocol from sanic.views import CompositionView, HTTPMethodView from sanic.views import stream as stream_decorator -from sanic.server import HttpProtocol data = "abc" * 1_000_000 diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py index 0b7d6405..d750dd1d 100644 --- a/tests/test_request_timeout.py +++ b/tests/test_request_timeout.py @@ -1,64 +1,54 @@ import asyncio +from typing import cast + +import httpcore import httpx +from httpcore._async.base import ( + AsyncByteStream, + AsyncHTTPTransport, + ConnectionState, + NewConnectionRequired, +) +from httpcore._async.connection import AsyncHTTPConnection +from httpcore._async.connection_pool import ResponseByteStream +from httpcore._exceptions import LocalProtocolError, UnsupportedProtocol +from httpcore._types import TimeoutDict +from httpcore._utils import url_to_origin + from sanic import Sanic from sanic.response import text from sanic.testing import SanicTestClient -class DelayableHTTPConnection(httpx.dispatch.connection.HTTPConnection): - def __init__(self, *args, **kwargs): - self._request_delay = None - if "request_delay" in kwargs: - self._request_delay = kwargs.pop("request_delay") - super().__init__(*args, **kwargs) - - async def send(self, request, timeout=None): - - if self.connection is None: - self.connection = await self.connect(timeout=timeout) +class DelayableHTTPConnection(httpcore._async.connection.AsyncHTTPConnection): + async def arequest(self, *args, **kwargs): + await asyncio.sleep(2) + return await super().arequest(*args, **kwargs) + async def _open_socket(self, *args, **kwargs): + retval = await super()._open_socket(*args, **kwargs) if self._request_delay: await asyncio.sleep(self._request_delay) - - response = await self.connection.send(request, timeout=timeout) - - return response + return retval -class DelayableSanicConnectionPool( - httpx.dispatch.connection_pool.ConnectionPool -): +class DelayableSanicConnectionPool(httpcore.AsyncConnectionPool): def __init__(self, request_delay=None, *args, **kwargs): self._request_delay = request_delay super().__init__(*args, **kwargs) - async def acquire_connection(self, origin, timeout=None): - connection = self.pop_connection(origin) - - if connection is None: - pool_timeout = None if timeout is None else timeout.pool_timeout - - await self.max_connections.acquire(timeout=pool_timeout) - connection = DelayableHTTPConnection( - origin, - ssl=self.ssl, - backend=self.backend, - release_func=self.release_connection, - uds=self.uds, - request_delay=self._request_delay, - ) - - self.active_connections.add(connection) - - return connection + async def _add_to_pool(self, connection, timeout): + connection.__class__ = DelayableHTTPConnection + connection._request_delay = self._request_delay + await super()._add_to_pool(connection, timeout) class DelayableSanicSession(httpx.AsyncClient): def __init__(self, request_delay=None, *args, **kwargs) -> None: - dispatch = DelayableSanicConnectionPool(request_delay=request_delay) - super().__init__(dispatch=dispatch, *args, **kwargs) + transport = DelayableSanicConnectionPool(request_delay=request_delay) + super().__init__(transport=transport, *args, **kwargs) class DelayableSanicTestClient(SanicTestClient): diff --git a/tests/test_requests.py b/tests/test_requests.py index 1ed94359..add05f4a 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -12,7 +12,14 @@ from sanic import Blueprint, Sanic from sanic.exceptions import ServerError from sanic.request import DEFAULT_HTTP_CONTENT_TYPE, Request, RequestParameters from sanic.response import html, json, text -from sanic.testing import ASGI_HOST, HOST, PORT, SanicTestClient +from sanic.testing import ( + ASGI_BASE_URL, + ASGI_HOST, + ASGI_PORT, + HOST, + PORT, + SanicTestClient, +) # ------------------------------------------------------------ # @@ -59,7 +66,10 @@ async def test_ip_asgi(app): request, response = await app.asgi_client.get("/") - assert response.text == "http://mockserver/" + if response.text.endswith("/") and not ASGI_BASE_URL.endswith("/"): + response.text[:-1] == ASGI_BASE_URL + else: + assert response.text == ASGI_BASE_URL def test_text(app): @@ -573,7 +583,7 @@ async def test_standard_forwarded_asgi(app): assert response.json() == {"for": "127.0.0.2", "proto": "ws"} assert request.remote_addr == "127.0.0.2" assert request.scheme == "ws" - assert request.server_port == 80 + assert request.server_port == ASGI_PORT app.config.FORWARDED_SECRET = "mySecret" request, response = await app.asgi_client.get("/", headers=headers) @@ -1044,9 +1054,9 @@ def test_url_attributes_no_ssl(app, path, query, expected_url): @pytest.mark.parametrize( "path,query,expected_url", [ - ("/foo", "", "http://{}/foo"), - ("/bar/baz", "", "http://{}/bar/baz"), - ("/moo/boo", "arg1=val1", "http://{}/moo/boo?arg1=val1"), + ("/foo", "", "{}/foo"), + ("/bar/baz", "", "{}/bar/baz"), + ("/moo/boo", "arg1=val1", "{}/moo/boo?arg1=val1"), ], ) @pytest.mark.asyncio @@ -1057,7 +1067,7 @@ async def test_url_attributes_no_ssl_asgi(app, path, query, expected_url): app.add_route(handler, path) request, response = await app.asgi_client.get(path + f"?{query}") - assert request.url == expected_url.format(ASGI_HOST) + assert request.url == expected_url.format(ASGI_BASE_URL) parsed = urlparse(request.url) diff --git a/tests/test_unix_socket.py b/tests/test_unix_socket.py index bbffc890..c592dcde 100644 --- a/tests/test_unix_socket.py +++ b/tests/test_unix_socket.py @@ -4,6 +4,7 @@ import os import subprocess import sys +import httpcore import httpx import pytest @@ -139,8 +140,9 @@ def test_unix_connection(): @app.listener("after_server_start") async def client(app, loop): + transport = httpcore.AsyncConnectionPool(uds=SOCKPATH) try: - async with httpx.AsyncClient(uds=SOCKPATH) as client: + async with httpx.AsyncClient(transport=transport) as client: r = await client.get("http://myhost.invalid/") assert r.status_code == 200 assert r.text == os.path.abspath(SOCKPATH) @@ -179,8 +181,9 @@ async def test_zero_downtime(): from time import monotonic as current_time async def client(): + transport = httpcore.AsyncConnectionPool(uds=SOCKPATH) for _ in range(40): - async with httpx.AsyncClient(uds=SOCKPATH) as client: + async with httpx.AsyncClient(transport=transport) as client: r = await client.get("http://localhost/sleep/0.1") assert r.status_code == 200 assert r.text == f"Slept 0.1 seconds.\n" diff --git a/tox.ini b/tox.ini index 09cbc0dd..69a8ce49 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = pytest-sanic pytest-sugar httpcore==0.3.0 - httpx==0.11.1 + httpx==0.15.4 chardet<=2.3.0 beautifulsoup4 gunicorn From bd4e1cdc1e6c211f399e712b28f19efbf5632c95 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 27 Sep 2020 10:27:12 +0300 Subject: [PATCH 6/7] squash --- sanic/response.py | 5 ++++- sanic/testing.py | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sanic/response.py b/sanic/response.py index d4dd39c1..24033336 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -249,7 +249,10 @@ def raw( :param content_type: the content type (string) of the response. """ return HTTPResponse( - body=body, status=status, headers=headers, content_type=content_type, + body=body, + status=status, + headers=headers, + content_type=content_type, ) diff --git a/sanic/testing.py b/sanic/testing.py index 1d13970b..541f035d 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -105,7 +105,9 @@ class SanicTestClient: if self.port: server_kwargs = dict( - host=host or self.host, port=self.port, **server_kwargs, + host=host or self.host, + port=self.port, + **server_kwargs, ) host, port = host or self.host, self.port else: From efa0aaf2c2faedda99ee611039fa70a956634472 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 27 Sep 2020 10:46:51 +0300 Subject: [PATCH 7/7] Add asyncio markers to tox.ini --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 399cc2ec..e1c9ecf1 100644 --- a/tox.ini +++ b/tox.ini @@ -56,6 +56,8 @@ commands = filterwarnings = ignore:.*async with lock.* instead:DeprecationWarning addopts = --strict-markers +markers = + asyncio [testenv:security] deps =