Skip to content

Commit 99be873

Browse files
authored
Improve CLI Tables and IDs handling (#4241)
1 parent cd6fdee commit 99be873

34 files changed

+2231
-1018
lines changed

docker/zenml-dev.Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH"
4343

4444
COPY README.md pyproject.toml ./
4545

46-
# We first copy the __init__.py file to allow pip install-ing the Python
46+
# We first copy the __init__.py files to allow pip install-ing the Python
4747
# dependencies as a separate cache layer. This way, we can avoid re-installing
4848
# the dependencies when the source code changes but the dependencies don't.
4949
COPY src/zenml/__init__.py ./src/zenml/
50+
COPY src/zenml_cli/__init__.py ./src/zenml_cli/
5051

5152
# Run pip install before copying the source files to install dependencies in
5253
# the virtual environment. Also create a requirements.txt file to keep track of

docker/zenml-server-dev.Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH"
7676

7777
COPY --chown=$USERNAME:$USER_GID README.md pyproject.toml ./
7878

79-
# We first copy the __init__.py file to allow pip install-ing the Python
79+
# We first copy the __init__.py files to allow pip install-ing the Python
8080
# dependencies as a separate cache layer. This way, we can avoid re-installing
8181
# the dependencies when the source code changes but the dependencies don't.
8282
COPY --chown=$USERNAME:$USER_GID src/zenml/__init__.py ./src/zenml/
83+
COPY --chown=$USERNAME:$USER_GID src/zenml_cli/__init__.py ./src/zenml_cli/
8384

8485
# Run pip install before copying the source files to install dependencies in
8586
# the virtual environment. Also create a requirements.txt file to keep track of

docs/book/reference/environment-variables.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,28 @@ To set the path to the global config file, used by ZenML to manage and store the
129129
export ZENML_CONFIG_PATH=/path/to/somewhere
130130
```
131131

132+
## CLI output formatting
133+
134+
### Default output format
135+
136+
Set the default output format for all CLI list commands:
137+
138+
```bash
139+
export ZENML_DEFAULT_OUTPUT=json
140+
```
141+
142+
Choose from `table` (default), `json`, `yaml`, `csv`, or `tsv`. This applies to commands like `zenml stack list`, `zenml pipeline list`, etc.
143+
144+
### Terminal width override
145+
146+
Override the automatic terminal width detection for table rendering:
147+
148+
```bash
149+
export ZENML_CLI_COLUMN_WIDTH=120
150+
```
151+
152+
This is useful when running ZenML in CI/CD environments or when you want to control table formatting regardless of your terminal size.
153+
132154
## Server configuration
133155

134156
For more information on server configuration, see the [ZenML Server documentation](../getting-started/deploying-zenml/deploy-with-docker.md#zenml-server-configuration-options) for more, especially the section entitled "ZenML server configuration options".

docs/book/reference/global-settings.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ Using the default local database.
4747
┗━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
4848
```
4949

50+
{% hint style="info" %}
51+
The output can be customized with an `--output` (json, yaml, csv, tsv, table) option and a `--columns` selection. See [environment variables](environment-variables.md#cli-output-formatting) for more details.
52+
{% endhint %}
53+
5054
The following is an example of the layout of the global config directory immediately after initialization:
5155

5256
```

docs/book/user-guide/best-practices/quick-wins.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ micro-setup (under 5 minutes) and any tips or gotchas to anticipate.
2525
| [Model Control Plane](#id-12-register-models-in-the-model-control-plane) | Track models and their lifecycle | Central hub for model lineage and governance |
2626
| [Parent Docker images](#id-13-create-a-parent-docker-image-for-faster-builds) | Pre-configure your dependencies in a base image | Faster builds and consistent environments |
2727
| [ZenML docs via MCP](#id-14-enable-ide-ai-zenml-docs-via-mcp-server) | Connect your IDE assistant to live ZenML docs | Faster, grounded answers and doc lookups while coding |
28+
| [Export CLI data](#id-15-export-cli-data-in-multiple-formats) | Get machine-readable output from list commands | Perfect for scripting, automation, and data analysis |
2829

2930

3031
## 1 Log rich metadata on every run
@@ -854,3 +855,61 @@ Using the zenmldocs MCP server, show me how to register an MLflow experiment tra
854855
- For bleeding-edge features on develop, consult the repo or develop docs directly
855856

856857
Learn more: [Access ZenML documentation via llms.txt and MCP](https://docs.zenml.io/reference/llms-txt)
858+
859+
## 15 Export CLI data in multiple formats
860+
861+
All `zenml list` commands support multiple output formats for scripting, CI/CD integration, and data analysis.
862+
863+
```bash
864+
# Get stack data as JSON for processing with jq
865+
zenml stack list --output=json | jq '.items[] | select(.name=="production")'
866+
867+
# Export pipeline runs to CSV for analysis
868+
zenml pipeline runs list --output=csv > pipeline_runs.csv
869+
870+
# Get deployment info as YAML for configuration management
871+
zenml deployment list --output=yaml
872+
873+
# Filter columns to see only what you need
874+
zenml stack list --columns=id,name,orchestrator
875+
876+
# Combine filtering with custom output formats
877+
zenml pipeline list --columns=id,name,num_runs --output=json
878+
```
879+
880+
**Available formats**
881+
- **json** - Structured data with pagination info, perfect for programmatic processing
882+
- **yaml** - Human-readable structured format, great for configuration
883+
- **csv** - Comma-separated values for spreadsheets and data analysis
884+
- **tsv** - Tab-separated values for simpler parsing
885+
- **table** (default) - Formatted tables with colors and alignment
886+
887+
**Key features**
888+
- **Column filtering** - Use `--columns` to show only the fields you need
889+
- **Scriptable** - Combine with tools like `jq`, `grep`, `awk` for powerful automation
890+
- **Environment control** - Set `ZENML_DEFAULT_OUTPUT` to change the default format
891+
- **Width control** - Override terminal width with `ZENML_CLI_COLUMN_WIDTH` for consistent formatting
892+
893+
**Best practices**
894+
- Use JSON format for robust parsing in scripts (includes pagination metadata)
895+
- Use CSV/TSV for importing into spreadsheet tools or databases
896+
- Use `--columns` to reduce noise and focus on relevant data
897+
- Set default formats via environment variables in CI/CD environments
898+
899+
**Example automation script**
900+
```bash
901+
#!/bin/bash
902+
# Export all production stacks to a report
903+
904+
export ZENML_DEFAULT_OUTPUT=json
905+
906+
# Get all stacks and filter for production
907+
zenml stack list | jq '.items[] | select(.name | contains("prod"))' > prod_stacks.json
908+
909+
# Generate a summary CSV
910+
zenml stack list --output=csv --columns=name,orchestrator,artifact_store > stack_summary.csv
911+
912+
echo "Reports generated: prod_stacks.json and stack_summary.csv"
913+
```
914+
915+
Learn more: [Environment Variables](https://docs.zenml.io/reference/environment-variables#cli-output-formatting)

docs/book/user-guide/production-guide/understand-stacks.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ Stack 'default' with id '...' is owned by user default and is 'private'.
4949
...
5050
```
5151

52+
{% hint style="info" %}
53+
You can customize the output using `--columns` to show specific fields or `--output` to change the format (json, yaml, csv, tsv). Learn more in the [Quick Wins guide](../best-practices/quick-wins.md#id-15-export-cli-data-in-multiple-formats).
54+
{% endhint %}
55+
5256
{% hint style="info" %}
5357
As you can see a stack can be **active** on your **client**. This simply means that any pipeline you run will be using the **active stack** as its environment.
5458
{% endhint %}

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
requires = ["uv_build >= 0.8.17, <0.9.0"]
33
build-backend = "uv_build"
44

5+
[tool.uv.build-backend]
6+
module-name = ["zenml", "zenml_cli"]
7+
58
[project]
69
name = "zenml"
710
version = "0.92.0"
@@ -51,7 +54,7 @@ Repository = "https://github.com/zenml-io/zenml"
5154
Issues = "https://github.com/zenml-io/zenml/issues"
5255

5356
[project.scripts]
54-
zenml = "zenml.cli.cli:cli"
57+
zenml = "zenml_cli:cli"
5558

5659
[project.optional-dependencies]
5760
local = [

src/zenml/cli/__init__.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,45 @@
225225
This syntax can also be combined to create more complex filters using the `or`
226226
and `and` keywords.
227227
228+
Output formats
229+
--------------
230+
231+
All ``list`` commands support multiple output formats for scripting,
232+
CI/CD integration, and data analysis.
233+
234+
Use the ``--output`` (or ``-o``) option to specify the format:
235+
236+
```bash
237+
# Get stack data as JSON for processing with jq
238+
zenml stack list --output=json | jq '.items[] | select(.name=="production")'
239+
240+
# Export pipeline runs to CSV for analysis
241+
zenml pipeline runs list --output=csv > pipeline_runs.csv
242+
243+
# Get deployment info as YAML for configuration management
244+
zenml deployment list --output=yaml
245+
246+
# Filter columns to see only what you need
247+
zenml stack list --columns=id,name,orchestrator
248+
249+
# Combine filtering with custom output formats
250+
zenml pipeline list --columns=id,name --output=json
251+
```
252+
253+
Available formats:
254+
255+
- **json** - Structured data with pagination info, ideal for programmatic use
256+
- **yaml** - Human-readable structured format, great for configuration
257+
- **csv** - Comma-separated values for spreadsheets and data analysis
258+
- **tsv** - Tab-separated values for simpler parsing
259+
- **table** (default) - Formatted tables with colors and alignment
260+
261+
You can also control the default output format and table width using
262+
environment variables:
263+
264+
- ``ZENML_DEFAULT_OUTPUT`` - Set the default output format (e.g., ``json``)
265+
- ``ZENML_CLI_COLUMN_WIDTH`` - Override terminal width for consistent formatting
266+
228267
Artifact Stores
229268
---------------
230269

src/zenml/cli/annotator.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,8 @@ def dataset_list(annotator: "BaseAnnotator") -> None:
7373
if not dataset_names:
7474
cli_utils.warning("No datasets found.")
7575
return
76-
cli_utils.print_list_items(
77-
list_items=dataset_names,
78-
column_title="DATASETS",
76+
cli_utils.print_table(
77+
[{"DATASETS": name} for name in sorted(dataset_names)]
7978
)
8079

8180
@dataset.command("stats")

src/zenml/cli/artifact.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
from zenml.cli import utils as cli_utils
2121
from zenml.cli.cli import TagGroup, cli
22+
from zenml.cli.utils import OutputFormat, list_options
2223
from zenml.client import Client
24+
from zenml.console import console
2325
from zenml.enums import CliCategories
2426
from zenml.logger import get_logger
2527
from zenml.models import ArtifactFilter, ArtifactVersionFilter
@@ -35,25 +37,27 @@ def artifact() -> None:
3537
"""Commands for interacting with artifacts."""
3638

3739

38-
@cli_utils.list_options(ArtifactFilter)
3940
@artifact.command("list", help="List all artifacts.")
40-
def list_artifacts(**kwargs: Any) -> None:
41+
@list_options(
42+
ArtifactFilter,
43+
default_columns=["id", "name", "latest_version_name", "tags"],
44+
)
45+
def list_artifacts(
46+
columns: str, output_format: OutputFormat, **kwargs: Any
47+
) -> None:
4148
"""List all artifacts.
4249
4350
Args:
51+
columns: Columns to display in output.
52+
output_format: Format for output (table/json/yaml/csv/tsv).
4453
**kwargs: Keyword arguments to filter artifacts by.
4554
"""
46-
artifacts = Client().list_artifacts(**kwargs)
47-
48-
if not artifacts:
49-
cli_utils.declare("No artifacts found.")
50-
return
55+
with console.status("Listing artifacts...\n"):
56+
artifacts = Client().list_artifacts(**kwargs)
5157

52-
to_print = []
53-
for artifact in artifacts:
54-
to_print.append(_artifact_to_print(artifact))
55-
56-
cli_utils.print_table(to_print)
58+
cli_utils.print_page(
59+
artifacts, columns, output_format, empty_message="No artifacts found."
60+
)
5761

5862

5963
@artifact.command("update", help="Update an artifact.")
@@ -115,25 +119,30 @@ def version() -> None:
115119
"""Commands for interacting with artifact versions."""
116120

117121

118-
@cli_utils.list_options(ArtifactVersionFilter)
119122
@version.command("list", help="List all artifact versions.")
120-
def list_artifact_versions(**kwargs: Any) -> None:
123+
@list_options(
124+
ArtifactVersionFilter,
125+
default_columns=["id", "artifact", "version", "type"],
126+
)
127+
def list_artifact_versions(
128+
columns: str, output_format: OutputFormat, **kwargs: Any
129+
) -> None:
121130
"""List all artifact versions.
122131
123132
Args:
133+
columns: Columns to display in output.
134+
output_format: Format for output (table/json/yaml/csv/tsv).
124135
**kwargs: Keyword arguments to filter artifact versions by.
125136
"""
126-
artifact_versions = Client().list_artifact_versions(**kwargs)
127-
128-
if not artifact_versions:
129-
cli_utils.declare("No artifact versions found.")
130-
return
131-
132-
to_print = []
133-
for artifact_version in artifact_versions:
134-
to_print.append(_artifact_version_to_print(artifact_version))
135-
136-
cli_utils.print_table(to_print)
137+
with console.status("Listing artifact versions...\n"):
138+
artifact_versions = Client().list_artifact_versions(**kwargs)
139+
140+
cli_utils.print_page(
141+
artifact_versions,
142+
columns,
143+
output_format,
144+
empty_message="No artifact versions found.",
145+
)
137146

138147

139148
@version.command("describe", help="Show details about an artifact version.")

0 commit comments

Comments
 (0)