Shared CLI plumbing: exit codes, the output contract, and error mapping.
The callers are scripts and agents, so:
- payload goes to stdout (:func:
emit), diagnostics to stderr (:func:note) - the exit code carries the meaning (the
EXIT_* table) - error text names the command that fixes the problem
CliError
Bases: Exception
A command failure with a specific exit code and a caller-facing message.
Source code in packages/bookshelf/src/bookshelf/_cli/_runtime.py
| class CliError(Exception):
"""A command failure with a specific exit code and a caller-facing message."""
def __init__(self, message: str, *, exit_code: int = EXIT_UNEXPECTED) -> None:
super().__init__(message)
self.exit_code = exit_code
|
command_errors()
Map SDK errors and :class:CliError onto the exit-code table.
Source code in packages/bookshelf/src/bookshelf/_cli/_runtime.py
| @contextmanager
def command_errors() -> Generator[None]:
"""Map SDK errors and :class:`CliError` onto the exit-code table."""
try:
yield
except CliError as exc:
note(f"Error: {exc}")
raise typer.Exit(code=exc.exit_code) from exc
except errors.BookshelfError as exc:
exit_code = _exit_code_for(exc)
detail = exc.detail if isinstance(exc, errors.APIError) else str(exc)
note(f"Error: {detail}")
remedy = _remedy_for(exit_code)
if remedy is not None:
note(remedy)
raise typer.Exit(code=exit_code) from exc
|
emit(payload)
Write payload to stdout.
Source code in packages/bookshelf/src/bookshelf/_cli/_runtime.py
| def emit(payload: str) -> None:
"""Write payload to stdout."""
typer.echo(payload)
|
emit_json(document)
Write one JSON document to stdout.
Source code in packages/bookshelf/src/bookshelf/_cli/_runtime.py
| def emit_json(document: Any) -> None:
"""Write one JSON document to stdout."""
typer.echo(json.dumps(document))
|
field(label, value)
Render an aligned label value row.
Source code in packages/bookshelf/src/bookshelf/_cli/_runtime.py
| def field(label: str, value: str) -> str:
"""Render an aligned ``label value`` row."""
return f"{label:<13} {value}"
|
human_bytes(count)
Render a byte count for the human summaries.
Source code in packages/bookshelf/src/bookshelf/_cli/_runtime.py
| def human_bytes(count: int) -> str:
"""Render a byte count for the human summaries."""
size = float(count)
for unit in ("B", "kB", "MB", "GB"):
if size < 1000 or unit == "GB":
return f"{size:.1f} {unit}" if unit != "B" else f"{int(size)} B"
size /= 1000
return f"{int(size)} B" # pragma: no cover - unreachable
|
iso(moment)
Render a datetime as UTC ISO-8601 with a Z suffix.
Source code in packages/bookshelf/src/bookshelf/_cli/_runtime.py
| def iso(moment: datetime | None) -> str | None:
"""Render a datetime as UTC ISO-8601 with a ``Z`` suffix."""
if moment is None:
return None
return moment.astimezone(UTC).isoformat().replace("+00:00", "Z")
|
note(message)
Write a diagnostic line to stderr.
Source code in packages/bookshelf/src/bookshelf/_cli/_runtime.py
| def note(message: str) -> None:
"""Write a diagnostic line to stderr."""
typer.echo(message, err=True)
|