fix: convert object get --filter values to the type the filter expects

LA
25d ago
+140-6

Why

infrahubctl object get --filter sends every filter value to Infrahub as a string. Filters whose GraphQL argument is not a string fail, for example --filter interfaces__isnull=true returns Boolean cannot represent a non boolean value: "true". The same filter works through client.filters(..., interfaces__isnull=True).

The goal is that --filter sends boolean and integer values with the type the filter expects, so these filters work from the CLI the same way they work from the SDK.

Non-goals:

  • peer attribute filters (<relationship>__<peer_attribute>__value) are still sent as strings, typing them would need the peer schema as well
  • __values list filters, --filter has no list syntax today
  • partial_match, which is a bool keyword of client.filters() and still receives a string when passed through --filter

Closes #1417

What changed

  • __isnull, __is_visible and __is_protected filters are sent as booleans (true, false, yes, no, case insensitive)
  • <attribute>__value filters on Boolean or Checkbox attributes are sent as booleans, and on Number or NumberPool attributes as integers, based on the node schema object get already loads
  • all other values stay strings, so name__value=123 on a Text attribute is still sent as "123"
  • a value that cannot be converted (for example interfaces__isnull=maybe) now fails in the CLI with a message naming the filter and the expected type, instead of a 500 from the server

parse_filter_args() gets an optional schema argument, so existing callers keep working. The true/false keyword map is now shared with --set parsing, the --set behaviour does not change.

How to review

The change is in infrahub_sdk/ctl/parsers.py, the one-line call site change is in infrahub_sdk/ctl/object/get.py.

The part I would like a second opinion on is the decision to type values from the filter suffix and the attribute kind, instead of running _coerce_value() on every value like --set does. Coercing everything would turn name__value=123 into an integer and break String filters the opposite way.

How to test

uv run pytest tests/unit/ctl/object/test_get.py tests/unit/ctl/test_parsers.py
  • test_get_list_mode_isnull_filter_passes_boolean reproduces #1417, it was committed first and fails without the fix
  • the new TestParseFilterArgs cases cover the boolean suffixes, Boolean and Number attribute values, Text and peer filter values that must stay strings, and invalid values

Locally invoke format, invoke lint-code and invoke docs-validate pass. I have not run the fixed command against a live Infrahub instance, the tests check the values passed to client.filters().

Impact & rollout

  • Backward compatibility: no breaking change. Filters that worked before still send the same values, only filters that the server rejected before now send typed values. A --filter value that cannot be converted for a boolean or integer filter now fails in the CLI instead of on the server.

Checklist

  • Tests added/updated
  • Changelog entry added (uv run towncrier create ...)
  • External docs updated (if user-facing or ops-facing change), not needed, the --filter help text and the generated CLI docs do not change
  • Internal .md docs updated (internal knowledge and AI code tools knowledge), not needed

Summary by cubic

Fixes infrahubctl object get --filter sending boolean and integer filter values as strings, which made filters like interfaces__isnull=true fail with a GraphQL type error. Values for __isnull, __is_visible, __is_protected, and __value filters on Boolean/Number attributes are now converted to the expected type based on the suffix and the node schema; all other values stay stringsesth.

Backward compatibility

  • No breaking change: filters that worked before still send the same values.
  • Values that cannot be converted now fail in the CLI with a clear error naming the filter and expected type, instead of a 500 from the server.