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
This commit is contained in:
Thomas Grainger 2021-06-09 10:05:56 +01:00 committed by GitHub
parent 141be0028d
commit 48f8b37b74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 33 additions and 4 deletions

View File

@ -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 () -> <Sanic app> 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:

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -39,7 +39,8 @@ commands =
[testenv:type-checking]
deps =
mypy
mypy>=0.901
types-ujson
commands =
mypy sanic