Merge branch 'master' into Improving-documentation
This commit is contained in:
commit
634b586df3
|
@ -7,8 +7,8 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from sanic import Sanic, response
|
|
||||||
|
|
||||||
|
from sanic import Sanic, response
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
@ -42,9 +42,7 @@ async def handler_file(request):
|
||||||
|
|
||||||
@app.route("/file_stream")
|
@app.route("/file_stream")
|
||||||
async def handler_file_stream(request):
|
async def handler_file_stream(request):
|
||||||
return await response.file_stream(
|
return await response.file_stream(Path("../") / "setup.py", chunk_size=1024)
|
||||||
Path("../") / "setup.py", chunk_size=1024
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/stream", stream=True)
|
@app.route("/stream", stream=True)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "20.9.0"
|
__version__ = "20.9.1"
|
||||||
|
|
|
@ -350,6 +350,8 @@ class ASGIApp:
|
||||||
if name not in (b"Set-Cookie",)
|
if name not in (b"Set-Cookie",)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
response.asgi = True
|
||||||
|
|
||||||
if "content-length" not in response.headers and not isinstance(
|
if "content-length" not in response.headers and not isinstance(
|
||||||
response, StreamingHTTPResponse
|
response, StreamingHTTPResponse
|
||||||
):
|
):
|
||||||
|
|
|
@ -22,6 +22,9 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
class BaseHTTPResponse:
|
class BaseHTTPResponse:
|
||||||
|
def __init__(self):
|
||||||
|
self.asgi = False
|
||||||
|
|
||||||
def _encode_body(self, data):
|
def _encode_body(self, data):
|
||||||
return data.encode() if hasattr(data, "encode") else data
|
return data.encode() if hasattr(data, "encode") else data
|
||||||
|
|
||||||
|
@ -80,6 +83,8 @@ class StreamingHTTPResponse(BaseHTTPResponse):
|
||||||
content_type="text/plain; charset=utf-8",
|
content_type="text/plain; charset=utf-8",
|
||||||
chunked=True,
|
chunked=True,
|
||||||
):
|
):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
self.content_type = content_type
|
self.content_type = content_type
|
||||||
self.streaming_fn = streaming_fn
|
self.streaming_fn = streaming_fn
|
||||||
self.status = status
|
self.status = status
|
||||||
|
@ -109,13 +114,14 @@ class StreamingHTTPResponse(BaseHTTPResponse):
|
||||||
"""
|
"""
|
||||||
if version != "1.1":
|
if version != "1.1":
|
||||||
self.chunked = False
|
self.chunked = False
|
||||||
headers = self.get_headers(
|
if not getattr(self, "asgi", False):
|
||||||
version,
|
headers = self.get_headers(
|
||||||
keep_alive=keep_alive,
|
version,
|
||||||
keep_alive_timeout=keep_alive_timeout,
|
keep_alive=keep_alive,
|
||||||
)
|
keep_alive_timeout=keep_alive_timeout,
|
||||||
await self.protocol.push_data(headers)
|
)
|
||||||
await self.protocol.drain()
|
await self.protocol.push_data(headers)
|
||||||
|
await self.protocol.drain()
|
||||||
await self.streaming_fn(self)
|
await self.streaming_fn(self)
|
||||||
if self.chunked:
|
if self.chunked:
|
||||||
await self.protocol.push_data(b"0\r\n\r\n")
|
await self.protocol.push_data(b"0\r\n\r\n")
|
||||||
|
@ -143,6 +149,8 @@ class HTTPResponse(BaseHTTPResponse):
|
||||||
content_type=None,
|
content_type=None,
|
||||||
body_bytes=b"",
|
body_bytes=b"",
|
||||||
):
|
):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
self.content_type = content_type
|
self.content_type = content_type
|
||||||
self.body = body_bytes if body is None else self._encode_body(body)
|
self.body = body_bytes if body is None else self._encode_body(body)
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -80,13 +80,13 @@ requirements = [
|
||||||
ujson,
|
ujson,
|
||||||
"aiofiles>=0.3.0",
|
"aiofiles>=0.3.0",
|
||||||
"websockets>=8.1,<9.0",
|
"websockets>=8.1,<9.0",
|
||||||
"multidict>=4.0,<5.0",
|
"multidict==5.0.0",
|
||||||
"httpx==0.15.4",
|
"httpx==0.15.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
tests_require = [
|
tests_require = [
|
||||||
"pytest==5.2.1",
|
"pytest==5.2.1",
|
||||||
"multidict>=4.0,<5.0",
|
"multidict==5.0.0",
|
||||||
"gunicorn",
|
"gunicorn",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"httpcore==0.3.0",
|
"httpcore==0.3.0",
|
||||||
|
@ -96,6 +96,7 @@ tests_require = [
|
||||||
"pytest-sanic",
|
"pytest-sanic",
|
||||||
"pytest-sugar",
|
"pytest-sugar",
|
||||||
"pytest-benchmark",
|
"pytest-benchmark",
|
||||||
|
"pytest-dependency",
|
||||||
]
|
]
|
||||||
|
|
||||||
docs_require = [
|
docs_require = [
|
||||||
|
|
|
@ -10,7 +10,7 @@ from sanic.utils import load_module_from_file_location
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def loaded_module_from_file_location():
|
def loaded_module_from_file_location():
|
||||||
return load_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,10 +20,11 @@ def test_load_module_from_file_location(loaded_module_from_file_location):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=["test_load_module_from_file_location"])
|
@pytest.mark.dependency(depends=["test_load_module_from_file_location"])
|
||||||
def test_loaded_module_from_file_location_name(
|
def test_loaded_module_from_file_location_name(loaded_module_from_file_location,):
|
||||||
loaded_module_from_file_location,
|
name = loaded_module_from_file_location.__name__
|
||||||
):
|
if "C:\\" in name:
|
||||||
assert loaded_module_from_file_location.__name__ == "app_test_config"
|
name = name.split("\\")[-1]
|
||||||
|
assert name == "app_test_config"
|
||||||
|
|
||||||
|
|
||||||
def test_load_module_from_file_location_with_non_existing_env_variable():
|
def test_load_module_from_file_location_with_non_existing_env_variable():
|
||||||
|
|
|
@ -235,6 +235,12 @@ def test_chunked_streaming_returns_correct_content(streaming_app):
|
||||||
assert response.text == "foo,bar"
|
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):
|
def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):
|
||||||
request, response = non_chunked_streaming_app.test_client.get("/")
|
request, response = non_chunked_streaming_app.test_client.get("/")
|
||||||
assert "Transfer-Encoding" not in response.headers
|
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"
|
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(
|
def test_non_chunked_streaming_returns_correct_content(
|
||||||
non_chunked_streaming_app,
|
non_chunked_streaming_app,
|
||||||
):
|
):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user