deltafi 2.8.0__tar.gz → 2.10.0__tar.gz

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.

Files changed (24) hide show
  1. {deltafi-2.8.0 → deltafi-2.10.0}/PKG-INFO +1 -1
  2. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/action.py +3 -3
  3. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/domain.py +5 -4
  4. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/plugin.py +54 -21
  5. {deltafi-2.8.0 → deltafi-2.10.0}/pyproject.toml +1 -1
  6. {deltafi-2.8.0 → deltafi-2.10.0}/README.md +0 -0
  7. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/__init__.py +0 -0
  8. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/actioneventqueue.py +0 -0
  9. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/actiontype.py +0 -0
  10. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/exception.py +0 -0
  11. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/genericmodel.py +0 -0
  12. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/input.py +0 -0
  13. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/logger.py +0 -0
  14. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/metric.py +0 -0
  15. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/result.py +0 -0
  16. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/storage.py +0 -0
  17. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/test_kit/__init__.py +0 -0
  18. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/test_kit/assertions.py +0 -0
  19. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/test_kit/compare_helpers.py +0 -0
  20. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/test_kit/constants.py +0 -0
  21. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/test_kit/egress.py +0 -0
  22. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/test_kit/framework.py +0 -0
  23. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/test_kit/timed_ingress.py +0 -0
  24. {deltafi-2.8.0 → deltafi-2.10.0}/deltafi/test_kit/transform.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: deltafi
3
- Version: 2.8.0
3
+ Version: 2.10.0
4
4
  Summary: SDK for DeltaFi plugins and actions
5
5
  License: Apache License, Version 2.0
6
6
  Keywords: deltafi
@@ -19,12 +19,13 @@
19
19
  from abc import ABC, abstractmethod
20
20
  from typing import Any, List
21
21
 
22
+ from pydantic import BaseModel
23
+
22
24
  from deltafi.actiontype import ActionType
23
- from deltafi.domain import Context, DeltaFileMessage
25
+ from deltafi.domain import DeltaFileMessage
24
26
  from deltafi.genericmodel import GenericModel
25
27
  from deltafi.input import EgressInput, TransformInput
26
28
  from deltafi.result import *
27
- from pydantic import BaseModel
28
29
 
29
30
 
30
31
  class Join(ABC):
@@ -42,7 +43,6 @@ class Action(ABC):
42
43
  self.action_type = action_type
43
44
  self.description = description
44
45
  self.valid_result_types = valid_result_types
45
- self.action_execution = None
46
46
 
47
47
  @abstractmethod
48
48
  def build_input(self, context: Context, delta_file_message: DeltaFileMessage):
@@ -28,6 +28,7 @@ from deltafi.storage import ContentService, Segment
28
28
  class ActionExecution(NamedTuple):
29
29
  clazz: str
30
30
  action: str
31
+ thread_num: int
31
32
  did: str
32
33
  start_time: datetime
33
34
 
@@ -36,7 +37,7 @@ class ActionExecution(NamedTuple):
36
37
 
37
38
  @property
38
39
  def key(self) -> str:
39
- return f"{self.clazz}:{self.action}:{self.did}"
40
+ return f"{self.clazz}:{self.action}#{self.thread_num}:{self.did}"
40
41
 
41
42
 
42
43
  class Context(NamedTuple):
@@ -163,9 +164,9 @@ class Content:
163
164
  Content: A deep copy of the Content object.
164
165
  """
165
166
  new_copy = Content(name=self.name,
166
- segments=copy.deepcopy(self.segments),
167
- media_type=self.media_type,
168
- content_service=self.content_service)
167
+ segments=copy.deepcopy(self.segments),
168
+ media_type=self.media_type,
169
+ content_service=self.content_service)
169
170
  new_copy.add_tags(self.tags.copy())
170
171
  return new_copy
171
172
 
@@ -33,6 +33,7 @@ from typing import List
33
33
 
34
34
  import requests
35
35
  import yaml
36
+
36
37
  from deltafi.action import Action, Join
37
38
  from deltafi.actioneventqueue import ActionEventQueue
38
39
  from deltafi.domain import Event, ActionExecution
@@ -129,6 +130,17 @@ def _setup_content_service():
129
130
  os.getenv('MINIO_SECRETKEY'))
130
131
 
131
132
 
133
+ class ActionThread(object):
134
+ def __init__(self, clazz: Action, thread_num: int, name: str, execution: ActionExecution = None):
135
+ self.clazz = clazz
136
+ self.thread_num = thread_num
137
+ self.name = name
138
+ self.execution = execution
139
+
140
+ def logger_name(self):
141
+ return f"{self.name}#{self.thread_num}"
142
+
143
+
132
144
  class PluginCoordinates(object):
133
145
  def __init__(self, group_id: str, artifact_id: str, version: str):
134
146
  self.group_id = group_id
@@ -148,7 +160,8 @@ LONG_RUNNING_TASK_DURATION = timedelta(seconds=5)
148
160
 
149
161
  class Plugin(object):
150
162
  def __init__(self, description: str, plugin_name: str = None, plugin_coordinates: PluginCoordinates = None,
151
- actions: List = None, action_package: str = None):
163
+ actions: List = None, action_package: str = None,
164
+ thread_config: dict = None):
152
165
  """
153
166
  Initialize the plugin object
154
167
  :param plugin_name: Name of the plugin project
@@ -157,12 +170,17 @@ class Plugin(object):
157
170
  environment variables
158
171
  :param actions: list of action classes to run
159
172
  :param action_package: name of the package containing the actions to run
173
+ :param thread_config: map of action class name and thread count. Actions not found default to 1 thread.
160
174
  """
161
175
  self.logger = get_logger()
162
176
 
163
177
  self.content_service = None
164
178
  self.queue = None
165
- self.actions = []
179
+ self.singleton_actions = []
180
+ self.action_threads = []
181
+ self.thread_config = {}
182
+ if thread_config is not None:
183
+ self.thread_config = thread_config
166
184
  self.core_url = os.getenv('CORE_URL')
167
185
  self.image = os.getenv('IMAGE')
168
186
  self.image_pull_secret = os.getenv('IMAGE_PULL_SECRET')
@@ -176,7 +194,7 @@ class Plugin(object):
176
194
  action_classes.extend(found_actions)
177
195
 
178
196
  unique_actions = dict.fromkeys(action_classes)
179
- self.actions = [action() for action in unique_actions]
197
+ self.singleton_actions = [action() for action in unique_actions]
180
198
 
181
199
  self.description = description
182
200
  self.display_name = os.getenv('PROJECT_NAME') if plugin_name is None else plugin_name
@@ -191,7 +209,7 @@ class Plugin(object):
191
209
  else:
192
210
  self.hostname = 'UNKNOWN'
193
211
 
194
- self.logger.debug(f"Initialized ActionRunner with actions {self.actions}")
212
+ self.logger.debug(f"Initialized ActionRunner with actions {self.singleton_actions}")
195
213
 
196
214
  @staticmethod
197
215
  def find_actions(package_name) -> List[object]:
@@ -280,7 +298,7 @@ class Plugin(object):
280
298
  variables = self.load_variables(flows_path, flow_files)
281
299
 
282
300
  flows = _load__all_resource(flows_path, flow_files)
283
- actions = [self._action_json(action) for action in self.actions]
301
+ actions = [self._action_json(action) for action in self.singleton_actions]
284
302
 
285
303
  test_files = self.load_integration_tests(tests_path)
286
304
  if len(test_files) == 0:
@@ -317,11 +335,25 @@ class Plugin(object):
317
335
 
318
336
  def run(self):
319
337
  self.logger.info("Plugin starting")
320
- self.queue = _setup_queue(len(self.actions) + 1)
338
+
339
+ for action in self.singleton_actions:
340
+ num_threads = 1;
341
+ if self.action_name(action) in self.thread_config:
342
+ maybe_num_threads = self.thread_config[self.action_name(action)]
343
+ if type(maybe_num_threads) == int and maybe_num_threads > 0:
344
+ num_threads = maybe_num_threads
345
+ else:
346
+ self.logger.error(f"Ignoring non-int or invalid thread value {maybe_num_threads}")
347
+ for i in range(num_threads):
348
+ action_thread = ActionThread(action, i, self.action_name(action))
349
+ self.action_threads.append(action_thread)
350
+
351
+ self.queue = _setup_queue(len(self.action_threads) + 1)
321
352
  self.content_service = _setup_content_service()
322
353
  self._register()
323
- for action in self.actions:
324
- threading.Thread(target=self._do_action, args=(action,)).start()
354
+
355
+ for action_thread in self.action_threads:
356
+ threading.Thread(target=self._do_action, args=(action_thread,)).start()
325
357
 
326
358
  hb_thread = threading.Thread(target=self._heartbeat)
327
359
  hb_thread.start()
@@ -339,14 +371,14 @@ class Plugin(object):
339
371
  while True:
340
372
  try:
341
373
  # Set heartbeats
342
- for action in self.actions:
343
- self.queue.heartbeat(self.action_name(action))
374
+ for action_thread in self.action_threads:
375
+ self.queue.heartbeat(action_thread.name)
344
376
 
345
377
  # Record long running tasks
346
378
  new_long_running_actions = set()
347
- for action in self.actions:
348
- if action.action_execution and action.action_execution.exceeds_duration(LONG_RUNNING_TASK_DURATION):
349
- action_execution = action.action_execution
379
+ for action_thread in self.action_threads:
380
+ action_execution = action_thread.execution
381
+ if action_execution and action_execution.exceeds_duration(LONG_RUNNING_TASK_DURATION):
350
382
  new_long_running_actions.add(action_execution)
351
383
  self.queue.record_long_running_task(action_execution)
352
384
 
@@ -378,22 +410,23 @@ class Plugin(object):
378
410
  response[result.result_key] = result.response()
379
411
  return response
380
412
 
381
- def _do_action(self, action):
382
- action_logger = get_logger(self.action_name(action))
413
+ def _do_action(self, action_thread: ActionThread):
414
+ action_logger = get_logger(action_thread.logger_name())
415
+ action_logger.info(f"Listening on {action_thread.name}")
383
416
 
384
- action_logger.info(f"Listening on {self.action_name(action)}")
385
417
  while True:
386
418
  try:
387
- event_string = self.queue.take(self.action_name(action))
419
+ event_string = self.queue.take(action_thread.name)
388
420
  event = Event.create(json.loads(event_string), self.content_service, action_logger)
389
421
  start_time = time.time()
390
422
  action_logger.debug(f"Processing event for did {event.context.did}")
391
423
 
392
- action.action_execution = ActionExecution(self.action_name(action), event.context.action_name,
393
- event.context.did, datetime.now(timezone.utc))
424
+ action_thread.execution = ActionExecution(action_thread.name, event.context.action_name,
425
+ action_thread.thread_num, event.context.did,
426
+ datetime.now(timezone.utc))
394
427
 
395
428
  try:
396
- result = action.execute_action(event)
429
+ result = action_thread.clazz.execute_action(event)
397
430
  except ExpectedContentException as e:
398
431
  result = ErrorResult(event.context,
399
432
  f"Action attempted to look up element {e.index + 1} (index {e.index}) from "
@@ -407,7 +440,7 @@ class Plugin(object):
407
440
  result = ErrorResult(event.context,
408
441
  f"Action execution {type(e)} exception", f"{str(e)}\n{traceback.format_exc()}")
409
442
 
410
- action.action_execution = None
443
+ action_thread.execution = None
411
444
 
412
445
  response = Plugin.to_response(
413
446
  event, start_time, time.time(), result)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "deltafi"
3
- version = "2.8.0"
3
+ version = "2.10.0"
4
4
  description = "SDK for DeltaFi plugins and actions"
5
5
  authors = ["DeltaFi <deltafi@systolic.com>"]
6
6
  license = "Apache License, Version 2.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes