Optional integrations¶
The core base-cli install has no Rich or OpenTelemetry dependency. Install
only the integration you use:
python -m pip install 'base-cli[rich]'
python -m pip install 'base-cli[telemetry]'
Rich human tables¶
Pass rich=True when constructing an app and pass the active context's flag to
the shared record renderer:
from typing import Any
import base_cli
app = base_cli.App(name="catalog", rich=True)
@app.command()
def list_items(ctx: base_cli.Context[Any, Any, Any]) -> None:
base_cli.render_records(
({"name": "base", "path": "/work/base"},),
requested_format="text",
columns=(("NAME", "name"), ("PATH", "path")),
rich=ctx.rich,
)
Rich is used only for interactive human text. Redirected text remains TSV, and CSV, TSV, JSON, and YAML contracts do not change. If Rich is missing or its renderer fails, the deterministic built-in table is used automatically.
OpenTelemetry lifecycle spans¶
Telemetry is opt-in and can use the application's configured global provider or an explicitly supplied tracer:
import base_cli
app = base_cli.App(
name="catalog",
telemetry=base_cli.TelemetryOptions(),
)
Each invocation emits a base_cli.run span with a start event and a finish
event. Safe attributes include the run ID, CLI name, environment, dry-run flag,
outcome, exit code, and duration. Raw argv, configuration values, filesystem
paths, and secrets are never attached. A missing API package, invalid provider,
or failing exporter is logged at debug level and treated as a no-op; it cannot
change the command's exit status or cleanup behavior.
The span status is OK for a successful invocation and ERROR for every
non-success outcome. Unexpected exceptions are recorded on failed spans;
successful SystemExit(0) and Click exit-control flow are not recorded as
exceptions. Interrupts and other non-success outcomes are therefore visible as
errors while retaining the existing base_cli.outcome attribute for detailed
dashboard filtering.
Child-process run and trace propagation¶
Use the active context when a command launches another CLI:
import subprocess
subprocess.run(["other-cli", "sync"], env=ctx.child_environment(), check=True)
Context.child_environment() returns a copy of the environment containing the
following language-neutral contract:
| Variable | Meaning |
|---|---|
BASE_CLI_RUN_ID |
The current run identity. It is validated as one safe path component. |
BASE_CLI_PARENT_RUN_ID |
The launching run identity. |
BASE_CLI_RUN_ROOT |
The absolute, framework-owned bundle to reuse when it exists. |
BASE_CLI_RUN_SCOPE |
internal for a propagated child, or primary for a deliberate top-level record. |
BASE_CLI_PROPAGATE |
Set to 0 to opt out of runtime and trace propagation. |
TRACEPARENT / TRACESTATE |
W3C trace context for OpenTelemetry-enabled consumers. |
The generic profile honors these variables without a custom RuntimeResolver.
An inherited BASE_CLI_RUN_ROOT must be absolute, non-symlinked, and contain
valid run.json metadata; malformed IDs, traversal, absolute IDs, and invalid
scopes fail with an actionable configuration error before any path is created.
The generic profile's explicit RuntimeResolver, when supplied, is
authoritative and is not overridden by environment values.
An inherited child reuses the parent's bundle and is marked internal; only
the process that created the bundle owns its lifecycle metadata and retention.
The framework cannot intercept arbitrary subprocess APIs, so callers must pass
the returned mapping (or selectively copy its documented variables). Use
ctx.child_environment(propagate=False) for an intentionally independent
child. OpenTelemetry extraction/injection remains best-effort and never makes
the optional dependency required.
Logger ownership¶
The lifecycle uses base_cli.<cli_name> and owns only the handlers it creates.
Consumer handlers remain attached and open after configuration and cleanup.
An explicit consumer logger level, or configuration on the base_cli parent,
is preserved, including logging.config.dictConfig() routing. Such levels may
filter records before the lifecycle handlers see them. Configure the consumer
logger at DEBUG if the persistent handler should receive every record.
Unconfigured CLI loggers use DEBUG with propagation disabled to avoid duplicate
terminal output. configure_logger(..., propagate=True) explicitly enables host
routing; False disables it, and the default None preserves consumer routing.
Use a consumer handler or configure the base_cli parent before invoking an App
when embedding it in a host with centralized logging.
Log timestamp environment variable¶
Set BASE_CLI_LOG_UTC=1 to make the default text formatter use UTC
timestamps. The namespaced variable takes precedence over the legacy setting.
LOG_UTC remains recognized during the 0.5 compatibility window, but emits a
BaseCliDeprecationWarning and is scheduled for removal no earlier than 0.7.