Small improvements to CLI experience (#2136)
* Small improvements to CLI experience * Add tests * Add test server for cli testing * Add LOGO logging to reloader and some additional context to logging debug * Cleanup tests
This commit is contained in:
parent
3a6fac7d59
commit
72a745bfd5
|
@ -5,6 +5,8 @@ from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
|||
from importlib import import_module
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sanic_routing import __version__ as __routing_version__ # type: ignore
|
||||
|
||||
from sanic import __version__
|
||||
from sanic.app import Sanic
|
||||
from sanic.config import BASE_LOGO
|
||||
|
@ -65,15 +67,22 @@ def main():
|
|||
default=1,
|
||||
help="number of worker processes [default 1]",
|
||||
)
|
||||
parser.add_argument("--debug", dest="debug", action="store_true")
|
||||
parser.add_bool_arguments(
|
||||
"--access-logs", dest="access_log", help="display access logs"
|
||||
parser.add_argument("-d", "--debug", dest="debug", action="store_true")
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--auto-reload",
|
||||
dest="auto_reload",
|
||||
action="store_true",
|
||||
help="Watch source directory for file changes and reload on changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"Sanic {__version__}",
|
||||
version=f"Sanic {__version__}; Routing {__routing_version__}",
|
||||
)
|
||||
parser.add_bool_arguments(
|
||||
"--access-logs", dest="access_log", help="display access logs"
|
||||
)
|
||||
parser.add_argument(
|
||||
"module", help="path to your Sanic app. Example: path.to.server:app"
|
||||
|
@ -85,12 +94,8 @@ def main():
|
|||
if module_path not in sys.path:
|
||||
sys.path.append(module_path)
|
||||
|
||||
if ":" in args.module:
|
||||
module_name, app_name = args.module.rsplit(":", 1)
|
||||
else:
|
||||
module_parts = args.module.split(".")
|
||||
module_name = ".".join(module_parts[:-1])
|
||||
app_name = module_parts[-1]
|
||||
delimiter = ":" if ":" in args.module else "."
|
||||
module_name, app_name = args.module.rsplit(delimiter, 1)
|
||||
|
||||
module = import_module(module_name)
|
||||
app = getattr(module, app_name, None)
|
||||
|
@ -102,28 +107,34 @@ def main():
|
|||
f"Perhaps you meant {args.module}.app?"
|
||||
)
|
||||
if args.cert is not None or args.key is not None:
|
||||
ssl = {
|
||||
ssl: Optional[Dict[str, Any]] = {
|
||||
"cert": args.cert,
|
||||
"key": args.key,
|
||||
} # type: Optional[Dict[str, Any]]
|
||||
}
|
||||
else:
|
||||
ssl = None
|
||||
|
||||
app.run(
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
unix=args.unix,
|
||||
workers=args.workers,
|
||||
debug=args.debug,
|
||||
access_log=args.access_log,
|
||||
ssl=ssl,
|
||||
)
|
||||
kwargs = {
|
||||
"host": args.host,
|
||||
"port": args.port,
|
||||
"unix": args.unix,
|
||||
"workers": args.workers,
|
||||
"debug": args.debug,
|
||||
"access_log": args.access_log,
|
||||
"ssl": ssl,
|
||||
}
|
||||
if args.auto_reload:
|
||||
kwargs["auto_reload"] = True
|
||||
app.run(**kwargs)
|
||||
except ImportError as e:
|
||||
if module_name.startswith(e.name):
|
||||
error_logger.error(
|
||||
f"No module named {e.name} found.\n"
|
||||
f" Example File: project/sanic_server.py -> app\n"
|
||||
f" Example Module: project.sanic_server.app"
|
||||
" Example File: project/sanic_server.py -> app\n"
|
||||
" Example Module: project.sanic_server.app"
|
||||
)
|
||||
else:
|
||||
raise e
|
||||
except ValueError:
|
||||
error_logger.exception("Failed to run app")
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ class Sanic(BaseSanic):
|
|||
"_future_signals",
|
||||
"_test_client",
|
||||
"_test_manager",
|
||||
"auto_reload",
|
||||
"asgi",
|
||||
"blueprints",
|
||||
"config",
|
||||
|
@ -150,6 +151,7 @@ class Sanic(BaseSanic):
|
|||
self._test_client = None
|
||||
self._test_manager = None
|
||||
self.asgi = False
|
||||
self.auto_reload = False
|
||||
self.blueprints: Dict[str, Blueprint] = {}
|
||||
self.config = Config(load_env=load_env, env_prefix=env_prefix)
|
||||
self.configure_logging = configure_logging
|
||||
|
@ -876,8 +878,9 @@ class Sanic(BaseSanic):
|
|||
)
|
||||
|
||||
if auto_reload or auto_reload is None and debug:
|
||||
self.auto_reload = True
|
||||
if os.environ.get("SANIC_SERVER_RUNNING") != "true":
|
||||
return reloader_helpers.watchdog(1.0)
|
||||
return reloader_helpers.watchdog(1.0, self)
|
||||
|
||||
if sock is None:
|
||||
host, port = host or "127.0.0.1", port or 8000
|
||||
|
@ -1176,6 +1179,10 @@ class Sanic(BaseSanic):
|
|||
else:
|
||||
logger.info(f"Goin' Fast @ {proto}://{host}:{port}")
|
||||
|
||||
debug_mode = "enabled" if self.debug else "disabled"
|
||||
logger.debug("Sanic auto-reload: enabled")
|
||||
logger.debug(f"Sanic debug mode: {debug_mode}")
|
||||
|
||||
return server_settings
|
||||
|
||||
def _build_endpoint_name(self, *parts):
|
||||
|
|
|
@ -5,6 +5,9 @@ import sys
|
|||
|
||||
from time import sleep
|
||||
|
||||
from sanic.config import BASE_LOGO
|
||||
from sanic.log import logger
|
||||
|
||||
|
||||
def _iter_module_files():
|
||||
"""This iterates over all relevant Python files.
|
||||
|
@ -56,7 +59,7 @@ def restart_with_reloader():
|
|||
)
|
||||
|
||||
|
||||
def watchdog(sleep_interval):
|
||||
def watchdog(sleep_interval, app):
|
||||
"""Watch project files, restart worker process if a change happened.
|
||||
|
||||
:param sleep_interval: interval in second.
|
||||
|
@ -73,6 +76,11 @@ def watchdog(sleep_interval):
|
|||
|
||||
worker_process = restart_with_reloader()
|
||||
|
||||
if app.config.LOGO:
|
||||
logger.debug(
|
||||
app.config.LOGO if isinstance(app.config.LOGO, str) else BASE_LOGO
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
need_reload = False
|
||||
|
|
32
tests/fake/server.py
Normal file
32
tests/fake/server.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from sanic import Sanic, text
|
||||
from sanic.log import LOGGING_CONFIG_DEFAULTS, logger
|
||||
|
||||
|
||||
LOGGING_CONFIG = {**LOGGING_CONFIG_DEFAULTS}
|
||||
LOGGING_CONFIG["formatters"]["generic"]["format"] = "%(message)s"
|
||||
LOGGING_CONFIG["loggers"]["sanic.root"]["level"] = "DEBUG"
|
||||
|
||||
app = Sanic(__name__, log_config=LOGGING_CONFIG)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def handler(request):
|
||||
return text(request.ip)
|
||||
|
||||
|
||||
@app.before_server_start
|
||||
async def app_info_dump(app: Sanic, _):
|
||||
app_data = {
|
||||
"access_log": app.config.ACCESS_LOG,
|
||||
"auto_reload": app.auto_reload,
|
||||
"debug": app.debug,
|
||||
}
|
||||
logger.info(json.dumps(app_data))
|
||||
|
||||
|
||||
@app.after_server_start
|
||||
async def shutdown(app: Sanic, _):
|
||||
app.stop()
|
125
tests/test_cli.py
Normal file
125
tests/test_cli.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
import json
|
||||
import subprocess
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from sanic_routing import __version__ as __routing_version__
|
||||
|
||||
from sanic import __version__
|
||||
from sanic.config import BASE_LOGO
|
||||
|
||||
|
||||
def capture(command):
|
||||
proc = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=Path(__file__).parent,
|
||||
)
|
||||
try:
|
||||
out, err = proc.communicate(timeout=0.5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
out, err = proc.communicate()
|
||||
return out, err, proc.returncode
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appname", ("fake.server.app", "fake.server:app"))
|
||||
def test_server_run(appname):
|
||||
command = ["sanic", appname]
|
||||
out, err, exitcode = capture(command)
|
||||
lines = out.split(b"\n")
|
||||
firstline = lines[6]
|
||||
|
||||
assert exitcode != 1
|
||||
assert firstline == b"Goin' Fast @ http://127.0.0.1:8000"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cmd",
|
||||
(
|
||||
("--host=localhost", "--port=9999"),
|
||||
("-H", "localhost", "-p", "9999"),
|
||||
),
|
||||
)
|
||||
def test_host_port(cmd):
|
||||
command = ["sanic", "fake.server.app", *cmd]
|
||||
out, err, exitcode = capture(command)
|
||||
lines = out.split(b"\n")
|
||||
firstline = lines[6]
|
||||
|
||||
assert exitcode != 1
|
||||
assert firstline == b"Goin' Fast @ http://localhost:9999"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num,cmd",
|
||||
(
|
||||
(1, (f"--workers={1}",)),
|
||||
(2, (f"--workers={2}",)),
|
||||
(4, (f"--workers={4}",)),
|
||||
(1, ("-w", "1")),
|
||||
(2, ("-w", "2")),
|
||||
(4, ("-w", "4")),
|
||||
),
|
||||
)
|
||||
def test_num_workers(num, cmd):
|
||||
command = ["sanic", "fake.server.app", *cmd]
|
||||
out, err, exitcode = capture(command)
|
||||
lines = out.split(b"\n")
|
||||
|
||||
worker_lines = [line for line in lines if b"worker" in line]
|
||||
assert exitcode != 1
|
||||
assert len(worker_lines) == num * 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cmd", ("--debug", "-d"))
|
||||
def test_debug(cmd):
|
||||
command = ["sanic", "fake.server.app", cmd]
|
||||
out, err, exitcode = capture(command)
|
||||
lines = out.split(b"\n")
|
||||
|
||||
app_info = lines[9]
|
||||
info = json.loads(app_info)
|
||||
|
||||
assert (b"\n".join(lines[:6])).decode("utf-8") == BASE_LOGO
|
||||
assert info["debug"] is True
|
||||
assert info["auto_reload"] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cmd", ("--auto-reload", "-r"))
|
||||
def test_auto_reload(cmd):
|
||||
command = ["sanic", "fake.server.app", cmd]
|
||||
out, err, exitcode = capture(command)
|
||||
lines = out.split(b"\n")
|
||||
|
||||
app_info = lines[9]
|
||||
info = json.loads(app_info)
|
||||
|
||||
assert info["debug"] is False
|
||||
assert info["auto_reload"] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cmd,expected", (("--access-log", True), ("--no-access-log", False))
|
||||
)
|
||||
def test_access_logs(cmd, expected):
|
||||
command = ["sanic", "fake.server.app", cmd]
|
||||
out, err, exitcode = capture(command)
|
||||
lines = out.split(b"\n")
|
||||
|
||||
app_info = lines[9]
|
||||
info = json.loads(app_info)
|
||||
|
||||
assert info["access_log"] is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cmd", ("--version", "-v"))
|
||||
def test_version(cmd):
|
||||
command = ["sanic", cmd]
|
||||
out, err, exitcode = capture(command)
|
||||
version_string = f"Sanic {__version__}; Routing {__routing_version__}\n"
|
||||
|
||||
assert out == version_string.encode("utf-8")
|
Loading…
Reference in New Issue
Block a user