Compare commits
	
		
			3 Commits
		
	
	
		
			websocket-
			...
			v21.3.3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9e889fc20b | ||
|   | bae2d4cb57 | ||
|   | 492d6fd19d | 
| @@ -164,10 +164,12 @@ class ASGIApp: | |||||||
|         Read and stream the body in chunks from an incoming ASGI message. |         Read and stream the body in chunks from an incoming ASGI message. | ||||||
|         """ |         """ | ||||||
|         message = await self.transport.receive() |         message = await self.transport.receive() | ||||||
|  |         body = message.get("body", b"") | ||||||
|         if not message.get("more_body", False): |         if not message.get("more_body", False): | ||||||
|             self.request_body = False |             self.request_body = False | ||||||
|  |             if not body: | ||||||
|                 return None |                 return None | ||||||
|         return message.get("body", b"") |         return body | ||||||
|  |  | ||||||
|     async def __aiter__(self): |     async def __aiter__(self): | ||||||
|         while self.request_body: |         while self.request_body: | ||||||
|   | |||||||
| @@ -1,9 +1,13 @@ | |||||||
| from collections.abc import MutableSequence | from collections.abc import MutableSequence | ||||||
| from typing import List, Optional, Union | from typing import TYPE_CHECKING, List, Optional, Union | ||||||
|  |  | ||||||
| import sanic | import sanic | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if TYPE_CHECKING: | ||||||
|  |     from sanic.blueprints import Blueprint | ||||||
|  |  | ||||||
|  |  | ||||||
| class BlueprintGroup(MutableSequence): | class BlueprintGroup(MutableSequence): | ||||||
|     """ |     """ | ||||||
|     This class provides a mechanism to implement a Blueprint Group |     This class provides a mechanism to implement a Blueprint Group | ||||||
| @@ -56,7 +60,12 @@ class BlueprintGroup(MutableSequence): | |||||||
|  |  | ||||||
|     __slots__ = ("_blueprints", "_url_prefix", "_version", "_strict_slashes") |     __slots__ = ("_blueprints", "_url_prefix", "_version", "_strict_slashes") | ||||||
|  |  | ||||||
|     def __init__(self, url_prefix=None, version=None, strict_slashes=None): |     def __init__( | ||||||
|  |         self, | ||||||
|  |         url_prefix: Optional[str] = None, | ||||||
|  |         version: Optional[Union[int, str, float]] = None, | ||||||
|  |         strict_slashes: Optional[bool] = None, | ||||||
|  |     ): | ||||||
|         """ |         """ | ||||||
|         Create a new Blueprint Group |         Create a new Blueprint Group | ||||||
|  |  | ||||||
| @@ -65,13 +74,13 @@ class BlueprintGroup(MutableSequence): | |||||||
|             inherited by each of the Blueprint |             inherited by each of the Blueprint | ||||||
|         :param strict_slashes: URL Strict slash behavior indicator |         :param strict_slashes: URL Strict slash behavior indicator | ||||||
|         """ |         """ | ||||||
|         self._blueprints = [] |         self._blueprints: List[Blueprint] = [] | ||||||
|         self._url_prefix = url_prefix |         self._url_prefix = url_prefix | ||||||
|         self._version = version |         self._version = version | ||||||
|         self._strict_slashes = strict_slashes |         self._strict_slashes = strict_slashes | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def url_prefix(self) -> str: |     def url_prefix(self) -> Optional[Union[int, str, float]]: | ||||||
|         """ |         """ | ||||||
|         Retrieve the URL prefix being used for the Current Blueprint Group |         Retrieve the URL prefix being used for the Current Blueprint Group | ||||||
|  |  | ||||||
|   | |||||||
| @@ -70,7 +70,7 @@ class Blueprint(BaseSanic): | |||||||
|         name: str, |         name: str, | ||||||
|         url_prefix: Optional[str] = None, |         url_prefix: Optional[str] = None, | ||||||
|         host: Optional[str] = None, |         host: Optional[str] = None, | ||||||
|         version: Optional[int] = None, |         version: Optional[Union[int, str, float]] = None, | ||||||
|         strict_slashes: Optional[bool] = None, |         strict_slashes: Optional[bool] = None, | ||||||
|     ): |     ): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|   | |||||||
| @@ -82,6 +82,7 @@ class Http: | |||||||
|         "request_max_size", |         "request_max_size", | ||||||
|         "response", |         "response", | ||||||
|         "response_func", |         "response_func", | ||||||
|  |         "response_size", | ||||||
|         "response_bytes_left", |         "response_bytes_left", | ||||||
|         "upgrade_websocket", |         "upgrade_websocket", | ||||||
|     ] |     ] | ||||||
| @@ -270,6 +271,7 @@ class Http: | |||||||
|         size = len(data) |         size = len(data) | ||||||
|         headers = res.headers |         headers = res.headers | ||||||
|         status = res.status |         status = res.status | ||||||
|  |         self.response_size = size | ||||||
|  |  | ||||||
|         if not isinstance(status, int) or status < 200: |         if not isinstance(status, int) or status < 200: | ||||||
|             raise RuntimeError(f"Invalid response status {status!r}") |             raise RuntimeError(f"Invalid response status {status!r}") | ||||||
| @@ -424,7 +426,9 @@ class Http: | |||||||
|         req, res = self.request, self.response |         req, res = self.request, self.response | ||||||
|         extra = { |         extra = { | ||||||
|             "status": getattr(res, "status", 0), |             "status": getattr(res, "status", 0), | ||||||
|             "byte": getattr(self, "response_bytes_left", -1), |             "byte": getattr( | ||||||
|  |                 self, "response_bytes_left", getattr(self, "response_size", -1) | ||||||
|  |             ), | ||||||
|             "host": "UNKNOWN", |             "host": "UNKNOWN", | ||||||
|             "request": "nil", |             "request": "nil", | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ class RouteMixin: | |||||||
|         host: Optional[str] = None, |         host: Optional[str] = None, | ||||||
|         strict_slashes: Optional[bool] = None, |         strict_slashes: Optional[bool] = None, | ||||||
|         stream: bool = False, |         stream: bool = False, | ||||||
|         version: Optional[int] = None, |         version: Optional[Union[int, str, float]] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         ignore_body: bool = False, |         ignore_body: bool = False, | ||||||
|         apply: bool = True, |         apply: bool = True, | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ class Router(BaseRouter): | |||||||
|             return self.resolve( |             return self.resolve( | ||||||
|                 path=path, |                 path=path, | ||||||
|                 method=method, |                 method=method, | ||||||
|                 extra={"host": host}, |                 extra={"host": host} if host else None, | ||||||
|             ) |             ) | ||||||
|         except RoutingNotFound as e: |         except RoutingNotFound as e: | ||||||
|             raise NotFound("Requested URL {} not found".format(e.path)) |             raise NotFound("Requested URL {} not found".format(e.path)) | ||||||
| @@ -161,7 +161,7 @@ class Router(BaseRouter): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def routes_all(self): |     def routes_all(self): | ||||||
|         return self.routes |         return {route.parts: route for route in self.routes} | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def routes_static(self): |     def routes_static(self): | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import asyncio | |||||||
| from inspect import isawaitable | from inspect import isawaitable | ||||||
| from typing import Any, Dict, List, Optional, Tuple, Union | from typing import Any, Dict, List, Optional, Tuple, Union | ||||||
|  |  | ||||||
| from sanic_routing import BaseRouter, Route  # type: ignore | from sanic_routing import BaseRouter, Route, RouteGroup  # type: ignore | ||||||
| from sanic_routing.exceptions import NotFound  # type: ignore | from sanic_routing.exceptions import NotFound  # type: ignore | ||||||
| from sanic_routing.utils import path_to_parts  # type: ignore | from sanic_routing.utils import path_to_parts  # type: ignore | ||||||
|  |  | ||||||
| @@ -20,17 +20,11 @@ RESERVED_NAMESPACES = ( | |||||||
|  |  | ||||||
|  |  | ||||||
| class Signal(Route): | class Signal(Route): | ||||||
|     def get_handler(self, raw_path, method, _): |     ... | ||||||
|         method = method or self.router.DEFAULT_METHOD |  | ||||||
|         raw_path = raw_path.lstrip(self.router.delimiter) |  | ||||||
|         try: | class SignalGroup(RouteGroup): | ||||||
|             return self.handlers[raw_path][method] |     ... | ||||||
|         except (IndexError, KeyError): |  | ||||||
|             raise self.router.method_handler_exception( |  | ||||||
|                 f"Method '{method}' not found on {self}", |  | ||||||
|                 method=method, |  | ||||||
|                 allowed_methods=set(self.methods[raw_path]), |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SignalRouter(BaseRouter): | class SignalRouter(BaseRouter): | ||||||
| @@ -38,6 +32,7 @@ class SignalRouter(BaseRouter): | |||||||
|         super().__init__( |         super().__init__( | ||||||
|             delimiter=".", |             delimiter=".", | ||||||
|             route_class=Signal, |             route_class=Signal, | ||||||
|  |             group_class=SignalGroup, | ||||||
|             stacking=True, |             stacking=True, | ||||||
|         ) |         ) | ||||||
|         self.ctx.loop = None |         self.ctx.loop = None | ||||||
| @@ -49,7 +44,13 @@ class SignalRouter(BaseRouter): | |||||||
|     ): |     ): | ||||||
|         extra = condition or {} |         extra = condition or {} | ||||||
|         try: |         try: | ||||||
|             return self.resolve(f".{event}", extra=extra) |             group, param_basket = self.find_route( | ||||||
|  |                 f".{event}", | ||||||
|  |                 self.DEFAULT_METHOD, | ||||||
|  |                 self, | ||||||
|  |                 {"__params__": {}}, | ||||||
|  |                 extra=extra, | ||||||
|  |             ) | ||||||
|         except NotFound: |         except NotFound: | ||||||
|             message = "Could not find signal %s" |             message = "Could not find signal %s" | ||||||
|             terms: List[Union[str, Optional[Dict[str, str]]]] = [event] |             terms: List[Union[str, Optional[Dict[str, str]]]] = [event] | ||||||
| @@ -58,15 +59,19 @@ class SignalRouter(BaseRouter): | |||||||
|                 terms.append(extra) |                 terms.append(extra) | ||||||
|             raise NotFound(message % tuple(terms)) |             raise NotFound(message % tuple(terms)) | ||||||
|  |  | ||||||
|  |         params = param_basket.pop("__params__") | ||||||
|  |         return group, [route.handler for route in group], params | ||||||
|  |  | ||||||
|     async def _dispatch( |     async def _dispatch( | ||||||
|         self, |         self, | ||||||
|         event: str, |         event: str, | ||||||
|         context: Optional[Dict[str, Any]] = None, |         context: Optional[Dict[str, Any]] = None, | ||||||
|         condition: Optional[Dict[str, str]] = None, |         condition: Optional[Dict[str, str]] = None, | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         signal, handlers, params = self.get(event, condition=condition) |         group, handlers, params = self.get(event, condition=condition) | ||||||
|  |  | ||||||
|         signal_event = signal.ctx.event |         events = [signal.ctx.event for signal in group] | ||||||
|  |         for signal_event in events: | ||||||
|             signal_event.set() |             signal_event.set() | ||||||
|         if context: |         if context: | ||||||
|             params.update(context) |             params.update(context) | ||||||
| @@ -78,6 +83,7 @@ class SignalRouter(BaseRouter): | |||||||
|                     if isawaitable(maybe_coroutine): |                     if isawaitable(maybe_coroutine): | ||||||
|                         await maybe_coroutine |                         await maybe_coroutine | ||||||
|         finally: |         finally: | ||||||
|  |             for signal_event in events: | ||||||
|                 signal_event.clear() |                 signal_event.clear() | ||||||
|  |  | ||||||
|     async def dispatch( |     async def dispatch( | ||||||
| @@ -116,7 +122,7 @@ class SignalRouter(BaseRouter): | |||||||
|             handler, |             handler, | ||||||
|             requirements=condition, |             requirements=condition, | ||||||
|             name=name, |             name=name, | ||||||
|             overwrite=True, |             append=True, | ||||||
|         )  # type: ignore |         )  # type: ignore | ||||||
|  |  | ||||||
|     def finalize(self, do_compile: bool = True): |     def finalize(self, do_compile: bool = True): | ||||||
| @@ -125,7 +131,7 @@ class SignalRouter(BaseRouter): | |||||||
|         except RuntimeError: |         except RuntimeError: | ||||||
|             raise RuntimeError("Cannot finalize signals outside of event loop") |             raise RuntimeError("Cannot finalize signals outside of event loop") | ||||||
|  |  | ||||||
|         for signal in self.routes.values(): |         for signal in self.routes: | ||||||
|             signal.ctx.event = asyncio.Event() |             signal.ctx.event = asyncio.Event() | ||||||
|  |  | ||||||
|         return super().finalize(do_compile=do_compile) |         return super().finalize(do_compile=do_compile) | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -83,7 +83,7 @@ ujson = "ujson>=1.35" + env_dependency | |||||||
| uvloop = "uvloop>=0.5.3" + env_dependency | uvloop = "uvloop>=0.5.3" + env_dependency | ||||||
|  |  | ||||||
| requirements = [ | requirements = [ | ||||||
|     "sanic-routing", |     "sanic-routing>=0.6.0", | ||||||
|     "httptools>=0.0.10", |     "httptools>=0.0.10", | ||||||
|     uvloop, |     uvloop, | ||||||
|     ujson, |     ujson, | ||||||
|   | |||||||
| @@ -80,6 +80,12 @@ def test_dont_load_env(): | |||||||
|     del environ["SANIC_TEST_ANSWER"] |     del environ["SANIC_TEST_ANSWER"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # @pytest.mark.parametrize("load_env", [None, False, "", "MYAPP_"]) | ||||||
|  | # def test_load_env_deprecation(load_env): | ||||||
|  | #     with pytest.warns(DeprecationWarning, match=r"21\.12"): | ||||||
|  | #         _ = Sanic(name=__name__, load_env=load_env) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_load_env_prefix(): | def test_load_env_prefix(): | ||||||
|     environ["MYAPP_TEST_ANSWER"] = "42" |     environ["MYAPP_TEST_ANSWER"] = "42" | ||||||
|     app = Sanic(name=__name__, load_env="MYAPP_") |     app = Sanic(name=__name__, load_env="MYAPP_") | ||||||
| @@ -87,6 +93,14 @@ def test_load_env_prefix(): | |||||||
|     del environ["MYAPP_TEST_ANSWER"] |     del environ["MYAPP_TEST_ANSWER"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # @pytest.mark.parametrize("env_prefix", [None, ""]) | ||||||
|  | # def test_empty_load_env_prefix(env_prefix): | ||||||
|  | #     environ["SANIC_TEST_ANSWER"] = "42" | ||||||
|  | #     app = Sanic(name=__name__, env_prefix=env_prefix) | ||||||
|  | #     assert getattr(app.config, "TEST_ANSWER", None) is None | ||||||
|  | #     del environ["SANIC_TEST_ANSWER"] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_load_env_prefix_float_values(): | def test_load_env_prefix_float_values(): | ||||||
|     environ["MYAPP_TEST_ROI"] = "2.3" |     environ["MYAPP_TEST_ROI"] = "2.3" | ||||||
|     app = Sanic(name=__name__, load_env="MYAPP_") |     app = Sanic(name=__name__, load_env="MYAPP_") | ||||||
|   | |||||||
| @@ -209,13 +209,13 @@ def test_named_static_routes(): | |||||||
|         return text("OK2") |         return text("OK2") | ||||||
|  |  | ||||||
|     assert app.router.routes_all[("test",)].name == "app.route_test" |     assert app.router.routes_all[("test",)].name == "app.route_test" | ||||||
|     assert app.router.routes_static[("test",)].name == "app.route_test" |     assert app.router.routes_static[("test",)][0].name == "app.route_test" | ||||||
|     assert app.url_for("route_test") == "/test" |     assert app.url_for("route_test") == "/test" | ||||||
|     with pytest.raises(URLBuildError): |     with pytest.raises(URLBuildError): | ||||||
|         app.url_for("handler1") |         app.url_for("handler1") | ||||||
|  |  | ||||||
|     assert app.router.routes_all[("pizazz",)].name == "app.route_pizazz" |     assert app.router.routes_all[("pizazz",)].name == "app.route_pizazz" | ||||||
|     assert app.router.routes_static[("pizazz",)].name == "app.route_pizazz" |     assert app.router.routes_static[("pizazz",)][0].name == "app.route_pizazz" | ||||||
|     assert app.url_for("route_pizazz") == "/pizazz" |     assert app.url_for("route_pizazz") == "/pizazz" | ||||||
|     with pytest.raises(URLBuildError): |     with pytest.raises(URLBuildError): | ||||||
|         app.url_for("handler2") |         app.url_for("handler2") | ||||||
| @@ -347,13 +347,13 @@ def test_static_add_named_route(): | |||||||
|     app.add_route(handler2, "/test2", name="route_test2") |     app.add_route(handler2, "/test2", name="route_test2") | ||||||
|  |  | ||||||
|     assert app.router.routes_all[("test",)].name == "app.route_test" |     assert app.router.routes_all[("test",)].name == "app.route_test" | ||||||
|     assert app.router.routes_static[("test",)].name == "app.route_test" |     assert app.router.routes_static[("test",)][0].name == "app.route_test" | ||||||
|     assert app.url_for("route_test") == "/test" |     assert app.url_for("route_test") == "/test" | ||||||
|     with pytest.raises(URLBuildError): |     with pytest.raises(URLBuildError): | ||||||
|         app.url_for("handler1") |         app.url_for("handler1") | ||||||
|  |  | ||||||
|     assert app.router.routes_all[("test2",)].name == "app.route_test2" |     assert app.router.routes_all[("test2",)].name == "app.route_test2" | ||||||
|     assert app.router.routes_static[("test2",)].name == "app.route_test2" |     assert app.router.routes_static[("test2",)][0].name == "app.route_test2" | ||||||
|     assert app.url_for("route_test2") == "/test2" |     assert app.url_for("route_test2") == "/test2" | ||||||
|     with pytest.raises(URLBuildError): |     with pytest.raises(URLBuildError): | ||||||
|         app.url_for("handler2") |         app.url_for("handler2") | ||||||
|   | |||||||
| @@ -104,7 +104,7 @@ def test_route_assigned_to_request(app): | |||||||
|         return response.empty() |         return response.empty() | ||||||
|  |  | ||||||
|     request, _ = app.test_client.get("/") |     request, _ = app.test_client.get("/") | ||||||
|     assert request.route is list(app.router.routes.values())[0] |     assert request.route is list(app.router.routes)[0] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_protocol_attribute(app): | def test_protocol_attribute(app): | ||||||
|   | |||||||
| @@ -253,6 +253,31 @@ async def test_empty_json_asgi(app): | |||||||
|     assert response.body == b"null" |     assert response.body == b"null" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_echo_json(app): | ||||||
|  |     @app.post("/") | ||||||
|  |     async def handler(request): | ||||||
|  |         return json(request.json) | ||||||
|  |  | ||||||
|  |     data = {"foo": "bar"} | ||||||
|  |     request, response = app.test_client.post("/", json=data) | ||||||
|  |  | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.json == data | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_echo_json_asgi(app): | ||||||
|  |     @app.post("/") | ||||||
|  |     async def handler(request): | ||||||
|  |         return json(request.json) | ||||||
|  |  | ||||||
|  |     data = {"foo": "bar"} | ||||||
|  |     request, response = await app.asgi_client.post("/", json=data) | ||||||
|  |  | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.json == data | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_invalid_json(app): | def test_invalid_json(app): | ||||||
|     @app.post("/") |     @app.post("/") | ||||||
|     async def handler(request): |     async def handler(request): | ||||||
| @@ -292,6 +317,17 @@ def test_query_string(app): | |||||||
|     assert request.args.get("test3", default="My value") == "My value" |     assert request.args.get("test3", default="My value") == "My value" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # def test_popped_stays_popped(app): | ||||||
|  | #     @app.route("/") | ||||||
|  | #     async def handler(request): | ||||||
|  | #         return text("OK") | ||||||
|  |  | ||||||
|  | #     request, response = app.test_client.get("/", params=[("test1", "1")]) | ||||||
|  |  | ||||||
|  | #     assert request.args.pop("test1") == ["1"] | ||||||
|  | #     assert "test1" not in request.args | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| async def test_query_string_asgi(app): | async def test_query_string_asgi(app): | ||||||
|     @app.route("/") |     @app.route("/") | ||||||
| @@ -2159,3 +2195,72 @@ def test_safe_method_with_body(app): | |||||||
|     assert request.body == data.encode("utf-8") |     assert request.body == data.encode("utf-8") | ||||||
|     assert request.json.get("test") == "OK" |     assert request.json.get("test") == "OK" | ||||||
|     assert response.body == b"OK" |     assert response.body == b"OK" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_conflicting_body_methods_overload(app): | ||||||
|  |     @app.put("/") | ||||||
|  |     @app.put("/p/") | ||||||
|  |     @app.put("/p/<foo>") | ||||||
|  |     async def put(request, foo=None): | ||||||
|  |         return json( | ||||||
|  |             {"name": request.route.name, "body": str(request.body), "foo": foo} | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     @app.delete("/p/<foo>") | ||||||
|  |     async def delete(request, foo): | ||||||
|  |         return json( | ||||||
|  |             {"name": request.route.name, "body": str(request.body), "foo": foo} | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     payload = {"test": "OK"} | ||||||
|  |     data = str(json_dumps(payload).encode()) | ||||||
|  |  | ||||||
|  |     _, response = app.test_client.put("/", json=payload) | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.json == { | ||||||
|  |         "name": "test_conflicting_body_methods_overload.put", | ||||||
|  |         "foo": None, | ||||||
|  |         "body": data, | ||||||
|  |     } | ||||||
|  |     _, response = app.test_client.put("/p", json=payload) | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.json == { | ||||||
|  |         "name": "test_conflicting_body_methods_overload.put", | ||||||
|  |         "foo": None, | ||||||
|  |         "body": data, | ||||||
|  |     } | ||||||
|  |     _, response = app.test_client.put("/p/test", json=payload) | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.json == { | ||||||
|  |         "name": "test_conflicting_body_methods_overload.put", | ||||||
|  |         "foo": "test", | ||||||
|  |         "body": data, | ||||||
|  |     } | ||||||
|  |     _, response = app.test_client.delete("/p/test") | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.json == { | ||||||
|  |         "name": "test_conflicting_body_methods_overload.delete", | ||||||
|  |         "foo": "test", | ||||||
|  |         "body": str("".encode()), | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_handler_overload(app): | ||||||
|  |     @app.get( | ||||||
|  |         "/long/sub/route/param_a/<param_a:string>/param_b/<param_b:string>" | ||||||
|  |     ) | ||||||
|  |     @app.post("/long/sub/route/") | ||||||
|  |     def handler(request, **kwargs): | ||||||
|  |         return json(kwargs) | ||||||
|  |  | ||||||
|  |     _, response = app.test_client.get( | ||||||
|  |         "/long/sub/route/param_a/foo/param_b/bar" | ||||||
|  |     ) | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.json == { | ||||||
|  |         "param_a": "foo", | ||||||
|  |         "param_b": "bar", | ||||||
|  |     } | ||||||
|  |     _, response = app.test_client.post("/long/sub/route") | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.json == {} | ||||||
|   | |||||||
| @@ -65,7 +65,9 @@ def test_method_not_allowed(): | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     request, response = app.test_client.post("/") |     request, response = app.test_client.post("/") | ||||||
|     assert set(response.headers["Allow"].split(", ")) == {"GET", "HEAD"} |     assert set(response.headers["Allow"].split(", ")) == { | ||||||
|  |         "GET", | ||||||
|  |     } | ||||||
|  |  | ||||||
|     app.router.reset() |     app.router.reset() | ||||||
|  |  | ||||||
| @@ -78,7 +80,6 @@ def test_method_not_allowed(): | |||||||
|     assert set(response.headers["Allow"].split(", ")) == { |     assert set(response.headers["Allow"].split(", ")) == { | ||||||
|         "GET", |         "GET", | ||||||
|         "POST", |         "POST", | ||||||
|         "HEAD", |  | ||||||
|     } |     } | ||||||
|     assert response.headers["Content-Length"] == "0" |     assert response.headers["Content-Length"] == "0" | ||||||
|  |  | ||||||
| @@ -87,7 +88,6 @@ def test_method_not_allowed(): | |||||||
|     assert set(response.headers["Allow"].split(", ")) == { |     assert set(response.headers["Allow"].split(", ")) == { | ||||||
|         "GET", |         "GET", | ||||||
|         "POST", |         "POST", | ||||||
|         "HEAD", |  | ||||||
|     } |     } | ||||||
|     assert response.headers["Content-Length"] == "0" |     assert response.headers["Content-Length"] == "0" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -543,9 +543,6 @@ def test_dynamic_route_regex(app): | |||||||
|     async def handler(request, folder_id): |     async def handler(request, folder_id): | ||||||
|         return text("OK") |         return text("OK") | ||||||
|  |  | ||||||
|     app.router.finalize() |  | ||||||
|     print(app.router.find_route_src) |  | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/folder/test") |     request, response = app.test_client.get("/folder/test") | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|  |  | ||||||
| @@ -587,6 +584,9 @@ def test_dynamic_route_path(app): | |||||||
|     async def handler(request, path): |     async def handler(request, path): | ||||||
|         return text("OK") |         return text("OK") | ||||||
|  |  | ||||||
|  |     app.router.finalize() | ||||||
|  |     print(app.router.find_route_src) | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/path/1/info") |     request, response = app.test_client.get("/path/1/info") | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|  |  | ||||||
| @@ -1008,14 +1008,8 @@ def test_unmergeable_overload_routes(app): | |||||||
|     async def handler2(request): |     async def handler2(request): | ||||||
|         return text("OK1") |         return text("OK1") | ||||||
|  |  | ||||||
|     assert ( |     assert len(app.router.static_routes) == 1 | ||||||
|         len( |     assert len(app.router.static_routes[("overload_whole",)].methods) == 3 | ||||||
|             dict(list(app.router.static_routes.values())[0].handlers)[ |  | ||||||
|                 "overload_whole" |  | ||||||
|             ] |  | ||||||
|         ) |  | ||||||
|         == 3 |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/overload_whole") |     request, response = app.test_client.get("/overload_whole") | ||||||
|     assert response.text == "OK1" |     assert response.text == "OK1" | ||||||
|   | |||||||
| @@ -28,7 +28,8 @@ def test_add_signal_decorator(app): | |||||||
|     async def async_signal(*_): |     async def async_signal(*_): | ||||||
|         ... |         ... | ||||||
|  |  | ||||||
|     assert len(app.signal_router.routes) == 1 |     assert len(app.signal_router.routes) == 2 | ||||||
|  |     assert len(app.signal_router.dynamic_routes) == 1 | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -79,13 +80,13 @@ async def test_dispatch_signal_triggers_triggers_event(app): | |||||||
|     def sync_signal(*args): |     def sync_signal(*args): | ||||||
|         nonlocal app |         nonlocal app | ||||||
|         nonlocal counter |         nonlocal counter | ||||||
|         signal, *_ = app.signal_router.get("foo.bar.baz") |         group, *_ = app.signal_router.get("foo.bar.baz") | ||||||
|  |         for signal in group: | ||||||
|             counter += signal.ctx.event.is_set() |             counter += signal.ctx.event.is_set() | ||||||
|  |  | ||||||
|     app.signal_router.finalize() |     app.signal_router.finalize() | ||||||
|  |  | ||||||
|     await app.dispatch("foo.bar.baz") |     await app.dispatch("foo.bar.baz") | ||||||
|     signal, *_ = app.signal_router.get("foo.bar.baz") |  | ||||||
|  |  | ||||||
|     assert counter == 1 |     assert counter == 1 | ||||||
|  |  | ||||||
| @@ -224,7 +225,7 @@ async def test_dispatch_signal_triggers_event_on_bp(app): | |||||||
|  |  | ||||||
|     app.blueprint(bp) |     app.blueprint(bp) | ||||||
|     app.signal_router.finalize() |     app.signal_router.finalize() | ||||||
|     signal, *_ = app.signal_router.get( |     signal_group, *_ = app.signal_router.get( | ||||||
|         "foo.bar.baz", condition={"blueprint": "bp"} |         "foo.bar.baz", condition={"blueprint": "bp"} | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
| @@ -233,6 +234,7 @@ async def test_dispatch_signal_triggers_event_on_bp(app): | |||||||
|     assert isawaitable(waiter) |     assert isawaitable(waiter) | ||||||
|  |  | ||||||
|     fut = asyncio.ensure_future(do_wait()) |     fut = asyncio.ensure_future(do_wait()) | ||||||
|  |     for signal in signal_group: | ||||||
|         signal.ctx.event.set() |         signal.ctx.event.set() | ||||||
|     await fut |     await fut | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,16 @@ | |||||||
| import inspect | import inspect | ||||||
|  | import logging | ||||||
| import os | import os | ||||||
|  |  | ||||||
|  | from collections import Counter | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from time import gmtime, strftime | from time import gmtime, strftime | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
|  | from sanic import text | ||||||
|  | from sanic.exceptions import FileNotFound | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture(scope="module") | @pytest.fixture(scope="module") | ||||||
| def static_file_directory(): | def static_file_directory(): | ||||||
| @@ -454,3 +459,51 @@ def test_nested_dir(app, static_file_directory): | |||||||
|  |  | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|     assert response.text == "foo\n" |     assert response.text == "foo\n" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_stack_trace_on_not_found(app, static_file_directory, caplog): | ||||||
|  |     app.static("/static", static_file_directory) | ||||||
|  |  | ||||||
|  |     with caplog.at_level(logging.INFO): | ||||||
|  |         _, response = app.test_client.get("/static/non_existing_file.file") | ||||||
|  |  | ||||||
|  |     counter = Counter([r[1] for r in caplog.record_tuples]) | ||||||
|  |  | ||||||
|  |     assert response.status == 404 | ||||||
|  |     assert counter[logging.INFO] == 5 | ||||||
|  |     assert counter[logging.ERROR] == 1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # def test_no_stack_trace_on_not_found(app, static_file_directory, caplog): | ||||||
|  | #     app.static("/static", static_file_directory) | ||||||
|  |  | ||||||
|  | #     @app.exception(FileNotFound) | ||||||
|  | #     async def file_not_found(request, exception): | ||||||
|  | #         return text(f"No file: {request.path}", status=404) | ||||||
|  |  | ||||||
|  | #     with caplog.at_level(logging.INFO): | ||||||
|  | #         _, response = app.test_client.get("/static/non_existing_file.file") | ||||||
|  |  | ||||||
|  | #     counter = Counter([r[1] for r in caplog.record_tuples]) | ||||||
|  |  | ||||||
|  | #     assert response.status == 404 | ||||||
|  | #     assert counter[logging.INFO] == 5 | ||||||
|  | #     assert logging.ERROR not in counter | ||||||
|  | #     assert response.text == "No file: /static/non_existing_file.file" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_multiple_statics(app, static_file_directory): | ||||||
|  |     app.static("/file", get_file_path(static_file_directory, "test.file")) | ||||||
|  |     app.static("/png", get_file_path(static_file_directory, "python.png")) | ||||||
|  |  | ||||||
|  |     _, response = app.test_client.get("/file") | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.body == get_file_content( | ||||||
|  |         static_file_directory, "test.file" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     _, response = app.test_client.get("/png") | ||||||
|  |     assert response.status == 200 | ||||||
|  |     assert response.body == get_file_content( | ||||||
|  |         static_file_directory, "python.png" | ||||||
|  |     ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user