Skip to content

Commit e609282

Browse files
committed
update
1 parent 8e1ef65 commit e609282

5 files changed

Lines changed: 62 additions & 43 deletions

File tree

src/tirith/providers/common.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pydash
22

3-
from typing import Dict
3+
from typing import Dict, Any
44

55

66
def create_result_dict(value=None, meta=None, err=None) -> Dict:
@@ -11,7 +11,7 @@ class PydashPathNotFound:
1111
pass
1212

1313

14-
def _get_path_value_from_dict_internal(splitted_paths, input_data, place_none_if_not_found=False):
14+
def _get_path_value_from_input_internal(splitted_paths, input_data, place_none_if_not_found=False):
1515

1616
if not splitted_paths:
1717
return [input_data] if input_data is not PydashPathNotFound else ([None] if place_none_if_not_found else [])
@@ -25,14 +25,14 @@ def _get_path_value_from_dict_internal(splitted_paths, input_data, place_none_if
2525
if isinstance(input_data, list):
2626
for item in input_data:
2727
if remaining_paths:
28-
results = _get_path_value_from_dict_internal(remaining_paths, item, place_none_if_not_found)
28+
results = _get_path_value_from_input_internal(remaining_paths, item, place_none_if_not_found)
2929
final_data.extend(results)
3030
else:
3131
final_data.append(item)
3232
elif isinstance(input_data, dict):
3333
for value in input_data.values():
3434
if remaining_paths:
35-
results = _get_path_value_from_dict_internal(remaining_paths, value, place_none_if_not_found)
35+
results = _get_path_value_from_input_internal(remaining_paths, value, place_none_if_not_found)
3636
final_data.extend(results)
3737
else:
3838
final_data.append(value)
@@ -56,17 +56,17 @@ def _get_path_value_from_dict_internal(splitted_paths, input_data, place_none_if
5656
# Skip the wildcard marker since iteration is implicit for lists
5757
paths_to_apply = remaining_paths[1:]
5858
for val in intermediate_val:
59-
results = _get_path_value_from_dict_internal(paths_to_apply, val, place_none_if_not_found)
59+
results = _get_path_value_from_input_internal(paths_to_apply, val, place_none_if_not_found)
6060
final_data.extend(results)
6161
elif isinstance(intermediate_val, dict) and remaining_paths[0] == "":
6262
# If it's a dict and next path is a wildcard, iterate over dict values
6363
# Skip the wildcard marker and apply remaining paths to each value
6464
for value in intermediate_val.values():
65-
results = _get_path_value_from_dict_internal(remaining_paths[1:], value, place_none_if_not_found)
65+
results = _get_path_value_from_input_internal(remaining_paths[1:], value, place_none_if_not_found)
6666
final_data.extend(results)
6767
else:
6868
# For non-wildcard paths, continue traversal without iteration
69-
results = _get_path_value_from_dict_internal(remaining_paths, intermediate_val, place_none_if_not_found)
69+
results = _get_path_value_from_input_internal(remaining_paths, intermediate_val, place_none_if_not_found)
7070
final_data.extend(results)
7171
else:
7272
# This is the final path segment
@@ -75,15 +75,16 @@ def _get_path_value_from_dict_internal(splitted_paths, input_data, place_none_if
7575
return final_data
7676

7777

78-
def get_path_value_from_dict(key_path: str, input_dict: dict, place_none_if_not_found: bool = False):
78+
def get_path_value_from_input(key_path: str, input: Any, place_none_if_not_found: bool = False):
7979
"""
80-
Retrieve values from a nested dictionary using a path expression with wildcard support.
80+
Retrieve values from a nested data structure using a path expression with wildcard support.
8181
82-
:param key_path: A dot-separated path to traverse the dictionary.
83-
Use ``*.`` for wildcards to match all items at that level.
82+
:param key_path: A dot-separated path to traverse the data structure.
83+
Use ``*`` for wildcard to match all items at that level.
84+
Supports nested structures including dictionaries, lists, and primitives.
8485
:type key_path: str
85-
:param input_dict: The input dictionary to search through.
86-
:type input_dict: dict
86+
:param input: The input data structure to search through (dict, list, or primitive).
87+
:type input: Any
8788
:param place_none_if_not_found: If True, returns [None] when a path is not found.
8889
If False, returns an empty list []. Defaults to False.
8990
:type place_none_if_not_found: bool
@@ -96,38 +97,57 @@ def get_path_value_from_dict(key_path: str, input_dict: dict, place_none_if_not_
9697
Basic path traversal::
9798
9899
>>> data = {"user": {"name": "Alice", "age": 30}}
99-
>>> get_path_value_from_dict("user.name", data)
100+
>>> get_path_value_from_input("user.name", data)
100101
["Alice"]
101102
102103
Wildcard with list items::
103104
104105
>>> data = {"users": [{"name": "Alice"}, {"name": "Bob"}]}
105-
>>> get_path_value_from_dict("users.*.name", data)
106+
>>> get_path_value_from_input("users.*.name", data)
106107
["Alice", "Bob"]
107108
108109
Wildcard with dictionary values::
109110
110111
>>> data = {"countries": {"US": {"capital": "Washington"}, "UK": {"capital": "London"}}}
111-
>>> get_path_value_from_dict("countries.*.capital", data)
112+
>>> get_path_value_from_input("countries.*.capital", data)
112113
["Washington", "London"]
113114
114-
Leading wildcard::
115+
Leading wildcard on lists::
115116
116117
>>> data = [{"name": "Alice"}, {"name": "Bob"}]
117-
>>> get_path_value_from_dict("*.name", data)
118+
>>> get_path_value_from_input("*.name", data)
118119
["Alice", "Bob"]
119120
121+
Wildcard on primitives::
122+
123+
>>> get_path_value_from_input("*", 42)
124+
[42]
125+
>>> get_path_value_from_input("*", "hello")
126+
["hello"]
127+
128+
Multiple wildcards::
129+
130+
>>> data = {"groups": [[{"id": 1}, {"id": 2}], [{"id": 3}]]}
131+
>>> get_path_value_from_input("groups.*.*.id", data)
132+
[1, 2, 3]
133+
134+
Empty path returns input as-is::
135+
136+
>>> data = {"key": "value"}
137+
>>> get_path_value_from_input("", data)
138+
[{"key": "value"}]
139+
120140
Path not found behavior::
121141
122142
>>> data = {"user": {"name": "Alice"}}
123-
>>> get_path_value_from_dict("missing.path", data)
143+
>>> get_path_value_from_input("missing.path", data)
124144
[]
125-
>>> get_path_value_from_dict("missing.path", data, place_none_if_not_found=True)
145+
>>> get_path_value_from_input("missing.path", data, place_none_if_not_found=True)
126146
[None]
127147
"""
128148
# Handle empty path - return the input data as is
129149
if not key_path:
130-
return [input_dict]
150+
return [input]
131151

132152
# Split the path by dots and replace '*' with empty string to mark wildcards
133153
# Empty strings act as markers to iterate over collections (lists or dict values)
@@ -137,7 +157,7 @@ def get_path_value_from_dict(key_path: str, input_dict: dict, place_none_if_not_
137157
splitted_attribute = key_path.split(".")
138158
splitted_attribute = ["" if part == "*" else part for part in splitted_attribute]
139159

140-
return _get_path_value_from_dict_internal(splitted_attribute, input_dict, place_none_if_not_found)
160+
return _get_path_value_from_input_internal(splitted_attribute, input, place_none_if_not_found)
141161

142162

143163
class ProviderError:

src/tirith/providers/json/handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from typing import Callable, Dict, List
2-
from ..common import create_result_dict, ProviderError, get_path_value_from_dict
2+
from ..common import create_result_dict, ProviderError, get_path_value_from_input
33

44

55
def get_value(provider_args: Dict, input_data: Dict) -> List[dict]:
66
# Must be validated first whether the provider args are valid for this op type
77
key_path: str = provider_args["key_path"]
88

9-
values = get_path_value_from_dict(key_path, input_data)
9+
values = get_path_value_from_input(key_path, input_data)
1010

1111
if len(values) == 0:
1212
severity_value = 2

src/tirith/providers/kubernetes/handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pydash
22

33
from typing import Callable, Dict, List
4-
from ..common import create_result_dict, ProviderError, get_path_value_from_dict
4+
from ..common import create_result_dict, ProviderError, get_path_value_from_input
55

66

77
def get_value(provider_args: Dict, input_data: Dict, outputs: list) -> Dict:
@@ -21,7 +21,7 @@ def get_value(provider_args: Dict, input_data: Dict, outputs: list) -> Dict:
2121
if resource["kind"] != target_kind:
2222
continue
2323
is_kind_found = True
24-
values = get_path_value_from_dict(attribute_path, resource, place_none_if_not_found=True)
24+
values = get_path_value_from_input(attribute_path, resource, place_none_if_not_found=True)
2525
if ".*." not in attribute_path:
2626
# If there's no * in the attribute path, the values always have 1 member
2727
values = values[0]

tests/providers/kubernetes/test_attribute.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import os
32

43
from tirith.core.core import start_policy_evaluation
@@ -10,4 +9,4 @@ def test_get_value():
109
policy_path = os.path.join(test_dir, "policy.json")
1110

1211
result = start_policy_evaluation(policy_path=policy_path, input_path=input_path)
13-
assert result["final_result"] == False
12+
assert result["final_result"] is False

tests/providers/test_common.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
from tirith.providers.common import get_path_value_from_dict
2+
from tirith.providers.common import get_path_value_from_input
33

44

55
# Test data for simple path access
@@ -91,28 +91,28 @@
9191
@pytest.mark.parametrize("data,path,expected", simple_path_cases)
9292
def test_simple_path_access(data, path, expected):
9393
"""Test basic path traversal without wildcards"""
94-
result = get_path_value_from_dict(path, data)
94+
result = get_path_value_from_input(path, data)
9595
assert result == expected
9696

9797

9898
@pytest.mark.parametrize("data,path,expected", wildcard_list_cases)
9999
def test_wildcard_with_list(data, path, expected):
100100
"""Test wildcard with list of dictionaries"""
101-
result = get_path_value_from_dict(path, data)
101+
result = get_path_value_from_input(path, data)
102102
assert result == expected
103103

104104

105105
@pytest.mark.parametrize("data,path,expected_set", wildcard_dict_cases)
106106
def test_wildcard_with_dict(data, path, expected_set):
107107
"""Test wildcard with dictionary values (order-independent)"""
108-
result = get_path_value_from_dict(path, data)
108+
result = get_path_value_from_input(path, data)
109109
assert set(result) == expected_set
110110

111111

112112
@pytest.mark.parametrize("data,path,expected", multiple_wildcard_cases)
113113
def test_multiple_wildcards(data, path, expected):
114114
"""Test multiple wildcards in the path"""
115-
result = get_path_value_from_dict(path, data)
115+
result = get_path_value_from_input(path, data)
116116
if isinstance(expected, set):
117117
assert set(result) == expected
118118
else:
@@ -122,28 +122,28 @@ def test_multiple_wildcards(data, path, expected):
122122
@pytest.mark.parametrize("data,path,expected", path_not_found_cases)
123123
def test_path_not_found_default(data, path, expected):
124124
"""Test that non-existent path returns empty list by default"""
125-
result = get_path_value_from_dict(path, data)
125+
result = get_path_value_from_input(path, data)
126126
assert result == expected
127127

128128

129129
@pytest.mark.parametrize("data,path,flag,expected", path_not_found_with_flag_cases)
130130
def test_path_not_found_with_flag(data, path, flag, expected):
131131
"""Test that non-existent path returns [None] when flag is True"""
132-
result = get_path_value_from_dict(path, data, place_none_if_not_found=flag)
132+
result = get_path_value_from_input(path, data, place_none_if_not_found=flag)
133133
assert result == expected
134134

135135

136136
@pytest.mark.parametrize("data,path,expected", special_value_cases)
137137
def test_special_values(data, path, expected):
138138
"""Test handling of special values like None and empty paths"""
139-
result = get_path_value_from_dict(path, data)
139+
result = get_path_value_from_input(path, data)
140140
assert result == expected
141141

142142

143143
@pytest.mark.parametrize("data,path,expected", complex_structure_cases)
144144
def test_complex_nested_structure(data, path, expected):
145145
"""Test complex real-world-like nested structures"""
146-
result = get_path_value_from_dict(path, data)
146+
result = get_path_value_from_input(path, data)
147147
assert result == expected
148148

149149

@@ -171,22 +171,22 @@ def test_complex_nested_structure(data, path, expected):
171171
@pytest.mark.parametrize("data,path,expected", edge_case_wildcard_cases)
172172
def test_edge_case_wildcards(data, path, expected):
173173
"""Test edge cases with wildcards like missing intermediate paths and partial matches"""
174-
result = get_path_value_from_dict(path, data)
174+
result = get_path_value_from_input(path, data)
175175
assert result == expected
176176

177177

178178
@pytest.mark.parametrize("data,path,flag,expected", empty_container_cases)
179179
def test_empty_containers(data, path, flag, expected):
180180
"""Test empty containers return empty list"""
181-
result = get_path_value_from_dict(path, data, place_none_if_not_found=flag)
181+
result = get_path_value_from_input(path, data, place_none_if_not_found=flag)
182182
assert result == expected
183183

184184

185185
@pytest.mark.parametrize("data,path,flag,expected", empty_container_with_flag_cases)
186186
def test_empty_containers_with_flag(data, path, flag, expected):
187187
"""Test empty containers with place_none_if_not_found flag"""
188188
# Empty containers don't trigger the flag since they exist but are empty
189-
result = get_path_value_from_dict(path, data, place_none_if_not_found=flag)
189+
result = get_path_value_from_input(path, data, place_none_if_not_found=flag)
190190
assert result == expected
191191

192192

@@ -226,19 +226,19 @@ def test_empty_containers_with_flag(data, path, flag, expected):
226226
@pytest.mark.parametrize("data,path,expected", wildcard_list_no_remaining_cases)
227227
def test_wildcard_list_no_remaining_paths(data, path, expected):
228228
"""Test wildcard at the end of path with list and no remaining paths - covers lines 31-32"""
229-
result = get_path_value_from_dict(path, data)
229+
result = get_path_value_from_input(path, data)
230230
assert result == expected
231231

232232

233233
@pytest.mark.parametrize("data,path,expected_set", wildcard_dict_no_remaining_cases)
234234
def test_wildcard_dict_no_remaining_paths(data, path, expected_set):
235235
"""Test wildcard at the end of path with dict and no remaining paths - covers lines 38-39"""
236-
result = get_path_value_from_dict(path, data)
236+
result = get_path_value_from_input(path, data)
237237
assert set(result) == expected_set
238238

239239

240240
@pytest.mark.parametrize("data,path,expected", wildcard_primitive_cases)
241241
def test_wildcard_primitive_no_remaining_paths(data, path, expected):
242242
"""Test wildcard applied to primitive value with no remaining paths - covers lines 42-43"""
243-
result = get_path_value_from_dict(path, data)
243+
result = get_path_value_from_input(path, data)
244244
assert result == expected

0 commit comments

Comments
 (0)