deltafi 2.0rc1730772574854__py3-none-any.whl → 2.1.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.

Potentially problematic release.


This version of deltafi might be problematic. Click here for more details.

deltafi/domain.py CHANGED
@@ -20,6 +20,7 @@ import copy
20
20
  from datetime import datetime, timedelta, timezone
21
21
  from logging import Logger
22
22
  from typing import Dict, List, NamedTuple
23
+ from uuid import uuid4
23
24
 
24
25
  from deltafi.storage import ContentService, Segment
25
26
 
@@ -119,6 +120,9 @@ class Context(NamedTuple):
119
120
  saved_content=[],
120
121
  logger=logger)
121
122
 
123
+ def child_context(self):
124
+ return self._replace(did=str(uuid4()))
125
+
122
126
 
123
127
  class Content:
124
128
  """
deltafi/plugin.py CHANGED
@@ -32,6 +32,7 @@ from pathlib import Path
32
32
  from typing import List
33
33
 
34
34
  import requests
35
+ import yaml
35
36
  from deltafi.action import Action, Join
36
37
  from deltafi.actioneventqueue import ActionEventQueue
37
38
  from deltafi.domain import Event, ActionExecution
@@ -45,6 +46,75 @@ def _coordinates():
45
46
  return PluginCoordinates(os.getenv('PROJECT_GROUP'), os.getenv('PROJECT_NAME'), os.getenv('PROJECT_VERSION'))
46
47
 
47
48
 
49
+ def _valid_file(filename: str):
50
+ return isfile(filename) and \
51
+ (filename.endswith(".json")
52
+ or filename.endswith(".yaml")
53
+ or filename.endswith(".yml"))
54
+
55
+
56
+ def _read_valid_files(path: str):
57
+ """
58
+ Read the contents of a directory, and returns a filtered list of files
59
+ that can be read/parsed for plugin usage, and ignores everything else.
60
+ :param path: name of the directory to scan
61
+ :return: list of filtered, parsable files
62
+ """
63
+ files = []
64
+ if isdir(path):
65
+ files = [f for f in os.listdir(path) if _valid_file(join(path, f))]
66
+ return files
67
+
68
+
69
+ def _load_resource(path: str, filename: str):
70
+ """
71
+ Read the content of a JSON or YAML file, and return a Python
72
+ object of its contents, typically as a dict or list.
73
+ To avoid exceptions, use only files returned by _read_valid_files().
74
+ :param path: directory which contains the file to load
75
+ :param filename: name of the file to load
76
+ :return: dict or list of file contents
77
+ """
78
+ with open(join(path, filename)) as file_in:
79
+ if filename.endswith(".json"):
80
+ return json.load(file_in)
81
+ elif filename.endswith(".yaml") or filename.endswith(".yml"):
82
+ results = []
83
+ yaml_docs = yaml.safe_load_all(file_in)
84
+ for doc_iter in yaml_docs:
85
+ # yaml_docs must be iterated
86
+ results.append(doc_iter)
87
+ if len(results) == 1:
88
+ # Single document YAML file
89
+ return results[0]
90
+ else:
91
+ # Multi-document YAML file
92
+ return results
93
+ raise RuntimeError(f"File type not supported: {filename}")
94
+
95
+
96
+ def _load__all_resource(path: str, file_list: List[str]):
97
+ resources = []
98
+ for f in file_list:
99
+ r = _load_resource(path, f)
100
+ if isinstance(r, list):
101
+ resources.extend(r)
102
+ else:
103
+ resources.append(r)
104
+ return resources
105
+
106
+
107
+ def _find_variables_filename(names: List[str]):
108
+ if 'variables.json' in names:
109
+ return 'variables.json'
110
+ elif 'variables.yaml' in names:
111
+ return 'variables.yaml'
112
+ elif 'variables.yml' in names:
113
+ return 'variables.yml'
114
+ else:
115
+ return None
116
+
117
+
48
118
  def _setup_queue(max_connections):
49
119
  url = os.getenv('VALKEY_URL', 'http://deltafi-valkey-master:6379')
50
120
  password = os.getenv('VALKEY_PASSWORD')
@@ -182,22 +252,40 @@ class Plugin(object):
182
252
  'docsMarkdown': self._load_action_docs(action)
183
253
  }
184
254
 
255
+ @staticmethod
256
+ def load_integration_tests(tests_path: str):
257
+ test_files = _read_valid_files(tests_path)
258
+ return _load__all_resource(tests_path, test_files)
259
+
260
+ @staticmethod
261
+ def load_variables(flows_path: str, flow_files: List[str]):
262
+ variables = []
263
+ variables_filename = _find_variables_filename(flow_files)
264
+ if variables_filename is not None:
265
+ flow_files.remove(variables_filename)
266
+ variables = _load__all_resource(flows_path, [variables_filename])
267
+ return variables
268
+
185
269
  def registration_json(self):
186
270
  flows_path = str(Path(os.path.dirname(os.path.abspath(sys.argv[0]))) / 'flows')
271
+ tests_path = str(Path(os.path.dirname(os.path.abspath(sys.argv[0]))) / 'integration')
187
272
 
188
- flow_files = []
189
273
  variables = []
190
- if isdir(flows_path):
191
- flow_files = [f for f in os.listdir(flows_path) if isfile(join(flows_path, f))]
192
- if 'variables.json' in flow_files:
193
- flow_files.remove('variables.json')
194
- variables = json.load(open(join(flows_path, 'variables.json')))
274
+ flow_files = _read_valid_files(flows_path)
275
+ if len(flow_files) == 0:
276
+ self.logger.warning(
277
+ f"Flows directory ({flows_path}) does not exist or contains no valid files. No flows will be installed.")
195
278
  else:
196
- self.logger.warning(f"Flows directory ({flows_path}) does not exist. No flows will be installed.")
279
+ variables = self.load_variables(flows_path, flow_files)
197
280
 
198
- flows = [json.load(open(join(flows_path, f))) for f in flow_files]
281
+ flows = _load__all_resource(flows_path, flow_files)
199
282
  actions = [self._action_json(action) for action in self.actions]
200
283
 
284
+ test_files = self.load_integration_tests(tests_path)
285
+ if len(test_files) == 0:
286
+ self.logger.warning(
287
+ f"tests directory ({tests_path}) does not exist or contains no valid files. No tests will be installed.")
288
+
201
289
  return {
202
290
  'pluginCoordinates': self.coordinates.__json__(),
203
291
  'displayName': self.display_name,
@@ -208,7 +296,8 @@ class Plugin(object):
208
296
  'dependencies': [],
209
297
  'actions': actions,
210
298
  'variables': variables,
211
- 'flowPlans': flows
299
+ 'flowPlans': flows,
300
+ 'integrationTests': test_files
212
301
  }
213
302
 
214
303
  def _register(self):
deltafi/result.py CHANGED
@@ -239,6 +239,7 @@ class TransformResult(Result):
239
239
 
240
240
  def json(self):
241
241
  return {
242
+ 'did': self.context.did,
242
243
  'content': [content.json() for content in self.content],
243
244
  'annotations': self.annotations,
244
245
  'metadata': self.metadata,
@@ -249,38 +250,38 @@ class TransformResult(Result):
249
250
  return [self.json()]
250
251
 
251
252
 
252
- class NamedTransformResult(NamedTuple):
253
- result: TransformResult
254
- name: str
253
+ class ChildTransformResult(TransformResult):
254
+ delta_file_name: str
255
255
 
256
- def get_segment_names(self):
257
- return self.result.get_segment_names()
256
+ def __init__(self, context: Context, delta_file_name: str = None):
257
+ super().__init__(context.child_context())
258
+ self.delta_file_name = delta_file_name
258
259
 
259
260
  def json(self):
260
- j = self.result.json()
261
- if self.name is not None:
262
- j['name'] = self.name
261
+ j = super().json()
262
+ if self.delta_file_name is not None:
263
+ j['name'] = self.delta_file_name
263
264
  return j
264
265
 
265
266
 
266
267
  class TransformResults(Result):
267
268
  def __init__(self, context: Context):
268
269
  super().__init__('transform', 'TRANSFORM', context)
269
- self.named_results = []
270
+ self.child_results = []
270
271
 
271
- def add_result(self, result: TransformResult, name: str = None):
272
- self.named_results.append(NamedTransformResult(result, name))
272
+ def add_result(self, result: ChildTransformResult):
273
+ self.child_results.append(result)
273
274
  return self
274
275
 
275
276
  def get_segment_names(self):
276
277
  segment_names = {}
277
- for named_result in self.named_results:
278
- segment_names.update(named_result.get_segment_names())
278
+ for child_result in self.child_results:
279
+ segment_names.update(child_result.get_segment_names())
279
280
  return segment_names
280
281
 
281
282
  def response(self):
282
283
  transform_events = []
283
- for named_result in self.named_results:
284
- json_dict = named_result.json()
284
+ for child_result in self.child_results:
285
+ json_dict = child_result.json()
285
286
  transform_events.append(json_dict)
286
287
  return transform_events
@@ -137,6 +137,7 @@ class TestCaseBase(ABC):
137
137
  - compare_tool: (optional) CompareHelper instanced for comparing output content
138
138
  - inputs: (optional) List[IOContent]: input content to action
139
139
  - parameters: (optional) Dict: map of action input parameters
140
+ - in_memo: (optional) str: Input 'memo' value for a TimedIngress context
140
141
  - in_meta: (optional) Dict: map of metadata as input to action
141
142
  - join_meta: (optional): List[Dict]: When a List is provided, this enables the JOIN portion of an action.
142
143
  When using JOIN, join_meta must match the size of inputs, though the Dict can be empty
@@ -161,6 +162,7 @@ class TestCaseBase(ABC):
161
162
  self.file_name = data["file_name"] if "file_name" in data else "filename"
162
163
  self.parameters = data["parameters"] if "parameters" in data else {}
163
164
  self.in_meta = data["in_meta"] if "in_meta" in data else {}
165
+ self.in_memo = data["in_memo"] if "in_memo" in data else None
164
166
  self.use_did = data["did"] if "did" in data else None
165
167
  self.expected_result_type = None
166
168
  self.err_or_filt_cause = None
@@ -280,6 +282,7 @@ class ActionTest(ABC):
280
282
  content_service=self.content_service,
281
283
  saved_content=[],
282
284
  join=join,
285
+ memo=test_case.in_memo,
283
286
  logger=get_logger())
284
287
  return self.context
285
288
 
@@ -0,0 +1,96 @@
1
+ #
2
+ # DeltaFi - Data transformation and enrichment platform
3
+ #
4
+ # Copyright 2021-2024 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 List
20
+
21
+ from deltafi.result import IngressResult, IngressResultItem, IngressStatusEnum
22
+
23
+ from .assertions import *
24
+ from .framework import TestCaseBase, ActionTest, IOContent
25
+
26
+
27
+ class TimedIngressTestCase(TestCaseBase):
28
+ def __init__(self, fields: Dict):
29
+ super().__init__(fields)
30
+ self.memo = None
31
+ self.results = []
32
+ self.execute_immediate = False
33
+ self.status = IngressStatusEnum.HEALTHY
34
+ self.status_message = None
35
+
36
+ def expect_ingress_result(self,
37
+ memo: str = None,
38
+ exec_immed: bool = False,
39
+ status: IngressStatusEnum = IngressStatusEnum.HEALTHY,
40
+ status_message: str = None):
41
+ self.expected_result_type = IngressResult
42
+ self.memo = memo
43
+ self.execute_immediate = exec_immed
44
+ self.status = status
45
+ self.status_message = status_message
46
+
47
+ def add_ingress_result_item(self, content: List[IOContent], metadata: Dict, name: str = None):
48
+ self.results.append(
49
+ {
50
+ 'content': content,
51
+ 'metadata': metadata,
52
+ 'name': name
53
+ }
54
+ )
55
+
56
+
57
+ class TimedIngressActionTest(ActionTest):
58
+ def __init__(self, package_name: str):
59
+ """
60
+ Provides structure for testing DeltaFi TimedIngress action
61
+ Args:
62
+ package_name: name of the actions package for finding resources
63
+ """
64
+ super().__init__(package_name)
65
+
66
+ def ingress(self, test_case: TimedIngressTestCase):
67
+ if test_case.expected_result_type == IngressResult:
68
+ self.expect_ingress_result(test_case)
69
+ else:
70
+ super().execute(test_case)
71
+
72
+ def expect_ingress_result(self, test_case: TimedIngressTestCase):
73
+ result = super().run_and_check_result_type(test_case, IngressResult)
74
+ self.assert_ingress_result(test_case, result)
75
+
76
+ def assert_ingress_result(self, test_case: TimedIngressTestCase, result: IngressResult):
77
+ assert_equal(test_case.memo, result.memo)
78
+ assert_equal(test_case.execute_immediate, result.execute_immediate)
79
+ assert_equal(test_case.status, result.status)
80
+ assert_equal(test_case.status_message, result.status_message)
81
+
82
+ assert_equal_len(test_case.results, result.ingress_result_items)
83
+ for index, ingress_item in enumerate(result.ingress_result_items):
84
+ self.compare_one_ingress_item(test_case, ingress_item, index)
85
+ expected = test_case.results[index]
86
+ if 'name' in expected:
87
+ assert_equal_with_label(expected["name"], ingress_item.delta_file_name, f"name[{index}]")
88
+
89
+ def compare_one_ingress_item(self, test_case: TimedIngressTestCase, result: IngressResultItem, index: int):
90
+ expected = test_case.results[index]
91
+
92
+ # Check output
93
+ self.compare_content_list(test_case.compare_tool, expected['content'], result.content)
94
+
95
+ # Check metadata
96
+ assert_keys_and_values(expected['metadata'], result.metadata)
@@ -74,12 +74,12 @@ class TransformActionTest(ActionTest):
74
74
  self.assert_transform_results(test_case, result)
75
75
 
76
76
  def assert_transform_results(self, test_case: TransformTestCase, result: TransformResults):
77
- assert_equal_len(test_case.results, result.named_results)
78
- for index, named_result in enumerate(result.named_results):
79
- self.compare_one_transform_result(test_case, named_result.result, index)
77
+ assert_equal_len(test_case.results, result.child_results)
78
+ for index, child_result in enumerate(result.child_results):
79
+ self.compare_one_transform_result(test_case, child_result, index)
80
80
  expected = test_case.results[index]
81
81
  if 'name' in expected:
82
- assert_equal_with_label(expected["name"], named_result.name, f"name[{index}]")
82
+ assert_equal_with_label(expected["name"], child_result.delta_file_name, f"name[{index}]")
83
83
 
84
84
  def assert_transform_result(self, test_case: TransformTestCase, result: TransformResult):
85
85
  # Check metrics
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deltafi
3
- Version: 2.0rc1730772574854
3
+ Version: 2.1.0
4
4
  Summary: SDK for DeltaFi plugins and actions
5
5
  License: Apache License, Version 2.0
6
6
  Keywords: deltafi
@@ -19,13 +19,14 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Topic :: Software Development
22
+ Requires-Dist: PyYAML (==6.0.2)
22
23
  Requires-Dist: deepdiff (==6.7.1)
23
24
  Requires-Dist: json-logging (==1.3.0)
24
- Requires-Dist: minio (==7.2.7)
25
- Requires-Dist: pydantic (==2.8.2)
26
- Requires-Dist: redis (==5.0.7)
25
+ Requires-Dist: minio (==7.2.10)
26
+ Requires-Dist: pydantic (==2.9.2)
27
+ Requires-Dist: redis (==5.2.0)
27
28
  Requires-Dist: requests (==2.32.3)
28
- Requires-Dist: urllib3 (==2.2.2)
29
+ Requires-Dist: urllib3 (==2.2.3)
29
30
  Project-URL: Bug Reports, https://chat.deltafi.org/deltafi/channels/bug-reports
30
31
  Project-URL: Documentation, https://docs.deltafi.org/#/
31
32
  Project-URL: Source Code, https://gitlab.com/deltafi/deltafi
@@ -2,22 +2,23 @@ deltafi/__init__.py,sha256=sSGWjpvzcCzgLywoEg55z_0satt0g_LPTwNIWtylgF4,709
2
2
  deltafi/action.py,sha256=kuDY7-qcJsiRTB3ZigiYg-XOcNeahnDU3NGSZBy10vM,5753
3
3
  deltafi/actioneventqueue.py,sha256=_05aDeqVpUbGWG3jFf0AalOsVfAvOTn0oBxjpQIQxIs,2847
4
4
  deltafi/actiontype.py,sha256=CpVI0wk9C-eu44e0jYvqBzE6yIX_zfRrW5RCf4ohu4Y,913
5
- deltafi/domain.py,sha256=YIfkxnjIuDXRlEm0MvrKh0CY5KczTZqZiTmnhc7wkOo,12082
5
+ deltafi/domain.py,sha256=wCWqCjWbQ02thAMHYXTuUMHY58QZyyTRkYNZFd3kPPo,12182
6
6
  deltafi/exception.py,sha256=LNNFqc_lA89I-RvnVsbTQ2vnWQ9WZXdQqLwiXwNgc_w,943
7
7
  deltafi/genericmodel.py,sha256=WU8zfqEO_n84CZ0KpH9FhgTsL9jyU0EXSuhL0IdLWFw,1152
8
8
  deltafi/input.py,sha256=ydAhuw68N9qMeacc9YE4U79zRqxWiinZNkm1AxoFBEk,1656
9
9
  deltafi/logger.py,sha256=mKfJTnuupf3sto6hV-SIXajtcP-xTSSdJ2Ufd-lJPTo,2140
10
10
  deltafi/metric.py,sha256=79Gb2C1qYXeQYshgFPu2BcMT5oEb0LhmNfwyulSqsIc,972
11
- deltafi/plugin.py,sha256=FzzUEahdpWzGCULf7qhkfBsxZg4YgHGthq2wg0d2Ajc,14952
12
- deltafi/result.py,sha256=Xv156aM-PgYNB1Y6L-0Wl22YMj46srt88aAXtgc0U74,8957
11
+ deltafi/plugin.py,sha256=QQ197MWyi3dCkax6rjA-kylbfMrMWHEaG8jIVsOGRW4,17980
12
+ deltafi/result.py,sha256=2mefttQAdvYhsw6IjvouY4L5W1n3s6tP7oJUhLtyyVk,9047
13
13
  deltafi/storage.py,sha256=vCE29Yzk5s7ub4O1PdaxXBKPBQy4OHs7j46z3O5MezE,2979
14
14
  deltafi/test_kit/__init__.py,sha256=sSGWjpvzcCzgLywoEg55z_0satt0g_LPTwNIWtylgF4,709
15
15
  deltafi/test_kit/assertions.py,sha256=MdUXENLn0aDvknMtsnSAb-DwvpzXlMkaYQ6-RRkWG8s,1378
16
16
  deltafi/test_kit/compare_helpers.py,sha256=qRINvCQqBUaalT1DG2oz2egSkUjiSOfqKF5U7WyeT_g,12957
17
17
  deltafi/test_kit/constants.py,sha256=Suygx9CEob2Skw4UyzzMCibQ8hRhGHC_d_Xab8AJMFE,833
18
18
  deltafi/test_kit/egress.py,sha256=53SBeJJmphDl-jZdqkC1dKYWZ4ATWlzY02TUt0Sj1zs,1899
19
- deltafi/test_kit/framework.py,sha256=g7NRl_KxUFio8aZ1xo1gEBKqL9qquYx9qAyFpPLHsmM,15150
20
- deltafi/test_kit/transform.py,sha256=bnV6kcOkuShi_9uCtzkWcgz-wMejnI9Q2_MbyVm_A7U,4085
21
- deltafi-2.0rc1730772574854.dist-info/METADATA,sha256=2INpnVNi_YzmOEk2MWkTiRE9jYfi2tbHEezzTLIGGPs,1497
22
- deltafi-2.0rc1730772574854.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
23
- deltafi-2.0rc1730772574854.dist-info/RECORD,,
19
+ deltafi/test_kit/framework.py,sha256=6txr5vnLSFIDaXgx-cXjDuYJSPcSHR7ez1x4N8TsNNw,15337
20
+ deltafi/test_kit/timed_ingress.py,sha256=ztE57hCuoZTrtFn_HlyHpHMFJN5Oyo3pPhVfbAI_weM,3790
21
+ deltafi/test_kit/transform.py,sha256=Qj9LIyZ8RYj0tCeq7agrDlEkkgtBR86TNinV9oncTnM,4089
22
+ deltafi-2.1.0.dist-info/METADATA,sha256=3NnbYSKHSIZx_m3Gh9wRBQyy3v0wzNV_QHnE5tQlCMs,1517
23
+ deltafi-2.1.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
24
+ deltafi-2.1.0.dist-info/RECORD,,