Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e5aed4c067 | ||
|   | 9e048bc0c3 | ||
|   | 5d7b0735ce | ||
|   | 7dbd3eb5e8 | ||
|   | 96364aacc0 | ||
|   | fc18f86964 | ||
|   | fb3d368a78 | 
| @@ -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) | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| __version__ = "20.9.0" | ||||
| __version__ = "20.9.1" | ||||
|   | ||||
| @@ -676,9 +676,10 @@ class Sanic: | ||||
|         :param strict_slashes: Instruct :class:`Sanic` to check if the request | ||||
|             URLs need to terminate with a */* | ||||
|         :param content_type: user defined content type for header | ||||
|         :return: None | ||||
|         :return: routes registered on the router | ||||
|         :rtype: List[sanic.router.Route] | ||||
|         """ | ||||
|         static_register( | ||||
|         return static_register( | ||||
|             self, | ||||
|             uri, | ||||
|             file_or_directory, | ||||
|   | ||||
| @@ -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 | ||||
|         ): | ||||
|   | ||||
| @@ -143,7 +143,18 @@ class Blueprint: | ||||
|             if _routes: | ||||
|                 routes += _routes | ||||
|  | ||||
|         # Static Files | ||||
|         for future in self.statics: | ||||
|             # Prepend the blueprint URI prefix if available | ||||
|             uri = url_prefix + future.uri if url_prefix else future.uri | ||||
|             _routes = app.static( | ||||
|                 uri, future.file_or_directory, *future.args, **future.kwargs | ||||
|             ) | ||||
|             if _routes: | ||||
|                 routes += _routes | ||||
|  | ||||
|         route_names = [route.name for route in routes if route] | ||||
|  | ||||
|         # Middleware | ||||
|         for future in self.middlewares: | ||||
|             if future.args or future.kwargs: | ||||
| @@ -160,14 +171,6 @@ class Blueprint: | ||||
|         for future in self.exceptions: | ||||
|             app.exception(*future.args, **future.kwargs)(future.handler) | ||||
|  | ||||
|         # Static Files | ||||
|         for future in self.statics: | ||||
|             # Prepend the blueprint URI prefix if available | ||||
|             uri = url_prefix + future.uri if url_prefix else future.uri | ||||
|             app.static( | ||||
|                 uri, future.file_or_directory, *future.args, **future.kwargs | ||||
|             ) | ||||
|  | ||||
|         # Event listeners | ||||
|         for event, listeners in self.listeners.items(): | ||||
|             for listener in listeners: | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -134,6 +134,8 @@ def register( | ||||
|                               threshold size to switch to file_stream() | ||||
|     :param name: user defined name used for url_for | ||||
|     :param content_type: user defined content type for header | ||||
|     :return: registered static routes | ||||
|     :rtype: List[sanic.router.Route] | ||||
|     """ | ||||
|     # If we're not trying to match a file directly, | ||||
|     # serve from the folder | ||||
| @@ -155,10 +157,11 @@ def register( | ||||
|         ) | ||||
|     ) | ||||
|  | ||||
|     app.route( | ||||
|     _routes, _ = app.route( | ||||
|         uri, | ||||
|         methods=["GET", "HEAD"], | ||||
|         name=name, | ||||
|         host=host, | ||||
|         strict_slashes=strict_slashes, | ||||
|     )(_handler) | ||||
|     return _routes | ||||
|   | ||||
							
								
								
									
										4
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								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", | ||||
|   | ||||
| @@ -735,6 +735,36 @@ def test_static_blueprint_name(app: Sanic, static_file_directory, file_name): | ||||
|     _, response = app.test_client.get("/static/test.file/") | ||||
|     assert response.status == 200 | ||||
|  | ||||
| @pytest.mark.parametrize("file_name", ["test.file"]) | ||||
| def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): | ||||
|     current_file = inspect.getfile(inspect.currentframe()) | ||||
|     with open(current_file, "rb") as file: | ||||
|         file.read() | ||||
|  | ||||
|     triggered = False | ||||
|  | ||||
|     bp = Blueprint(name="test_mw", url_prefix="") | ||||
|  | ||||
|     @bp.middleware('request') | ||||
|     def bp_mw1(request): | ||||
|         nonlocal triggered | ||||
|         triggered = True | ||||
|  | ||||
|     bp.static( | ||||
|         "/test.file", | ||||
|         get_file_path(static_file_directory, file_name), | ||||
|         strict_slashes=True, | ||||
|         name="static" | ||||
|     ) | ||||
|  | ||||
|     app.blueprint(bp) | ||||
|  | ||||
|     uri = app.url_for("test_mw.static") | ||||
|     assert uri == "/test.file" | ||||
|  | ||||
|     _, response = app.test_client.get("/test.file") | ||||
|     assert triggered is True | ||||
|  | ||||
|  | ||||
| def test_route_handler_add(app: Sanic): | ||||
|     view = CompositionView() | ||||
|   | ||||
| @@ -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,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"]) | ||||
| def test_loaded_module_from_file_location_name( | ||||
|     loaded_module_from_file_location, | ||||
| ): | ||||
|     assert loaded_module_from_file_location.__name__ == "app_test_config" | ||||
| def test_loaded_module_from_file_location_name(loaded_module_from_file_location,): | ||||
|     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(): | ||||
|   | ||||
| @@ -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, | ||||
| ): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user