Compare commits
	
		
			5 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c4da66bf1f | ||
|   | d50d3b8448 | ||
|   | 313f97ac77 | ||
|   | a23547d73b | ||
|   | 34d1dee407 | 
							
								
								
									
										8
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,9 +2,13 @@ name: "CodeQL" | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ main ] | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|   pull_request: | ||||
|     branches: [ main ] | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|   schedule: | ||||
|     - cron: '25 16 * * 0' | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/coverage.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/coverage.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     tags: | ||||
|       - "!*" # Do not execute on tags | ||||
|   pull_request: | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/pr-bandit.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-bandit.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/pr-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-docs.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/pr-linter.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-linter.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/pr-python310.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-python310.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/pr-python37.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-python37.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/pr-python38.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-python38.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/pr-python39.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-python39.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   | ||||
							
								
								
									
										3
									
								
								.github/workflows/pr-type-check.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/pr-type-check.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
| @@ -15,7 +16,7 @@ jobs: | ||||
|       matrix: | ||||
|         os: [ubuntu-latest] | ||||
|         config: | ||||
|           - { python-version: 3.7, tox-env: type-checking} | ||||
|           # - { python-version: 3.7, tox-env: type-checking} | ||||
|           - { python-version: 3.8, tox-env: type-checking} | ||||
|           - { python-version: 3.9, tox-env: type-checking} | ||||
|           - { python-version: "3.10", tox-env: type-checking} | ||||
|   | ||||
							
								
								
									
										1
									
								
								.github/workflows/pr-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-windows.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   | ||||
| @@ -1,3 +1,9 @@ | ||||
| ## Version 21.12.1 | ||||
|  | ||||
| - [#2349](https://github.com/sanic-org/sanic/pull/2349) Only display MOTD on startup | ||||
| - [#2354](https://github.com/sanic-org/sanic/pull/2354) Ignore name argument in Python 3.7 | ||||
| - [#2355](https://github.com/sanic-org/sanic/pull/2355) Add config.update support for all config values | ||||
|  | ||||
| ## Version 21.12.0 | ||||
|  | ||||
| ### Features | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| __version__ = "21.12.0" | ||||
| __version__ = "21.12.1" | ||||
|   | ||||
							
								
								
									
										58
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -1552,10 +1552,19 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): | ||||
|         name: Optional[str] = None, | ||||
|         register: bool = True, | ||||
|     ) -> Task: | ||||
|         prepped = cls._prep_task(task, app, loop) | ||||
|         task = loop.create_task(prepped, name=name) | ||||
|         if not isinstance(task, Future): | ||||
|             prepped = cls._prep_task(task, app, loop) | ||||
|             if sys.version_info < (3, 8): | ||||
|                 if name: | ||||
|                     error_logger.warning( | ||||
|                         "Cannot set a name for a task when using Python 3.7. " | ||||
|                         "Your task will be created without a name." | ||||
|                     ) | ||||
|                 task = loop.create_task(prepped) | ||||
|             else: | ||||
|                 task = loop.create_task(prepped, name=name) | ||||
|  | ||||
|         if name and register: | ||||
|         if name and register and sys.version_info > (3, 7): | ||||
|             app._task_registry[name] = task | ||||
|  | ||||
|         return task | ||||
| @@ -1617,10 +1626,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): | ||||
|     def get_task( | ||||
|         self, name: str, *, raise_exception: bool = True | ||||
|     ) -> Optional[Task]: | ||||
|         if sys.version_info == (3, 7): | ||||
|             raise RuntimeError( | ||||
|                 "This feature is only supported on using Python 3.8+." | ||||
|         if sys.version_info < (3, 8): | ||||
|             error_logger.warning( | ||||
|                 "This feature (get_task) is only supported on using " | ||||
|                 "Python 3.8+." | ||||
|             ) | ||||
|             return | ||||
|         try: | ||||
|             return self._task_registry[name] | ||||
|         except KeyError: | ||||
| @@ -1637,10 +1648,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): | ||||
|         *, | ||||
|         raise_exception: bool = True, | ||||
|     ) -> None: | ||||
|         if sys.version_info == (3, 7): | ||||
|             raise RuntimeError( | ||||
|                 "This feature is only supported on using Python 3.8+." | ||||
|         if sys.version_info < (3, 8): | ||||
|             error_logger.warning( | ||||
|                 "This feature (cancel_task) is only supported on using " | ||||
|                 "Python 3.8+." | ||||
|             ) | ||||
|             return | ||||
|         task = self.get_task(name, raise_exception=raise_exception) | ||||
|         if task and not task.cancelled(): | ||||
|             args: Tuple[str, ...] = () | ||||
| @@ -1659,10 +1672,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): | ||||
|                 ... | ||||
|  | ||||
|     def purge_tasks(self): | ||||
|         if sys.version_info == (3, 7): | ||||
|             raise RuntimeError( | ||||
|                 "This feature is only supported on using Python 3.8+." | ||||
|         if sys.version_info < (3, 8): | ||||
|             error_logger.warning( | ||||
|                 "This feature (purge_tasks) is only supported on using " | ||||
|                 "Python 3.8+." | ||||
|             ) | ||||
|             return | ||||
|         for task in self.tasks: | ||||
|             if task.done() or task.cancelled(): | ||||
|                 name = task.get_name() | ||||
| @@ -1675,10 +1690,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): | ||||
|     def shutdown_tasks( | ||||
|         self, timeout: Optional[float] = None, increment: float = 0.1 | ||||
|     ): | ||||
|         if sys.version_info == (3, 7): | ||||
|             raise RuntimeError( | ||||
|                 "This feature is only supported on using Python 3.8+." | ||||
|         if sys.version_info < (3, 8): | ||||
|             error_logger.warning( | ||||
|                 "This feature (shutdown_tasks) is only supported on using " | ||||
|                 "Python 3.8+." | ||||
|             ) | ||||
|             return | ||||
|         for task in self.tasks: | ||||
|             task.cancel() | ||||
|  | ||||
| @@ -1692,10 +1709,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): | ||||
|  | ||||
|     @property | ||||
|     def tasks(self): | ||||
|         if sys.version_info == (3, 7): | ||||
|             raise RuntimeError( | ||||
|                 "This feature is only supported on using Python 3.8+." | ||||
|         if sys.version_info < (3, 8): | ||||
|             error_logger.warning( | ||||
|                 "This feature (tasks) is only supported on using " | ||||
|                 "Python 3.8+." | ||||
|             ) | ||||
|             return | ||||
|         return iter(self._task_registry.values()) | ||||
|  | ||||
|     # -------------------------------------------------------------------- # | ||||
| @@ -1709,7 +1728,8 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): | ||||
|         details: https://asgi.readthedocs.io/en/latest | ||||
|         """ | ||||
|         self.asgi = True | ||||
|         self.motd("") | ||||
|         if scope["type"] == "lifespan": | ||||
|             self.motd("") | ||||
|         self._asgi_app = await ASGIApp.create(self, scope, receive, send) | ||||
|         asgi_app = self._asgi_app | ||||
|         await asgi_app() | ||||
|   | ||||
| @@ -124,22 +124,27 @@ class Config(dict, metaclass=DescriptorMeta): | ||||
|             raise AttributeError(f"Config has no '{ke.args[0]}'") | ||||
|  | ||||
|     def __setattr__(self, attr, value) -> None: | ||||
|         if attr in self.__class__.__setters__: | ||||
|             try: | ||||
|                 super().__setattr__(attr, value) | ||||
|             except AttributeError: | ||||
|                 ... | ||||
|             else: | ||||
|                 return None | ||||
|         self.update({attr: value}) | ||||
|  | ||||
|     def __setitem__(self, attr, value) -> None: | ||||
|         self.update({attr: value}) | ||||
|  | ||||
|     def update(self, *other, **kwargs) -> None: | ||||
|         other_mapping = {k: v for item in other for k, v in dict(item).items()} | ||||
|         super().update(*other, **kwargs) | ||||
|         for attr, value in {**other_mapping, **kwargs}.items(): | ||||
|         kwargs.update({k: v for item in other for k, v in dict(item).items()}) | ||||
|         setters: Dict[str, Any] = { | ||||
|             k: kwargs.pop(k) | ||||
|             for k in {**kwargs}.keys() | ||||
|             if k in self.__class__.__setters__ | ||||
|         } | ||||
|  | ||||
|         for key, value in setters.items(): | ||||
|             try: | ||||
|                 super().__setattr__(key, value) | ||||
|             except AttributeError: | ||||
|                 ... | ||||
|  | ||||
|         super().update(**kwargs) | ||||
|         for attr, value in {**setters, **kwargs}.items(): | ||||
|             self._post_set(attr, value) | ||||
|  | ||||
|     def _post_set(self, attr, value) -> None: | ||||
|   | ||||
| @@ -5,7 +5,7 @@ from os import environ | ||||
| from pathlib import Path | ||||
| from tempfile import TemporaryDirectory | ||||
| from textwrap import dedent | ||||
| from unittest.mock import Mock | ||||
| from unittest.mock import Mock, call | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| @@ -385,5 +385,24 @@ def test_config_set_methods(app, monkeypatch): | ||||
|     post_set.assert_called_once_with("FOO", 5) | ||||
|     post_set.reset_mock() | ||||
|  | ||||
|     app.config.update_config({"FOO": 6}) | ||||
|     post_set.assert_called_once_with("FOO", 6) | ||||
|     app.config.update({"FOO": 6}, {"BAR": 7}) | ||||
|     post_set.assert_has_calls( | ||||
|         calls=[ | ||||
|             call("FOO", 6), | ||||
|             call("BAR", 7), | ||||
|         ] | ||||
|     ) | ||||
|     post_set.reset_mock() | ||||
|  | ||||
|     app.config.update({"FOO": 8}, BAR=9) | ||||
|     post_set.assert_has_calls( | ||||
|         calls=[ | ||||
|             call("FOO", 8), | ||||
|             call("BAR", 9), | ||||
|         ], | ||||
|         any_order=True, | ||||
|     ) | ||||
|     post_set.reset_mock() | ||||
|  | ||||
|     app.config.update_config({"FOO": 10}) | ||||
|     post_set.assert_called_once_with("FOO", 10) | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import asyncio | ||||
| import sys | ||||
|  | ||||
| from threading import Event | ||||
| from unittest.mock import Mock | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| @@ -77,6 +78,25 @@ def test_create_named_task(app): | ||||
|     app.run() | ||||
|  | ||||
|  | ||||
| def test_named_task_called(app): | ||||
|     e = Event() | ||||
|  | ||||
|     async def coro(): | ||||
|         e.set() | ||||
|  | ||||
|     @app.route("/") | ||||
|     async def isset(request): | ||||
|         await asyncio.sleep(0.05) | ||||
|         return text(str(e.is_set())) | ||||
|  | ||||
|     @app.before_server_start | ||||
|     async def setup(app, _): | ||||
|         app.add_task(coro, name="dummy_task") | ||||
|  | ||||
|     request, response = app.test_client.get("/") | ||||
|     assert response.body == b"True" | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif(sys.version_info < (3, 8), reason="Not supported in 3.7") | ||||
| def test_create_named_task_fails_outside_app(app): | ||||
|     async def dummy(): | ||||
|   | ||||
| @@ -334,6 +334,22 @@ def test_config_fallback_before_and_after_startup(app): | ||||
|     assert response.content_type == "text/plain; charset=utf-8" | ||||
|  | ||||
|  | ||||
| def test_config_fallback_using_update_dict(app): | ||||
|     app.config.update({"FALLBACK_ERROR_FORMAT": "text"}) | ||||
|  | ||||
|     _, response = app.test_client.get("/error") | ||||
|     assert response.status == 500 | ||||
|     assert response.content_type == "text/plain; charset=utf-8" | ||||
|  | ||||
|  | ||||
| def test_config_fallback_using_update_kwarg(app): | ||||
|     app.config.update(FALLBACK_ERROR_FORMAT="text") | ||||
|  | ||||
|     _, response = app.test_client.get("/error") | ||||
|     assert response.status == 500 | ||||
|     assert response.content_type == "text/plain; charset=utf-8" | ||||
|  | ||||
|  | ||||
| def test_config_fallback_bad_value(app): | ||||
|     message = "Unknown format: fake" | ||||
|     with pytest.raises(SanicException, match=message): | ||||
|   | ||||
| @@ -62,19 +62,15 @@ def test_streaming_body_requests(app): | ||||
|  | ||||
|     data = ["hello", "world"] | ||||
|  | ||||
|     class Data(AsyncByteStream): | ||||
|         def __init__(self, data): | ||||
|             self.data = data | ||||
|  | ||||
|         async def __aiter__(self): | ||||
|             for value in self.data: | ||||
|                 yield value.encode("utf-8") | ||||
|  | ||||
|     client = ReusableClient(app, port=1234) | ||||
|  | ||||
|     async def stream(data): | ||||
|         for value in data: | ||||
|             yield value.encode("utf-8") | ||||
|  | ||||
|     with client: | ||||
|         _, response1 = client.post("/", data=Data(data)) | ||||
|         _, response2 = client.post("/", data=Data(data)) | ||||
|         _, response1 = client.post("/", data=stream(data)) | ||||
|         _, response2 = client.post("/", data=stream(data)) | ||||
|  | ||||
|     assert response1.status == response2.status == 200 | ||||
|     assert response1.json["data"] == response2.json["data"] == data | ||||
|   | ||||
| @@ -4,8 +4,8 @@ from unittest.mock import Mock, patch | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from sanic.server import loop | ||||
| from sanic.compat import OS_IS_WINDOWS, UVLOOP_INSTALLED | ||||
| from sanic.server import loop | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user