Merge pull request 'clan_lib flake: fix handling of maybes and empty sets' (#4890) from select_fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4890
This commit is contained in:
@@ -279,11 +279,15 @@ def parse_selector(selector: str) -> list[Selector]:
|
|||||||
submode = ""
|
submode = ""
|
||||||
acc_str = ""
|
acc_str = ""
|
||||||
elif c == "}":
|
elif c == "}":
|
||||||
if submode == "maybe":
|
# Only append selector if we have accumulated content
|
||||||
set_select_type = SetSelectorType.MAYBE
|
if acc_str != "" or submode != "":
|
||||||
else:
|
if submode == "maybe":
|
||||||
set_select_type = SetSelectorType.STR
|
set_select_type = SetSelectorType.MAYBE
|
||||||
acc_selectors.append(SetSelector(type=set_select_type, value=acc_str))
|
else:
|
||||||
|
set_select_type = SetSelectorType.STR
|
||||||
|
acc_selectors.append(
|
||||||
|
SetSelector(type=set_select_type, value=acc_str)
|
||||||
|
)
|
||||||
# Check for invalid multiselect patterns with outPath
|
# Check for invalid multiselect patterns with outPath
|
||||||
for subselector in acc_selectors:
|
for subselector in acc_selectors:
|
||||||
if subselector.value == "outPath":
|
if subselector.value == "outPath":
|
||||||
@@ -554,16 +558,37 @@ class FlakeCacheEntry:
|
|||||||
return self.value[selector.value].select(selectors[1:])
|
return self.value[selector.value].select(selectors[1:])
|
||||||
|
|
||||||
# if we are a MAYBE selector, we check if the key exists in the dict
|
# 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)
|
assert isinstance(selector.value, str)
|
||||||
if selector.value in self.value:
|
if isinstance(self.value, dict):
|
||||||
if self.value[selector.value].exists:
|
if selector.value in self.value:
|
||||||
return {
|
if self.value[selector.value].exists:
|
||||||
selector.value: self.value[selector.value].select(selectors[1:])
|
return {
|
||||||
}
|
selector.value: self.value[selector.value].select(
|
||||||
|
selectors[1:]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
# Key not found, return empty dict for MAYBE selector
|
||||||
return {}
|
return {}
|
||||||
if self.fetched_all:
|
# 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 {}
|
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
|
# otherwise we return a list or a dict
|
||||||
if isinstance(self.value, dict):
|
if isinstance(self.value, dict):
|
||||||
@@ -590,13 +615,24 @@ class FlakeCacheEntry:
|
|||||||
|
|
||||||
# if we are a list, return a list
|
# if we are a list, return a list
|
||||||
if self.is_list:
|
if self.is_list:
|
||||||
result = []
|
result_list: list[Any] = []
|
||||||
for index in keys_to_select:
|
for index in keys_to_select:
|
||||||
result.append(self.value[index].select(selectors[1:]))
|
result_list.append(self.value[index].select(selectors[1:]))
|
||||||
return result
|
return result_list
|
||||||
|
|
||||||
# otherwise return a dict
|
# 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
|
# return a KeyError if we cannot fetch the key
|
||||||
str_selector: str
|
str_selector: str
|
||||||
|
|||||||
@@ -157,6 +157,21 @@ def test_select() -> None:
|
|||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
test_cache.select(selectors)
|
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:
|
def test_out_path() -> None:
|
||||||
testdict = {"x": {"y": [123, 345, 456], "z": "/nix/store/abc-bla"}}
|
testdict = {"x": {"y": [123, 345, 456], "z": "/nix/store/abc-bla"}}
|
||||||
|
|||||||
Reference in New Issue
Block a user