From d45fe5be2893bc4c94446c5fe41673cb005bdbf0 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 10 Dec 2020 10:02:02 +0200 Subject: [PATCH 1/5] Add app level registry --- sanic/app.py | 42 +++++++++++++++++++++++++++++++++--------- tests/conftest.py | 1 + tests/test_app.py | 30 ++++++++++++++++++++---------- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index 047879ca94..6f4454edeb 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -7,7 +7,7 @@ from asyncio import CancelledError, Protocol, ensure_future, get_event_loop from collections import defaultdict, deque from functools import partial -from inspect import getmodulename, isawaitable, signature, stack +from inspect import isawaitable, signature from socket import socket from ssl import Purpose, SSLContext, create_default_context from traceback import format_exc @@ -38,6 +38,9 @@ class Sanic: + _app_registry: Dict[str, "Sanic"] = {} + test_mode = False + def __init__( self, name=None, @@ -52,15 +55,10 @@ def __init__( # Get name from previous stack frame if name is None: - warnings.warn( - "Sanic(name=None) is deprecated and None value support " - "for `name` will be removed in the next release. " + raise SanicException( + "Sanic instance cannot be unnamed. " "Please use Sanic(name='your_application_name') instead.", - DeprecationWarning, - stacklevel=2, ) - frame_records = stack()[1] - name = getmodulename(frame_records[1]) # logging if configure_logging: @@ -90,7 +88,8 @@ def __init__( self.named_response_middleware = {} # Register alternative method names self.go_fast = self.run - self.test_mode = False + + self.__class__.register_app(self) @property def loop(self): @@ -1453,9 +1452,34 @@ async def __call__(self, scope, receive, send): # -------------------------------------------------------------------- # # Configuration # -------------------------------------------------------------------- # + def update_config(self, config: Union[bytes, str, dict, Any]): """Update app.config. Please refer to config.py::Config.update_config for documentation.""" self.config.update_config(config) + + # -------------------------------------------------------------------- # + # Class methods + # -------------------------------------------------------------------- # + + @classmethod + def register_app(cls, app: "Sanic") -> None: + """Register a Sanic instance""" + if not isinstance(app, cls): + raise SanicException("Registered app must be an instance of Sanic") + + name = app.name + if name in cls._app_registry and not cls.test_mode: + raise SanicException(f'Sanic app name "{name}" already in use.') + + cls._app_registry[name] = app + + @classmethod + def get_app(cls, name: str) -> "Sanic": + """Retrieve an instantiated Sanic instance""" + try: + return cls._app_registry[name] + except KeyError: + raise SanicException(f'Sanic app name "{name}" not found.') diff --git a/tests/conftest.py b/tests/conftest.py index 459638633b..3d57ac733d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ random.seed("Pack my box with five dozen liquor jugs.") +Sanic.test_mode = True if sys.platform in ["win32", "cygwin"]: collect_ignore = ["test_worker.py"] diff --git a/tests/test_app.py b/tests/test_app.py index ad85c3c2f7..1027ae9e52 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -258,7 +258,7 @@ def handler(request): def test_app_name_required(): - with pytest.deprecated_call(): + with pytest.raises(SanicException): Sanic() @@ -274,14 +274,24 @@ def handler(request): assert response.status == 200 -# @pytest.mark.asyncio -# async def test_app_has_test_mode_async(): -# app = Sanic("test") +def test_app_registry(): + instance = Sanic("test") + assert Sanic._app_registry["test"] is instance -# @app.get("/") -# async def handler(request): -# assert request.app.test_mode -# return text("test") -# _, response = await app.asgi_client.get("/") -# assert response.status == 200 +def test_app_registry_wrong_type(): + with pytest.raises(SanicException): + Sanic.register_app(1) + + +def test_app_registry_name_reuse(): + Sanic("test") + Sanic.test_mode = False + with pytest.raises(SanicException): + Sanic("test") + Sanic.test_mode = True + + +def test_app_registry_retrieval(): + instance = Sanic("test") + assert Sanic.get_app("test") is instance From bfec8146e3716b023eb85b73b849783f62f2469c Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 10 Dec 2020 10:08:02 +0200 Subject: [PATCH 2/5] Add documentation for app registry --- docs/sanic/getting_started.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/sanic/getting_started.rst b/docs/sanic/getting_started.rst index 97613360f3..66bacb77c0 100644 --- a/docs/sanic/getting_started.rst +++ b/docs/sanic/getting_started.rst @@ -60,3 +60,21 @@ Open the address `http://0.0.0.0:8000 `_ in your web browse the message *Hello world!*. You now have a working Sanic server! + +5. Application registry +----------------------- + +When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible. + +.. code-block:: python + + # ./path/to/server.py + from sanic import Sanic + + app = Sanic("my_awesome_server") + + # ./path/to/somewhere_else.py + from sanic import Sanic + + app = Sanic.get_app("my_awesome_server") + From 2a7d2fc6ea15b35426c291a46a34757670eacb48 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Fri, 25 Dec 2020 12:18:00 +0200 Subject: [PATCH 3/5] Remove unused import --- sanic/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sanic/app.py b/sanic/app.py index 3c3b73da08..99fd231dbd 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -2,7 +2,6 @@ import logging.config import os import re -import warnings from asyncio import CancelledError, Protocol, ensure_future, get_event_loop from collections import defaultdict, deque From 92c417f372d3adbab23d1bc05abd9f8243236deb Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Mon, 28 Dec 2020 21:59:37 +0200 Subject: [PATCH 4/5] Add force_create keyword to Sanic.get_app --- sanic/app.py | 4 +++- tests/test_app.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/sanic/app.py b/sanic/app.py index 99fd231dbd..81cd66d170 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -1424,9 +1424,11 @@ def register_app(cls, app: "Sanic") -> None: cls._app_registry[name] = app @classmethod - def get_app(cls, name: str) -> "Sanic": + def get_app(cls, name: str, *, force_create: bool = False) -> "Sanic": """Retrieve an instantiated Sanic instance""" try: return cls._app_registry[name] except KeyError: + if force_create: + return cls(name) raise SanicException(f'Sanic app name "{name}" not found.') diff --git a/tests/test_app.py b/tests/test_app.py index 1027ae9e52..7527b1c202 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -295,3 +295,14 @@ def test_app_registry_name_reuse(): def test_app_registry_retrieval(): instance = Sanic("test") assert Sanic.get_app("test") is instance + + +def test_get_app_does_not_exist(): + with pytest.raises(SanicException): + Sanic.get_app("does-not-exist") + + +def test_get_app_does_not_exist_force_create(): + assert isinstance( + Sanic.get_app("does-not-exist", force_create=True), Sanic + ) From 54f3a2ce87b2cf85c6f3b68d12bbae19b75622a3 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Mon, 28 Dec 2020 22:27:48 +0200 Subject: [PATCH 5/5] Add force_commit to docs --- docs/sanic/getting_started.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/sanic/getting_started.rst b/docs/sanic/getting_started.rst index 66bacb77c0..a781e43ab7 100644 --- a/docs/sanic/getting_started.rst +++ b/docs/sanic/getting_started.rst @@ -78,3 +78,8 @@ When you instantiate a Sanic instance, that can be retrieved at a later time fro app = Sanic.get_app("my_awesome_server") +If you call ``Sanic.get_app("non-existing")`` on an app that does not exist, it will raise ``SanicException`` by default. You can, instead, force the method to return a new instance of ``Sanic`` with that name: + +.. code-block:: python + + app = Sanic.get_app("my_awesome_server", force_create=True)