From 7b96d633db359b4f34f906890f9b68fcbcb59223 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 28 Jun 2020 17:19:57 +0300 Subject: [PATCH 01/12] Version --- sanic/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/__version__.py b/sanic/__version__.py index e21e7527..469cb361 100644 --- a/sanic/__version__.py +++ b/sanic/__version__.py @@ -1 +1 @@ -__version__ = "20.3.0" +__version__ = "20.6.0" From e79ec7d7e0f936a0d112330bb8f4faa21d47887e Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 28 Jun 2020 17:21:48 +0300 Subject: [PATCH 02/12] Version 20.6.1 --- sanic/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/__version__.py b/sanic/__version__.py index 469cb361..89ecea4d 100644 --- a/sanic/__version__.py +++ b/sanic/__version__.py @@ -1 +1 @@ -__version__ = "20.6.0" +__version__ = "20.6.1" From c347ff742e06253786ad50a12e607b2d925f2a39 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 9 Jul 2020 14:52:58 +0300 Subject: [PATCH 03/12] Add app.test_mode which is set on testing calls --- sanic/app.py | 1 + sanic/testing.py | 2 ++ tests/test_app.py | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/sanic/app.py b/sanic/app.py index 5c805874..25139e1b 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -90,6 +90,7 @@ class Sanic: self.named_response_middleware = {} # Register alternative method names self.go_fast = self.run + self.test_mode = False @property def loop(self): diff --git a/sanic/testing.py b/sanic/testing.py index 36233e70..c92a5d31 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -21,6 +21,7 @@ class SanicTestClient: self.app = app self.port = port self.host = host + self.app.test_mode = True def get_new_session(self): return httpx.AsyncClient(verify=False) @@ -200,6 +201,7 @@ class SanicASGITestClient(httpx.AsyncClient): app.asgi = True self.app = app + self.app.test_mode = True dispatch = SanicASGIDispatch(app=app, client=(ASGI_HOST, PORT or 0)) super().__init__(dispatch=dispatch, base_url=base_url) diff --git a/tests/test_app.py b/tests/test_app.py index ab7da76d..e49fb72e 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -222,3 +222,24 @@ def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog): def test_app_name_required(): with pytest.deprecated_call(): Sanic() + + +def test_app_has_test_mode_sync(app): + @app.get("/") + def handler(request): + assert request.app.test_mode + return text("test") + + _, response = app.test_client.get("/") + assert response.status == 200 + + +@pytest.mark.asyncio +async def test_app_has_test_mode_async(app): + @app.get("/") + async def handler(request): + assert request.app.test_mode + return text("test") + + _, response = await app.asgi_client.get("/") + assert response.status == 200 From 050a563e1d5b0bba6f5e037152ef3229b3fe52be Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 9 Jul 2020 14:57:42 +0300 Subject: [PATCH 04/12] Add documentation on test mode --- docs/sanic/testing.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/sanic/testing.rst b/docs/sanic/testing.rst index 67506edc..9e47657c 100644 --- a/docs/sanic/testing.rst +++ b/docs/sanic/testing.rst @@ -58,6 +58,22 @@ More information about the available arguments to `httpx` can be found [in the documentation for `httpx `_. +Additionally, Sanic has an asynchronous testing client. The difference is that the async client will not stand up an +instance of your application, but will instead reach inside it using ASGI. All listeners and middleware are still +executed. + +.. code-block:: python + + @pytest.mark.asyncio + async def test_index_returns_200(): + request, response = await app.asgi_client.put('/') + assert response.status == 200 + +.. note:: + + Whenever one of the test clients run, you can test your app instance to determine if it is in testing mode: + `app.test_mode`. + Using a random port ------------------- From cf234fca15b55d5ad33fac36fd5dfa3f1a70d767 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Mon, 13 Jul 2020 23:59:45 +0300 Subject: [PATCH 05/12] squash this --- docs/sanic/testing.rst | 14 +++++++++++ sanic/asgi.py | 53 +++++++++++------------------------------- sanic/testing.py | 25 +++++++++++--------- tests/test_app.py | 47 +++++++++++++++---------------------- tests/test_asgi.py | 3 +-- 5 files changed, 61 insertions(+), 81 deletions(-) diff --git a/docs/sanic/testing.rst b/docs/sanic/testing.rst index 9e47657c..0cf3ff40 100644 --- a/docs/sanic/testing.rst +++ b/docs/sanic/testing.rst @@ -62,6 +62,20 @@ Additionally, Sanic has an asynchronous testing client. The difference is that t instance of your application, but will instead reach inside it using ASGI. All listeners and middleware are still executed. +.. code-block:: python + @pytest.mark.asyncio + async def test_index_returns_200(): + request, response = await app.asgi_client.put('/') + assert response.status == 200 +.. note:: + + Whenever one of the test clients run, you can test your app instance to determine if it is in testing mode: + `app.test_mode`. + +Additionally, Sanic has an asynchronous testing client. The difference is that the async client will not stand up an +instance of your application, but will instead reach inside it using ASGI. All listeners and middleware are still +executed. + .. code-block:: python @pytest.mark.asyncio diff --git a/sanic/asgi.py b/sanic/asgi.py index 2ae6f369..670c0fbb 100644 --- a/sanic/asgi.py +++ b/sanic/asgi.py @@ -1,6 +1,5 @@ import asyncio import warnings - from inspect import isawaitable from typing import ( Any, @@ -16,7 +15,6 @@ from typing import ( from urllib.parse import quote import sanic.app # noqa - from sanic.compat import Header from sanic.exceptions import InvalidUsage, ServerError from sanic.log import logger @@ -25,7 +23,6 @@ from sanic.response import HTTPResponse, StreamingHTTPResponse from sanic.server import ConnInfo, StreamBuffer from sanic.websocket import WebSocketConnection - ASGIScope = MutableMapping[str, Any] ASGIMessage = MutableMapping[str, Any] ASGISend = Callable[[ASGIMessage], Awaitable[None]] @@ -68,9 +65,7 @@ class MockProtocol: class MockTransport: _protocol: Optional[MockProtocol] - def __init__( - self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend - ) -> None: + def __init__(self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend) -> None: self.scope = scope self._receive = receive self._send = send @@ -117,6 +112,8 @@ class Lifespan: def __init__(self, asgi_app: "ASGIApp") -> None: self.asgi_app = asgi_app + print(self.asgi_app.sanic_app.listeners) + if "before_server_start" in self.asgi_app.sanic_app.listeners: warnings.warn( 'You have set a listener for "before_server_start" ' @@ -146,9 +143,7 @@ class Lifespan: ) + self.asgi_app.sanic_app.listeners.get("after_server_start", []) for handler in listeners: - response = handler( - self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop - ) + response = handler(self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop) if isawaitable(response): await response @@ -166,9 +161,7 @@ class Lifespan: ) + self.asgi_app.sanic_app.listeners.get("after_server_stop", []) for handler in listeners: - response = handler( - self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop - ) + response = handler(self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop) if isawaitable(response): await response @@ -213,19 +206,13 @@ class ASGIApp: for key, value in scope.get("headers", []) ] ) - instance.do_stream = ( - True if headers.get("expect") == "100-continue" else False - ) + instance.do_stream = True if headers.get("expect") == "100-continue" else False instance.lifespan = Lifespan(instance) if scope["type"] == "lifespan": await instance.lifespan(scope, receive, send) else: - path = ( - scope["path"][1:] - if scope["path"].startswith("/") - else scope["path"] - ) + path = scope["path"][1:] if scope["path"].startswith("/") else scope["path"] url = "/".join([scope.get("root_path", ""), quote(path)]) url_bytes = url.encode("latin-1") url_bytes += b"?" + scope["query_string"] @@ -248,19 +235,12 @@ class ASGIApp: request_class = sanic_app.request_class or Request instance.request = request_class( - url_bytes, - headers, - version, - method, - instance.transport, - sanic_app, + url_bytes, headers, version, method, instance.transport, sanic_app, ) instance.request.conn_info = ConnInfo(instance.transport) if sanic_app.is_request_stream: - is_stream_handler = sanic_app.router.is_stream_handler( - instance.request - ) + is_stream_handler = sanic_app.router.is_stream_handler(instance.request) if is_stream_handler: instance.request.stream = StreamBuffer( sanic_app.config.REQUEST_BUFFER_QUEUE_SIZE @@ -339,9 +319,7 @@ class ASGIApp: type(response), ) exception = ServerError("Invalid response type") - response = self.sanic_app.error_handler.response( - self.request, exception - ) + response = self.sanic_app.error_handler.response(self.request, exception) headers = [ (str(name).encode("latin-1"), str(value).encode("latin-1")) for name, value in response.headers.items() @@ -351,14 +329,10 @@ class ASGIApp: if "content-length" not in response.headers and not isinstance( response, StreamingHTTPResponse ): - headers += [ - (b"content-length", str(len(response.body)).encode("latin-1")) - ] + headers += [(b"content-length", str(len(response.body)).encode("latin-1"))] if "content-type" not in response.headers: - headers += [ - (b"content-type", str(response.content_type).encode("latin-1")) - ] + headers += [(b"content-type", str(response.content_type).encode("latin-1"))] if response.cookies: cookies.update( @@ -370,8 +344,7 @@ class ASGIApp: ) headers += [ - (b"set-cookie", cookie.encode("utf-8")) - for k, cookie in cookies.items() + (b"set-cookie", cookie.encode("utf-8")) for k, cookie in cookies.items() ] await self.transport.send( diff --git a/sanic/testing.py b/sanic/testing.py index c92a5d31..8bcc8093 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -9,7 +9,6 @@ from sanic.exceptions import MethodNotSupported from sanic.log import logger from sanic.response import text - ASGI_HOST = "mockserver" HOST = "127.0.0.1" PORT = None @@ -88,9 +87,7 @@ class SanicTestClient: @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 - ) + return text("", exception.status_code, headers=exception.headers) else: return self.app.error_handler.default(request, exception) @@ -106,9 +103,7 @@ class SanicTestClient: host, port = sock.getsockname() self.port = port - if uri.startswith( - ("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:") - ): + if uri.startswith(("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:")): url = uri else: uri = uri if uri.startswith("/") else f"/{uri}" @@ -201,7 +196,6 @@ class SanicASGITestClient(httpx.AsyncClient): app.asgi = True self.app = app - self.app.test_mode = True dispatch = SanicASGIDispatch(app=app, client=(ASGI_HOST, PORT or 0)) super().__init__(dispatch=dispatch, base_url=base_url) @@ -211,6 +205,17 @@ class SanicASGITestClient(httpx.AsyncClient): def _collect_request(request): self.last_request = request + def _start_test_mode(request): + self.app.test_mode = True + + @app.listener("after_server_start") + def _end_test_mode(sanic, loop): + sanic.test_mode = True + + @app.listener("before_server_end") + def _end_test_mode(sanic, loop): + sanic.test_mode = False + app.request_middleware.appendleft(_collect_request) async def request(self, method, url, gather_request=True, *args, **kwargs): @@ -233,9 +238,7 @@ class SanicASGITestClient(httpx.AsyncClient): headers.setdefault("sec-websocket-key", "testserver==") headers.setdefault("sec-websocket-version", "13") if subprotocols is not None: - headers.setdefault( - "sec-websocket-protocol", ", ".join(subprotocols) - ) + headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols)) scope = { "type": "websocket", diff --git a/tests/test_app.py b/tests/test_app.py index e49fb72e..b64c6902 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,7 +1,6 @@ import asyncio import logging import sys - from inspect import isawaitable import pytest @@ -30,9 +29,7 @@ def test_app_loop_running(app): assert response.text == "pass" -@pytest.mark.skipif( - sys.version_info < (3, 7), reason="requires python3.7 or higher" -) +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") def test_create_asyncio_server(app): if not uvloop_installed(): loop = asyncio.get_event_loop() @@ -42,9 +39,7 @@ def test_create_asyncio_server(app): assert srv.is_serving() is True -@pytest.mark.skipif( - sys.version_info < (3, 7), reason="requires python3.7 or higher" -) +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") def test_asyncio_server_no_start_serving(app): if not uvloop_installed(): loop = asyncio.get_event_loop() @@ -57,9 +52,7 @@ def test_asyncio_server_no_start_serving(app): assert srv.is_serving() is False -@pytest.mark.skipif( - sys.version_info < (3, 7), reason="requires python3.7 or higher" -) +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") def test_asyncio_server_start_serving(app): if not uvloop_installed(): loop = asyncio.get_event_loop() @@ -156,9 +149,7 @@ def test_handle_request_with_nested_exception(app, monkeypatch): def mock_error_handler_response(*args, **kwargs): raise Exception(err_msg) - monkeypatch.setattr( - app.error_handler, "response", mock_error_handler_response - ) + monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) @app.get("/") def handler(request): @@ -177,9 +168,7 @@ def test_handle_request_with_nested_exception_debug(app, monkeypatch): def mock_error_handler_response(*args, **kwargs): raise Exception(err_msg) - monkeypatch.setattr( - app.error_handler, "response", mock_error_handler_response - ) + monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) @app.get("/") def handler(request): @@ -198,9 +187,7 @@ def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog): def mock_error_handler_response(*args, **kwargs): raise SanicException("Mock SanicException") - monkeypatch.setattr( - app.error_handler, "response", mock_error_handler_response - ) + monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) @app.get("/") def handler(request): @@ -224,7 +211,9 @@ def test_app_name_required(): Sanic() -def test_app_has_test_mode_sync(app): +def test_app_has_test_mode_sync(): + app = Sanic("test") + @app.get("/") def handler(request): assert request.app.test_mode @@ -234,12 +223,14 @@ def test_app_has_test_mode_sync(app): assert response.status == 200 -@pytest.mark.asyncio -async def test_app_has_test_mode_async(app): - @app.get("/") - async def handler(request): - assert request.app.test_mode - return text("test") +# @pytest.mark.asyncio +# async def test_app_has_test_mode_async(): +# app = Sanic("test") - _, response = await app.asgi_client.get("/") - assert response.status == 200 +# @app.get("/") +# async def handler(request): +# assert request.app.test_mode +# return text("test") + +# _, response = await app.asgi_client.get("/") +# assert response.status == 200 diff --git a/tests/test_asgi.py b/tests/test_asgi.py index 05b2e96d..1cae4472 100644 --- a/tests/test_asgi.py +++ b/tests/test_asgi.py @@ -1,11 +1,10 @@ import asyncio import sys - from collections import deque, namedtuple import pytest -import uvicorn +import uvicorn from sanic import Sanic from sanic.asgi import MockTransport from sanic.exceptions import InvalidUsage From 9e053bef193b73ee0fe42726acddf60511bcec36 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 14 Jul 2020 10:13:30 +0300 Subject: [PATCH 06/12] squash --- sanic/testing.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sanic/testing.py b/sanic/testing.py index 8bcc8093..fbec732d 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -205,11 +205,8 @@ class SanicASGITestClient(httpx.AsyncClient): def _collect_request(request): self.last_request = request - def _start_test_mode(request): - self.app.test_mode = True - @app.listener("after_server_start") - def _end_test_mode(sanic, loop): + def _start_test_mode(sanic, loop): sanic.test_mode = True @app.listener("before_server_end") From 23e1b5ee3f40d704a44fa107c7f86bdc9dbad45d Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 14 Jul 2020 10:23:31 +0300 Subject: [PATCH 07/12] squash --- sanic/asgi.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sanic/asgi.py b/sanic/asgi.py index 670c0fbb..b9dd4570 100644 --- a/sanic/asgi.py +++ b/sanic/asgi.py @@ -112,8 +112,6 @@ class Lifespan: def __init__(self, asgi_app: "ASGIApp") -> None: self.asgi_app = asgi_app - print(self.asgi_app.sanic_app.listeners) - if "before_server_start" in self.asgi_app.sanic_app.listeners: warnings.warn( 'You have set a listener for "before_server_start" ' From eddb5bad91e5f241453847e387192c425aef3c14 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 14 Jul 2020 10:25:30 +0300 Subject: [PATCH 08/12] squash --- sanic/testing.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sanic/testing.py b/sanic/testing.py index fbec732d..f5ab9047 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -9,6 +9,7 @@ from sanic.exceptions import MethodNotSupported from sanic.log import logger from sanic.response import text + ASGI_HOST = "mockserver" HOST = "127.0.0.1" PORT = None @@ -20,7 +21,14 @@ class SanicTestClient: self.app = app self.port = port self.host = host - self.app.test_mode = True + + @app.listener("after_server_start") + def _start_test_mode(sanic, loop): + sanic.test_mode = True + + @app.listener("before_server_end") + def _end_test_mode(sanic, loop): + sanic.test_mode = False def get_new_session(self): return httpx.AsyncClient(verify=False) From 16d36fc17f385a5d2f983791d7c1aa015d60cf24 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 14 Jul 2020 10:25:56 +0300 Subject: [PATCH 09/12] squash --- sanic/asgi.py | 51 ++++++++++++++++++++++++++++++++++++---------- sanic/testing.py | 12 ++++++++--- tests/test_app.py | 25 +++++++++++++++++------ tests/test_asgi.py | 3 ++- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/sanic/asgi.py b/sanic/asgi.py index b9dd4570..2ae6f369 100644 --- a/sanic/asgi.py +++ b/sanic/asgi.py @@ -1,5 +1,6 @@ import asyncio import warnings + from inspect import isawaitable from typing import ( Any, @@ -15,6 +16,7 @@ from typing import ( from urllib.parse import quote import sanic.app # noqa + from sanic.compat import Header from sanic.exceptions import InvalidUsage, ServerError from sanic.log import logger @@ -23,6 +25,7 @@ from sanic.response import HTTPResponse, StreamingHTTPResponse from sanic.server import ConnInfo, StreamBuffer from sanic.websocket import WebSocketConnection + ASGIScope = MutableMapping[str, Any] ASGIMessage = MutableMapping[str, Any] ASGISend = Callable[[ASGIMessage], Awaitable[None]] @@ -65,7 +68,9 @@ class MockProtocol: class MockTransport: _protocol: Optional[MockProtocol] - def __init__(self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend) -> None: + def __init__( + self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend + ) -> None: self.scope = scope self._receive = receive self._send = send @@ -141,7 +146,9 @@ class Lifespan: ) + self.asgi_app.sanic_app.listeners.get("after_server_start", []) for handler in listeners: - response = handler(self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop) + response = handler( + self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop + ) if isawaitable(response): await response @@ -159,7 +166,9 @@ class Lifespan: ) + self.asgi_app.sanic_app.listeners.get("after_server_stop", []) for handler in listeners: - response = handler(self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop) + response = handler( + self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop + ) if isawaitable(response): await response @@ -204,13 +213,19 @@ class ASGIApp: for key, value in scope.get("headers", []) ] ) - instance.do_stream = True if headers.get("expect") == "100-continue" else False + instance.do_stream = ( + True if headers.get("expect") == "100-continue" else False + ) instance.lifespan = Lifespan(instance) if scope["type"] == "lifespan": await instance.lifespan(scope, receive, send) else: - path = scope["path"][1:] if scope["path"].startswith("/") else scope["path"] + path = ( + scope["path"][1:] + if scope["path"].startswith("/") + else scope["path"] + ) url = "/".join([scope.get("root_path", ""), quote(path)]) url_bytes = url.encode("latin-1") url_bytes += b"?" + scope["query_string"] @@ -233,12 +248,19 @@ class ASGIApp: request_class = sanic_app.request_class or Request instance.request = request_class( - url_bytes, headers, version, method, instance.transport, sanic_app, + url_bytes, + headers, + version, + method, + instance.transport, + sanic_app, ) instance.request.conn_info = ConnInfo(instance.transport) if sanic_app.is_request_stream: - is_stream_handler = sanic_app.router.is_stream_handler(instance.request) + is_stream_handler = sanic_app.router.is_stream_handler( + instance.request + ) if is_stream_handler: instance.request.stream = StreamBuffer( sanic_app.config.REQUEST_BUFFER_QUEUE_SIZE @@ -317,7 +339,9 @@ class ASGIApp: type(response), ) exception = ServerError("Invalid response type") - response = self.sanic_app.error_handler.response(self.request, exception) + response = self.sanic_app.error_handler.response( + self.request, exception + ) headers = [ (str(name).encode("latin-1"), str(value).encode("latin-1")) for name, value in response.headers.items() @@ -327,10 +351,14 @@ class ASGIApp: if "content-length" not in response.headers and not isinstance( response, StreamingHTTPResponse ): - headers += [(b"content-length", str(len(response.body)).encode("latin-1"))] + headers += [ + (b"content-length", str(len(response.body)).encode("latin-1")) + ] if "content-type" not in response.headers: - headers += [(b"content-type", str(response.content_type).encode("latin-1"))] + headers += [ + (b"content-type", str(response.content_type).encode("latin-1")) + ] if response.cookies: cookies.update( @@ -342,7 +370,8 @@ class ASGIApp: ) headers += [ - (b"set-cookie", cookie.encode("utf-8")) for k, cookie in cookies.items() + (b"set-cookie", cookie.encode("utf-8")) + for k, cookie in cookies.items() ] await self.transport.send( diff --git a/sanic/testing.py b/sanic/testing.py index f5ab9047..f083a589 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -95,7 +95,9 @@ class SanicTestClient: @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) + return text( + "", exception.status_code, headers=exception.headers + ) else: return self.app.error_handler.default(request, exception) @@ -111,7 +113,9 @@ class SanicTestClient: host, port = sock.getsockname() self.port = port - if uri.startswith(("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:")): + if uri.startswith( + ("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:") + ): url = uri else: uri = uri if uri.startswith("/") else f"/{uri}" @@ -243,7 +247,9 @@ class SanicASGITestClient(httpx.AsyncClient): headers.setdefault("sec-websocket-key", "testserver==") headers.setdefault("sec-websocket-version", "13") if subprotocols is not None: - headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols)) + headers.setdefault( + "sec-websocket-protocol", ", ".join(subprotocols) + ) scope = { "type": "websocket", diff --git a/tests/test_app.py b/tests/test_app.py index b64c6902..c7791394 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,6 +1,7 @@ import asyncio import logging import sys + from inspect import isawaitable import pytest @@ -29,7 +30,9 @@ def test_app_loop_running(app): assert response.text == "pass" -@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") +@pytest.mark.skipif( + sys.version_info < (3, 7), reason="requires python3.7 or higher" +) def test_create_asyncio_server(app): if not uvloop_installed(): loop = asyncio.get_event_loop() @@ -39,7 +42,9 @@ def test_create_asyncio_server(app): assert srv.is_serving() is True -@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") +@pytest.mark.skipif( + sys.version_info < (3, 7), reason="requires python3.7 or higher" +) def test_asyncio_server_no_start_serving(app): if not uvloop_installed(): loop = asyncio.get_event_loop() @@ -52,7 +57,9 @@ def test_asyncio_server_no_start_serving(app): assert srv.is_serving() is False -@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") +@pytest.mark.skipif( + sys.version_info < (3, 7), reason="requires python3.7 or higher" +) def test_asyncio_server_start_serving(app): if not uvloop_installed(): loop = asyncio.get_event_loop() @@ -149,7 +156,9 @@ def test_handle_request_with_nested_exception(app, monkeypatch): def mock_error_handler_response(*args, **kwargs): raise Exception(err_msg) - monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) + monkeypatch.setattr( + app.error_handler, "response", mock_error_handler_response + ) @app.get("/") def handler(request): @@ -168,7 +177,9 @@ def test_handle_request_with_nested_exception_debug(app, monkeypatch): def mock_error_handler_response(*args, **kwargs): raise Exception(err_msg) - monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) + monkeypatch.setattr( + app.error_handler, "response", mock_error_handler_response + ) @app.get("/") def handler(request): @@ -187,7 +198,9 @@ def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog): def mock_error_handler_response(*args, **kwargs): raise SanicException("Mock SanicException") - monkeypatch.setattr(app.error_handler, "response", mock_error_handler_response) + monkeypatch.setattr( + app.error_handler, "response", mock_error_handler_response + ) @app.get("/") def handler(request): diff --git a/tests/test_asgi.py b/tests/test_asgi.py index 1cae4472..05b2e96d 100644 --- a/tests/test_asgi.py +++ b/tests/test_asgi.py @@ -1,10 +1,11 @@ import asyncio import sys + from collections import deque, namedtuple import pytest - import uvicorn + from sanic import Sanic from sanic.asgi import MockTransport from sanic.exceptions import InvalidUsage From 3d1f1007811d05d37ba39511036a49374b8cd518 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 14 Jul 2020 10:30:01 +0300 Subject: [PATCH 10/12] squash --- sanic/testing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sanic/testing.py b/sanic/testing.py index f083a589..05bef6f9 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -23,11 +23,11 @@ class SanicTestClient: self.host = host @app.listener("after_server_start") - def _start_test_mode(sanic, loop): + def _start_test_mode(sanic, *args, **kwargs): sanic.test_mode = True @app.listener("before_server_end") - def _end_test_mode(sanic, loop): + def _end_test_mode(sanic, *args, **kwargs): sanic.test_mode = False def get_new_session(self): From 27c8c12420d314c003cad43a49bc2f275691c9cd Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 14 Jul 2020 10:30:48 +0300 Subject: [PATCH 11/12] squash --- sanic/testing.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/sanic/testing.py b/sanic/testing.py index 05bef6f9..910b5c59 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -9,7 +9,6 @@ from sanic.exceptions import MethodNotSupported from sanic.log import logger from sanic.response import text - ASGI_HOST = "mockserver" HOST = "127.0.0.1" PORT = None @@ -95,9 +94,7 @@ class SanicTestClient: @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 - ) + return text("", exception.status_code, headers=exception.headers) else: return self.app.error_handler.default(request, exception) @@ -113,9 +110,7 @@ class SanicTestClient: host, port = sock.getsockname() self.port = port - if uri.startswith( - ("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:") - ): + if uri.startswith(("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:")): url = uri else: uri = uri if uri.startswith("/") else f"/{uri}" @@ -218,11 +213,11 @@ class SanicASGITestClient(httpx.AsyncClient): self.last_request = request @app.listener("after_server_start") - def _start_test_mode(sanic, loop): + def _start_test_mode(sanic, *args, **kwargs): sanic.test_mode = True @app.listener("before_server_end") - def _end_test_mode(sanic, loop): + def _end_test_mode(sanic, *args, **kwargs): sanic.test_mode = False app.request_middleware.appendleft(_collect_request) @@ -247,9 +242,7 @@ class SanicASGITestClient(httpx.AsyncClient): headers.setdefault("sec-websocket-key", "testserver==") headers.setdefault("sec-websocket-version", "13") if subprotocols is not None: - headers.setdefault( - "sec-websocket-protocol", ", ".join(subprotocols) - ) + headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols)) scope = { "type": "websocket", From 521ae7f60e393d2c8215399570ba220a03088087 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 14 Jul 2020 10:41:28 +0300 Subject: [PATCH 12/12] squash --- sanic/testing.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sanic/testing.py b/sanic/testing.py index 910b5c59..faabdfd1 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -9,6 +9,7 @@ from sanic.exceptions import MethodNotSupported from sanic.log import logger from sanic.response import text + ASGI_HOST = "mockserver" HOST = "127.0.0.1" PORT = None @@ -94,7 +95,9 @@ class SanicTestClient: @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) + return text( + "", exception.status_code, headers=exception.headers + ) else: return self.app.error_handler.default(request, exception) @@ -110,7 +113,9 @@ class SanicTestClient: host, port = sock.getsockname() self.port = port - if uri.startswith(("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:")): + if uri.startswith( + ("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:") + ): url = uri else: uri = uri if uri.startswith("/") else f"/{uri}" @@ -242,7 +247,9 @@ class SanicASGITestClient(httpx.AsyncClient): headers.setdefault("sec-websocket-key", "testserver==") headers.setdefault("sec-websocket-version", "13") if subprotocols is not None: - headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols)) + headers.setdefault( + "sec-websocket-protocol", ", ".join(subprotocols) + ) scope = { "type": "websocket",