deltafi 0.109.0__py3-none-any.whl → 2.40.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ #
2
+ # DeltaFi - Data transformation and enrichment platform
3
+ #
4
+ # Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ #
@@ -0,0 +1,56 @@
1
+ #
2
+ # DeltaFi - Data transformation and enrichment platform
3
+ #
4
+ # Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ from typing import Dict
20
+
21
+ from .constants import IGNORE_VALUE
22
+
23
+
24
+ def assert_equal(e, a):
25
+ assert e == a, f"{e} != {a}"
26
+
27
+
28
+ def assert_equal_with_label(e, a, l):
29
+ assert e == a, f"{l}. Expected:\n<<{e}>>\nBut was:\n<<{a}>>"
30
+
31
+
32
+ def assert_equal_short(e, a, l):
33
+ assert e == a, f"{l}. E:{e}, A::{a}"
34
+
35
+
36
+ def assert_equal_len(e, a):
37
+ assert len(e) == len(a), f"{len(e)} != {len(a)}"
38
+
39
+
40
+ def assert_equal_len_with_label(e, a, l):
41
+ assert len(e) == len(a), f"{l}. {len(e)} != {len(a)}"
42
+
43
+
44
+ def assert_key_in(k, m):
45
+ assert k in m, f"{k} not found"
46
+
47
+
48
+ def assert_key_not_in(k, m):
49
+ assert k not in m, f"{k} found, but not expected"
50
+
51
+
52
+ def assert_keys_and_values(expected: Dict, actual: Dict):
53
+ for key in expected:
54
+ assert_key_in(key, actual)
55
+ if expected[key] != IGNORE_VALUE:
56
+ assert_equal_short(expected[key], actual[key], f"invalid value for key {key}")
@@ -0,0 +1,293 @@
1
+ #
2
+ # DeltaFi - Data transformation and enrichment platform
3
+ #
4
+ # Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ import json
20
+ import re
21
+ from abc import ABC
22
+ from abc import abstractmethod
23
+ from itertools import repeat
24
+
25
+ from deepdiff import DeepDiff, DeepSearch
26
+
27
+ from .assertions import *
28
+
29
+
30
+ class CompareHelper(ABC):
31
+ @abstractmethod
32
+ def compare(self, expected: str, actual: str, label: str):
33
+ pass
34
+
35
+
36
+ class GenericCompareHelper(CompareHelper):
37
+ def compare(self, expected: str, actual: str, label: str):
38
+ assert_equal_with_label(expected, actual, label)
39
+
40
+
41
+ class JsonCompareHelper(CompareHelper):
42
+ """Provides helper functions for comparing JSON/dict objects.
43
+
44
+ Are these two JSON/dict objects equivalent?
45
+ - compare(...)
46
+
47
+ Are these two lists equivalent?
48
+ - compare_lists(...)
49
+
50
+ Is this list a subset/superset of that list?
51
+ - compare_lists_subset(...)
52
+ - compare_lists_superset(...)
53
+
54
+ Select a list of values from an existing list and put them into a new list to facilitate list comparisons:
55
+ - create_list_from_list_using_filter_regex(...)
56
+
57
+ Is this value found (or not) in this JSON/dict object?
58
+ - is_found(...)
59
+ - is_not_found(...)
60
+ """
61
+
62
+ def __init__(self, regex_exclusion_list=None, ignore_order=True):
63
+ """Creates and configures a JsonCompareHelper object. If the optional 'ignore_order' is true, then the order of
64
+ data is ignored when checking else order is enforced. The optional 'regex_exclusion_list' excludes
65
+ paths within the object from comparison; if empty or not provided, then no excludes are applied."""
66
+ if regex_exclusion_list is None:
67
+ regex_exclusion_list = []
68
+ self.excludes = regex_exclusion_list
69
+ self.ignore_order = ignore_order
70
+
71
+ def __perform_find(self, obj: object, item):
72
+ """Returns a dict of matches of the 'item' in the object 'obj'. Both keys and values of dicts are included in
73
+ the search. The item may be compiled regex pattern or a string that compiles to a regex pattern. The
74
+ returned dict is empty if there are no matches. Excludes path(s) determined by the constructor."""
75
+ return DeepSearch(obj, item, verbose_level=2, exclude_regex_paths=self.excludes, use_regexp=True)
76
+
77
+ def is_not_found(self, obj: object, item):
78
+ """Returns None if there are no occurrences of 'item' in object 'obj' else raises a ValueError. Both keys and
79
+ values of dicts are included in the search. If 'item' is a list, then all elements of item must not be
80
+ found in list, else a ValueError is raised. The argument 'item' may be a compiled regex pattern, a
81
+ string that compiles to a regex pattern, or a list of either or both. Excludes path(s) and failure on
82
+ ordering of elements are determined by the constructor."""
83
+
84
+ all_matches = []
85
+
86
+ if isinstance(item, list):
87
+ for value in item:
88
+ matches = self.__perform_find(obj, value)
89
+ if len(matches) > 0:
90
+ all_matches.append(matches)
91
+ else:
92
+ matches = self.__perform_find(obj, item)
93
+ if len(matches) > 0:
94
+ all_matches.append(matches)
95
+
96
+ if len(all_matches) > 0:
97
+ raise ValueError("Matches found for items '" + f"{all_matches}" + "'")
98
+
99
+ assert len(all_matches) == 0
100
+
101
+ def is_found(self, obj: object, item):
102
+ """Returns None if 'item' occurs in object 'obj' else raises a ValueError. Both keys and values of dicts are
103
+ included in the search. If 'item' is a list, then all elements of item must occur in the object else a
104
+ ValueError is returned. The argument 'item' may be a compiled regex pattern, a string that compiles to
105
+ a regex pattern, or a list of either or both. Excludes path(s) and failure on ordering of elements are
106
+ determined by the constructor."""
107
+
108
+ not_found_items = []
109
+
110
+ if isinstance(item, list):
111
+ for value in item:
112
+ matches = self.__perform_find(obj, value)
113
+ if len(matches) == 0:
114
+ not_found_items.append(value)
115
+ else:
116
+ matches = self.__perform_find(obj, item)
117
+ if len(matches) == 0:
118
+ not_found_items.append(item)
119
+
120
+ if len(not_found_items) > 0:
121
+ raise ValueError("No matches found for items '" + f"{not_found_items}" + "'")
122
+
123
+ assert len(not_found_items) == 0
124
+
125
+ def __perform_diff(self, expected, actual):
126
+ """Returns a dict with differences between 'expected' and 'actual'. The returned dict is empty if 'expected'
127
+ and 'actual' are equivalent. Both 'expected' and 'actual' must be dicts. Excludes path and failure on
128
+ ordering of elements are determined by the constructor. Elements must match number of repetitions."""
129
+ return DeepDiff(expected, actual, ignore_order=self.ignore_order, report_repetition=True,
130
+ exclude_regex_paths=self.excludes)
131
+
132
+ def __perform_diff_with_eval(self, expected, actual):
133
+ """Returns None if 'expected' and 'actual' are equivalent else returns a ValueError. Both 'expected' and
134
+ 'actual' must be dicts. Excludes path and failure on ordering of elements are determined by the
135
+ constructor. Elements must match number of repetitions."""
136
+
137
+ diffs = self.__perform_diff(expected, actual)
138
+
139
+ if len(diffs) > 0:
140
+ raise ValueError(f"{diffs}")
141
+
142
+ assert len(diffs) == 0
143
+
144
+ def compare(self, expected, actual, label: str):
145
+ """Returns None if 'expected' and 'actual' are equivalent else returns a ValueError. Both 'expected' and
146
+ 'actual' must be either dicts or strings that parse as JSON to dicts. Excludes path and failure on
147
+ ordering of elements are determined by the constructor. Elements must match number of repetitions."""
148
+
149
+ if isinstance(expected, str):
150
+ exp = json.loads(expected)
151
+ else:
152
+ exp = expected
153
+
154
+ if isinstance(actual, str):
155
+ act = json.loads(actual)
156
+ else:
157
+ act = actual
158
+
159
+ return self.__perform_diff_with_eval(exp, act)
160
+
161
+ def compare_lists(self, expected, actual: list, label: str):
162
+ """Returns None if 'actual' is equivalent to 'expected' else returns a ValueError.
163
+
164
+ The 'actual' argument must be a list. The 'expected' argument may be a list or a dict. If a list, then
165
+ 'expected' and 'actual' are compared against each other. If a dict, then 'actual' is equivalent if it
166
+ contains elements with the same repetitions as defined in the 'expected' dict with the key equal to the
167
+ element in 'actual' and the value equal to the number of repetitions.
168
+
169
+ Order of elements is ignored. Elements must match number of repetitions."""
170
+
171
+ expected_list = []
172
+
173
+ if isinstance(expected, dict):
174
+ for key, value in expected.items():
175
+ expected_list += list(repeat(key, value))
176
+ else:
177
+ expected_list = expected
178
+
179
+ return self.__perform_diff_with_eval({"json-compare-helper-internal-list": expected_list},
180
+ {"json-compare-helper-internal-list": actual})
181
+
182
+ @staticmethod
183
+ def compare_lists_subset(expected_subset, actual: list):
184
+ """Returns None if the 'actual' list contains at least 'expected_subset' else returns a ValueError. The
185
+ 'actual' list may contain 0 or more additional elements than defined in 'expected_subset'.
186
+
187
+ The 'actual' argument must be a list. The argument 'expected_subset' may be a list or a dict. In the
188
+ latter case, the key defines the item that must appear in 'actual' and the value defines the number of
189
+ repetitions.
190
+
191
+ Order of elements is ignored. Elements must match number of repetitions."""
192
+
193
+ expected_subset_map = {}
194
+
195
+ if isinstance(expected_subset, list):
196
+ for item in expected_subset:
197
+ value = expected_subset_map.get(item)
198
+ if value is None:
199
+ expected_subset_map[item] = 1
200
+ else:
201
+ expected_subset_map[item] = value + 1
202
+ else:
203
+ expected_subset_map = expected_subset
204
+
205
+ actual_map = {}
206
+
207
+ for item in actual:
208
+ value = actual_map.get(item)
209
+ if value is None:
210
+ actual_map[item] = 1
211
+ else:
212
+ actual_map[item] = value + 1
213
+
214
+ for key, value in expected_subset_map.items():
215
+ actual_value = actual_map.get(key)
216
+ if actual_value is None:
217
+ raise ValueError("Actual list did not contain element '" + str(key) + "'")
218
+ else:
219
+ if actual_value != value:
220
+ raise ValueError("Actual list had item '" + str(key) + "' with repetition " + str(actual_value)
221
+ + " but required repetition " + str(value))
222
+
223
+ return None
224
+
225
+ @staticmethod
226
+ def compare_lists_superset(expected_superset, actual: list):
227
+ """Returns None if the 'actual' list contains only elements that appear in 'expected_superset' else returns a
228
+ ValueError. The 'actual' list cannot contain more elements than are defined in 'expected_superset'; the
229
+ 'expected_superset' may define 0 or more values not contained in 'actual'.
230
+
231
+ The 'actual' argument must be a list. The argument 'expected_superset' may be a list or a dict. In the
232
+ latter case, the key defines the item that must appear in 'actual' and the value defines the number of
233
+ repetitions.
234
+
235
+ Order of elements is ignored. Elements must match number of repetitions."""
236
+
237
+ expected_superset_map = {}
238
+
239
+ if isinstance(expected_superset, list):
240
+ for item in expected_superset:
241
+ value = expected_superset_map.get(item)
242
+ if value is None:
243
+ expected_superset_map[item] = 1
244
+ else:
245
+ expected_superset_map[item] = value + 1
246
+ else:
247
+ expected_superset_map = expected_superset
248
+
249
+ actual_map = {}
250
+
251
+ for item in actual:
252
+ value = actual_map.get(item)
253
+ if value is None:
254
+ actual_map[item] = 1
255
+ else:
256
+ actual_map[item] = value + 1
257
+
258
+ for key, value in actual_map.items():
259
+ expected_value = expected_superset_map.get(key)
260
+ if expected_value is None:
261
+ raise ValueError("Actual list contained element '" + str(key)
262
+ + "' that did not appear in the expected superset")
263
+ else:
264
+ if expected_value != value:
265
+ raise ValueError("Actual list had item '" + str(key) + "' with repetition " + str(value)
266
+ + " but required repetition " + str(expected_value))
267
+
268
+ return None
269
+
270
+ @staticmethod
271
+ def create_list_from_list_using_filter_regex(regex, source_list: list):
272
+ """Creates and returns a list of those items in the 'source_list' matching the regex in the 'regex'. The
273
+ argument 'regex' may be a string or a compiled regex object.
274
+
275
+ For the 'regex', consider using anchors to explicitly match from the start (or end) of a string. For
276
+ example, a string such as "^malware--" will explicitly match from the start of a string."""
277
+
278
+ def search_funct(element):
279
+
280
+ if isinstance(regex, str):
281
+ # 'regex' is a string
282
+ match = re.search(regex, element)
283
+ else:
284
+ # assume 'regex' is a compiled regex pattern object
285
+ match = regex.match(element)
286
+
287
+ if match is None:
288
+ return False
289
+ else:
290
+ return True
291
+
292
+ return list(filter(search_funct, source_list))
293
+
@@ -0,0 +1,23 @@
1
+ #
2
+ # DeltaFi - Data transformation and enrichment platform
3
+ #
4
+ # Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ INGRESS_FLOW = "ingress-flow"
20
+ EGRESS_FLOW = "egress-flow"
21
+ HOSTNAME = "HOSTNAME"
22
+ SYSTEM = "SYSTEM"
23
+ IGNORE_VALUE = "%%IGNORE%%"
@@ -0,0 +1,54 @@
1
+ #
2
+ # DeltaFi - Data transformation and enrichment platform
3
+ #
4
+ # Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ from deltafi.result import EgressResult
20
+
21
+ from .assertions import *
22
+ from .framework import TestCaseBase, ActionTest
23
+
24
+
25
+ class EgressTestCase(TestCaseBase):
26
+ def __init__(self, fields: Dict):
27
+ super().__init__(fields)
28
+
29
+ def expect_egress_result(self):
30
+ self.expected_result_type = EgressResult
31
+
32
+
33
+ class EgressActionTest(ActionTest):
34
+ def __init__(self, package_name: str):
35
+ """
36
+ Provides structure for testing DeltaFi Egress action
37
+ Args:
38
+ package_name: name of the actions package for finding resources
39
+ """
40
+ super().__init__(package_name)
41
+
42
+ def egress(self, test_case: EgressTestCase):
43
+ if test_case.expected_result_type == EgressResult:
44
+ self.expect_egress_result(test_case)
45
+ else:
46
+ super().execute(test_case)
47
+
48
+ def expect_egress_result(self, test_case: EgressTestCase):
49
+ result = super().run_and_check_result_type(test_case, EgressResult)
50
+ self.assert_egress_result(test_case, result)
51
+
52
+ def assert_egress_result(self, test_case: EgressTestCase, result: EgressResult):
53
+ self.compare_metrics(test_case.expected_metrics, result.metrics)
54
+ self.compare_log_messages(test_case.expected_messages, result.messages)