Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions docs/commands/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ differ per command (e.g., `-p` is a short form for `--precision` only on
`config` and `quantize`); check the **Flags** section of each command page
rather than assuming they transfer.

`--no-color` is accepted by every command and disables colored output for
that invocation. Color is also auto-disabled when `NO_COLOR=1` or `CI=true`
is set in the environment.

## See also

- [How winml-cli Works](../concepts/how-it-works.md) — end-to-end pipeline overview
Expand Down
3 changes: 2 additions & 1 deletion src/winml/modelkit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from . import __version__
from .telemetry import ActionGroup
from .telemetry import telemetry as _telemetry_mod
from .utils.cli import verbosity_options
from .utils.cli import no_color_option, verbosity_options
from .utils.logging import configure_logging, flush_ort_startup_logs


Expand Down Expand Up @@ -249,6 +249,7 @@ def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) ->
)
@click.version_option(version=__version__, prog_name="winml")
@verbosity_options()
@no_color_option()
@click.option(
"--debug",
is_flag=True,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ def _build_runtime_debug_output_path(model_path: Path, ep_name: str, device_name
),
)
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@cli_utils.build_config_option()
@cli_utils.output_option("Save JSON output to file")
@cli_utils.overwrite_option(optional_message="Applies to both --output and --optim-config.")
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ def _validate_loader_tasks_for_model(
optional_message="Trust remote code for custom model architectures (e.g., Mu2)."
)
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def build(
ctx: click.Context,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
)
@cli_utils.build_config_option()
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def compile(
ctx: click.Context,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def _apply_stage_overrides(cfg: Any, *, no_quant: bool, no_compile: bool) -> Non
)
@cli_utils.trust_remote_code_option()
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def config(
ctx: click.Context,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
@cli_utils.format_option()
@cli_utils.build_config_option()
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def eval(
ctx: click.Context,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def _delete_onnx_with_external_data(onnx_path: Path) -> None:
)
@cli_utils.build_config_option()
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def export(
ctx: click.Context,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def _looks_like_local_path(model_id: str) -> bool:
help="Override model class (e.g., BertForMaskedLM) — can be used without --model",
)
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def inspect(
ctx: click.Context,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def capability_options(func: F) -> F:
help="Configuration file (YAML/JSON)",
)
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@capability_options
@click.pass_context # type: ignore[arg-type] # capability_options widens the signature; click stubs want positional-only ctx but we keep it keyword-callable for back-compat
def optimize(
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/perf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,7 @@ def _run_simple_loop(
@cli_utils.format_option()
@cli_utils.build_config_option()
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def perf(
ctx: click.Context,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/quantize.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
)
@cli_utils.build_config_option()
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def quantize(
ctx: click.Context,
Expand Down
1 change: 1 addition & 0 deletions src/winml/modelkit/commands/sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ def _output_ep_text(eps: list[dict[str, Any]]) -> None:
help="List available execution providers",
)
@cli_utils.verbosity_options()
@cli_utils.no_color_option()
@click.pass_context
def sysinfo(
ctx: click.Context,
Expand Down
29 changes: 29 additions & 0 deletions src/winml/modelkit/utils/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from __future__ import annotations

import json
import os
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar

Expand Down Expand Up @@ -514,6 +515,34 @@ def decorator(f: F) -> F:
return decorator


def no_color_option() -> Callable[[F], F]:
"""Add a ``--no-color`` flag that disables colored output.

Rich honors the ``NO_COLOR`` environment variable for every Console, so the
flag's callback just sets ``NO_COLOR=1`` for the remainder of the run — this
covers all consoles regardless of how they are constructed and matches the
existing ``NO_COLOR=1`` / ``CI=true`` environment behavior. The change lives
only in the current process, so the next invocation is colored again.

Returns:
Decorator function adding the ``--no-color`` flag (no exposed param).
"""

def _disable_color(ctx: click.Context, param: click.Parameter, value: bool) -> bool:
if value:
os.environ["NO_COLOR"] = "1"
return value

return click.option(
"--no-color",
is_flag=True,
default=False,
expose_value=False,
callback=_disable_color,
help="Disable colored output (also via NO_COLOR=1 or CI=true).",
)


def resolve_verbosity(ctx: click.Context, verbose: int, quiet: bool) -> tuple[int, bool]:
"""Merge subcommand ``--verbose``/``--quiet`` with the parent group's values.

Expand Down
40 changes: 40 additions & 0 deletions tests/unit/utils/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

import os
from typing import TYPE_CHECKING

import click
Expand All @@ -18,6 +19,7 @@
guard_output,
ignored_build_flags_warning,
max_optim_iterations_option,
no_color_option,
optimize_option,
overwrite_option,
parse_ep_options,
Expand Down Expand Up @@ -74,6 +76,44 @@ def test_empty_key_raises(self) -> None:
parse_ep_options(("=value",))


class TestNoColorOption:
"""Tests for the shared no_color_option() decorator."""

@staticmethod
def _make_cmd() -> click.Command:
@click.command()
@no_color_option()
def cmd() -> None:
click.echo("NO_COLOR" in os.environ)

return cmd

def test_no_flag_leaves_env_unset(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""Without --no-color, NO_COLOR is not set by the callback."""
monkeypatch.delenv("NO_COLOR", raising=False)
result = CliRunner().invoke(self._make_cmd(), [])
assert result.exit_code == 0
assert result.output.strip() == "False"

def test_flag_sets_no_color_env(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""--no-color sets NO_COLOR=1 so Rich disables color for the run."""
monkeypatch.delenv("NO_COLOR", raising=False)
result = CliRunner().invoke(self._make_cmd(), ["--no-color"])
assert result.exit_code == 0
assert result.output.strip() == "True"

def test_flag_not_exposed_as_param(self) -> None:
"""expose_value=False: the command takes no extra parameter."""
result = CliRunner().invoke(self._make_cmd(), ["--no-color"])
assert result.exit_code == 0

def test_help_documents_env_vars(self) -> None:
"""Help mentions the NO_COLOR / CI environment fallbacks."""
result = CliRunner().invoke(self._make_cmd(), ["--help"])
assert "--no-color" in result.output
assert "NO_COLOR" in result.output


class TestPrecisionOption:
"""Tests for the shared precision_option() decorator."""

Expand Down
Loading