From 2f3ca5542b9aeaf532364fbd30006b1de76671da Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:14:33 +0530 Subject: [PATCH 1/2] docs: document optional output dependencies --- docs/index.md | 2 + docs/optional-output-dependencies.md | 78 +++++++++++++++++++ mkdocs.yml | 1 + .../test_optional_output_dependencies_docs.py | 27 +++++++ 4 files changed, 108 insertions(+) create mode 100644 docs/optional-output-dependencies.md create mode 100644 tests/test_optional_output_dependencies_docs.py diff --git a/docs/index.md b/docs/index.md index 7152c1d..4f67c56 100644 --- a/docs/index.md +++ b/docs/index.md @@ -77,6 +77,8 @@ application. before building automation around command output. - Use the [strict JSON consumer guide](strict-json-consumer.md) to validate envelope and NDJSON records with a cross-language parser. +- Use the [optional output dependency guide](optional-output-dependencies.md) + to map YAML and other integrations to their explicit extras and fallbacks. ## Design principles diff --git a/docs/optional-output-dependencies.md b/docs/optional-output-dependencies.md new file mode 100644 index 0000000..3b0c324 --- /dev/null +++ b/docs/optional-output-dependencies.md @@ -0,0 +1,78 @@ +# Optional output dependencies + +The core `base-cli` package keeps its dependency boundary small. Install an +extra only when the consumer selects the corresponding integration or output +path; do not add optional dependencies to the core runtime just to make a +format available. + +## Capability map + +| Capability | Install extra | Core or optional behavior | +| --- | --- | --- | +| Human text, CSV, TSV, JSON, and NDJSON records | none | Available from the core package through `base_cli.output`; redirected text is TSV. | +| YAML configuration or YAML output | `base-cli[yaml]` | Provides `PyYAML>=6.0,<7`; loading or rendering YAML without it fails with an actionable error. | +| Typer application adapter | `base-cli[typer]` | Provides `typer>=0.12,<0.28`; importing the adapter without it fails with an actionable error. | +| Rich interactive tables | `base-cli[rich]` | Optional presentation enhancement; the built-in deterministic table remains the fallback. | +| OpenTelemetry lifecycle spans | `base-cli[telemetry]` | Optional instrumentation; missing or unhealthy telemetry is a no-op for command status. | + +The authoritative version windows are in [Dependency Support](dependency-support.md) +and `pyproject.toml`. The complete rendering rules are in [Output Contracts](output-contracts.md). + +## YAML: install it or choose a core format + +When a consumer intentionally renders YAML, install the extra in the same +environment as the package: + +```bash +python -m pip install 'base-cli[yaml]' +``` + +Without PyYAML, the public renderer reports: + +```text +PyYAML is required for YAML output. Install the optional dependency with `python -m pip install 'base-cli[yaml]'`. +``` + +If YAML is not required, choose JSON, CSV, or NDJSON in the consumer's command +before calling the renderer. JSON and NDJSON are available from the core +package and are usually the safest machine-facing fallback; the consumer must +still document which fields it reads. + +## Consumer-side selection + +The framework does not invent a format flag for an application. A consumer +owns the option and can pass the selected value to the public renderer: + +```python +base_cli.render_records( + records, + requested_format=output_format, + columns=(("NAME", "name"),), +) +``` + +When `output_format` is `yaml`, the consumer should either install +`base-cli[yaml]` or return the documented dependency diagnostic. When it is +`json` or `ndjson`, no optional output dependency is needed. Do not silently +turn an explicitly requested YAML result into another format; make the +fallback visible and preserve a nonzero status when the requested contract is +part of the command's required behavior. + +## Other optional integrations + +The same boundary applies outside output formats: + +- `base-cli[typer]` is required before calling `base_cli.attach_typer()`; the + adapter's current error names the install command and the `typer` extra. +- `base-cli[rich]` is an optional human-table enhancement. Missing Rich does + not change CSV, TSV, JSON, or NDJSON output, and the built-in text renderer + remains available. +- `base-cli[telemetry]` enables lifecycle spans. A missing API package, + invalid provider, or failing exporter is logged at debug level and must not + change the command's exit status or cleanup behavior. + +Validate the selected path from both an installed package and a source +checkout when it matters to the consumer. Keep the extra, Python version, and +base-cli version in the consumer lock file; consult [API Stability](api-stability.md) +before widening a dependency window. This page documents current behavior and +does not change package metadata or runtime error classification. diff --git a/mkdocs.yml b/mkdocs.yml index 021ff7a..6f5e7b6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,6 +43,7 @@ nav: - Public API reference: api-reference.md - API stability: api-stability.md - Dependency support: dependency-support.md + - Optional output dependencies: optional-output-dependencies.md - Migration guide: migrations.md - Click migration: migration-click.md - Typer migration: migration-typer.md diff --git a/tests/test_optional_output_dependencies_docs.py b/tests/test_optional_output_dependencies_docs.py new file mode 100644 index 0000000..ab5c7fb --- /dev/null +++ b/tests/test_optional_output_dependencies_docs.py @@ -0,0 +1,27 @@ +from pathlib import Path + +ROOT = Path(__file__).parents[1] +DOC = ROOT / "docs" / "optional-output-dependencies.md" +INDEX = ROOT / "docs" / "index.md" + + +def test_optional_output_dependency_guide_is_linked_from_the_documentation_index() -> None: + assert "optional-output-dependencies.md" in INDEX.read_text(encoding="utf-8") + + +def test_optional_output_dependency_guide_matches_declared_extras_and_fallbacks() -> None: + text = DOC.read_text(encoding="utf-8") + + for required in ( + "base-cli[yaml]", + "PyYAML>=6.0,<7", + "base-cli[typer]", + "base-cli[rich]", + "base-cli[telemetry]", + "base_cli.render_records", + "base_cli.output", + "PyYAML is required for YAML output.", + "output-contracts.md", + "dependency-support.md", + ): + assert required in text From 828582281b20f0b4414bd3084039c5339fa93ada Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:23:46 +0530 Subject: [PATCH 2/2] docs: register dependency guide in doc validation --- scripts/validate_docs.py | 1 + tests/test_optional_output_dependencies_docs.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/scripts/validate_docs.py b/scripts/validate_docs.py index da960f5..27f15c9 100644 --- a/scripts/validate_docs.py +++ b/scripts/validate_docs.py @@ -34,6 +34,7 @@ "migration-typer.md", "migrations.md", "output-contracts.md", + "optional-output-dependencies.md", "performance.md", "testing.md", "platform-support.md", diff --git a/tests/test_optional_output_dependencies_docs.py b/tests/test_optional_output_dependencies_docs.py index ab5c7fb..70746a7 100644 --- a/tests/test_optional_output_dependencies_docs.py +++ b/tests/test_optional_output_dependencies_docs.py @@ -3,10 +3,12 @@ ROOT = Path(__file__).parents[1] DOC = ROOT / "docs" / "optional-output-dependencies.md" INDEX = ROOT / "docs" / "index.md" +VALIDATOR = ROOT / "scripts" / "validate_docs.py" def test_optional_output_dependency_guide_is_linked_from_the_documentation_index() -> None: assert "optional-output-dependencies.md" in INDEX.read_text(encoding="utf-8") + assert "optional-output-dependencies.md" in VALIDATOR.read_text(encoding="utf-8") def test_optional_output_dependency_guide_matches_declared_extras_and_fallbacks() -> None: