Compare commits
	
		
			1 Commits
		
	
	
		
			v22.3.0
			...
			flaky-test
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a61c5ff55b | 
							
								
								
									
										1
									
								
								.github/workflows/pr-python310.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-python310.yml
									
									
									
									
										vendored
									
									
								
							| @@ -8,7 +8,6 @@ on: | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   testPy310: |   testPy310: | ||||||
|     if: github.event.pull_request.draft == false |  | ||||||
|     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} |     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} | ||||||
|     runs-on: ${{ matrix.os }} |     runs-on: ${{ matrix.os }} | ||||||
|     strategy: |     strategy: | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| 📜 Changelog | 📜 Changelog | ||||||
| ============ | ============ | ||||||
|  |  | ||||||
| .. mdinclude:: ./releases/22/22.3.md |  | ||||||
| .. mdinclude:: ./releases/21/21.12.md | .. mdinclude:: ./releases/21/21.12.md | ||||||
| .. mdinclude:: ./releases/21/21.9.md | .. mdinclude:: ./releases/21/21.9.md | ||||||
| .. include:: ../../CHANGELOG.rst | .. include:: ../../CHANGELOG.rst | ||||||
|   | |||||||
| @@ -1,52 +0,0 @@ | |||||||
| ## Version 22.3.0 |  | ||||||
|  |  | ||||||
| ### Features |  | ||||||
| - [#2347](https://github.com/sanic-org/sanic/pull/2347) API for multi-application server |  | ||||||
|     - 🚨 *BREAKING CHANGE*: The old `sanic.worker.GunicornWorker` has been **removed**. To run Sanic with `gunicorn`, you should use it thru `uvicorn` [as described in their docs](https://www.uvicorn.org/#running-with-gunicorn). |  | ||||||
|     - 🧁 *SIDE EFFECT*: Named background tasks are now supported, even in Python 3.7 |  | ||||||
| - [#2357](https://github.com/sanic-org/sanic/pull/2357) Parse `Authorization` header as `Request.credentials` |  | ||||||
| - [#2361](https://github.com/sanic-org/sanic/pull/2361) Add config option to skip `Touchup` step in application startup |  | ||||||
| - [#2372](https://github.com/sanic-org/sanic/pull/2372) Updates to CLI help messaging |  | ||||||
| - [#2382](https://github.com/sanic-org/sanic/pull/2382) Downgrade warnings to backwater debug messages  |  | ||||||
| - [#2396](https://github.com/sanic-org/sanic/pull/2396) Allow for `multidict` v0.6 |  | ||||||
| - [#2401](https://github.com/sanic-org/sanic/pull/2401) Upgrade CLI catching for alternative application run types |  | ||||||
| - [#2402](https://github.com/sanic-org/sanic/pull/2402) Conditionally inject CLI arguments into factory |  | ||||||
| - [#2413](https://github.com/sanic-org/sanic/pull/2413) Add new start and stop event listeners to reloader process |  | ||||||
| - [#2414](https://github.com/sanic-org/sanic/pull/2414) Remove loop as required listener arg |  | ||||||
| - [#2415](https://github.com/sanic-org/sanic/pull/2415) Better exception for bad URL parsing |  | ||||||
| - [sanic-routing#47](https://github.com/sanic-org/sanic-routing/pull/47) Add a new extention parameter type: `<file:ext>`, `<file:ext=jpg>`, `<file:ext=jpg|png|gif|svg>`, `<file=int:ext>`, `<file=int:ext=jpg|png|gif|svg>`, `<file=float:ext=tar.gz>` |  | ||||||
|     - 👶 *BETA FEATURE*: This feature will not work with `path` type matching, and is being released as a beta feature only. |  | ||||||
| - [sanic-routing#57](https://github.com/sanic-org/sanic-routing/pull/57) Change `register_pattern` to accept a `str` or `Pattern` |  | ||||||
| - [sanic-routing#58](https://github.com/sanic-org/sanic-routing/pull/58) Default matching on non-empty strings only, and new `strorempty` pattern type |  | ||||||
|     - 🚨 *BREAKING CHANGE*: Previously a route with a dynamic string parameter (`/<foo>` or `/<foo:str>`) would match on any string, including empty strings. It will now **only** match a non-empty string. To retain the old behavior, you should use the new parameter type: `/<foo:strorempty>`. |  | ||||||
|  |  | ||||||
| ### Bugfixes |  | ||||||
| - [#2373](https://github.com/sanic-org/sanic/pull/2373) Remove `error_logger` on websockets |  | ||||||
| - [#2381](https://github.com/sanic-org/sanic/pull/2381) Fix newly assigned `None` in task registry |  | ||||||
| - [sanic-routing#52](https://github.com/sanic-org/sanic-routing/pull/52) Add type casting to regex route matching |  | ||||||
| - [sanic-routing#60](https://github.com/sanic-org/sanic-routing/pull/60) Add requirements check on regex routes (this resolves, for example, multiple static directories with differing `host` values) |  | ||||||
|  |  | ||||||
| ### Deprecations and Removals |  | ||||||
| - [#2362](https://github.com/sanic-org/sanic/pull/2362) 22.3 Deprecations and changes |  | ||||||
|     1. `debug=True` and `--debug` do _NOT_ automatically run `auto_reload` |  | ||||||
|     2. Default error render is with plain text (browsers still get HTML by default because `auto` looks at headers) |  | ||||||
|     3. `config` is required for `ErrorHandler.finalize` |  | ||||||
|     4. `ErrorHandler.lookup` requires two positional args |  | ||||||
|     5. Unused websocket protocol args removed |  | ||||||
| - [#2344](https://github.com/sanic-org/sanic/pull/2344) Deprecate loading of lowercase environment variables |  | ||||||
|  |  | ||||||
| ### Developer infrastructure |  | ||||||
| - [#2363](https://github.com/sanic-org/sanic/pull/2363) Revert code coverage back to Codecov |  | ||||||
| - [#2405](https://github.com/sanic-org/sanic/pull/2405) Upgrade tests for `sanic-routing` changes |  | ||||||
| - [sanic-testing#35](https://github.com/sanic-org/sanic-testing/pull/35) Allow for httpx v0.22 |  | ||||||
|  |  | ||||||
| ### Improved Documentation |  | ||||||
| - [#2350](https://github.com/sanic-org/sanic/pull/2350) Fix link in README for ASGI |  | ||||||
| - [#2398](https://github.com/sanic-org/sanic/pull/2398) Document middleware on_request and on_response |  | ||||||
| - [#2409](https://github.com/sanic-org/sanic/pull/2409) Add missing documentation for `Request.respond` |  | ||||||
|  |  | ||||||
| ### Miscellaneous |  | ||||||
| - [#2376](https://github.com/sanic-org/sanic/pull/2376) Fix typing for `ListenerMixin.listener` |  | ||||||
| - [#2383](https://github.com/sanic-org/sanic/pull/2383) Clear deprecation warning in `asyncio.wait` |  | ||||||
| - [#2387](https://github.com/sanic-org/sanic/pull/2387) Cleanup `__slots__` implementations |  | ||||||
| - [#2390](https://github.com/sanic-org/sanic/pull/2390) Clear deprecation warning in `asyncio.get_event_loop` |  | ||||||
| @@ -4,7 +4,6 @@ from sanic import Sanic, response | |||||||
|  |  | ||||||
|  |  | ||||||
| app = Sanic("DelayedResponseApp", strict_slashes=True) | app = Sanic("DelayedResponseApp", strict_slashes=True) | ||||||
| app.config.AUTO_EXTEND = False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.get("/") | @app.get("/") | ||||||
| @@ -12,7 +11,7 @@ async def handler(request): | |||||||
|     return response.redirect("/sleep/3") |     return response.redirect("/sleep/3") | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.get("/sleep/<t:float>") | @app.get("/sleep/<t:number>") | ||||||
| async def handler2(request, t=0.3): | async def handler2(request, t=0.3): | ||||||
|     await sleep(t) |     await sleep(t) | ||||||
|     return response.text(f"Slept {t:.1f} seconds.\n") |     return response.text(f"Slept {t:.1f} seconds.\n") | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| __version__ = "22.3.0" | __version__ = "22.3.0.dev1" | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -11,6 +11,7 @@ from asyncio import ( | |||||||
|     CancelledError, |     CancelledError, | ||||||
|     Task, |     Task, | ||||||
|     ensure_future, |     ensure_future, | ||||||
|  |     get_event_loop, | ||||||
|     get_running_loop, |     get_running_loop, | ||||||
|     wait_for, |     wait_for, | ||||||
| ) | ) | ||||||
| @@ -43,11 +44,8 @@ from typing import ( | |||||||
| from urllib.parse import urlencode, urlunparse | from urllib.parse import urlencode, urlunparse | ||||||
| from warnings import filterwarnings | from warnings import filterwarnings | ||||||
|  |  | ||||||
| from sanic_routing.exceptions import (  # type: ignore | from sanic_routing.exceptions import FinalizationError, NotFound | ||||||
|     FinalizationError, | from sanic_routing.route import Route | ||||||
|     NotFound, |  | ||||||
| ) |  | ||||||
| from sanic_routing.route import Route  # type: ignore |  | ||||||
|  |  | ||||||
| from sanic.application.ext import setup_ext | from sanic.application.ext import setup_ext | ||||||
| from sanic.application.state import ApplicationState, Mode, ServerStage | from sanic.application.state import ApplicationState, Mode, ServerStage | ||||||
| @@ -252,7 +250,7 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta): | |||||||
|                 "Loop can only be retrieved after the app has started " |                 "Loop can only be retrieved after the app has started " | ||||||
|                 "running. Not supported with `create_server` function" |                 "running. Not supported with `create_server` function" | ||||||
|             ) |             ) | ||||||
|         return get_running_loop() |         return get_event_loop() | ||||||
|  |  | ||||||
|     # -------------------------------------------------------------------- # |     # -------------------------------------------------------------------- # | ||||||
|     # Registration |     # Registration | ||||||
| @@ -1130,10 +1128,7 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta): | |||||||
|     async def _listener( |     async def _listener( | ||||||
|         app: Sanic, loop: AbstractEventLoop, listener: ListenerType |         app: Sanic, loop: AbstractEventLoop, listener: ListenerType | ||||||
|     ): |     ): | ||||||
|         try: |         maybe_coro = listener(app, loop) | ||||||
|             maybe_coro = listener(app)  # type: ignore |  | ||||||
|         except TypeError: |  | ||||||
|             maybe_coro = listener(app, loop)  # type: ignore |  | ||||||
|         if maybe_coro and isawaitable(maybe_coro): |         if maybe_coro and isawaitable(maybe_coro): | ||||||
|             await maybe_coro |             await maybe_coro | ||||||
|  |  | ||||||
| @@ -1510,8 +1505,7 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta): | |||||||
|             if not Sanic.test_mode: |             if not Sanic.test_mode: | ||||||
|                 raise e |                 raise e | ||||||
|  |  | ||||||
|     def signalize(self, allow_fail_builtin=True): |     def signalize(self): | ||||||
|         self.signal_router.allow_fail_builtin = allow_fail_builtin |  | ||||||
|         try: |         try: | ||||||
|             self.signal_router.finalize() |             self.signal_router.finalize() | ||||||
|         except FinalizationError as e: |         except FinalizationError as e: | ||||||
| @@ -1526,11 +1520,8 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta): | |||||||
|         if hasattr(self, "_ext"): |         if hasattr(self, "_ext"): | ||||||
|             self.ext._display() |             self.ext._display() | ||||||
|  |  | ||||||
|         if self.state.is_debug: |  | ||||||
|             self.config.TOUCHUP = False |  | ||||||
|  |  | ||||||
|         # Setup routers |         # Setup routers | ||||||
|         self.signalize(self.config.TOUCHUP) |         self.signalize() | ||||||
|         self.finalize() |         self.finalize() | ||||||
|  |  | ||||||
|         # TODO: Replace in v22.6 to check against apps in app registry |         # TODO: Replace in v22.6 to check against apps in app registry | ||||||
| @@ -1550,7 +1541,6 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta): | |||||||
|             # TODO: |             # TODO: | ||||||
|             # - Raise warning if secondary apps have error handler config |             # - Raise warning if secondary apps have error handler config | ||||||
|             ErrorHandler.finalize(self.error_handler, config=self.config) |             ErrorHandler.finalize(self.error_handler, config=self.config) | ||||||
|             if self.config.TOUCHUP: |  | ||||||
|             TouchUp.run(self) |             TouchUp.run(self) | ||||||
|  |  | ||||||
|         self.state.is_started = True |         self.state.is_started = True | ||||||
|   | |||||||
| @@ -68,13 +68,6 @@ Or, a path to a directory to run as a simple HTTP server: | |||||||
|         legacy_version = len(sys.argv) == 2 and sys.argv[-1] == "-v" |         legacy_version = len(sys.argv) == 2 and sys.argv[-1] == "-v" | ||||||
|         parse_args = ["--version"] if legacy_version else None |         parse_args = ["--version"] if legacy_version else None | ||||||
|  |  | ||||||
|         if not parse_args: |  | ||||||
|             parsed, unknown = self.parser.parse_known_args() |  | ||||||
|             if unknown and parsed.factory: |  | ||||||
|                 for arg in unknown: |  | ||||||
|                     if arg.startswith("--"): |  | ||||||
|                         self.parser.add_argument(arg.split("=")[0]) |  | ||||||
|  |  | ||||||
|         self.args = self.parser.parse_args(args=parse_args) |         self.args = self.parser.parse_args(args=parse_args) | ||||||
|         self._precheck() |         self._precheck() | ||||||
|  |  | ||||||
| @@ -120,14 +113,6 @@ Or, a path to a directory to run as a simple HTTP server: | |||||||
|                 delimiter = ":" if ":" in self.args.module else "." |                 delimiter = ":" if ":" in self.args.module else "." | ||||||
|                 module_name, app_name = self.args.module.rsplit(delimiter, 1) |                 module_name, app_name = self.args.module.rsplit(delimiter, 1) | ||||||
|  |  | ||||||
|                 if module_name == "" and os.path.isdir(self.args.module): |  | ||||||
|                     raise ValueError( |  | ||||||
|                         "App not found.\n" |  | ||||||
|                         "   Please use --simple if you are passing a " |  | ||||||
|                         "directory to sanic.\n" |  | ||||||
|                         f"   eg. sanic {self.args.module} --simple" |  | ||||||
|                     ) |  | ||||||
|  |  | ||||||
|                 if app_name.endswith("()"): |                 if app_name.endswith("()"): | ||||||
|                     self.args.factory = True |                     self.args.factory = True | ||||||
|                     app_name = app_name[:-2] |                     app_name = app_name[:-2] | ||||||
| @@ -135,26 +120,14 @@ Or, a path to a directory to run as a simple HTTP server: | |||||||
|                 module = import_module(module_name) |                 module = import_module(module_name) | ||||||
|                 app = getattr(module, app_name, None) |                 app = getattr(module, app_name, None) | ||||||
|                 if self.args.factory: |                 if self.args.factory: | ||||||
|                     try: |  | ||||||
|                         app = app(self.args) |  | ||||||
|                     except TypeError: |  | ||||||
|                     app = app() |                     app = app() | ||||||
|  |  | ||||||
|                 app_type_name = type(app).__name__ |                 app_type_name = type(app).__name__ | ||||||
|  |  | ||||||
|                 if not isinstance(app, Sanic): |                 if not isinstance(app, Sanic): | ||||||
|                     if callable(app): |  | ||||||
|                         solution = f"sanic {self.args.module} --factory" |  | ||||||
|                         raise ValueError( |  | ||||||
|                             "Module is not a Sanic app, it is a" |  | ||||||
|                             f"{app_type_name}\n" |  | ||||||
|                             "  If this callable returns a" |  | ||||||
|                             f"Sanic instance try: \n{solution}" |  | ||||||
|                         ) |  | ||||||
|  |  | ||||||
|                     raise ValueError( |                     raise ValueError( | ||||||
|                         f"Module is not a Sanic app, it is a {app_type_name}\n" |                         f"Module is not a Sanic app, it is a {app_type_name}\n" | ||||||
|                         f"  Perhaps you meant {self.args.module}:app?" |                         f"  Perhaps you meant {self.args.module}.app?" | ||||||
|                     ) |                     ) | ||||||
|         except ImportError as e: |         except ImportError as e: | ||||||
|             if module_name.startswith(e.name): |             if module_name.startswith(e.name): | ||||||
|   | |||||||
| @@ -38,7 +38,6 @@ DEFAULT_CONFIG = { | |||||||
|     "REQUEST_MAX_SIZE": 100000000,  # 100 megabytes |     "REQUEST_MAX_SIZE": 100000000,  # 100 megabytes | ||||||
|     "REQUEST_TIMEOUT": 60,  # 60 seconds |     "REQUEST_TIMEOUT": 60,  # 60 seconds | ||||||
|     "RESPONSE_TIMEOUT": 60,  # 60 seconds |     "RESPONSE_TIMEOUT": 60,  # 60 seconds | ||||||
|     "TOUCHUP": True, |  | ||||||
|     "USE_UVLOOP": _default, |     "USE_UVLOOP": _default, | ||||||
|     "WEBSOCKET_MAX_SIZE": 2**20,  # 1 megabyte |     "WEBSOCKET_MAX_SIZE": 2**20,  # 1 megabyte | ||||||
|     "WEBSOCKET_PING_INTERVAL": 20, |     "WEBSOCKET_PING_INTERVAL": 20, | ||||||
| @@ -82,7 +81,6 @@ class Config(dict, metaclass=DescriptorMeta): | |||||||
|     REQUEST_TIMEOUT: int |     REQUEST_TIMEOUT: int | ||||||
|     RESPONSE_TIMEOUT: int |     RESPONSE_TIMEOUT: int | ||||||
|     SERVER_NAME: str |     SERVER_NAME: str | ||||||
|     TOUCHUP: bool |  | ||||||
|     USE_UVLOOP: Union[Default, bool] |     USE_UVLOOP: Union[Default, bool] | ||||||
|     WEBSOCKET_MAX_SIZE: int |     WEBSOCKET_MAX_SIZE: int | ||||||
|     WEBSOCKET_PING_INTERVAL: int |     WEBSOCKET_PING_INTERVAL: int | ||||||
|   | |||||||
| @@ -51,10 +51,6 @@ class InvalidUsage(SanicException): | |||||||
|     quiet = True |     quiet = True | ||||||
|  |  | ||||||
|  |  | ||||||
| class BadURL(InvalidUsage): |  | ||||||
|     ... |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MethodNotSupported(SanicException): | class MethodNotSupported(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 405 Method Not Allowed |     **Status**: 405 Method Not Allowed | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
| from enum import Enum, auto | from enum import Enum, auto | ||||||
| from functools import partial | from functools import partial | ||||||
| from typing import Callable, List, Optional, Union, overload | from typing import List, Optional, Union | ||||||
|  |  | ||||||
| from sanic.base.meta import SanicMeta | from sanic.base.meta import SanicMeta | ||||||
| from sanic.exceptions import InvalidUsage |  | ||||||
| from sanic.models.futures import FutureListener | from sanic.models.futures import FutureListener | ||||||
| from sanic.models.handler_types import ListenerType, Sanic | from sanic.models.handler_types import ListenerType, Sanic | ||||||
|  |  | ||||||
| @@ -18,8 +17,6 @@ class ListenerEvent(str, Enum): | |||||||
|     AFTER_SERVER_STOP = "server.shutdown.after" |     AFTER_SERVER_STOP = "server.shutdown.after" | ||||||
|     MAIN_PROCESS_START = auto() |     MAIN_PROCESS_START = auto() | ||||||
|     MAIN_PROCESS_STOP = auto() |     MAIN_PROCESS_STOP = auto() | ||||||
|     RELOAD_PROCESS_START = auto() |  | ||||||
|     RELOAD_PROCESS_STOP = auto() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ListenerMixin(metaclass=SanicMeta): | class ListenerMixin(metaclass=SanicMeta): | ||||||
| @@ -29,33 +26,12 @@ class ListenerMixin(metaclass=SanicMeta): | |||||||
|     def _apply_listener(self, listener: FutureListener): |     def _apply_listener(self, listener: FutureListener): | ||||||
|         raise NotImplementedError  # noqa |         raise NotImplementedError  # noqa | ||||||
|  |  | ||||||
|     @overload |  | ||||||
|     def listener( |  | ||||||
|         self, |  | ||||||
|         listener_or_event: ListenerType[Sanic], |  | ||||||
|         event_or_none: str, |  | ||||||
|         apply: bool = ..., |  | ||||||
|     ) -> ListenerType[Sanic]: |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     @overload |  | ||||||
|     def listener( |  | ||||||
|         self, |  | ||||||
|         listener_or_event: str, |  | ||||||
|         event_or_none: None = ..., |  | ||||||
|         apply: bool = ..., |  | ||||||
|     ) -> Callable[[ListenerType[Sanic]], ListenerType[Sanic]]: |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     def listener( |     def listener( | ||||||
|         self, |         self, | ||||||
|         listener_or_event: Union[ListenerType[Sanic], str], |         listener_or_event: Union[ListenerType[Sanic], str], | ||||||
|         event_or_none: Optional[str] = None, |         event_or_none: Optional[str] = None, | ||||||
|         apply: bool = True, |         apply: bool = True, | ||||||
|     ) -> Union[ |     ) -> ListenerType[Sanic]: | ||||||
|         ListenerType[Sanic], |  | ||||||
|         Callable[[ListenerType[Sanic]], ListenerType[Sanic]], |  | ||||||
|     ]: |  | ||||||
|         """ |         """ | ||||||
|         Create a listener from a decorated function. |         Create a listener from a decorated function. | ||||||
|  |  | ||||||
| @@ -73,9 +49,7 @@ class ListenerMixin(metaclass=SanicMeta): | |||||||
|         :param event: event to listen to |         :param event: event to listen to | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         def register_listener( |         def register_listener(listener, event): | ||||||
|             listener: ListenerType[Sanic], event: str |  | ||||||
|         ) -> ListenerType[Sanic]: |  | ||||||
|             nonlocal apply |             nonlocal apply | ||||||
|  |  | ||||||
|             future_listener = FutureListener(listener, event) |             future_listener = FutureListener(listener, event) | ||||||
| @@ -85,10 +59,6 @@ class ListenerMixin(metaclass=SanicMeta): | |||||||
|             return listener |             return listener | ||||||
|  |  | ||||||
|         if callable(listener_or_event): |         if callable(listener_or_event): | ||||||
|             if event_or_none is None: |  | ||||||
|                 raise InvalidUsage( |  | ||||||
|                     "Invalid event registration: Missing event name." |  | ||||||
|                 ) |  | ||||||
|             return register_listener(listener_or_event, event_or_none) |             return register_listener(listener_or_event, event_or_none) | ||||||
|         else: |         else: | ||||||
|             return partial(register_listener, event=listener_or_event) |             return partial(register_listener, event=listener_or_event) | ||||||
| @@ -103,16 +73,6 @@ class ListenerMixin(metaclass=SanicMeta): | |||||||
|     ) -> ListenerType[Sanic]: |     ) -> ListenerType[Sanic]: | ||||||
|         return self.listener(listener, "main_process_stop") |         return self.listener(listener, "main_process_stop") | ||||||
|  |  | ||||||
|     def reload_process_start( |  | ||||||
|         self, listener: ListenerType[Sanic] |  | ||||||
|     ) -> ListenerType[Sanic]: |  | ||||||
|         return self.listener(listener, "reload_process_start") |  | ||||||
|  |  | ||||||
|     def reload_process_stop( |  | ||||||
|         self, listener: ListenerType[Sanic] |  | ||||||
|     ) -> ListenerType[Sanic]: |  | ||||||
|         return self.listener(listener, "reload_process_stop") |  | ||||||
|  |  | ||||||
|     def before_server_start( |     def before_server_start( | ||||||
|         self, listener: ListenerType[Sanic] |         self, listener: ListenerType[Sanic] | ||||||
|     ) -> ListenerType[Sanic]: |     ) -> ListenerType[Sanic]: | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ from asyncio import ( | |||||||
|     all_tasks, |     all_tasks, | ||||||
|     get_event_loop, |     get_event_loop, | ||||||
|     get_running_loop, |     get_running_loop, | ||||||
|     new_event_loop, |  | ||||||
| ) | ) | ||||||
| from contextlib import suppress | from contextlib import suppress | ||||||
| from functools import partial | from functools import partial | ||||||
| @@ -33,7 +32,6 @@ from sanic.models.handler_types import ListenerType | |||||||
| from sanic.server import Signal as ServerSignal | from sanic.server import Signal as ServerSignal | ||||||
| from sanic.server import try_use_uvloop | from sanic.server import try_use_uvloop | ||||||
| from sanic.server.async_server import AsyncioServer | from sanic.server.async_server import AsyncioServer | ||||||
| from sanic.server.events import trigger_events |  | ||||||
| from sanic.server.protocols.http_protocol import HttpProtocol | from sanic.server.protocols.http_protocol import HttpProtocol | ||||||
| from sanic.server.protocols.websocket_protocol import WebSocketProtocol | from sanic.server.protocols.websocket_protocol import WebSocketProtocol | ||||||
| from sanic.server.runners import serve, serve_multiple, serve_single | from sanic.server.runners import serve, serve_multiple, serve_single | ||||||
| @@ -540,21 +538,15 @@ class RunnerMixin(metaclass=SanicMeta): | |||||||
|             except IndexError: |             except IndexError: | ||||||
|                 raise RuntimeError("Did not find any applications.") |                 raise RuntimeError("Did not find any applications.") | ||||||
|  |  | ||||||
|         reloader_start = primary.listeners.get("reload_process_start") |  | ||||||
|         reloader_stop = primary.listeners.get("reload_process_stop") |  | ||||||
|         # We want to run auto_reload if ANY of the applications have it enabled |         # We want to run auto_reload if ANY of the applications have it enabled | ||||||
|         if ( |         if ( | ||||||
|             cls.should_auto_reload() |             cls.should_auto_reload() | ||||||
|             and os.environ.get("SANIC_SERVER_RUNNING") != "true" |             and os.environ.get("SANIC_SERVER_RUNNING") != "true" | ||||||
|         ):  # no cov |         ): | ||||||
|             loop = new_event_loop() |  | ||||||
|             trigger_events(reloader_start, loop, primary) |  | ||||||
|             reload_dirs: Set[Path] = primary.state.reload_dirs.union( |             reload_dirs: Set[Path] = primary.state.reload_dirs.union( | ||||||
|                 *(app.state.reload_dirs for app in apps) |                 *(app.state.reload_dirs for app in apps) | ||||||
|             ) |             ) | ||||||
|             reloader_helpers.watchdog(1.0, reload_dirs) |             return reloader_helpers.watchdog(1.0, reload_dirs) | ||||||
|             trigger_events(reloader_stop, loop, primary) |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         # This exists primarily for unit testing |         # This exists primarily for unit testing | ||||||
|         if not primary.state.server_info:  # no cov |         if not primary.state.server_info:  # no cov | ||||||
|   | |||||||
| @@ -1,13 +1,11 @@ | |||||||
| from asyncio.events import AbstractEventLoop | from asyncio.events import AbstractEventLoop | ||||||
| from typing import Any, Callable, Coroutine, Optional, TypeVar, Union | from typing import Any, Callable, Coroutine, Optional, TypeVar, Union | ||||||
|  |  | ||||||
| import sanic |  | ||||||
|  |  | ||||||
| from sanic.request import Request | from sanic.request import Request | ||||||
| from sanic.response import BaseHTTPResponse, HTTPResponse | from sanic.response import BaseHTTPResponse, HTTPResponse | ||||||
|  |  | ||||||
|  |  | ||||||
| Sanic = TypeVar("Sanic", bound="sanic.Sanic") | Sanic = TypeVar("Sanic") | ||||||
|  |  | ||||||
| MiddlewareResponse = Union[ | MiddlewareResponse = Union[ | ||||||
|     Optional[HTTPResponse], Coroutine[Any, Any, Optional[HTTPResponse]] |     Optional[HTTPResponse], Coroutine[Any, Any, Optional[HTTPResponse]] | ||||||
| @@ -20,9 +18,8 @@ ErrorMiddlewareType = Callable[ | |||||||
|     [Request, BaseException], Optional[Coroutine[Any, Any, None]] |     [Request, BaseException], Optional[Coroutine[Any, Any, None]] | ||||||
| ] | ] | ||||||
| MiddlewareType = Union[RequestMiddlewareType, ResponseMiddlewareType] | MiddlewareType = Union[RequestMiddlewareType, ResponseMiddlewareType] | ||||||
| ListenerType = Union[ | ListenerType = Callable[ | ||||||
|     Callable[[Sanic], Optional[Coroutine[Any, Any, None]]], |     [Sanic, AbstractEventLoop], Optional[Coroutine[Any, Any, None]] | ||||||
|     Callable[[Sanic, AbstractEventLoop], Optional[Coroutine[Any, Any, None]]], |  | ||||||
| ] | ] | ||||||
| RouteHandler = Callable[..., Coroutine[Any, Any, Optional[HTTPResponse]]] | RouteHandler = Callable[..., Coroutine[Any, Any, Optional[HTTPResponse]]] | ||||||
| SignalHandler = Callable[..., Coroutine[Any, Any, None]] | SignalHandler = Callable[..., Coroutine[Any, Any, None]] | ||||||
|   | |||||||
| @@ -30,11 +30,10 @@ from types import SimpleNamespace | |||||||
| from urllib.parse import parse_qs, parse_qsl, unquote, urlunparse | from urllib.parse import parse_qs, parse_qsl, unquote, urlunparse | ||||||
|  |  | ||||||
| from httptools import parse_url  # type: ignore | from httptools import parse_url  # type: ignore | ||||||
| from httptools.parser.errors import HttpParserInvalidURLError  # type: ignore |  | ||||||
|  |  | ||||||
| from sanic.compat import CancelledErrors, Header | from sanic.compat import CancelledErrors, Header | ||||||
| from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE | from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE | ||||||
| from sanic.exceptions import BadURL, InvalidUsage, ServerError | from sanic.exceptions import InvalidUsage, ServerError | ||||||
| from sanic.headers import ( | from sanic.headers import ( | ||||||
|     AcceptContainer, |     AcceptContainer, | ||||||
|     Options, |     Options, | ||||||
| @@ -130,10 +129,8 @@ class Request: | |||||||
|     ): |     ): | ||||||
|  |  | ||||||
|         self.raw_url = url_bytes |         self.raw_url = url_bytes | ||||||
|         try: |         # TODO: Content-Encoding detection | ||||||
|         self._parsed_url = parse_url(url_bytes) |         self._parsed_url = parse_url(url_bytes) | ||||||
|         except HttpParserInvalidURLError: |  | ||||||
|             raise BadURL(f"Bad URL: {url_bytes.decode()}") |  | ||||||
|         self._id: Optional[Union[uuid.UUID, str, int]] = None |         self._id: Optional[Union[uuid.UUID, str, int]] = None | ||||||
|         self._name: Optional[str] = None |         self._name: Optional[str] = None | ||||||
|         self.app = app |         self.app = app | ||||||
| @@ -200,53 +197,6 @@ class Request: | |||||||
|         headers: Optional[Union[Header, Dict[str, str]]] = None, |         headers: Optional[Union[Header, Dict[str, str]]] = None, | ||||||
|         content_type: Optional[str] = None, |         content_type: Optional[str] = None, | ||||||
|     ): |     ): | ||||||
|         """Respond to the request without returning. |  | ||||||
|  |  | ||||||
|         This method can only be called once, as you can only respond once. |  | ||||||
|         If no ``response`` argument is passed, one will be created from the |  | ||||||
|         ``status``, ``headers`` and ``content_type`` arguments. |  | ||||||
|  |  | ||||||
|         **The first typical usecase** is if you wish to respond to the |  | ||||||
|         request without returning from the handler: |  | ||||||
|  |  | ||||||
|         .. code-block:: python |  | ||||||
|  |  | ||||||
|             @app.get("/") |  | ||||||
|             async def handler(request: Request): |  | ||||||
|                 data = ...  # Process something |  | ||||||
|  |  | ||||||
|                 json_response = json({"data": data}) |  | ||||||
|                 await request.respond(json_response) |  | ||||||
|  |  | ||||||
|                 # You are now free to continue executing other code |  | ||||||
|                 ... |  | ||||||
|  |  | ||||||
|             @app.on_response |  | ||||||
|             async def add_header(_, response: HTTPResponse): |  | ||||||
|                 # Middlewares still get executed as expected |  | ||||||
|                 response.headers["one"] = "two" |  | ||||||
|  |  | ||||||
|         **The second possible usecase** is for when you want to directly |  | ||||||
|         respond to the request: |  | ||||||
|  |  | ||||||
|         .. code-block:: python |  | ||||||
|  |  | ||||||
|             response = await request.respond(content_type="text/csv") |  | ||||||
|             await response.send("foo,") |  | ||||||
|             await response.send("bar") |  | ||||||
|  |  | ||||||
|             # You can control the completion of the response by calling |  | ||||||
|             # the 'eof()' method: |  | ||||||
|             await response.eof() |  | ||||||
|  |  | ||||||
|         :param response: response instance to send |  | ||||||
|         :param status: status code to return in the response |  | ||||||
|         :param headers: headers to return in the response |  | ||||||
|         :param content_type: Content-Type header of the response |  | ||||||
|         :return: final response being sent (may be different from the |  | ||||||
|             ``response`` parameter because of middlewares) which can be |  | ||||||
|             used to manually send data |  | ||||||
|         """ |  | ||||||
|         try: |         try: | ||||||
|             if self.stream is not None and self.stream.response: |             if self.stream is not None and self.stream.response: | ||||||
|                 raise ServerError("Second respond call is not allowed.") |                 raise ServerError("Second respond call is not allowed.") | ||||||
|   | |||||||
| @@ -1,18 +1,8 @@ | |||||||
| from __future__ import annotations |  | ||||||
|  |  | ||||||
| from inspect import isawaitable | from inspect import isawaitable | ||||||
| from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional | from typing import Any, Callable, Iterable, Optional | ||||||
|  |  | ||||||
|  |  | ||||||
| if TYPE_CHECKING:  # no cov | def trigger_events(events: Optional[Iterable[Callable[..., Any]]], loop): | ||||||
|     from sanic import Sanic |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def trigger_events( |  | ||||||
|     events: Optional[Iterable[Callable[..., Any]]], |  | ||||||
|     loop, |  | ||||||
|     app: Optional[Sanic] = None, |  | ||||||
| ): |  | ||||||
|     """ |     """ | ||||||
|     Trigger event callbacks (functions or async) |     Trigger event callbacks (functions or async) | ||||||
|  |  | ||||||
| @@ -21,9 +11,6 @@ def trigger_events( | |||||||
|     """ |     """ | ||||||
|     if events: |     if events: | ||||||
|         for event in events: |         for event in events: | ||||||
|             try: |             result = event(loop) | ||||||
|                 result = event() if not app else event(app) |  | ||||||
|             except TypeError: |  | ||||||
|                 result = event(loop) if not app else event(app, loop) |  | ||||||
|             if isawaitable(result): |             if isawaitable(result): | ||||||
|                 loop.run_until_complete(result) |                 loop.run_until_complete(result) | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ from websockets.server import ServerConnection | |||||||
| from websockets.typing import Subprotocol | from websockets.typing import Subprotocol | ||||||
|  |  | ||||||
| from sanic.exceptions import ServerError | from sanic.exceptions import ServerError | ||||||
| from sanic.log import logger | from sanic.log import error_logger | ||||||
| from sanic.server import HttpProtocol | from sanic.server import HttpProtocol | ||||||
|  |  | ||||||
| from ..websockets.impl import WebsocketImplProtocol | from ..websockets.impl import WebsocketImplProtocol | ||||||
| @@ -104,7 +104,7 @@ class WebSocketProtocol(HttpProtocol): | |||||||
|                 max_size=self.websocket_max_size, |                 max_size=self.websocket_max_size, | ||||||
|                 subprotocols=subprotocols, |                 subprotocols=subprotocols, | ||||||
|                 state=OPEN, |                 state=OPEN, | ||||||
|                 logger=logger, |                 logger=error_logger, | ||||||
|             ) |             ) | ||||||
|             resp: "http11.Response" = ws_conn.accept(request) |             resp: "http11.Response" = ws_conn.accept(request) | ||||||
|         except Exception: |         except Exception: | ||||||
|   | |||||||
| @@ -80,7 +80,6 @@ class SignalRouter(BaseRouter): | |||||||
|             group_class=SignalGroup, |             group_class=SignalGroup, | ||||||
|             stacking=True, |             stacking=True, | ||||||
|         ) |         ) | ||||||
|         self.allow_fail_builtin = True |  | ||||||
|         self.ctx.loop = None |         self.ctx.loop = None | ||||||
|  |  | ||||||
|     def get(  # type: ignore |     def get(  # type: ignore | ||||||
| @@ -130,8 +129,7 @@ class SignalRouter(BaseRouter): | |||||||
|         try: |         try: | ||||||
|             group, handlers, params = self.get(event, condition=condition) |             group, handlers, params = self.get(event, condition=condition) | ||||||
|         except NotFound as e: |         except NotFound as e: | ||||||
|             is_reserved = event.split(".", 1)[0] in RESERVED_NAMESPACES |             if fail_not_found: | ||||||
|             if fail_not_found and (not is_reserved or self.allow_fail_builtin): |  | ||||||
|                 raise e |                 raise e | ||||||
|             else: |             else: | ||||||
|                 if self.ctx.app.debug and self.ctx.app.state.verbosity >= 1: |                 if self.ctx.app.debug and self.ctx.app.state.verbosity >= 1: | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								setup.py
									
									
									
									
									
								
							| @@ -84,17 +84,17 @@ ujson = "ujson>=1.35" + env_dependency | |||||||
| uvloop = "uvloop>=0.5.3" + env_dependency | uvloop = "uvloop>=0.5.3" + env_dependency | ||||||
| types_ujson = "types-ujson" + env_dependency | types_ujson = "types-ujson" + env_dependency | ||||||
| requirements = [ | requirements = [ | ||||||
|     "sanic-routing>=22.3.0,<22.6.0", |     "sanic-routing~=0.7", | ||||||
|     "httptools>=0.0.10", |     "httptools>=0.0.10", | ||||||
|     uvloop, |     uvloop, | ||||||
|     ujson, |     ujson, | ||||||
|     "aiofiles>=0.6.0", |     "aiofiles>=0.6.0", | ||||||
|     "websockets>=10.0", |     "websockets>=10.0", | ||||||
|     "multidict>=5.0,<7.0", |     "multidict>=5.0,<6.0", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| tests_require = [ | tests_require = [ | ||||||
|     "sanic-testing>=22.3.0", |     "sanic-testing>=0.7.0", | ||||||
|     "pytest==6.2.5", |     "pytest==6.2.5", | ||||||
|     "coverage==5.3", |     "coverage==5.3", | ||||||
|     "gunicorn==20.0.4", |     "gunicorn==20.0.4", | ||||||
|   | |||||||
| @@ -34,12 +34,3 @@ async def shutdown(app: Sanic, _): | |||||||
|  |  | ||||||
| def create_app(): | def create_app(): | ||||||
|     return app |     return app | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_app_with_args(args): |  | ||||||
|     try: |  | ||||||
|         print(f"foo={args.foo}") |  | ||||||
|     except AttributeError: |  | ||||||
|         print(f"module={args.module}") |  | ||||||
|  |  | ||||||
|     return app |  | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ from pathlib import Path | |||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
|  | from pyparsing import line | ||||||
| from sanic_routing import __version__ as __routing_version__ | from sanic_routing import __version__ as __routing_version__ | ||||||
|  |  | ||||||
| from sanic import __version__ | from sanic import __version__ | ||||||
| @@ -39,68 +40,26 @@ def read_app_info(lines): | |||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "appname,extra", |     "appname", | ||||||
|     ( |     ( | ||||||
|         ("fake.server.app", None), |         "fake.server.app", | ||||||
|         ("fake.server:create_app", "--factory"), |         "fake.server:app", | ||||||
|         ("fake.server.create_app()", None), |         "fake.server:create_app()", | ||||||
|  |         "fake.server.create_app()", | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
| def test_server_run(appname, extra): | def test_server_run(appname): | ||||||
|     command = ["sanic", appname] |     command = ["sanic", appname] | ||||||
|     if extra: |  | ||||||
|         command.append(extra) |  | ||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     firstline = lines[starting_line(lines) + 1] |     firstline = lines[starting_line(lines) + 1] | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert exitcode != 1 |     assert exitcode != 1 | ||||||
|  |     assert lines, error_message | ||||||
|     assert firstline == b"Goin' Fast @ http://127.0.0.1:8000" |     assert firstline == b"Goin' Fast @ http://127.0.0.1:8000" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_server_run_factory_with_args(): |  | ||||||
|     command = [ |  | ||||||
|         "sanic", |  | ||||||
|         "fake.server.create_app_with_args", |  | ||||||
|         "--factory", |  | ||||||
|     ] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     lines = out.split(b"\n") |  | ||||||
|  |  | ||||||
|     assert exitcode != 1, lines |  | ||||||
|     assert b"module=fake.server.create_app_with_args" in lines |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_server_run_factory_with_args_arbitrary(): |  | ||||||
|     command = [ |  | ||||||
|         "sanic", |  | ||||||
|         "fake.server.create_app_with_args", |  | ||||||
|         "--factory", |  | ||||||
|         "--foo=bar", |  | ||||||
|     ] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     lines = out.split(b"\n") |  | ||||||
|  |  | ||||||
|     assert exitcode != 1, lines |  | ||||||
|     assert b"foo=bar" in lines |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_error_with_function_as_instance_without_factory_arg(): |  | ||||||
|     command = ["sanic", "fake.server.create_app"] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     assert b"try: \nsanic fake.server.create_app --factory" in err |  | ||||||
|     assert exitcode != 1 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_error_with_path_as_instance_without_simple_arg(): |  | ||||||
|     command = ["sanic", "./fake/"] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     assert ( |  | ||||||
|         b"Please use --simple if you are passing a directory to sanic." in err |  | ||||||
|     ) |  | ||||||
|     assert exitcode != 1 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "cmd", |     "cmd", | ||||||
|     ( |     ( | ||||||
| @@ -124,6 +83,9 @@ def test_tls_options(cmd): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     assert exitcode != 1 |     assert exitcode != 1 | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|  |     assert lines, error_message | ||||||
|     firstline = lines[starting_line(lines) + 1] |     firstline = lines[starting_line(lines) + 1] | ||||||
|     assert firstline == b"Goin' Fast @ https://127.0.0.1:9999" |     assert firstline == b"Goin' Fast @ https://127.0.0.1:9999" | ||||||
|  |  | ||||||
| @@ -146,7 +108,9 @@ def test_tls_wrong_options(cmd): | |||||||
|     assert exitcode == 1 |     assert exitcode == 1 | ||||||
|     assert not out |     assert not out | ||||||
|     lines = err.decode().split("\n") |     lines = err.decode().split("\n") | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|  |     assert lines, error_message | ||||||
|     errmsg = lines[6] |     errmsg = lines[6] | ||||||
|     assert errmsg == "TLS certificates must be specified by either of:" |     assert errmsg == "TLS certificates must be specified by either of:" | ||||||
|  |  | ||||||
| @@ -163,9 +127,11 @@ def test_host_port_localhost(cmd): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     expected = b"Goin' Fast @ http://localhost:9999" |     expected = b"Goin' Fast @ http://localhost:9999" | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert exitcode != 1 |     assert exitcode != 1 | ||||||
|     assert expected in lines, f"Lines found: {lines}\nErr output: {err}" |     assert lines, error_message | ||||||
|  |     assert expected in lines, error_message | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -180,9 +146,11 @@ def test_host_port_ipv4(cmd): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     expected = b"Goin' Fast @ http://127.0.0.127:9999" |     expected = b"Goin' Fast @ http://127.0.0.127:9999" | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert exitcode != 1 |     assert exitcode != 1 | ||||||
|     assert expected in lines, f"Lines found: {lines}\nErr output: {err}" |     assert lines, error_message | ||||||
|  |     assert expected in lines, error_message | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -197,9 +165,11 @@ def test_host_port_ipv6_any(cmd): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     expected = b"Goin' Fast @ http://[::]:9999" |     expected = b"Goin' Fast @ http://[::]:9999" | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert exitcode != 1 |     assert exitcode != 1 | ||||||
|     assert expected in lines, f"Lines found: {lines}\nErr output: {err}" |     assert lines, error_message | ||||||
|  |     assert expected in lines, error_message | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -214,9 +184,11 @@ def test_host_port_ipv6_loopback(cmd): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     expected = b"Goin' Fast @ http://[::1]:9999" |     expected = b"Goin' Fast @ http://[::1]:9999" | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert exitcode != 1 |     assert exitcode != 1 | ||||||
|     assert expected in lines, f"Lines found: {lines}\nErr output: {err}" |     assert lines, error_message | ||||||
|  |     assert expected in lines, error_message | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -250,12 +222,12 @@ def test_debug(cmd): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     info = read_app_info(lines) |     info = read_app_info(lines) | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert info["debug"] is True, f"Lines found: {lines}\nErr output: {err}" |     assert info, error_message | ||||||
|     assert ( |     assert info["debug"] is True, error_message | ||||||
|         info["auto_reload"] is False |     assert info["auto_reload"] is False, error_message | ||||||
|     ), f"Lines found: {lines}\nErr output: {err}" |     assert "dev" not in info, error_message | ||||||
|     assert "dev" not in info, f"Lines found: {lines}\nErr output: {err}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("cmd", ("--dev", "-d")) | @pytest.mark.parametrize("cmd", ("--dev", "-d")) | ||||||
| @@ -264,11 +236,11 @@ def test_dev(cmd): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     info = read_app_info(lines) |     info = read_app_info(lines) | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert info["debug"] is True, f"Lines found: {lines}\nErr output: {err}" |     assert info, error_message | ||||||
|     assert ( |     assert info["debug"] is True, error_message | ||||||
|         info["auto_reload"] is True |     assert info["auto_reload"] is True, error_message | ||||||
|     ), f"Lines found: {lines}\nErr output: {err}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("cmd", ("--auto-reload", "-r")) | @pytest.mark.parametrize("cmd", ("--auto-reload", "-r")) | ||||||
| @@ -277,12 +249,12 @@ def test_auto_reload(cmd): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     info = read_app_info(lines) |     info = read_app_info(lines) | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert info["debug"] is False, f"Lines found: {lines}\nErr output: {err}" |     assert info, error_message | ||||||
|     assert ( |     assert info["debug"] is False, error_message | ||||||
|         info["auto_reload"] is True |     assert info["auto_reload"] is True, error_message | ||||||
|     ), f"Lines found: {lines}\nErr output: {err}" |     assert "dev" not in info, error_message | ||||||
|     assert "dev" not in info, f"Lines found: {lines}\nErr output: {err}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
| @@ -293,10 +265,10 @@ def test_access_logs(cmd, expected): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     info = read_app_info(lines) |     info = read_app_info(lines) | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert ( |     assert info, error_message | ||||||
|         info["access_log"] is expected |     assert info["access_log"] is expected, error_message | ||||||
|     ), f"Lines found: {lines}\nErr output: {err}" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("cmd", ("--version", "-v")) | @pytest.mark.parametrize("cmd", ("--version", "-v")) | ||||||
| @@ -320,7 +292,7 @@ def test_noisy_exceptions(cmd, expected): | |||||||
|     out, err, exitcode = capture(command) |     out, err, exitcode = capture(command) | ||||||
|     lines = out.split(b"\n") |     lines = out.split(b"\n") | ||||||
|     info = read_app_info(lines) |     info = read_app_info(lines) | ||||||
|  |     error_message = f"Lines found: {lines}\nErr output: {err}" | ||||||
|  |  | ||||||
|     assert ( |     assert info, error_message | ||||||
|         info["noisy_exceptions"] is expected |     assert info["noisy_exceptions"] is expected, error_message | ||||||
|     ), f"Lines found: {lines}\nErr output: {err}" |  | ||||||
|   | |||||||
| @@ -164,12 +164,11 @@ def test_raw_headers(app): | |||||||
|         }, |         }, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     assert b"Host: example.com" in request.raw_headers |     assert request.raw_headers == ( | ||||||
|     assert b"Accept: */*" in request.raw_headers |         b"Host: example.com\r\nAccept: */*\r\nAccept-Encoding: gzip, " | ||||||
|     assert b"Accept-Encoding: gzip, deflate" in request.raw_headers |         b"deflate\r\nConnection: keep-alive\r\nUser-Agent: " | ||||||
|     assert b"Connection: keep-alive" in request.raw_headers |         b"Sanic-Testing\r\nFOO: bar" | ||||||
|     assert b"User-Agent: Sanic-Testing" in request.raw_headers |     ) | ||||||
|     assert b"FOO: bar" in request.raw_headers |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_request_line(app): | def test_request_line(app): | ||||||
|   | |||||||
| @@ -58,36 +58,6 @@ def write_app(filename, **runargs): | |||||||
|     return text |     return text | ||||||
|  |  | ||||||
|  |  | ||||||
| def write_listener_app(filename, **runargs): |  | ||||||
|     start_text = secrets.token_urlsafe() |  | ||||||
|     stop_text = secrets.token_urlsafe() |  | ||||||
|     with open(filename, "w") as f: |  | ||||||
|         f.write( |  | ||||||
|             dedent( |  | ||||||
|                 f"""\ |  | ||||||
|             import os |  | ||||||
|             from sanic import Sanic |  | ||||||
|  |  | ||||||
|             app = Sanic(__name__) |  | ||||||
|  |  | ||||||
|             app.route("/")(lambda x: x) |  | ||||||
|  |  | ||||||
|             @app.reload_process_start |  | ||||||
|             async def reload_start(*_): |  | ||||||
|                 print("reload_start", os.getpid(), {start_text!r}) |  | ||||||
|  |  | ||||||
|             @app.reload_process_stop |  | ||||||
|             async def reload_stop(*_): |  | ||||||
|                 print("reload_stop", os.getpid(), {stop_text!r}) |  | ||||||
|  |  | ||||||
|             if __name__ == "__main__": |  | ||||||
|                 app.run(**{runargs!r}) |  | ||||||
|             """ |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|     return start_text, stop_text |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def write_json_config_app(filename, jsonfile, **runargs): | def write_json_config_app(filename, jsonfile, **runargs): | ||||||
|     with open(filename, "w") as f: |     with open(filename, "w") as f: | ||||||
|         f.write( |         f.write( | ||||||
| @@ -122,10 +92,10 @@ def write_file(filename): | |||||||
|     return text |     return text | ||||||
|  |  | ||||||
|  |  | ||||||
| def scanner(proc, trigger="complete"): | def scanner(proc): | ||||||
|     for line in proc.stdout: |     for line in proc.stdout: | ||||||
|         line = line.decode().strip() |         line = line.decode().strip() | ||||||
|         if line.startswith(trigger): |         if line.startswith("complete"): | ||||||
|             yield line |             yield line | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -138,7 +108,7 @@ argv = dict( | |||||||
|         "sanic", |         "sanic", | ||||||
|         "--port", |         "--port", | ||||||
|         "42204", |         "42204", | ||||||
|         "--auto-reload", |         "--debug", | ||||||
|         "reloader.app", |         "reloader.app", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
| @@ -148,7 +118,7 @@ argv = dict( | |||||||
|     "runargs, mode", |     "runargs, mode", | ||||||
|     [ |     [ | ||||||
|         (dict(port=42202, auto_reload=True), "script"), |         (dict(port=42202, auto_reload=True), "script"), | ||||||
|         (dict(port=42203, auto_reload=True), "module"), |         (dict(port=42203, debug=True), "module"), | ||||||
|         ({}, "sanic"), |         ({}, "sanic"), | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
| @@ -181,7 +151,7 @@ async def test_reloader_live(runargs, mode): | |||||||
|     "runargs, mode", |     "runargs, mode", | ||||||
|     [ |     [ | ||||||
|         (dict(port=42302, auto_reload=True), "script"), |         (dict(port=42302, auto_reload=True), "script"), | ||||||
|         (dict(port=42303, auto_reload=True), "module"), |         (dict(port=42303, debug=True), "module"), | ||||||
|         ({}, "sanic"), |         ({}, "sanic"), | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
| @@ -213,30 +183,3 @@ async def test_reloader_live_with_dir(runargs, mode): | |||||||
|             terminate(proc) |             terminate(proc) | ||||||
|             with suppress(TimeoutExpired): |             with suppress(TimeoutExpired): | ||||||
|                 proc.wait(timeout=3) |                 proc.wait(timeout=3) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_reload_listeners(): |  | ||||||
|     with TemporaryDirectory() as tmpdir: |  | ||||||
|         filename = os.path.join(tmpdir, "reloader.py") |  | ||||||
|         start_text, stop_text = write_listener_app( |  | ||||||
|             filename, port=42305, auto_reload=True |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         proc = Popen( |  | ||||||
|             argv["script"], cwd=tmpdir, stdout=PIPE, creationflags=flags |  | ||||||
|         ) |  | ||||||
|         try: |  | ||||||
|             timeout = Timer(TIMER_DELAY, terminate, [proc]) |  | ||||||
|             timeout.start() |  | ||||||
|             # Python apparently keeps using the old source sometimes if |  | ||||||
|             # we don't sleep before rewrite (pycache timestamp problem?) |  | ||||||
|             sleep(1) |  | ||||||
|             line = scanner(proc, "reload_start") |  | ||||||
|             assert start_text in next(line) |  | ||||||
|             line = scanner(proc, "reload_stop") |  | ||||||
|             assert stop_text in next(line) |  | ||||||
|         finally: |  | ||||||
|             timeout.cancel() |  | ||||||
|             terminate(proc) |  | ||||||
|             with suppress(TimeoutExpired): |  | ||||||
|                 proc.wait(timeout=3) |  | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ from uuid import UUID, uuid4 | |||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from sanic import Sanic, response | from sanic import Sanic, response | ||||||
| from sanic.exceptions import BadURL |  | ||||||
| from sanic.request import Request, uuid | from sanic.request import Request, uuid | ||||||
| from sanic.server import HttpProtocol | from sanic.server import HttpProtocol | ||||||
|  |  | ||||||
| @@ -177,17 +176,3 @@ def test_request_accept(): | |||||||
|         "text/x-dvi; q=0.8", |         "text/x-dvi; q=0.8", | ||||||
|         "text/plain; q=0.5", |         "text/plain; q=0.5", | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bad_url_parse(): |  | ||||||
|     message = "Bad URL: my.redacted-domain.com:443" |  | ||||||
|     with pytest.raises(BadURL, match=message): |  | ||||||
|         Request( |  | ||||||
|             b"my.redacted-domain.com:443", |  | ||||||
|             Mock(), |  | ||||||
|             Mock(), |  | ||||||
|             Mock(), |  | ||||||
|             Mock(), |  | ||||||
|             Mock(), |  | ||||||
|             Mock(), |  | ||||||
|         ) |  | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import asyncio | import asyncio | ||||||
| import re | import re | ||||||
|  |  | ||||||
|  | from unittest.mock import Mock | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from sanic_routing.exceptions import ( | from sanic_routing.exceptions import ( | ||||||
| @@ -254,7 +256,7 @@ def test_route_strict_slash(app): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_route_invalid_parameter_syntax(app): | def test_route_invalid_parameter_syntax(app): | ||||||
|     with pytest.raises(InvalidUsage): |     with pytest.raises(ValueError): | ||||||
|  |  | ||||||
|         @app.get("/get/<:str>", strict_slashes=True) |         @app.get("/get/<:str>", strict_slashes=True) | ||||||
|         def handler(request): |         def handler(request): | ||||||
|   | |||||||
| @@ -33,14 +33,6 @@ def create_listener(listener_name, in_list): | |||||||
|     return _listener |     return _listener | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_listener_no_loop(listener_name, in_list): |  | ||||||
|     async def _listener(app): |  | ||||||
|         print(f"DEBUG MESSAGE FOR PYTEST for {listener_name}") |  | ||||||
|         in_list.insert(0, app.name + listener_name) |  | ||||||
|  |  | ||||||
|     return _listener |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def start_stop_app(random_name_app, **run_kwargs): | def start_stop_app(random_name_app, **run_kwargs): | ||||||
|     def stop_on_alarm(signum, frame): |     def stop_on_alarm(signum, frame): | ||||||
|         random_name_app.stop() |         random_name_app.stop() | ||||||
| @@ -64,17 +56,6 @@ def test_single_listener(app, listener_name): | |||||||
|     assert app.name + listener_name == output.pop() |     assert app.name + listener_name == output.pop() | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipif_no_alarm |  | ||||||
| @pytest.mark.parametrize("listener_name", AVAILABLE_LISTENERS) |  | ||||||
| def test_single_listener_no_loop(app, listener_name): |  | ||||||
|     """Test that listeners on their own work""" |  | ||||||
|     output = [] |  | ||||||
|     # Register listener |  | ||||||
|     app.listener(listener_name)(create_listener_no_loop(listener_name, output)) |  | ||||||
|     start_stop_app(app) |  | ||||||
|     assert app.name + listener_name == output.pop() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipif_no_alarm | @skipif_no_alarm | ||||||
| @pytest.mark.parametrize("listener_name", AVAILABLE_LISTENERS) | @pytest.mark.parametrize("listener_name", AVAILABLE_LISTENERS) | ||||||
| def test_register_listener(app, listener_name): | def test_register_listener(app, listener_name): | ||||||
| @@ -218,16 +199,3 @@ async def test_missing_startup_raises_exception(app): | |||||||
|  |  | ||||||
|     with pytest.raises(SanicException): |     with pytest.raises(SanicException): | ||||||
|         await srv.before_start() |         await srv.before_start() | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_reload_listeners_attached(app): |  | ||||||
|     async def dummy(*_): |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     app.reload_process_start(dummy) |  | ||||||
|     app.reload_process_stop(dummy) |  | ||||||
|     app.listener("reload_process_start")(dummy) |  | ||||||
|     app.listener("reload_process_stop")(dummy) |  | ||||||
|  |  | ||||||
|     assert len(app.listeners.get("reload_process_start")) == 2 |  | ||||||
|     assert len(app.listeners.get("reload_process_stop")) == 2 |  | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ import pytest | |||||||
| from sanic_testing.testing import HOST, PORT | from sanic_testing.testing import HOST, PORT | ||||||
|  |  | ||||||
| from sanic.compat import ctrlc_workaround_for_windows | from sanic.compat import ctrlc_workaround_for_windows | ||||||
| from sanic.exceptions import InvalidUsage |  | ||||||
| from sanic.response import HTTPResponse | from sanic.response import HTTPResponse | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -109,17 +108,3 @@ def test_windows_workaround(): | |||||||
|     assert res == "OK" |     assert res == "OK" | ||||||
|     res = loop.run_until_complete(atest(True)) |     res = loop.run_until_complete(atest(True)) | ||||||
|     assert res == "OK" |     assert res == "OK" | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.skipif(os.name == "nt", reason="May hang CI on py38/windows") |  | ||||||
| def test_signals_with_invalid_invocation(app): |  | ||||||
|     """Test if sanic register fails with invalid invocation""" |  | ||||||
|  |  | ||||||
|     @app.route("/hello") |  | ||||||
|     async def hello_route(request): |  | ||||||
|         return HTTPResponse() |  | ||||||
|  |  | ||||||
|     with pytest.raises( |  | ||||||
|         InvalidUsage, match="Invalid event registration: Missing event name" |  | ||||||
|     ): |  | ||||||
|         app.listener(stop) |  | ||||||
|   | |||||||
| @@ -2,8 +2,6 @@ import logging | |||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from sanic_routing.exceptions import NotFound |  | ||||||
|  |  | ||||||
| from sanic.signals import RESERVED_NAMESPACES | from sanic.signals import RESERVED_NAMESPACES | ||||||
| from sanic.touchup import TouchUp | from sanic.touchup import TouchUp | ||||||
|  |  | ||||||
| @@ -30,50 +28,3 @@ async def test_ode_removes_dispatch_events(app, caplog, verbosity, result): | |||||||
|             ) |             ) | ||||||
|             in logs |             in logs | ||||||
|         ) is result |         ) is result | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("skip_it,result", ((False, True), (True, False))) |  | ||||||
| async def test_skip_touchup(app, caplog, skip_it, result): |  | ||||||
|     app.config.TOUCHUP = not skip_it |  | ||||||
|     with caplog.at_level(logging.DEBUG, logger="sanic.root"): |  | ||||||
|         app.state.verbosity = 2 |  | ||||||
|         await app._startup() |  | ||||||
|     assert app.signal_router.allow_fail_builtin is (not skip_it) |  | ||||||
|     logs = caplog.record_tuples |  | ||||||
|  |  | ||||||
|     for signal in RESERVED_NAMESPACES["http"]: |  | ||||||
|         assert ( |  | ||||||
|             ( |  | ||||||
|                 "sanic.root", |  | ||||||
|                 logging.DEBUG, |  | ||||||
|                 f"Disabling event: {signal}", |  | ||||||
|             ) |  | ||||||
|             in logs |  | ||||||
|         ) is result |  | ||||||
|     not_found_exceptions = 0 |  | ||||||
|     # Skip-touchup disables NotFound exceptions on the dispatcher |  | ||||||
|     for signal in RESERVED_NAMESPACES["http"]: |  | ||||||
|         try: |  | ||||||
|             await app.dispatch(event=signal, inline=True) |  | ||||||
|         except NotFound: |  | ||||||
|             not_found_exceptions += 1 |  | ||||||
|     assert (not_found_exceptions > 0) is result |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("skip_it,result", ((False, True), (True, True))) |  | ||||||
| async def test_skip_touchup_non_reserved(app, caplog, skip_it, result): |  | ||||||
|     app.config.TOUCHUP = not skip_it |  | ||||||
|  |  | ||||||
|     @app.signal("foo.bar.one") |  | ||||||
|     def sync_signal(*_): |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     await app._startup() |  | ||||||
|     assert app.signal_router.allow_fail_builtin is (not skip_it) |  | ||||||
|     not_found_exception = False |  | ||||||
|     # Skip-touchup doesn't disable NotFound exceptions for user-defined signals |  | ||||||
|     try: |  | ||||||
|         await app.dispatch(event="foo.baz.two", inline=True) |  | ||||||
|     except NotFound: |  | ||||||
|         not_found_exception = True |  | ||||||
|     assert not_found_exception is result |  | ||||||
|   | |||||||
| @@ -199,7 +199,7 @@ async def test_zero_downtime(): | |||||||
|         for _ in range(40): |         for _ in range(40): | ||||||
|             async with httpx.AsyncClient(transport=transport) as client: |             async with httpx.AsyncClient(transport=transport) as client: | ||||||
|                 r = await client.get("http://localhost/sleep/0.1") |                 r = await client.get("http://localhost/sleep/0.1") | ||||||
|                 assert r.status_code == 200, r.text |                 assert r.status_code == 200, r.content | ||||||
|                 assert r.text == "Slept 0.1 seconds.\n" |                 assert r.text == "Slept 0.1 seconds.\n" | ||||||
|  |  | ||||||
|     def spawn(): |     def spawn(): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user