-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.py
More file actions
101 lines (80 loc) · 2.79 KB
/
Copy pathcli.py
File metadata and controls
101 lines (80 loc) · 2.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""click CLI for the example app.
Two subcommands:
teesql-example migrate — one-shot schema bootstrap. Run once per
cluster (or after a burn-down) before
the first `serve`.
teesql-example serve — start the FastAPI app under uvicorn.
Holds primary + secondary connections
for the lifetime of the process.
Mirrors hivemind-core's `hivemind` click CLI shape.
"""
from __future__ import annotations
import logging
import sys
import click
import uvicorn
from .config import load_settings
from .connection import connect
from .migrate import apply_migrations
log = logging.getLogger(__name__)
def _setup_logging(level: str) -> None:
logging.basicConfig(
level=level,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
datefmt="%Y-%m-%dT%H:%M:%S%z",
)
@click.group(help="Example client for connecting to a teesql cluster.")
@click.pass_context
def cli(ctx: click.Context) -> None:
settings = load_settings()
_setup_logging(settings.log_level)
ctx.obj = settings
@cli.command(help="Idempotent schema bootstrap. Run once per cluster.")
@click.pass_obj
def migrate(settings) -> None: # type: ignore[no-untyped-def]
# Migration is a one-shot — short-lived `connect()` is fine.
with connect(settings, role=settings.primary_role) as conn:
apply_migrations(conn)
click.echo("migrations complete")
@cli.command(help="Start the FastAPI app (POST /events + GET /ws/events).")
@click.option(
"--host",
default=None,
help="Bind host (overrides TEESQL_EXAMPLE_HOST)",
)
@click.option(
"--port",
default=None,
type=int,
help="Bind port (overrides TEESQL_EXAMPLE_PORT)",
)
@click.pass_obj
def serve(settings, host: str | None, port: int | None) -> None: # type: ignore[no-untyped-def]
bind_host = host or settings.host
bind_port = port or settings.port
log.info("serving on %s:%d", bind_host, bind_port)
uvicorn.run(
"teesql_example.server:create_app",
host=bind_host,
port=bind_port,
log_level=settings.log_level.lower(),
factory=True,
)
def main() -> None:
"""Entry point exposed as `teesql-example` in pyproject.toml.
Wraps the click group in a small try/except so unexpected exceptions
surface a clear error rather than a Python traceback (which would
include the cluster_secret if it appears in a frame's locals).
"""
try:
cli()
except SystemExit:
raise
except KeyboardInterrupt:
click.echo("interrupted", err=True)
sys.exit(130)
except Exception as e: # noqa: BLE001
click.echo(f"error: {e}", err=True)
sys.exit(1)
if __name__ == "__main__":
main()