Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
f5d863b
Improving the list outputs and adding new formats
znegrin Jul 30, 2025
78af737
Merge develop into feat/improved-cli-tables
znegrin Jul 30, 2025
4ecfdfc
Fix type annotation for overflow parameter in table_utils
znegrin Jul 30, 2025
6403d92
Update service connector table to bring back previous data
znegrin Aug 1, 2025
4514cfc
Merge branch 'develop' into feat/improved-cli-tables
znegrin Aug 1, 2025
8f361b6
Fix docstring issues in CLI table functions
znegrin Aug 1, 2025
cd2b987
Fix remaining docstring issues in CLI functions
znegrin Aug 1, 2025
6604636
Fix missing parameter and return documentation in CLI docstrings
znegrin Aug 1, 2025
cea4272
Fix mypy type annotation issues across all CLI files
znegrin Aug 1, 2025
ebebd80
Apply code formatting to CLI files
znegrin Aug 1, 2025
038940d
Merge branch 'develop' into feat/improved-cli-tables
bcdurak Aug 4, 2025
7bed4be
fix one test
bcdurak Aug 4, 2025
bf13b44
removed unused import
bcdurak Aug 6, 2025
fbeef0c
new module for the CLI
bcdurak Aug 6, 2025
15d2247
merged develop
bcdurak Aug 6, 2025
783304d
formatting
bcdurak Aug 6, 2025
a0b63dd
Merge branch 'develop' into feat/improved-cli-tables
bcdurak Aug 6, 2025
a36632a
new changes
bcdurak Aug 7, 2025
ff9acec
removing warning suppresion
bcdurak Aug 7, 2025
6fb4e3c
Merge branch 'develop' into feat/improved-cli-tables
bcdurak Aug 7, 2025
79e75a7
checkpoint
bcdurak Aug 7, 2025
4eea33f
some other fixes
bcdurak Aug 7, 2025
8fa6b5a
merged
bcdurak Aug 7, 2025
c383943
docstring changes
bcdurak Aug 27, 2025
783f8eb
merged develop, resolved conflicts
bcdurak Aug 29, 2025
81045c5
removed unused stuff
bcdurak Aug 29, 2025
277e774
new formatting
bcdurak Sep 1, 2025
932d61d
docstrings and linting
bcdurak Sep 1, 2025
6ef6bc7
Merge branch 'develop' into feat/improved-cli-tables
bcdurak Sep 1, 2025
39a7c43
formatting linting
bcdurak Sep 1, 2025
06e2ad5
formatting
bcdurak Sep 1, 2025
494df98
add id to pipeline runs
bcdurak Sep 1, 2025
ffe3bf1
some more formatting
bcdurak Sep 1, 2025
1ea0f32
removed unneccessary tests
bcdurak Sep 1, 2025
0341fd5
Merge branch 'develop' into feat/improved-cli-tables
bcdurak Sep 11, 2025
44a7b1c
fixed merge conflicts
bcdurak Sep 17, 2025
99399c0
some checkpoint
bcdurak Sep 17, 2025
4a7f75c
Merge branch 'develop' into feat/improved-cli-tables
safoinme Nov 19, 2025
1450ab6
initial clean fix
safoinme Nov 19, 2025
15b0b4f
Merge branch 'develop' into feat/improved-cli-tables
safoinme Nov 21, 2025
70fc109
cleanup
safoinme Nov 21, 2025
aa71a7a
delete tests for now
safoinme Nov 21, 2025
fa56ab5
Merge branch 'develop' into feature/improve-cli-tables
safoinme Nov 21, 2025
07cde10
mypy
safoinme Nov 22, 2025
2185a74
cleanup
safoinme Nov 22, 2025
cc677e0
cleanup and fixes
safoinme Nov 22, 2025
b69ce6b
mypy and fixes
safoinme Nov 23, 2025
68f7d33
mypy
safoinme Nov 23, 2025
25a7af5
format and mypy
safoinme Nov 24, 2025
0041acb
Merge branch 'develop' into feature/improve-cli-tables
safoinme Nov 24, 2025
b319f39
dosctring
safoinme Nov 24, 2025
8f3fb41
updates and cleanup
safoinme Nov 24, 2025
9c52f36
mypy
safoinme Nov 24, 2025
12500a2
final touches
safoinme Nov 24, 2025
ab824f5
fix tests
safoinme Nov 24, 2025
65f023a
Merge branch 'develop' into feature/improve-cli-tables
safoinme Nov 25, 2025
6766bac
small fixes
safoinme Nov 25, 2025
47c6331
iason,michael review
safoinme Nov 25, 2025
8036df0
zuri review
safoinme Nov 25, 2025
349a7f9
reviews
safoinme Nov 25, 2025
503b54f
highlight syntax for json and yaml - iason review
safoinme Nov 25, 2025
0179b60
last round of review
safoinme Nov 25, 2025
5ae3a09
Merge branch 'develop' into feature/improve-cli-tables
safoinme Nov 25, 2025
e2b5b7f
update pyproject to match uv build
safoinme Nov 25, 2025
1a20b51
Merge branch 'feature/improve-cli-tables' of https://github.com/zenml…
safoinme Nov 25, 2025
6d1ed4f
iason review
safoinme Nov 25, 2025
4defc73
update dockerfiles
safoinme Nov 26, 2025
a5aaa67
fix resource process
safoinme Nov 26, 2025
88899c3
fix details tables
safoinme Nov 26, 2025
18fc1f9
Merge branch 'develop' into feature/improve-cli-tables
safoinme Nov 26, 2025
bf89631
Merge branch 'develop' into feature/improve-cli-tables
safoinme Nov 27, 2025
cd14186
Merge branch 'develop' into feature/improve-cli-tables
safoinme Nov 27, 2025
9e75e1c
fix service connector error
safoinme Nov 28, 2025
28d9da0
Merge branch 'feature/improve-cli-tables' of https://github.com/zenml…
safoinme Nov 28, 2025
7f448e6
fix output to files
safoinme Dec 1, 2025
f11d4f5
Merge branch 'develop' into feature/improve-cli-tables
safoinme Dec 1, 2025
1933cb5
Update docs/book/reference/global-settings.md
safoinme Dec 1, 2025
d2e59f7
baris review
safoinme Dec 1, 2025
62f2315
add hydrate
safoinme Dec 1, 2025
cd8d706
fix doc
safoinme Dec 1, 2025
da8d0fb
remove hydrate
safoinme Dec 1, 2025
6820e4f
Merge branch 'develop' into feature/improve-cli-tables
safoinme Dec 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docker/zenml-dev.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH"

COPY README.md pyproject.toml ./

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

# Run pip install before copying the source files to install dependencies in
# the virtual environment. Also create a requirements.txt file to keep track of
Expand Down
3 changes: 2 additions & 1 deletion docker/zenml-server-dev.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH"

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

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

# Run pip install before copying the source files to install dependencies in
# the virtual environment. Also create a requirements.txt file to keep track of
Expand Down
22 changes: 22 additions & 0 deletions docs/book/reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,28 @@ To set the path to the global config file, used by ZenML to manage and store the
export ZENML_CONFIG_PATH=/path/to/somewhere
```

## CLI output formatting

### Default output format

Set the default output format for all CLI list commands:

```bash
export ZENML_DEFAULT_OUTPUT=json
```

Choose from `table` (default), `json`, `yaml`, `csv`, or `tsv`. This applies to commands like `zenml stack list`, `zenml pipeline list`, etc.

### Terminal width override

Override the automatic terminal width detection for table rendering:

```bash
export ZENML_CLI_COLUMN_WIDTH=120
```

This is useful when running ZenML in CI/CD environments or when you want to control table formatting regardless of your terminal size.

## Server configuration

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".
Expand Down
4 changes: 4 additions & 0 deletions docs/book/reference/global-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ Using the default local database.
┗━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
```

{% hint style="info" %}
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.
{% endhint %}

The following is an example of the layout of the global config directory immediately after initialization:

```
Expand Down
59 changes: 59 additions & 0 deletions docs/book/user-guide/best-practices/quick-wins.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ micro-setup (under 5 minutes) and any tips or gotchas to anticipate.
| [Model Control Plane](#id-12-register-models-in-the-model-control-plane) | Track models and their lifecycle | Central hub for model lineage and governance |
| [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 |
| [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 |
| [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 |


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

Learn more: [Access ZenML documentation via llms.txt and MCP](https://docs.zenml.io/reference/llms-txt)

## 15 Export CLI data in multiple formats

All `zenml list` commands support multiple output formats for scripting, CI/CD integration, and data analysis.

```bash
# Get stack data as JSON for processing with jq
zenml stack list --output=json | jq '.items[] | select(.name=="production")'

# Export pipeline runs to CSV for analysis
zenml pipeline runs list --output=csv > pipeline_runs.csv

# Get deployment info as YAML for configuration management
zenml deployment list --output=yaml

# Filter columns to see only what you need
zenml stack list --columns=id,name,orchestrator

# Combine filtering with custom output formats
zenml pipeline list --columns=id,name,num_runs --output=json
```

**Available formats**
- **json** - Structured data with pagination info, perfect for programmatic processing
- **yaml** - Human-readable structured format, great for configuration
- **csv** - Comma-separated values for spreadsheets and data analysis
- **tsv** - Tab-separated values for simpler parsing
- **table** (default) - Formatted tables with colors and alignment

**Key features**
- **Column filtering** - Use `--columns` to show only the fields you need
- **Scriptable** - Combine with tools like `jq`, `grep`, `awk` for powerful automation
- **Environment control** - Set `ZENML_DEFAULT_OUTPUT` to change the default format
- **Width control** - Override terminal width with `ZENML_CLI_COLUMN_WIDTH` for consistent formatting

**Best practices**
- Use JSON format for robust parsing in scripts (includes pagination metadata)
- Use CSV/TSV for importing into spreadsheet tools or databases
- Use `--columns` to reduce noise and focus on relevant data
- Set default formats via environment variables in CI/CD environments

**Example automation script**
```bash
#!/bin/bash
# Export all production stacks to a report

export ZENML_DEFAULT_OUTPUT=json

# Get all stacks and filter for production
zenml stack list | jq '.items[] | select(.name | contains("prod"))' > prod_stacks.json

# Generate a summary CSV
zenml stack list --output=csv --columns=name,orchestrator,artifact_store > stack_summary.csv

echo "Reports generated: prod_stacks.json and stack_summary.csv"
```

Learn more: [Environment Variables](https://docs.zenml.io/reference/environment-variables#cli-output-formatting)
4 changes: 4 additions & 0 deletions docs/book/user-guide/production-guide/understand-stacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Stack 'default' with id '...' is owned by user default and is 'private'.
...
```

{% hint style="info" %}
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).
{% endhint %}

{% hint style="info" %}
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.
{% endhint %}
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
requires = ["uv_build >= 0.8.17, <0.9.0"]
build-backend = "uv_build"

[tool.uv.build-backend]
module-name = ["zenml", "zenml_cli"]

[project]
name = "zenml"
version = "0.91.2"
Expand Down Expand Up @@ -51,7 +54,7 @@ Repository = "https://github.com/zenml-io/zenml"
Issues = "https://github.com/zenml-io/zenml/issues"

[project.scripts]
zenml = "zenml.cli.cli:cli"
zenml = "zenml_cli:cli"

[project.optional-dependencies]
local = [
Expand Down
39 changes: 39 additions & 0 deletions src/zenml/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,45 @@
This syntax can also be combined to create more complex filters using the `or`
and `and` keywords.

Output formats
--------------

All ``list`` commands support multiple output formats for scripting,
CI/CD integration, and data analysis.

Use the ``--output`` (or ``-o``) option to specify the format:

```bash
# Get stack data as JSON for processing with jq
zenml stack list --output=json | jq '.items[] | select(.name=="production")'

# Export pipeline runs to CSV for analysis
zenml pipeline runs list --output=csv > pipeline_runs.csv

# Get deployment info as YAML for configuration management
zenml deployment list --output=yaml

# Filter columns to see only what you need
zenml stack list --columns=id,name,orchestrator

# Combine filtering with custom output formats
zenml pipeline list --columns=id,name --output=json
```

Available formats:

- **json** - Structured data with pagination info, ideal for programmatic use
- **yaml** - Human-readable structured format, great for configuration
- **csv** - Comma-separated values for spreadsheets and data analysis
- **tsv** - Tab-separated values for simpler parsing
- **table** (default) - Formatted tables with colors and alignment

You can also control the default output format and table width using
environment variables:

- ``ZENML_DEFAULT_OUTPUT`` - Set the default output format (e.g., ``json``)
- ``ZENML_CLI_COLUMN_WIDTH`` - Override terminal width for consistent formatting

Artifact Stores
---------------

Expand Down
5 changes: 2 additions & 3 deletions src/zenml/cli/annotator.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,8 @@ def dataset_list(annotator: "BaseAnnotator") -> None:
if not dataset_names:
cli_utils.warning("No datasets found.")
return
cli_utils.print_list_items(
list_items=dataset_names,
column_title="DATASETS",
cli_utils.print_table(
[{"DATASETS": name} for name in sorted(dataset_names)]
)

@dataset.command("stats")
Expand Down
59 changes: 34 additions & 25 deletions src/zenml/cli/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

from zenml.cli import utils as cli_utils
from zenml.cli.cli import TagGroup, cli
from zenml.cli.utils import OutputFormat, list_options
from zenml.client import Client
from zenml.console import console
from zenml.enums import CliCategories
from zenml.logger import get_logger
from zenml.models import ArtifactFilter, ArtifactVersionFilter
Expand All @@ -35,25 +37,27 @@ def artifact() -> None:
"""Commands for interacting with artifacts."""


@cli_utils.list_options(ArtifactFilter)
@artifact.command("list", help="List all artifacts.")
def list_artifacts(**kwargs: Any) -> None:
@list_options(
ArtifactFilter,
default_columns=["id", "name", "latest_version_name", "tags"],
)
def list_artifacts(
columns: str, output_format: OutputFormat, **kwargs: Any
) -> None:
"""List all artifacts.

Args:
columns: Columns to display in output.
output_format: Format for output (table/json/yaml/csv/tsv).
**kwargs: Keyword arguments to filter artifacts by.
"""
artifacts = Client().list_artifacts(**kwargs)

if not artifacts:
cli_utils.declare("No artifacts found.")
return
with console.status("Listing artifacts...\n"):
artifacts = Client().list_artifacts(**kwargs)

to_print = []
for artifact in artifacts:
to_print.append(_artifact_to_print(artifact))

cli_utils.print_table(to_print)
cli_utils.print_page(
artifacts, columns, output_format, empty_message="No artifacts found."
)


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


@cli_utils.list_options(ArtifactVersionFilter)
@version.command("list", help="List all artifact versions.")
def list_artifact_versions(**kwargs: Any) -> None:
@list_options(
ArtifactVersionFilter,
default_columns=["id", "artifact", "version", "type"],
)
def list_artifact_versions(
columns: str, output_format: OutputFormat, **kwargs: Any
) -> None:
"""List all artifact versions.

Args:
columns: Columns to display in output.
output_format: Format for output (table/json/yaml/csv/tsv).
**kwargs: Keyword arguments to filter artifact versions by.
"""
artifact_versions = Client().list_artifact_versions(**kwargs)

if not artifact_versions:
cli_utils.declare("No artifact versions found.")
return

to_print = []
for artifact_version in artifact_versions:
to_print.append(_artifact_version_to_print(artifact_version))

cli_utils.print_table(to_print)
with console.status("Listing artifact versions...\n"):
artifact_versions = Client().list_artifact_versions(**kwargs)

cli_utils.print_page(
artifact_versions,
columns,
output_format,
empty_message="No artifact versions found.",
)


@version.command("describe", help="Show details about an artifact version.")
Expand Down
33 changes: 22 additions & 11 deletions src/zenml/cli/authorized_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from zenml.cli import utils as cli_utils
from zenml.cli.cli import TagGroup, cli
from zenml.cli.utils import list_options
from zenml.cli.utils import OutputFormat, list_options
from zenml.client import Client
from zenml.console import console
from zenml.enums import CliCategories
Expand Down Expand Up @@ -59,24 +59,35 @@ def describe_authorized_device(id_or_prefix: str) -> None:
@authorized_device.command(
"list", help="List all authorized devices for the current user."
)
@list_options(OAuthDeviceFilter)
def list_authorized_devices(**kwargs: Any) -> None:
@list_options(
OAuthDeviceFilter,
default_columns=[
"id",
"status",
"expires",
"hostname",
"os",
],
)
def list_authorized_devices(
columns: str, output_format: OutputFormat, **kwargs: Any
) -> None:
"""List all authorized devices.

Args:
columns: Columns to display in output.
output_format: Format for output (table/json/yaml/csv/tsv).
**kwargs: Keyword arguments to filter authorized devices.
"""
with console.status("Listing authorized devices...\n"):
devices = Client().list_authorized_devices(**kwargs)

if not devices.items:
cli_utils.declare("No authorized devices found for this filter.")
return

cli_utils.print_pydantic_models(
devices,
columns=["id", "status", "ip_address", "hostname", "os"],
)
cli_utils.print_page(
devices,
columns,
output_format,
empty_message="No authorized devices found for this filter.",
)


@authorized_device.command("lock")
Expand Down
6 changes: 5 additions & 1 deletion src/zenml/cli/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,11 @@ def info(
write_yaml(file, user_info)
declare(f"Wrote user debug info to file at '{file_write_path}'.")
else:
cli_utils.print_user_info(user_info)
for key, value in user_info.items():
if key in ["packages", "query_packages"] and not bool(value):
continue

declare(f"{key.upper()}: {value}")

if stack:
try:
Expand Down
Loading
Loading