Compare commits
	
		
			12 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 5930bb67a6 | ||
|   | 4c360f43fd | ||
|   | 6387f5ddc9 | ||
|   | 23a0308d40 | ||
|   | a7d563d566 | ||
|   | f2d91bd4d2 | ||
|   | 8c628c69fb | ||
|   | 9b24fbb2f3 | ||
|   | 468f4ac7f1 | ||
|   | be1ca93a23 | ||
|   | 662c7c7f62 | ||
|   | 3e4bec7f2c | 
| @@ -1 +1 @@ | ||||
| __version__ = "19.12.3" | ||||
| __version__ = "19.12.5" | ||||
|   | ||||
| @@ -1474,3 +1474,5 @@ class Sanic: | ||||
|         self.asgi = True | ||||
|         asgi_app = await ASGIApp.create(self, scope, receive, send) | ||||
|         await asgi_app() | ||||
|  | ||||
|     _asgi_single_callable = True  # We conform to ASGI 3.0 single-callable | ||||
|   | ||||
| @@ -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 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,11 +248,18 @@ 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, | ||||
|             ) | ||||
|  | ||||
|             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 | ||||
| @@ -287,13 +309,17 @@ class ASGIApp: | ||||
|         callback = None if self.ws else self.stream_callback | ||||
|         await handler(self.request, None, callback) | ||||
|  | ||||
|     _asgi_single_callable = True  # We conform to ASGI 3.0 single-callable | ||||
|  | ||||
|     async def stream_callback(self, response: HTTPResponse) -> None: | ||||
|         """ | ||||
|         Write the response. | ||||
|         """ | ||||
|         headers: List[Tuple[bytes, bytes]] = [] | ||||
|         cookies: Dict[str, str] = {} | ||||
|         content_length: List[str] = [] | ||||
|         try: | ||||
|             content_length = response.headers.popall("content-length", []) | ||||
|             cookies = { | ||||
|                 v.key: v | ||||
|                 for _, v in list( | ||||
| @@ -316,7 +342,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() | ||||
| @@ -324,14 +352,28 @@ class ASGIApp: | ||||
|             ] | ||||
|  | ||||
|         response.asgi = True | ||||
|  | ||||
|         if "content-length" not in response.headers and not isinstance( | ||||
|             response, StreamingHTTPResponse | ||||
|         ): | ||||
|             headers += [(b"content-length", str(len(response.body)).encode("latin-1"))] | ||||
|         is_streaming = isinstance(response, StreamingHTTPResponse) | ||||
|         if is_streaming and getattr(response, "chunked", False): | ||||
|             # disable sanic chunking, this is done at the ASGI-server level | ||||
|             setattr(response, "chunked", False) | ||||
|             # content-length header is removed to signal to the ASGI-server | ||||
|             # to use automatic-chunking if it supports it | ||||
|         elif len(content_length) > 0: | ||||
|             headers += [ | ||||
|                 (b"content-length", str(content_length[0]).encode("latin-1")) | ||||
|             ] | ||||
|         elif not is_streaming: | ||||
|             headers += [ | ||||
|                 ( | ||||
|                     b"content-length", | ||||
|                     str(len(getattr(response, "body", b""))).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( | ||||
| @@ -343,7 +385,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( | ||||
|   | ||||
| @@ -80,6 +80,8 @@ class StreamingHTTPResponse(BaseHTTPResponse): | ||||
|         if type(data) != bytes: | ||||
|             data = self._encode_body(data) | ||||
|  | ||||
|         # `chunked` will always be False in ASGI-mode, even if the underlying | ||||
|         # ASGI Transport implements Chunked transport. That does it itself. | ||||
|         if self.chunked: | ||||
|             await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data)) | ||||
|         else: | ||||
|   | ||||
							
								
								
									
										12
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								setup.py
									
									
									
									
									
								
							| @@ -5,6 +5,7 @@ import codecs | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
|  | ||||
| from distutils.util import strtobool | ||||
|  | ||||
| from setuptools import setup | ||||
| @@ -24,6 +25,7 @@ class PyTest(TestCommand): | ||||
|  | ||||
|     def run_tests(self): | ||||
|         import shlex | ||||
|  | ||||
|         import pytest | ||||
|  | ||||
|         errno = pytest.main(shlex.split(self.pytest_args)) | ||||
| @@ -38,7 +40,9 @@ def open_local(paths, mode="r", encoding="utf8"): | ||||
|  | ||||
| with open_local(["sanic", "__version__.py"], encoding="latin1") as fp: | ||||
|     try: | ||||
|         version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0] | ||||
|         version = re.findall( | ||||
|             r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M | ||||
|         )[0] | ||||
|     except IndexError: | ||||
|         raise RuntimeError("Unable to determine version.") | ||||
|  | ||||
| @@ -68,9 +72,11 @@ setup_kwargs = { | ||||
|     ], | ||||
| } | ||||
|  | ||||
| env_dependency = '; sys_platform != "win32" ' 'and implementation_name == "cpython"' | ||||
| env_dependency = ( | ||||
|     '; sys_platform != "win32" ' 'and implementation_name == "cpython"' | ||||
| ) | ||||
| ujson = "ujson>=1.35" + env_dependency | ||||
| uvloop = "uvloop>=0.5.3" + env_dependency | ||||
| uvloop = "uvloop>=0.5.3,<0.15.0" + env_dependency | ||||
|  | ||||
| requirements = [ | ||||
|     "httptools>=0.0.10", | ||||
|   | ||||
| @@ -235,7 +235,7 @@ def test_chunked_streaming_returns_correct_content(streaming_app): | ||||
| @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" | ||||
|     assert response.text == "foo,bar" | ||||
|  | ||||
|  | ||||
| def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user