py/facett_demo/cli.py
"""`facett-demo` — start the demo, or say what it is (build-version-githash LAW).
facett-demo # the app on http://127.0.0.1:8501
facett-demo run [streamlit options] # e.g. run --server.port 8502
facett-demo --version | about
Streamlit is started IN-PROCESS through its own Python entry point
(``streamlit.web.cli``) — the same call the ``streamlit`` console script makes — rather
than by spawning a ``streamlit`` executable, so the demo needs nothing on ``PATH`` and
runs under exactly the interpreter it was installed into.
The default bind address is LOOPBACK (``127.0.0.1``), on purpose: nothing about a demo
is authenticated, and a 0.0.0.0 bind on a workstation once published it to the
internet. Pass ``--server.address`` to choose otherwise.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from .buildinfo import build_info, version_string
#: The app script Streamlit runs — inside the INSTALLED package.
APP = Path(__file__).resolve().parent / "app.py"
#: Options the demo sets unless the caller passes its own.
DEFAULTS: dict[str, str] = {
"--server.address": "127.0.0.1",
"--server.port": "8501",
"--server.headless": "true",
"--browser.gatherUsageStats": "false",
}
def _about() -> str:
bi = build_info()
from .client import DEFAULT_TARGET, KORP_EXTRA_HINT, korp_available
korp = "installed" if korp_available() else f"NOT installed — offline mode ({KORP_EXTRA_HINT})"
lines = [
version_string(),
f" version : {bi['version']}",
f" git-hash : {bi['git_hash']}",
f" build-time : {bi['build_time']}",
f" hash-source: {bi['source']}",
f" korp wire : {korp}",
f" korp-server: {DEFAULT_TARGET} (override with $KORP_SERVER)",
" panes : Komponenter (every wasm component, no server) · Map(Assets+MapQuery) · "
"Graph(Graph) · Population(IcebergTables+TablePreview+Population) · Errors",
]
return "\n".join(lines)
def streamlit_argv(extra: list[str]) -> list[str]:
"""The ``streamlit run`` arguments: the app, the caller's options, then any of
:data:`DEFAULTS` the caller did not set (``--opt value`` or ``--opt=value``)."""
given = {a.split("=", 1)[0] for a in extra if a.startswith("--")}
argv = ["run", str(APP), *extra]
for opt, value in DEFAULTS.items():
if opt not in given:
argv += [opt, value]
return argv
def run(extra: list[str]) -> int:
"""Run the app in THIS process, through Streamlit's own CLI entry point."""
try:
from streamlit.web import cli as stcli
except ImportError as e: # facett[streamlit] is a hard dependency; say so if it is gone
print(f"facett-demo needs Streamlit (pip install 'facett[streamlit]'): {e}", file=sys.stderr)
return 2
try:
stcli.main(args=streamlit_argv(extra), prog_name="facett-demo", standalone_mode=True)
except SystemExit as e: # click exits; turn it into our return code
return int(e.code or 0) if not isinstance(e.code, str) else 1
return 0
def main(argv: list[str] | None = None) -> int:
argv = sys.argv[1:] if argv is None else argv
# `run` passes everything after it to Streamlit untouched (its own --server.* flags).
if argv[:1] == ["run"]:
return run(argv[1:])
p = argparse.ArgumentParser(prog="facett-demo", description="facett's components in a Streamlit app")
p.add_argument("--version", action="store_true", help="print version + git-hash and exit")
sub = p.add_subparsers(dest="cmd")
sub.add_parser("about", help="print the full build/about banner")
sub.add_parser("run", help="start the app (default); Streamlit options follow")
args = p.parse_args(argv)
if args.version:
print(version_string())
return 0
if args.cmd == "about":
print(_about())
return 0
return run([])
if __name__ == "__main__":
sys.exit(main())