deltafi 2.0rc1731040010625__py3-none-any.whl → 2.1.1__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/plugin.py CHANGED
@@ -25,7 +25,6 @@ import sys
25
25
  import threading
26
26
  import time
27
27
  import traceback
28
- import yaml
29
28
  from datetime import datetime, timezone, timedelta
30
29
  from importlib import metadata
31
30
  from os.path import isdir, isfile, join
@@ -33,6 +32,7 @@ from pathlib import Path
33
32
  from typing import List
34
33
 
35
34
  import requests
35
+ import yaml
36
36
  from deltafi.action import Action, Join
37
37
  from deltafi.actioneventqueue import ActionEventQueue
38
38
  from deltafi.domain import Event, ActionExecution
@@ -46,6 +46,75 @@ def _coordinates():
46
46
  return PluginCoordinates(os.getenv('PROJECT_GROUP'), os.getenv('PROJECT_NAME'), os.getenv('PROJECT_VERSION'))
47
47
 
48
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
+
49
118
  def _setup_queue(max_connections):
50
119
  url = os.getenv('VALKEY_URL', 'http://deltafi-valkey-master:6379')
51
120
  password = os.getenv('VALKEY_PASSWORD')
@@ -183,34 +252,40 @@ class Plugin(object):
183
252
  'docsMarkdown': self._load_action_docs(action)
184
253
  }
185
254
 
186
- def _integration_tests(self):
187
- tests_path = str(Path(os.path.dirname(os.path.abspath(sys.argv[0]))) / 'integration')
188
-
189
- test_files = []
190
- if isdir(tests_path):
191
- test_files = [f for f in os.listdir(tests_path) if isfile(join(tests_path, f))]
192
- else:
193
- self.logger.warning(f"tests directory ({tests_path}) does not exist. No tests will be installed.")
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)
194
259
 
195
- tests = [json.load(open(join(tests_path, f))) for f in test_files]
196
- return tests
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
197
268
 
198
269
  def registration_json(self):
199
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')
200
272
 
201
- flow_files = []
202
273
  variables = []
203
- if isdir(flows_path):
204
- flow_files = [f for f in os.listdir(flows_path) if isfile(join(flows_path, f))]
205
- if 'variables.json' in flow_files:
206
- flow_files.remove('variables.json')
207
- 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.")
208
278
  else:
209
- 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)
210
280
 
211
- flows = [json.load(open(join(flows_path, f))) for f in flow_files]
281
+ flows = _load__all_resource(flows_path, flow_files)
212
282
  actions = [self._action_json(action) for action in self.actions]
213
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
+
214
289
  return {
215
290
  'pluginCoordinates': self.coordinates.__json__(),
216
291
  'displayName': self.display_name,
@@ -222,7 +297,7 @@ class Plugin(object):
222
297
  'actions': actions,
223
298
  'variables': variables,
224
299
  'flowPlans': flows,
225
- 'integrationTests': self._integration_tests()
300
+ 'integrationTests': test_files
226
301
  }
227
302
 
228
303
  def _register(self):
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deltafi
3
- Version: 2.0rc1731040010625
3
+ Version: 2.1.1
4
4
  Summary: SDK for DeltaFi plugins and actions
5
5
  License: Apache License, Version 2.0
6
6
  Keywords: deltafi
@@ -8,7 +8,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=7e4wblHAG41UCOZ1RcWjkw-0RJF4_dAksx5b4XCNIU4,15521
11
+ deltafi/plugin.py,sha256=QQ197MWyi3dCkax6rjA-kylbfMrMWHEaG8jIVsOGRW4,17980
12
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
@@ -16,8 +16,9 @@ deltafi/test_kit/assertions.py,sha256=MdUXENLn0aDvknMtsnSAb-DwvpzXlMkaYQ6-RRkWG8
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
19
+ deltafi/test_kit/framework.py,sha256=6txr5vnLSFIDaXgx-cXjDuYJSPcSHR7ez1x4N8TsNNw,15337
20
+ deltafi/test_kit/timed_ingress.py,sha256=ztE57hCuoZTrtFn_HlyHpHMFJN5Oyo3pPhVfbAI_weM,3790
20
21
  deltafi/test_kit/transform.py,sha256=Qj9LIyZ8RYj0tCeq7agrDlEkkgtBR86TNinV9oncTnM,4089
21
- deltafi-2.0rc1731040010625.dist-info/METADATA,sha256=515J328ll7iZ6v_RfUpNTeOCPkK242WEYZjzekh63FE,1530
22
- deltafi-2.0rc1731040010625.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
23
- deltafi-2.0rc1731040010625.dist-info/RECORD,,
22
+ deltafi-2.1.1.dist-info/METADATA,sha256=TMTU52HQuV2krmuKdx-wf6_3V944bfJ6tlRhdW6jAfw,1517
23
+ deltafi-2.1.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
24
+ deltafi-2.1.1.dist-info/RECORD,,