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:
parent
141be0028d
commit
48f8b37b74
|
@ -75,6 +75,14 @@ def main():
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Watch source directory for file changes and reload on changes",
|
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(
|
parser.add_argument(
|
||||||
"-v",
|
"-v",
|
||||||
"--version",
|
"--version",
|
||||||
|
@ -97,13 +105,20 @@ def main():
|
||||||
delimiter = ":" if ":" in args.module else "."
|
delimiter = ":" if ":" in args.module else "."
|
||||||
module_name, app_name = args.module.rsplit(delimiter, 1)
|
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)
|
module = import_module(module_name)
|
||||||
app = getattr(module, app_name, None)
|
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):
|
if not isinstance(app, Sanic):
|
||||||
raise ValueError(
|
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?"
|
f"Perhaps you meant {args.module}.app?"
|
||||||
)
|
)
|
||||||
if args.cert is not None or args.key is not None:
|
if args.cert is not None or args.key is not None:
|
||||||
|
|
|
@ -105,6 +105,7 @@ def load_module_from_file_location(
|
||||||
_mod_spec = spec_from_file_location(
|
_mod_spec = spec_from_file_location(
|
||||||
name, location, *args, **kwargs
|
name, location, *args, **kwargs
|
||||||
)
|
)
|
||||||
|
assert _mod_spec is not None # type assertion for mypy
|
||||||
module = module_from_spec(_mod_spec)
|
module = module_from_spec(_mod_spec)
|
||||||
_mod_spec.loader.exec_module(module) # type: ignore
|
_mod_spec.loader.exec_module(module) # type: ignore
|
||||||
|
|
||||||
|
|
|
@ -30,3 +30,7 @@ async def app_info_dump(app: Sanic, _):
|
||||||
@app.after_server_start
|
@app.after_server_start
|
||||||
async def shutdown(app: Sanic, _):
|
async def shutdown(app: Sanic, _):
|
||||||
app.stop()
|
app.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
return app
|
||||||
|
|
|
@ -26,7 +26,15 @@ def capture(command):
|
||||||
return out, err, proc.returncode
|
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):
|
def test_server_run(appname):
|
||||||
command = ["sanic", appname]
|
command = ["sanic", appname]
|
||||||
out, err, exitcode = capture(command)
|
out, err, exitcode = capture(command)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user