deltafi 2.0rc1714076410617__tar.gz → 2.0rc1715691282958__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 (26) hide show
  1. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/PKG-INFO +5 -5
  2. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/action.py +13 -9
  3. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/actiontype.py +2 -0
  4. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/domain.py +52 -24
  5. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/plugin.py +7 -4
  6. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/result.py +4 -4
  7. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/compare_helpers.py +23 -18
  8. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/framework.py +16 -52
  9. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/pyproject.toml +8 -8
  10. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/README.md +0 -0
  11. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/__init__.py +0 -0
  12. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/actioneventqueue.py +0 -0
  13. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/exception.py +0 -0
  14. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/genericmodel.py +0 -0
  15. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/input.py +0 -0
  16. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/logger.py +0 -0
  17. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/metric.py +0 -0
  18. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/storage.py +0 -0
  19. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/__init__.py +0 -0
  20. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/assertions.py +0 -0
  21. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/constants.py +0 -0
  22. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/domain.py +0 -0
  23. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/egress.py +0 -0
  24. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/enrich.py +0 -0
  25. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/transform.py +0 -0
  26. {deltafi-2.0rc1714076410617 → deltafi-2.0rc1715691282958}/deltafi/test_kit/validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deltafi
3
- Version: 2.0rc1714076410617
3
+ Version: 2.0rc1715691282958
4
4
  Summary: SDK for DeltaFi plugins and actions
5
5
  License: Apache License, Version 2.0
6
6
  Keywords: deltafi
@@ -20,11 +20,11 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Topic :: Software Development
21
21
  Requires-Dist: deepdiff (>=6.7.1)
22
22
  Requires-Dist: json-logging (>=1.3.0)
23
- Requires-Dist: minio (>=7.2.3)
24
- Requires-Dist: pydantic (>=2.5.3)
25
- Requires-Dist: redis (>=5.0.1)
23
+ Requires-Dist: minio (>=7.2.5)
24
+ Requires-Dist: pydantic (>=2.7.1)
25
+ Requires-Dist: redis (>=5.0.4)
26
26
  Requires-Dist: requests (>=2.31.0)
27
- Requires-Dist: urllib3 (>=2.1.0)
27
+ Requires-Dist: urllib3 (>=2.2.1)
28
28
  Project-URL: Bug Reports, https://chat.deltafi.org/deltafi/channels/bug-reports
29
29
  Project-URL: Documentation, https://docs.deltafi.org/#/
30
30
  Project-URL: Source Code, https://gitlab.com/deltafi/deltafi
@@ -20,8 +20,8 @@ from abc import ABC, abstractmethod
20
20
  from typing import Any, List
21
21
 
22
22
  from deltafi.actiontype import ActionType
23
- from deltafi.genericmodel import GenericModel
24
23
  from deltafi.domain import Context, DeltaFileMessage
24
+ from deltafi.genericmodel import GenericModel
25
25
  from deltafi.input import EgressInput, TransformInput
26
26
  from deltafi.result import *
27
27
  from pydantic import BaseModel
@@ -48,20 +48,23 @@ class Action(ABC):
48
48
  def execute_action(self, event):
49
49
  if event.delta_file_messages is None or not len(event.delta_file_messages):
50
50
  raise RuntimeError(f"Received event with no delta file messages for did {event.context.did}")
51
-
52
51
  if event.context.collect is not None:
53
- result = self.execute(event.context, self.collect([self.build_input(event.context, delta_file_message)
54
- for delta_file_message in event.delta_file_messages]),
55
- self.param_class().model_validate(event.params))
52
+ result = self.execute(
53
+ event.context,
54
+ self.collect([self.build_input(event.context, delta_file_message)
55
+ for delta_file_message in event.delta_file_messages]),
56
+ self.param_class().model_validate(event.params))
56
57
  else:
57
- result = self.execute(event.context, self.build_input(event.context, event.delta_file_messages[0]),
58
- self.param_class().model_validate(event.params))
58
+ result = self.execute(
59
+ event.context,
60
+ self.build_input(event.context, event.delta_file_messages[0]),
61
+ self.param_class().model_validate(event.params))
59
62
 
60
63
  self.validate_type(result)
61
64
  return result
62
65
 
63
66
  @staticmethod
64
- def param_class( ):
67
+ def param_class():
65
68
  """Factory method to create and return an empty GenericModel instance.
66
69
 
67
70
  Returns
@@ -110,7 +113,8 @@ class TimedIngressAction(Action, ABC):
110
113
 
111
114
  class TransformAction(Action, ABC):
112
115
  def __init__(self, description: str):
113
- super().__init__(ActionType.TRANSFORM, description, (TransformResult, TransformResults, ErrorResult, FilterResult))
116
+ super().__init__(ActionType.TRANSFORM, description,
117
+ (TransformResult, TransformResults, ErrorResult, FilterResult))
114
118
 
115
119
  def build_input(self, context: Context, delta_file_message: DeltaFileMessage):
116
120
  return TransformInput(content=delta_file_message.content_list, metadata=delta_file_message.metadata)
@@ -20,7 +20,9 @@ from enum import Enum
20
20
 
21
21
 
22
22
  class ActionType(Enum):
23
+ INGRESS = "INGRESS"
23
24
  TIMED_INGRESS = "TIMED_INGRESS"
24
25
  TRANSFORM = "TRANSFORM"
25
26
  EGRESS = "EGRESS"
27
+ PUBLISH = "PUBLISH"
26
28
  UNKNOWN = "UNKNOWN"
@@ -40,13 +40,15 @@ class ActionExecution(NamedTuple):
40
40
 
41
41
  class Context(NamedTuple):
42
42
  did: str
43
- action_flow: str
43
+ delta_file_name: str
44
+ data_source: str
45
+ flow_name: str
46
+ flow_id: str
44
47
  action_name: str
45
- source_filename: str
46
- ingress_flow: str
47
- egress_flow: str
48
- system: str
48
+ action_id: str
49
+ action_version: str
49
50
  hostname: str
51
+ system_name: str
50
52
  content_service: ContentService
51
53
  collect: dict = None
52
54
  collected_dids: List[str] = None
@@ -56,19 +58,42 @@ class Context(NamedTuple):
56
58
  @classmethod
57
59
  def create(cls, context: dict, hostname: str, content_service: ContentService, logger: Logger):
58
60
  did = context['did']
59
- action_name_parts = context['name'].split(".")
60
- action_flow = action_name_parts[0]
61
- action_name = action_name_parts[1]
62
- if 'sourceFilename' in context:
63
- source_filename = context['sourceFilename']
61
+ if 'deltaFileName' in context:
62
+ delta_file_name = context['deltaFileName']
64
63
  else:
65
- source_filename = None
66
- ingress_flow = context['ingressFlow']
67
- if 'egressFlow' in context:
68
- egress_flow = context['egressFlow']
64
+ delta_file_name = None
65
+ if 'dataSource' in context:
66
+ data_source = context['dataSource']
69
67
  else:
70
- egress_flow = None
71
- system = context['systemName']
68
+ data_source = None
69
+ if 'flowName' in context:
70
+ flow_name = context['flowName']
71
+ else:
72
+ flow_name = None
73
+ if 'flowId' in context:
74
+ flow_id = context['flowId']
75
+ else:
76
+ flow_id = None
77
+ if 'actionName' in context:
78
+ action_name = context['actionName']
79
+ else:
80
+ action_name = None
81
+ if 'actionId' in context:
82
+ action_id = context['actionId']
83
+ else:
84
+ action_id = None
85
+ if 'actionVersion' in context:
86
+ action_version = context['actionVersion']
87
+ else:
88
+ action_version = None
89
+ if 'hostname' in context:
90
+ hostname = context['hostname']
91
+ else:
92
+ hostname = None
93
+ if 'systemName' in context:
94
+ system_name = context['systemName']
95
+ else:
96
+ system_name = None
72
97
  if 'collect' in context:
73
98
  collect = context['collect']
74
99
  else:
@@ -81,18 +106,21 @@ class Context(NamedTuple):
81
106
  memo = context['memo']
82
107
  else:
83
108
  memo = None
109
+
84
110
  return Context(did=did,
85
- action_flow=action_flow,
111
+ delta_file_name=delta_file_name,
112
+ data_source=data_source,
113
+ flow_name=flow_name,
114
+ flow_id=flow_id,
86
115
  action_name=action_name,
87
- source_filename=source_filename,
88
- ingress_flow=ingress_flow,
89
- egress_flow=egress_flow,
90
- system=system,
116
+ action_id=action_id,
117
+ action_version=action_version,
91
118
  hostname=hostname,
92
- content_service=content_service,
119
+ system_name=system_name,
93
120
  collect=collect,
94
121
  collected_dids=collected_dids,
95
122
  memo=memo,
123
+ content_service=content_service,
96
124
  logger=logger)
97
125
 
98
126
 
@@ -197,7 +225,6 @@ class Content:
197
225
 
198
226
  return new_segments
199
227
 
200
-
201
228
  def get_size(self):
202
229
  """
203
230
  Returns the size of the content in bytes.
@@ -318,7 +345,8 @@ class Event(NamedTuple):
318
345
 
319
346
  @classmethod
320
347
  def create(cls, event: dict, hostname: str, content_service: ContentService, logger: Logger):
321
- delta_file_messages = [DeltaFileMessage.from_dict(delta_file_message, content_service) for delta_file_message in event['deltaFileMessages']]
348
+ delta_file_messages = [DeltaFileMessage.from_dict(delta_file_message, content_service) for delta_file_message in
349
+ event['deltaFileMessages']]
322
350
  context = Context.create(event['actionContext'], hostname, content_service, logger)
323
351
  params = event['actionParams']
324
352
  queue_name = None
@@ -46,9 +46,9 @@ def _coordinates():
46
46
 
47
47
 
48
48
  def _setup_queue(max_connections):
49
- redis_url = os.getenv('REDIS_URL', 'http://deltafi-redis-master:6379')
50
- password = os.getenv('REDIS_PASSWORD')
51
- return ActionEventQueue(redis_url, max_connections, password)
49
+ url = os.getenv('VALKEY_URL', 'http://deltafi-valkey-master:6379')
50
+ password = os.getenv('VALKEY_PASSWORD')
51
+ return ActionEventQueue(url, max_connections, password)
52
52
 
53
53
 
54
54
  def _setup_content_service():
@@ -286,7 +286,10 @@ class Plugin(object):
286
286
 
287
287
  response = {
288
288
  'did': event.context.did,
289
- 'action': event.context.action_flow + "." + event.context.action_name,
289
+ 'flowName': event.context.flow_name,
290
+ 'flowId': event.context.flow_id,
291
+ 'actionName': event.context.action_name,
292
+ 'actionId': event.context.action_id,
290
293
  'start': start_time,
291
294
  'stop': time.time(),
292
295
  'type': result.result_type,
@@ -96,12 +96,12 @@ class FilterResult(Result):
96
96
 
97
97
 
98
98
  class IngressResultItem:
99
- def __init__(self, context: Context, filename: str):
99
+ def __init__(self, context: Context, delta_file_name: str):
100
100
  self.context = context
101
- self.filename = filename
102
101
  self._did = str(uuid.uuid4())
103
102
  self.content = []
104
103
  self.metadata = {}
104
+ self.delta_file_name = delta_file_name
105
105
 
106
106
  @property
107
107
  def did(self):
@@ -140,7 +140,7 @@ class IngressResultItem:
140
140
  def response(self):
141
141
  return {
142
142
  'did': self._did,
143
- 'filename': self.filename,
143
+ 'deltaFileName': self.delta_file_name,
144
144
  'metadata': self.metadata,
145
145
  'content': [content.json() for content in self.content]
146
146
  }
@@ -156,8 +156,8 @@ class IngressResult(Result):
156
156
  def __init__(self, context: Context):
157
157
  super().__init__('ingress', 'INGRESS', context)
158
158
  self.memo = None
159
- self.execute_immediate = False
160
159
  self.ingress_result_items = []
160
+ self.execute_immediate = False
161
161
  self.status = IngressStatusEnum.HEALTHY
162
162
  self.statusMessage = None
163
163
 
@@ -69,14 +69,17 @@ class JsonCompareHelper(CompareHelper):
69
69
  self.ignore_order = ignore_order
70
70
 
71
71
  def __perform_find(self, obj: object, item):
72
- """Returns a dict of matches of the 'item' in the object 'obj'. The returned dict is empty if there are no
73
- matches. Excludes path determined by the constructor."""
74
- return DeepSearch(obj, item, verbose_level=2, exclude_regex_paths=self.excludes)
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)
75
76
 
76
77
  def is_not_found(self, obj: object, item):
77
- """Returns None if there are no occurrences of 'item' in object 'obj' else returns a ValueError. The argument
78
- 'item' may be a scalar or a list. Excludes path and failure on ordering of elements are determined by
79
- the constructor."""
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."""
80
83
 
81
84
  all_matches = []
82
85
 
@@ -91,31 +94,33 @@ class JsonCompareHelper(CompareHelper):
91
94
  all_matches.append(matches)
92
95
 
93
96
  if len(all_matches) > 0:
94
- raise ValueError(f"{all_matches}")
97
+ raise ValueError("Matches found for items '" + f"{all_matches}" + "'")
95
98
 
96
99
  assert len(all_matches) == 0
97
100
 
98
101
  def is_found(self, obj: object, item):
99
- """Returns None if there are no occurrences of 'item' in object 'obj' else returns a ValueError. The argument
100
- 'item' may be a scalar or a list. Excludes path and failure on ordering of elements are determined by
101
- the constructor."""
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."""
102
107
 
103
- all_matches = []
108
+ not_found_items = []
104
109
 
105
110
  if isinstance(item, list):
106
111
  for value in item:
107
112
  matches = self.__perform_find(obj, value)
108
- if len(matches) > 0:
109
- all_matches.append(matches)
113
+ if len(matches) == 0:
114
+ not_found_items.append(value)
110
115
  else:
111
116
  matches = self.__perform_find(obj, item)
112
- if len(matches) > 0:
113
- all_matches.append(matches)
117
+ if len(matches) == 0:
118
+ not_found_items.append(item)
114
119
 
115
- if len(all_matches) == 0:
116
- raise ValueError("No matches found for '" + str(item) + "'")
120
+ if len(not_found_items) > 0:
121
+ raise ValueError("No matches found for items '" + f"{not_found_items}" + "'")
117
122
 
118
- assert len(all_matches) > 0
123
+ assert len(not_found_items) == 0
119
124
 
120
125
  def __perform_diff(self, expected, actual):
121
126
  """Returns a dict with differences between 'expected' and 'actual'. The returned dict is empty if 'expected'
@@ -188,21 +188,10 @@ class ActionTest(ABC):
188
188
 
189
189
  def __reset__(self, did: str):
190
190
  self.content_service = InternalContentService()
191
- <<<<<<< HEAD
192
191
  if did is None:
193
192
  self.did = str(uuid.uuid4())
194
193
  else:
195
194
  self.did = did
196
- self.expected_outputs = []
197
- ||||||| parent of 831733c7 (2.0 Refactor)
198
- self.did = str(uuid.uuid4())
199
- self.expected_outputs = []
200
- =======
201
- if did is None:
202
- self.did = str(uuid.uuid4())
203
- else:
204
- self.did = did
205
- >>>>>>> 831733c7 (2.0 Refactor)
206
195
  self.loaded_inputs = []
207
196
  self.res_path = ""
208
197
 
@@ -237,36 +226,23 @@ class ActionTest(ABC):
237
226
  content_list = self.make_content_list(test_case)
238
227
  self.content_service.load(self.loaded_inputs)
239
228
 
240
- <<<<<<< HEAD
241
- return DeltaFileMessage(
242
- metadata=test_case.in_meta,
243
- content_list=content_list,
244
- domains=test_case.in_domains,
245
- enrichments=test_case.in_enrichments)
246
- ||||||| parent of 831733c7 (2.0 Refactor)
247
- return DeltaFileMessage(metadata=test_case.in_meta,
248
- content_list=content_list,
249
- domains=test_case.in_domains,
250
- enrichments=test_case.in_enrichments)
251
- =======
252
229
  return DeltaFileMessage(metadata=test_case.in_meta,
253
230
  content_list=content_list)
254
- >>>>>>> 831733c7 (2.0 Refactor)
255
231
 
256
232
  def make_context(self, test_case: TestCaseBase):
257
233
  action_name = INGRESS_FLOW + "." + test_case.action.__class__.__name__
258
234
  return Context(
259
235
  did=self.did,
260
- action_flow=INGRESS_FLOW,
236
+ delta_file_name=test_case.file_name,
237
+ data_source="DATASRC",
238
+ flow_name=INGRESS_FLOW,
239
+ flow_id="FLOWID",
261
240
  action_name=action_name,
262
- source_filename=test_case.file_name,
263
- ingress_flow=INGRESS_FLOW,
264
- egress_flow=EGRESS_FLOW,
265
- system=SYSTEM,
241
+ action_id="ACTIONID",
242
+ action_version="1.0",
266
243
  hostname=HOSTNAME,
244
+ system_name=SYSTEM,
267
245
  content_service=self.content_service,
268
- collect=None,
269
- collected_dids=None,
270
246
  logger=get_logger())
271
247
 
272
248
  def make_event(self, test_case: TestCaseBase):
@@ -312,39 +288,27 @@ class ActionTest(ABC):
312
288
  else:
313
289
  raise ValueError(f"unknown type: {test_case.expected_result_type}")
314
290
 
315
- def compare_content_details(self, expected: LoadedContent, actual: Content):
291
+ @staticmethod
292
+ def compare_content_details(expected: LoadedContent, actual: Content):
316
293
  assert_equal(expected.content_type, actual.media_type)
317
294
  assert_equal(expected.name, actual.name)
318
295
 
319
- def compare_one_content(self, comparitor: CompareHelper, expected: LoadedContent, actual, index):
296
+ def compare_one_content(self, comparator: CompareHelper, expected: LoadedContent, actual, index):
320
297
  self.compare_content_details(expected, actual)
321
298
  seg_id = actual.segments[0].uuid
322
- comparitor.compare(expected.data, self.content_service.get_output(seg_id), f"Content[{index}]")
299
+ comparator.compare(expected.data, self.content_service.get_output(seg_id), f"Content[{index}]")
323
300
 
324
- def compare_content_list(self, comparitor: CompareHelper, expected_outputs: List[IOContent], content: List):
301
+ def compare_content_list(self, comparator: CompareHelper, expected_outputs: List[IOContent], content: List):
325
302
  assert_equal_len(expected_outputs, content)
326
303
  for index, expected_ioc in enumerate(expected_outputs):
327
304
  if len(expected_ioc.content_bytes) == 0:
328
- expected = LoadedContent(self.did, expected_ioc, self.load_file(output_ioc))
305
+ expected = LoadedContent(self.did, expected_ioc, self.load_file(expected_ioc))
329
306
  else:
330
307
  expected = LoadedContent(self.did, expected_ioc, None)
331
- self.compare_one_content(comparitor, expected, content[index], index)
332
-
333
- def compare_one_metric(self, expected: Metric, result: Metric):
334
- assert expected.name == result.name
335
- assert_equal_with_label(expected.value, result.value, expected.name)
336
- assert_keys_and_values(expected.tags, result.tags)
337
-
338
- expected_value = expected['value']
339
- if type(expected_value) == str:
340
- comparitor.compare(expected_value, actual['value'], f"Domain[{index}]")
341
- elif type(expected_value) == IOContent:
342
- expected_data = self.load_file(expected_value)
343
- comparitor.compare(expected_data, actual['value'], f"Domain[{index}]")
344
- else:
345
- raise ValueError(f"unknown expected_value type: {type(expected_value)}")
308
+ self.compare_one_content(comparator, expected, content[index], index)
346
309
 
347
- def compare_one_metric(self, expected: Metric, result: Metric):
310
+ @staticmethod
311
+ def compare_one_metric(expected: Metric, result: Metric):
348
312
  assert expected.name == result.name
349
313
  assert_equal_with_label(expected.value, result.value, expected.name)
350
314
  assert_keys_and_values(expected.tags, result.tags)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "deltafi"
3
- version = "2.0rc1714076410617"
3
+ version = "2.0rc1715691282958"
4
4
  description = "SDK for DeltaFi plugins and actions"
5
5
  authors = ["DeltaFi <deltafi@systolic.com>"]
6
6
  license = "Apache License, Version 2.0"
@@ -23,19 +23,19 @@ classifiers = [
23
23
  python = "^3.9"
24
24
  deepdiff = ">=6.7.1"
25
25
  json-logging = ">=1.3.0"
26
- minio = ">=7.2.3"
27
- pydantic = ">=2.5.3"
28
- redis = ">=5.0.1"
26
+ minio = ">=7.2.5"
27
+ pydantic = ">=2.7.1"
28
+ redis = ">=5.0.4"
29
29
  requests = ">=2.31.0"
30
- urllib3 = ">=2.1.0"
30
+ urllib3 = ">=2.2.1"
31
31
 
32
32
  [tool.poetry.group.test]
33
33
  optional = true
34
34
 
35
35
  [tool.poetry.group.test.dependencies]
36
- pytest = ">=7.4.4"
37
- pytest-mock = ">=3.12.0"
38
- mockito = ">=1.4.0"
36
+ pytest = ">=8.1.1"
37
+ pytest-mock = ">=3.14.0"
38
+ mockito = ">=1.5.0"
39
39
 
40
40
  [tool.poetry.urls]
41
41
  'Source Code' = "https://gitlab.com/deltafi/deltafi"