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
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
# Changelog

## 5.6.1 - July 30, 2026

### Dependencies & Python support

- **`toon_format` moved to an optional `toon` extra** (was a hard dependency).
The only `toon_format` release in our supported range is a pre-release
(`0.9.0b1`), and uv before 0.12 refuses pre-releases reached through another
package's metadata, so it cannot install any limacharlie version that
requires one. It backtracks silently to 5.3.0 instead, so
`uv tool install limacharlie` and `uvx limacharlie` land on the last release
predating the dependency, with `uv tool upgrade` reporting "Nothing to
upgrade". Pinning the exact pre-release does not help: those uv versions
honour pre-release specifiers on direct requirements only. uv 0.12.0 changed
its default to resolve transitive pre-releases the way pip does, so uv 0.12
and later install the newest release either way; the extra is what unblocks
everyone still on 0.11 or older. **Behavior change:** a default install no
longer supports `--output toon`. `pip install 'limacharlie[toon]'` restores
it. On uv older than 0.12, name the package directly
(`uv tool install limacharlie --with 'toon-format>=0.9.0b1'`), because asking
those versions for the extra hits the same transitive pre-release rule. The
other five output formats are unaffected. Found and fixed by
[@Nynir](https://github.com/Nynir) in #325, rebased and extended in #331
(#324).
- **`--output toon` without the encoder now fails before the command runs.**
The check moved into the CLI's argument handling, so a search no longer runs
to completion, bills the organization, and buffers every page only to find
at render time that it cannot encode the result. `--help` and shell
completion still work without the extra. The error names both install forms
(#331).

### Thanks

- [@Nynir](https://github.com/Nynir) for tracking down why uv installs were
silently stuck on 5.3.0 and sending the fix (#324, #325). The failure mode
was easy to miss from our side: uv reported success, just for a release
three months of features behind.

## 5.6.0 - July 30, 2026

### Search
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ pip install limacharlie
docker run refractionpoint/limacharlie:latest --help
```

The `toon` extra adds the TOON output format (`limacharlie --output toon`); every other format works with a default install.

```bash
pip install 'limacharlie[toon]'
```

On uv older than 0.12 the extra is not enough, because `toon_format` publishes only a pre-release and those versions resolve pre-releases for directly named requirements only. Name it directly there: `uv tool install limacharlie --with 'toon-format>=0.9.0b1'`. See [the CLI output formats guide](doc/cli/README.md#output-formats) for details.

See [Getting Started](doc/getting-started.md) for Docker credential mounting and first steps.

## Quick Start
Expand Down
14 changes: 14 additions & 0 deletions doc/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ limacharlie sensor list --output table # Rich table (default for TTY)
limacharlie sensor list --output jsonl # Newline-delimited JSON
```

`--output toon` needs the optional `toon` extra; the other formats work with a default install. Asking for TOON without it fails immediately, before the command runs.

```bash
pip install 'limacharlie[toon]'
```

On uv older than 0.12, name the package directly instead. `toon_format` only publishes a pre-release, and those uv versions resolve pre-releases for directly named requirements only, so asking them for the extra makes them fall back to an older `limacharlie`:

```bash
uv tool install limacharlie --with 'toon-format>=0.9.0b1'
```

uv 0.12.0 changed its default to resolve transitive pre-releases the way pip does, so on 0.12 and later `uv tool install 'limacharlie[toon]'` works and the `--with` form is unnecessary.

## Filtering with JMESPath

Use `--filter` with a [JMESPath](https://jmespath.org/) expression to extract or transform output. This works with every command and any output format.
Expand Down
2 changes: 2 additions & 0 deletions doc/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
pip install limacharlie
```

The `--output toon` format needs the optional `toon` extra (`pip install 'limacharlie[toon]'`); everything else works with the install above. See [Output Formats](cli/README.md#output-formats) for the uv caveat.

Docker:

```bash
Expand Down
26 changes: 25 additions & 1 deletion limacharlie/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,23 @@ def _config_no_warnings() -> bool:
return False


# Flags that make an invocation describe a command instead of running it.
# --ai-help is ours (see ai_help.py); -h/--help come from the group's
# help_option_names.
_HELP_FLAGS = frozenset({"-h", "--help", "--ai-help"})


def _wants_help() -> bool:
"""Whether this invocation only asks a command to describe itself.

Click resolves the root group's parameters before a subcommand parses its
own ``--help``, so the root callback cannot tell the two apart from its
arguments alone and has to read the command line. ``main()`` reads
``sys.argv`` the same way to decide about ``--debug``.
"""
return any(arg in _HELP_FLAGS for arg in sys.argv[1:])


# Static mapping: Click command name -> (module_name, attribute_name).
# This allows resolving any command to its module without importing it,
# enabling truly lazy per-command loading. Generated from the current
Expand Down Expand Up @@ -387,12 +404,19 @@ def cli(ctx: click.Context, oid: str | None, output_format: str | None, debug: b
# Lazy import: output pulls in jmespath, tabulate, yaml, csv (~14ms).
# Deferring to here avoids that cost for fast paths like --help, --version,
# and --ai-help that never render command output.
from .output import set_filter_expr, set_wide_mode, set_fields, set_sort_by, set_reverse
from .output import ensure_format_available, set_filter_expr, set_wide_mode, set_fields, set_sort_by, set_reverse
set_wide_mode(wide)
set_filter_expr(filter_expr)
set_fields(field_list)
set_sort_by(sort_by)
set_reverse(reverse)
# Reject a format whose encoder ships in an extra before the subcommand
# runs, rather than after it has already spent the user's time and quota.
# Help and completion must keep working without the extra: this callback
# runs before a subcommand's own --help is parsed, so describing a command
# would otherwise fail on an unrelated --output.
if not ctx.resilient_parsing and not _wants_help():
ensure_format_available(output_format)


# Inject --ai-help on the root cli group itself (subcommands get it lazily
Expand Down
31 changes: 27 additions & 4 deletions limacharlie/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@
except ImportError:
_toon_format = None

# Raised wherever TOON output is requested without the 'toon' extra. uv before
# 0.12 resolves toon_format's pre-release only when toon-format is named as a
# direct requirement, so asking those versions for the extra is not enough;
# uv 0.12 and later need only the pip form.
_MISSING_TOON_MESSAGE = (
"toon_format is required for --output toon. "
"Install with: pip install 'limacharlie[toon]'\n"
"On uv older than 0.12: uv tool install limacharlie --with 'toon-format>=0.9.0b1'"
)

# Module-level flags set by the CLI before any command runs.
_wide_mode: bool = False
_filter_expr: str | None = None
Expand Down Expand Up @@ -83,6 +93,22 @@ def detect_output_format() -> str:
return "json"


def ensure_format_available(fmt: str | None) -> None:
"""Raise ImportError if fmt needs an optional dependency that is missing.

Formats whose encoder ships in an extra can only fail once there is
something to render, which is after the command has already done its work:
a search would run to completion, bill the organization, and buffer every
page before `--output toon` discovered it had no encoder. Calling this as
the format is selected turns that into an up-front refusal.

Only 'toon' is optional; every other format is satisfied by a default
install, so any other value (including None) is a no-op.
"""
if fmt == "toon" and _toon_format is None:
raise ImportError(_MISSING_TOON_MESSAGE)


def format_output(
data: Any,
fmt: str | None = None,
Expand Down Expand Up @@ -181,10 +207,7 @@ def format_toon(data: Any) -> str:
See https://toonformat.dev for the spec.
"""
if _toon_format is None:
raise ImportError(
"toon_format is required for --output toon. "
"Install with: pip install toon_format"
)
raise ImportError(_MISSING_TOON_MESSAGE)
return _toon_format.encode(data)


Expand Down
12 changes: 11 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,17 @@ dependencies = [
"jmespath==1.1.0",
"orjson>=3.10.0",
"websockets>=13.0",
"toon_format>=0.9.0b1,<1.0",
]

[project.optional-dependencies]
# TOON is one of six opt-in --output formats, and the only toon_format release
# in our supported range is a pre-release (0.9.0b1). uv before 0.12 rejects
# pre-releases reached through another package's metadata, so it cannot install
# any version of limacharlie that requires one. Keeping toon_format optional
# lets those uv versions install us: pip install 'limacharlie[toon]'.
toon = [
"toon_format>=0.9.0b1,<1.0",
]
dev = [
# pytest 9.x carries the CVE-2025-71176 fix and requires Python >= 3.10,
# which matches our minimum supported version.
Expand All @@ -49,6 +56,9 @@ dev = [
"pytest-benchmark>=5.0.0",
# tomllib is stdlib from 3.11; Python 3.10 still needs the tomli backport.
"tomli>=1.0; python_version < '3.11'",
# Keep the optional TOON output path under test. Self-referencing the extra
# rather than repeating its requirement keeps one version range to bump.
"limacharlie[toon]",
]

[project.scripts]
Expand Down
Loading