lumilake-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
lumilake_cli/cli.py ADDED
@@ -0,0 +1,54 @@
1
+ """Lumilake CLI entrypoint.
2
+
3
+ Installs a typer-aware handler on the shared ``Lumilake`` logger root so
4
+ records from every workspace package render with the CLI colour palette.
5
+ """
6
+
7
+ import logging
8
+
9
+ import typer
10
+ from lumilake.log import ColorFormatter, get_default_logger
11
+
12
+ from .commands import register
13
+ from .core.typer import get_typer
14
+
15
+ root_app = get_typer(help="Lumilake command line interface.")
16
+
17
+
18
+ class _TyperHandler(logging.Handler):
19
+ """Routes records through ``typer.echo``: stdout for INFO/DEBUG,
20
+ stderr for WARNING/ERROR/CRITICAL. Honours typer's color detection.
21
+ Coloring is left to ``ColorFormatter``.
22
+ """
23
+
24
+ def emit(self, record: logging.LogRecord) -> None:
25
+ try:
26
+ message = self.format(record)
27
+ except Exception:
28
+ self.handleError(record)
29
+ return
30
+ typer.echo(message, err=record.levelno >= logging.WARNING)
31
+
32
+
33
+ def _install_handler() -> None:
34
+ root = get_default_logger()
35
+ if any(isinstance(h, _TyperHandler) for h in root.handlers):
36
+ return
37
+ # Drop get_default_logger's StreamHandler so records aren't doubled.
38
+ for existing in list(root.handlers):
39
+ root.removeHandler(existing)
40
+ handler = _TyperHandler()
41
+ handler.setFormatter(ColorFormatter())
42
+ root.addHandler(handler)
43
+ root.propagate = False
44
+
45
+
46
+ def main() -> None:
47
+ """Attach command groups and dispatch to Typer."""
48
+ _install_handler()
49
+ register(root_app)
50
+ root_app()
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
@@ -0,0 +1,69 @@
1
+ """Command modules for the Lumilake CLI."""
2
+
3
+ from importlib.util import find_spec
4
+
5
+ import typer
6
+
7
+ from .base import app as base_app
8
+ from .job import app as job_app
9
+ from .trace import app as trace_app
10
+ from .worker import app as worker_app
11
+
12
+ # The ``[deploy]`` extra pulls ``lumilake-deploy`` and
13
+ # ``flowmesh-cli-stack``; gate on both up front so real ImportError
14
+ # from inside .deploy still bubbles up loudly.
15
+ _DEPLOY_AVAILABLE = (
16
+ find_spec("lumilake_deploy") is not None
17
+ and find_spec("flowmesh_cli_stack") is not None
18
+ )
19
+
20
+ if _DEPLOY_AVAILABLE:
21
+ from .deploy import app as deploy_app
22
+ else:
23
+ _DEPLOY_INSTALL_HINT = (
24
+ "lumilake deploy requires the `deploy` extra. Install with "
25
+ "`pip install 'lumilake[cli]'` or "
26
+ "`pip install 'lumilake-cli[deploy]'`."
27
+ )
28
+ deploy_app = typer.Typer(
29
+ help=(
30
+ "Deploy and manage the Lumilake stack. Install with "
31
+ "`pip install 'lumilake[cli]'` or "
32
+ "`pip install 'lumilake-cli[deploy]'` to enable."
33
+ )
34
+ )
35
+
36
+ def _deploy_hint() -> None:
37
+ typer.echo(_DEPLOY_INSTALL_HINT, err=True)
38
+ raise typer.Exit(code=2)
39
+
40
+ @deploy_app.callback(invoke_without_command=True)
41
+ def _deploy_no_subcommand(ctx: typer.Context) -> None:
42
+ if ctx.resilient_parsing or ctx.invoked_subcommand is not None:
43
+ return
44
+ _deploy_hint()
45
+
46
+ for _name in (
47
+ "init",
48
+ "doctor",
49
+ "build",
50
+ "pull",
51
+ "up",
52
+ "down",
53
+ "clean",
54
+ "reset",
55
+ "status",
56
+ "restart",
57
+ "logs",
58
+ "update-flowmesh",
59
+ ):
60
+ deploy_app.command(_name)(_deploy_hint)
61
+
62
+
63
+ def register(app: typer.Typer) -> None:
64
+ """Register command groups on the root app."""
65
+ app.add_typer(base_app)
66
+ app.add_typer(deploy_app, name="deploy")
67
+ app.add_typer(job_app, name="job")
68
+ app.add_typer(worker_app, name="worker")
69
+ app.add_typer(trace_app, name="trace")
@@ -0,0 +1,41 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import typer
5
+
6
+ from ..core import logging
7
+ from ..core.config import DEFAULT_CONFIG_PATH
8
+ from ..core.http import HttpError, client_from_config, resolve_base_url
9
+ from ..core.typer import get_typer
10
+
11
+ app = get_typer()
12
+
13
+
14
+ @app.command()
15
+ def config(
16
+ config_path: Path = typer.Option(
17
+ DEFAULT_CONFIG_PATH, "--config", help="Path to configuration file"
18
+ ),
19
+ ) -> None:
20
+ """Show the resolved Lumilake CLI configuration."""
21
+ base_url, source = resolve_base_url(config_path)
22
+ payload = {"base_url": base_url, "source": source, "config_path": str(config_path)}
23
+ logging.log(json.dumps(payload, indent=2))
24
+
25
+
26
+ @app.command()
27
+ def info() -> None:
28
+ """Query server health status."""
29
+ client = client_from_config()
30
+ try:
31
+ response = client.get("/healthz")
32
+ except HttpError as exc:
33
+ logging.error(str(exc))
34
+ raise typer.Exit(code=1)
35
+ logging.log(json.dumps(response.json(), indent=2))
36
+
37
+
38
+ @app.command()
39
+ def health() -> None:
40
+ """Check if the Lumilake server is reachable and healthy."""
41
+ info()