testsAndMisc/python_pkg/sql_drills/__main__.py
2026-11-02 08:24:46 +01:00

91 lines
2.8 KiB
Python

"""CLI: ``python3 -m python_pkg.sql_drills start|stop|list|show|check``."""
from __future__ import annotations
import argparse
import importlib
import logging
from pathlib import Path
import sys
from python_pkg.sql_drills import _check, _drills, _engine
_EXIT_CODES = {"pass": 0, "fail": 1, "error": 2}
_LOG = logging.getLogger(__name__)
def _heartbeat() -> None:
"""Tell the optional focus tracker that practice is happening.
Soft dependency: absent module -> silent no-op; a tracker error is logged
(file or registry errors) and never changes the drill's result or
exit code.
"""
try:
tracker = importlib.import_module("focus_project")
except ImportError:
return
try:
tracker.record_activity("sql_drills", "heartbeat")
except OSError, ValueError:
_LOG.exception("focus tracker heartbeat failed")
def _out(line: str) -> None:
sys.stdout.write(f"{line}\n")
def _err(line: str) -> None:
sys.stderr.write(f"{line}\n")
def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="sql_drills", description=__doc__)
sub = parser.add_subparsers(dest="cmd", required=True)
sub.add_parser("start", help="start the MySQL 8.0.33 container and wait")
sub.add_parser("stop", help="remove the container")
sub.add_parser("list", help="list drills in priority order")
show = sub.add_parser("show", help="print task, schema, expected sample")
show.add_argument("drill", help="drill id or number, e.g. 03")
check = sub.add_parser("check", help="grade a .sql file against a drill")
check.add_argument("drill", help="drill id or number, e.g. 03")
check.add_argument("sql_file", type=Path)
return parser
def _dispatch(args: argparse.Namespace) -> int:
if args.cmd == "start":
_engine.start()
_out(f"{_engine.CONTAINER} ready ({_engine.IMAGE})")
elif args.cmd == "stop":
_engine.stop()
_out(f"{_engine.CONTAINER} removed")
elif args.cmd == "list":
for drill in _drills.list_drills():
_out(f"{drill.id} {_drills.title_of(drill)}")
elif args.cmd == "show":
_heartbeat()
sys.stdout.write(_drills.render_show(_drills.get_drill(args.drill)))
else:
_heartbeat()
result = _check.run_check(_drills.get_drill(args.drill), args.sql_file)
_out(result.message)
return _EXIT_CODES[result.status]
return 0
def main(argv: list[str] | None = None) -> int:
"""Entry point; returns the process exit code."""
args = _build_parser().parse_args(argv)
try:
return _dispatch(args)
except _drills.UnknownDrillError as exc:
_err(exc.args[0])
except _engine.EngineError as exc:
_err(f"error: {exc}")
return 2
if __name__ == "__main__":
sys.exit(main())