From 48f8b37b749d8f373db8c25a2de91243b7b3e282 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 9 Jun 2021 10:05:56 +0100 Subject: [PATCH] support app factory patten in CLI (#2157) * support app factory patten in CLI * Update sanic/__main__.py * fix mypy errors * Update mypy further * Update sanic/utils.py * Update sanic/utils.py * support hypercorn/gunicorn style 'asgi.app:create_app()' * add test for app factory --- sanic/__main__.py | 19 +++++++++++++++++-- sanic/utils.py | 1 + tests/fake/server.py | 4 ++++ tests/test_cli.py | 10 +++++++++- tox.ini | 3 ++- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/sanic/__main__.py b/sanic/__main__.py index 53ef5e64..cf392510 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -75,6 +75,14 @@ def main(): action="store_true", help="Watch source directory for file changes and reload on changes", ) + parser.add_argument( + "--factory", + action="store_true", + help=( + "Treat app as an application factory, " + "i.e. a () -> callable." + ), + ) parser.add_argument( "-v", "--version", @@ -97,13 +105,20 @@ def main(): delimiter = ":" if ":" in args.module else "." module_name, app_name = args.module.rsplit(delimiter, 1) + if app_name.endswith("()"): + args.factory = True + app_name = app_name[:-2] + module = import_module(module_name) app = getattr(module, app_name, None) - app_name = type(app).__name__ + if args.factory: + app = app() + + app_type_name = type(app).__name__ if not isinstance(app, Sanic): raise ValueError( - f"Module is not a Sanic app, it is a {app_name}. " + f"Module is not a Sanic app, it is a {app_type_name}. " f"Perhaps you meant {args.module}.app?" ) if args.cert is not None or args.key is not None: diff --git a/sanic/utils.py b/sanic/utils.py index 519d3c29..ef91ec9d 100644 --- a/sanic/utils.py +++ b/sanic/utils.py @@ -105,6 +105,7 @@ def load_module_from_file_location( _mod_spec = spec_from_file_location( name, location, *args, **kwargs ) + assert _mod_spec is not None # type assertion for mypy module = module_from_spec(_mod_spec) _mod_spec.loader.exec_module(module) # type: ignore diff --git a/tests/fake/server.py b/tests/fake/server.py index 520f5123..9c28f54a 100644 --- a/tests/fake/server.py +++ b/tests/fake/server.py @@ -30,3 +30,7 @@ async def app_info_dump(app: Sanic, _): @app.after_server_start async def shutdown(app: Sanic, _): app.stop() + + +def create_app(): + return app diff --git a/tests/test_cli.py b/tests/test_cli.py index 3ff4d333..df2d0e61 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -26,7 +26,15 @@ def capture(command): return out, err, proc.returncode -@pytest.mark.parametrize("appname", ("fake.server.app", "fake.server:app")) +@pytest.mark.parametrize( + "appname", + ( + "fake.server.app", + "fake.server:app", + "fake.server:create_app()", + "fake.server.create_app()", + ) +) def test_server_run(appname): command = ["sanic", appname] out, err, exitcode = capture(command) diff --git a/tox.ini b/tox.ini index 825c742b..25b29346 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,8 @@ commands = [testenv:type-checking] deps = - mypy + mypy>=0.901 + types-ujson commands = mypy sanic