fix: convert object get --filter values to the type the filter expects
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 __valueslist filters,--filterhas no list syntax todaypartial_match, which is aboolkeyword ofclient.filters()and still receives a string when passed through--filter
Closes #1417
What changed
__isnull,__is_visibleand__is_protectedfilters are sent as booleans (true,false,yes,no, case insensitive)<attribute>__valuefilters on Boolean or Checkbox attributes are sent as booleans, and on Number or NumberPool attributes as integers, based on the node schemaobject getalready loads- all other values stay strings, so
name__value=123on 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_booleanreproduces #1417, it was committed first and fails without the fix- the new
TestParseFilterArgscases 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
--filtervalue 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
--filterhelp 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.