clan_lib flake: fix handling of maybes and empty sets

This commit is contained in:
lassulus
2025-08-22 23:16:18 +02:00
parent 8904cf27a4
commit 4dc90b3d39
2 changed files with 67 additions and 16 deletions

View File

@@ -279,11 +279,15 @@ def parse_selector(selector: str) -> list[Selector]:
submode = ""
acc_str = ""
elif c == "}":
# Only append selector if we have accumulated content
if acc_str != "" or submode != "":
if submode == "maybe":
set_select_type = SetSelectorType.MAYBE
else:
set_select_type = SetSelectorType.STR
acc_selectors.append(SetSelector(type=set_select_type, value=acc_str))
acc_selectors.append(
SetSelector(type=set_select_type, value=acc_str)
)
# Check for invalid multiselect patterns with outPath
for subselector in acc_selectors:
if subselector.value == "outPath":
@@ -554,16 +558,37 @@ class FlakeCacheEntry:
return self.value[selector.value].select(selectors[1:])
# if we are a MAYBE selector, we check if the key exists in the dict
if selector.type == SelectorType.MAYBE and isinstance(self.value, dict):
if selector.type == SelectorType.MAYBE:
assert isinstance(selector.value, str)
if isinstance(self.value, dict):
if selector.value in self.value:
if self.value[selector.value].exists:
return {
selector.value: self.value[selector.value].select(selectors[1:])
selector.value: self.value[selector.value].select(
selectors[1:]
)
}
return {}
if self.fetched_all:
# Key not found, return empty dict for MAYBE selector
return {}
# Non-dict value (including None), return empty dict for MAYBE selector
return {}
# Handle SET selector on non-dict values
if selector.type == SelectorType.SET and not isinstance(self.value, dict):
assert isinstance(selector.value, list)
# Empty set or all sub-selectors are MAYBE
if len(selector.value) == 0:
# Empty set, return empty dict
return {}
all_maybe = all(
subselector.type == SetSelectorType.MAYBE
for subselector in selector.value
)
if all_maybe:
# All sub-selectors are MAYBE, return empty dict for non-dict values
return {}
# Not all sub-selectors are MAYBE, fall through to raise KeyError
# otherwise we return a list or a dict
if isinstance(self.value, dict):
@@ -590,13 +615,24 @@ class FlakeCacheEntry:
# if we are a list, return a list
if self.is_list:
result = []
result_list: list[Any] = []
for index in keys_to_select:
result.append(self.value[index].select(selectors[1:]))
return result
result_list.append(self.value[index].select(selectors[1:]))
return result_list
# otherwise return a dict
return {k: self.value[k].select(selectors[1:]) for k in keys_to_select}
result_dict: dict[str, Any] = {}
for key in keys_to_select:
value = self.value[key].select(selectors[1:])
if self.value[key].exists:
# Skip empty dicts when the original value is None
if not (
isinstance(value, dict)
and len(value) == 0
and self.value[key].value is None
):
result_dict[key] = value
return result_dict
# return a KeyError if we cannot fetch the key
str_selector: str

View File

@@ -157,6 +157,21 @@ def test_select() -> None:
with pytest.raises(KeyError):
test_cache.select(selectors)
testdict2 = {"x": {"y": {"a": 1, "b": 2}, "z": None}, "n": {}}
test_cache.insert(testdict2, parse_selector("testdict2"))
assert test_cache.select(parse_selector("testdict2.n")) == {}
assert test_cache.select(parse_selector("testdict2.?n")) == {"n": {}}
assert test_cache.select(parse_selector("testdict2.x.*.?a")) == {"y": {"a": 1}}
assert test_cache.select(parse_selector("testdict2.x.z.?a")) == {}
assert test_cache.select(parse_selector("testdict2.x.z.{?a}")) == {}
assert test_cache.select(parse_selector("testdict2.x.z.{}")) == {}
with pytest.raises(KeyError):
test_cache.select(parse_selector("testdict2.x.z.a"))
with pytest.raises(KeyError):
test_cache.select(parse_selector("testdict2.x.z.{a}"))
with pytest.raises(KeyError):
test_cache.select(parse_selector("testdict2.x.z.{a,?b}"))
def test_out_path() -> None:
testdict = {"x": {"y": [123, 345, 456], "z": "/nix/store/abc-bla"}}