From fc18f86964e170c48632c614c86a0d26c9fbdd41 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sat, 24 Oct 2020 23:03:25 +0300 Subject: [PATCH 1/5] Resolve broken test in appveyor --- tests/test_load_module_from_file_location.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_load_module_from_file_location.py b/tests/test_load_module_from_file_location.py index 979c2bc0..015ad256 100644 --- a/tests/test_load_module_from_file_location.py +++ b/tests/test_load_module_from_file_location.py @@ -10,7 +10,7 @@ from sanic.utils import load_module_from_file_location @pytest.fixture def loaded_module_from_file_location(): return load_module_from_file_location( - str(Path(__file__).parent / "static/app_test_config.py") + str(Path(__file__).parent / "static" / "app_test_config.py") ) @@ -20,9 +20,7 @@ def test_load_module_from_file_location(loaded_module_from_file_location): @pytest.mark.dependency(depends=["test_load_module_from_file_location"]) -def test_loaded_module_from_file_location_name( - loaded_module_from_file_location, -): +def test_loaded_module_from_file_location_name(loaded_module_from_file_location,): assert loaded_module_from_file_location.__name__ == "app_test_config" From 96364aacc04d402ce8add0257792cb004f054171 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sat, 24 Oct 2020 23:42:38 +0300 Subject: [PATCH 2/5] squash --- tests/test_load_module_from_file_location.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_load_module_from_file_location.py b/tests/test_load_module_from_file_location.py index 015ad256..5dc42d5c 100644 --- a/tests/test_load_module_from_file_location.py +++ b/tests/test_load_module_from_file_location.py @@ -21,7 +21,10 @@ def test_load_module_from_file_location(loaded_module_from_file_location): @pytest.mark.dependency(depends=["test_load_module_from_file_location"]) def test_loaded_module_from_file_location_name(loaded_module_from_file_location,): - assert loaded_module_from_file_location.__name__ == "app_test_config" + name = loaded_module_from_file_location.__name__ + if "C:\\" in name: + name = name.split("\\")[-1] + assert name == "app_test_config" def test_load_module_from_file_location_with_non_existing_env_variable(): From 7dbd3eb5e83ef5369a04040f8516e1740bd2edf2 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sat, 24 Oct 2020 23:49:55 +0300 Subject: [PATCH 3/5] Update multidict version --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index e4ff74ee..f0351b91 100644 --- a/setup.py +++ b/setup.py @@ -80,13 +80,13 @@ requirements = [ ujson, "aiofiles>=0.3.0", "websockets>=8.1,<9.0", - "multidict>=4.0,<5.0", + "multidict==5.0.0", "httpx==0.15.4", ] tests_require = [ "pytest==5.2.1", - "multidict>=4.0,<5.0", + "multidict==5.0.0", "gunicorn", "pytest-cov", "httpcore==0.3.0", From e5aed4c067e28998ed72a707b616ecc9d048e9e0 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 25 Oct 2020 15:01:53 +0200 Subject: [PATCH 4/5] Ignore writing headers when in ASGI mode (#1957) * Ignore writing headers when in ASGI mode for streaming responses * Move asgi set on streaming until after response type check * Adds multidict==5.0.0 to pass tests * Bump version to 20.9.1 --- examples/run_asgi.py | 6 ++---- sanic/__version__.py | 2 +- sanic/asgi.py | 2 ++ sanic/response.py | 22 +++++++++++++++------- tests/test_response.py | 16 ++++++++++++++++ 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/examples/run_asgi.py b/examples/run_asgi.py index 44be25f5..e54d5d5d 100644 --- a/examples/run_asgi.py +++ b/examples/run_asgi.py @@ -7,8 +7,8 @@ """ from pathlib import Path -from sanic import Sanic, response +from sanic import Sanic, response app = Sanic(__name__) @@ -42,9 +42,7 @@ async def handler_file(request): @app.route("/file_stream") async def handler_file_stream(request): - return await response.file_stream( - Path("../") / "setup.py", chunk_size=1024 - ) + return await response.file_stream(Path("../") / "setup.py", chunk_size=1024) @app.route("/stream", stream=True) diff --git a/sanic/__version__.py b/sanic/__version__.py index d59f2279..0d8f82c6 100644 --- a/sanic/__version__.py +++ b/sanic/__version__.py @@ -1 +1 @@ -__version__ = "20.9.0" +__version__ = "20.9.1" diff --git a/sanic/asgi.py b/sanic/asgi.py index 5ec13cf4..2a3c4540 100644 --- a/sanic/asgi.py +++ b/sanic/asgi.py @@ -350,6 +350,8 @@ class ASGIApp: if name not in (b"Set-Cookie",) ] + response.asgi = True + if "content-length" not in response.headers and not isinstance( response, StreamingHTTPResponse ): diff --git a/sanic/response.py b/sanic/response.py index 24033336..1f7b12de 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -22,6 +22,9 @@ except ImportError: class BaseHTTPResponse: + def __init__(self): + self.asgi = False + def _encode_body(self, data): return data.encode() if hasattr(data, "encode") else data @@ -80,6 +83,8 @@ class StreamingHTTPResponse(BaseHTTPResponse): content_type="text/plain; charset=utf-8", chunked=True, ): + super().__init__() + self.content_type = content_type self.streaming_fn = streaming_fn self.status = status @@ -109,13 +114,14 @@ class StreamingHTTPResponse(BaseHTTPResponse): """ if version != "1.1": self.chunked = False - headers = self.get_headers( - version, - keep_alive=keep_alive, - keep_alive_timeout=keep_alive_timeout, - ) - await self.protocol.push_data(headers) - await self.protocol.drain() + if not getattr(self, "asgi", False): + headers = self.get_headers( + version, + keep_alive=keep_alive, + keep_alive_timeout=keep_alive_timeout, + ) + await self.protocol.push_data(headers) + await self.protocol.drain() await self.streaming_fn(self) if self.chunked: await self.protocol.push_data(b"0\r\n\r\n") @@ -143,6 +149,8 @@ class HTTPResponse(BaseHTTPResponse): content_type=None, body_bytes=b"", ): + super().__init__() + self.content_type = content_type self.body = body_bytes if body is None else self._encode_body(body) self.status = status diff --git a/tests/test_response.py b/tests/test_response.py index 625317d5..6e2f2a9a 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -235,6 +235,12 @@ def test_chunked_streaming_returns_correct_content(streaming_app): assert response.text == "foo,bar" +@pytest.mark.asyncio +async def test_chunked_streaming_returns_correct_content_asgi(streaming_app): + request, response = await streaming_app.asgi_client.get("/") + assert response.text == "4\r\nfoo,\r\n3\r\nbar\r\n0\r\n\r\n" + + def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): request, response = non_chunked_streaming_app.test_client.get("/") assert "Transfer-Encoding" not in response.headers @@ -242,6 +248,16 @@ def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): assert response.headers["Content-Length"] == "7" +@pytest.mark.asyncio +async def test_non_chunked_streaming_adds_correct_headers_asgi( + non_chunked_streaming_app, +): + request, response = await non_chunked_streaming_app.asgi_client.get("/") + assert "Transfer-Encoding" not in response.headers + assert response.headers["Content-Type"] == "text/csv" + assert response.headers["Content-Length"] == "7" + + def test_non_chunked_streaming_returns_correct_content( non_chunked_streaming_app, ): From 4ca3e98082b58e019c5b3624d19505de442251a4 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Mon, 26 Oct 2020 05:31:34 +1000 Subject: [PATCH 5/5] Add `pytest-dependency` requirement to tests_require list in setup.py (#1955) Co-authored-by: Adam Hopkins --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f0351b91..3a44fb5a 100644 --- a/setup.py +++ b/setup.py @@ -96,6 +96,7 @@ tests_require = [ "pytest-sanic", "pytest-sugar", "pytest-benchmark", + "pytest-dependency", ] docs_require = [