deltafi 2.0rc1720728217472__py3-none-any.whl → 2.0rc1720817063181__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.
deltafi/result.py CHANGED
@@ -17,9 +17,9 @@
17
17
  #
18
18
 
19
19
  import abc
20
- from enum import Enum
21
20
  import uuid
22
- from typing import Dict, List
21
+ from enum import Enum
22
+ from typing import NamedTuple
23
23
 
24
24
  from deltafi.domain import Content, Context
25
25
  from deltafi.metric import Metric
@@ -44,22 +44,8 @@ class Result:
44
44
 
45
45
  def add_metric(self, metric: Metric):
46
46
  self.metrics.append(metric)
47
-
48
-
49
- class DomainResult(Result):
50
- def __init__(self, context: Context):
51
- super().__init__('domain', 'DOMAIN', context)
52
- self.annotations = {}
53
-
54
- def annotate(self, key: str, value: str):
55
- self.annotations[key] = value
56
47
  return self
57
48
 
58
- def response(self):
59
- return {
60
- 'annotations': self.annotations
61
- }
62
-
63
49
 
64
50
  class EgressResult(Result):
65
51
  def __init__(self, context: Context, destination: str, bytes_egressed: int):
@@ -71,31 +57,6 @@ class EgressResult(Result):
71
57
  return None
72
58
 
73
59
 
74
- class EnrichResult(Result):
75
- def __init__(self, context: Context):
76
- super().__init__('enrich', 'ENRICH', context)
77
- self.enrichments = []
78
- self.annotations = {}
79
-
80
- def enrich(self, name: str, value: str, media_type: str):
81
- self.enrichments.append({
82
- 'name': name,
83
- 'value': value,
84
- 'mediaType': media_type
85
- })
86
- return self
87
-
88
- def annotate(self, key: str, value: str):
89
- self.annotations[key] = value
90
- return self
91
-
92
- def response(self):
93
- return {
94
- 'enrichments': self.enrichments,
95
- 'annotations': self.annotations
96
- }
97
-
98
-
99
60
  class ErrorResult(Result):
100
61
  def __init__(self, context: Context, error_cause: str, error_context: str):
101
62
  super().__init__('error', 'ERROR', context)
@@ -116,7 +77,7 @@ class ErrorResult(Result):
116
77
 
117
78
 
118
79
  class FilterResult(Result):
119
- def __init__(self, context: Context, filtered_cause: str, filtered_context: str=None):
80
+ def __init__(self, context: Context, filtered_cause: str, filtered_context: str = None):
120
81
  super().__init__('filter', 'FILTER', context)
121
82
  self.filtered_cause = filtered_cause
122
83
  self.filtered_context = filtered_context
@@ -134,87 +95,13 @@ class FilterResult(Result):
134
95
  }
135
96
 
136
97
 
137
- class FormatResult(Result):
138
- def __init__(self, context: Context):
139
- super().__init__('format', 'FORMAT', context)
140
- self.content = None
141
- self.delete_metadata_keys = []
142
- self.metadata = {}
143
-
144
- def set_metadata(self, metadata: dict):
145
- self.metadata = metadata
146
- return self
147
-
148
- def add_metadata(self, key: str, value: str):
149
- self.metadata[key] = value
150
- return self
151
-
152
- def delete_metadata_key(self, key: str):
153
- self.delete_metadata_keys.append(key)
154
- return self
155
-
156
- def set_content(self, content: Content):
157
- self.content = content
158
- return self
159
-
160
- def save_string_content(self, string_data: str, name: str, media_type: str):
161
- segment = self.context.content_service.put_str(self.context.did, string_data)
162
- self.content = Content(name=name, segments=[segment], media_type=media_type,
163
- content_service=self.context.content_service)
164
- return self
165
-
166
- def save_byte_content(self, byte_data: bytes, name: str, media_type: str):
167
- segment = self.context.content_service.put_bytes(self.context.did, byte_data)
168
- self.content = Content(name=name, segments=[segment], media_type=media_type,
169
- content_service=self.context.content_service)
170
- return self
171
-
172
- def response(self):
173
- return {
174
- 'content': self.content.json(),
175
- 'metadata': self.metadata,
176
- 'deleteMetadataKeys': self.delete_metadata_keys
177
- }
178
-
179
-
180
- class ChildFormatResult:
181
- def __init__(self, format_result: FormatResult = None):
182
- self._did = str(uuid.uuid4())
183
- self.format_result = format_result
184
-
185
- @property
186
- def did(self):
187
- return self._did
188
-
189
- def response(self):
190
- res = self.format_result.response()
191
- res["did"] = self._did
192
- return res
193
-
194
-
195
- class FormatManyResult(Result):
196
- def __init__(self, context: Context):
197
- super().__init__('formatMany', 'FORMAT_MANY', context)
198
- self.format_results = []
199
-
200
- def add_format_result(self, format_result):
201
- if isinstance(format_result, ChildFormatResult):
202
- self.format_results.append(format_result)
203
- else:
204
- self.format_results.append(ChildFormatResult(format_result))
205
- return self
206
-
207
- def response(self):
208
- return [format_result.response() for format_result in self.format_results]
209
-
210
-
211
98
  class IngressResultItem:
212
- def __init__(self, context: Context, filename: str):
99
+ def __init__(self, context: Context, delta_file_name: str):
213
100
  self.context = context
214
- self.filename = filename
215
101
  self._did = str(uuid.uuid4())
216
102
  self.content = []
217
103
  self.metadata = {}
104
+ self.delta_file_name = delta_file_name
218
105
 
219
106
  @property
220
107
  def did(self):
@@ -253,7 +140,7 @@ class IngressResultItem:
253
140
  def response(self):
254
141
  return {
255
142
  'did': self._did,
256
- 'filename': self.filename,
143
+ 'deltaFileName': self.delta_file_name,
257
144
  'metadata': self.metadata,
258
145
  'content': [content.json() for content in self.content]
259
146
  }
@@ -269,10 +156,10 @@ class IngressResult(Result):
269
156
  def __init__(self, context: Context):
270
157
  super().__init__('ingress', 'INGRESS', context)
271
158
  self.memo = None
272
- self.execute_immediate = False
273
159
  self.ingress_result_items = []
160
+ self.execute_immediate = False
274
161
  self.status = IngressStatusEnum.HEALTHY
275
- self.statusMessage = None
162
+ self.status_message = None
276
163
 
277
164
  def add_item(self, ingress_result_item: IngressResultItem):
278
165
  self.ingress_result_items.append(ingress_result_item)
@@ -284,17 +171,16 @@ class IngressResult(Result):
284
171
  'executeImmediate': self.execute_immediate,
285
172
  'ingressItems': [ingress_result_item.response() for ingress_result_item in self.ingress_result_items],
286
173
  'status': self.status.value,
287
- 'statusMessage': self.statusMessage
174
+ 'statusMessage': self.status_message
288
175
  }
289
176
 
290
177
 
291
- class LoadResult(Result):
178
+ class TransformResult(Result):
292
179
  def __init__(self, context: Context):
293
- super().__init__('load', 'LOAD', context)
180
+ super().__init__('transform', 'TRANSFORM', context)
294
181
  self.content = []
295
- self.metadata = {}
296
- self.domains = []
297
182
  self.annotations = {}
183
+ self.metadata = {}
298
184
  self.delete_metadata_keys = []
299
185
 
300
186
  # content can be a single Content or a List[Content]
@@ -327,13 +213,6 @@ class LoadResult(Result):
327
213
  self.metadata[key] = value
328
214
  return self
329
215
 
330
- def add_domain(self, name: str, value: str, media_type: str):
331
- self.domains.append({
332
- 'name': name,
333
- 'value': value,
334
- 'mediaType': media_type})
335
- return self
336
-
337
216
  def annotate(self, key: str, value: str):
338
217
  self.annotations[key] = value
339
218
  return self
@@ -342,133 +221,41 @@ class LoadResult(Result):
342
221
  self.delete_metadata_keys.append(key)
343
222
  return self
344
223
 
345
- def response(self):
224
+ def json(self):
346
225
  return {
347
- 'domains': self.domains,
348
226
  'content': [content.json() for content in self.content],
349
- 'metadata': self.metadata,
350
227
  'annotations': self.annotations,
228
+ 'metadata': self.metadata,
351
229
  'deleteMetadataKeys': self.delete_metadata_keys
352
230
  }
353
231
 
354
-
355
- class ChildLoadResult:
356
- def __init__(self, load_result: LoadResult = None):
357
- self._did = str(uuid.uuid4())
358
- self.load_result = load_result
359
-
360
- @property
361
- def did(self):
362
- return self._did
363
-
364
- def response(self):
365
- res = self.load_result.response()
366
- res["did"] = self._did
367
- return res
368
-
369
-
370
- class LoadManyResult(Result):
371
- def __init__(self, context: Context):
372
- super().__init__('loadMany', 'LOAD_MANY', context)
373
- self.load_results = []
374
-
375
- def add_load_result(self, load_result):
376
- if isinstance(load_result, ChildLoadResult):
377
- self.load_results.append(load_result)
378
- else:
379
- self.load_results.append(ChildLoadResult(load_result))
380
- return self
381
-
382
232
  def response(self):
383
- return [load_result.response() for load_result in self.load_results]
384
-
385
-
386
- class ReinjectResult(Result):
387
- class ReinjectChild:
388
- def __init__(self, filename: str, flow: str, content: List[Content], metadata: Dict[str, str]):
389
- self.filename = filename
390
- self.flow = flow
391
- self.content = content
392
- self.metadata = metadata
233
+ return [self.json()]
393
234
 
394
- def json(self):
395
- return {
396
- 'filename': self.filename,
397
- 'flow': self.flow,
398
- 'metadata': self.metadata,
399
- 'content': [content.json() for content in self.content]
400
- }
401
-
402
- def __init__(self, context: Context):
403
- super().__init__('reinject', 'REINJECT', context)
404
- self.children = []
405
235
 
406
- def add_child(self, filename: str, flow: str, content: List[Content], metadata: Dict[str, str]):
407
- child = ReinjectResult.ReinjectChild(filename, flow, content, metadata)
408
- self.children.append(child)
236
+ class NamedTransformResult(NamedTuple):
237
+ result: TransformResult
238
+ name: str
409
239
 
410
- def response(self):
411
- return [child.json() for child in self.children]
240
+ def json(self):
241
+ j = self.result.json()
242
+ if self.name is not None:
243
+ j['name'] = self.name
244
+ return j
412
245
 
413
246
 
414
- class TransformResult(Result):
247
+ class TransformResults(Result):
415
248
  def __init__(self, context: Context):
416
249
  super().__init__('transform', 'TRANSFORM', context)
417
- self.content = []
418
- self.metadata = {}
419
- self.annotations = {}
420
- self.delete_metadata_keys = []
421
-
422
- # content can be a single Content or a List[Content]
423
- def add_content(self, content):
424
- if content:
425
- if type(content) == list:
426
- self.content.extend(content)
427
- else:
428
- self.content.append(content)
250
+ self.named_results = []
429
251
 
430
- return self
431
-
432
- def save_string_content(self, string_data: str, name: str, media_type: str):
433
- segment = self.context.content_service.put_str(self.context.did, string_data)
434
- self.content.append(
435
- Content(name=name, segments=[segment], media_type=media_type, content_service=self.context.content_service))
436
- return self
437
-
438
- def save_byte_content(self, byte_data: bytes, name: str, media_type: str):
439
- segment = self.context.content_service.put_bytes(self.context.did, byte_data)
440
- self.content.append(
441
- Content(name=name, segments=[segment], media_type=media_type, content_service=self.context.content_service))
442
- return self
443
-
444
- def set_metadata(self, metadata: dict):
445
- self.metadata = metadata
446
- return self
447
-
448
- def add_metadata(self, key: str, value: str):
449
- self.metadata[key] = value
450
- return self
451
-
452
- def annotate(self, key: str, value: str):
453
- self.annotations[key] = value
454
- return self
455
-
456
- def delete_metadata_key(self, key: str):
457
- self.delete_metadata_keys.append(key)
252
+ def add_result(self, result: TransformResult, name: str = None):
253
+ self.named_results.append(NamedTransformResult(result, name))
458
254
  return self
459
255
 
460
256
  def response(self):
461
- return {
462
- 'content': [content.json() for content in self.content],
463
- 'metadata': self.metadata,
464
- 'annotations': self.annotations,
465
- 'deleteMetadataKeys': self.delete_metadata_keys
466
- }
467
-
468
-
469
- class ValidateResult(Result):
470
- def __init__(self, context: Context):
471
- super().__init__(None, 'VALIDATE', context)
472
-
473
- def response(self):
474
- return None
257
+ transform_events = []
258
+ for named_result in self.named_results:
259
+ json_dict = named_result.json()
260
+ transform_events.append(json_dict)
261
+ return transform_events
@@ -37,15 +37,20 @@ class IOContent:
37
37
  The IOContent class holds the details for loading input or output
38
38
  content into the test framework.
39
39
  Attributes:
40
- file_name (str): The name of file in test/data.
41
- content_name (str): The name of the content.
42
- content_type (str): The media type of the content
43
- offset (int): Offset to use in Segment
44
- content_bytes (str): Bypass file read, and uses these bytes for content
40
+ file_name (str) : The name of file in test/data.
41
+ content_name (str) : The name of the content.
42
+ content_type (str) : The media type of the content
43
+ offset (int) : Offset to use in Segment
44
+ content_bytes (str): Optional. If set to a String of length greater than zero, indicates to consumers of this
45
+ IOContent that they should bypass file read and use these bytes for content.
46
+ no_content (bool) : Optional. If 'True', then consumers should not attempt to interpret content but should
47
+ apply other aspects of this IOContent. When 'True', 'content_bytes' should be ignored and
48
+ loaded content, if any, should be interpreted as empty String or otherwise as documented by
49
+ the consumer.
45
50
  """
46
51
 
47
52
  def __init__(self, file_name: str, content_name: str = None, content_type: str = None, offset: int = 0,
48
- content_bytes: str = ""):
53
+ content_bytes: str = "", no_content: bool = False):
49
54
  self.file_name = file_name
50
55
  if content_name is None:
51
56
  self.content_name = file_name
@@ -56,7 +61,11 @@ class IOContent:
56
61
  else:
57
62
  self.content_type = content_type
58
63
  self.offset = offset
59
- self.content_bytes = content_bytes
64
+ self.no_content = no_content
65
+ if no_content:
66
+ self.content_bytes = None
67
+ else:
68
+ self.content_bytes = content_bytes
60
69
  self.segment_uuid = uuid.uuid4()
61
70
 
62
71
  @classmethod
@@ -79,7 +88,10 @@ class LoadedContent:
79
88
  if data is not None:
80
89
  self.data = data
81
90
  else:
82
- self.data = ioc.content_bytes
91
+ if ioc.no_content:
92
+ self.data = ""
93
+ else:
94
+ self.data = ioc.content_bytes
83
95
  self.segment = Segment.from_dict(
84
96
  {"uuid": str(ioc.segment_uuid), "offset": self.offset, "size": len(self.data), "did": did})
85
97
 
@@ -103,6 +115,10 @@ class InternalContentService:
103
115
  seg_id = segments[0].uuid
104
116
  return self.loaded_content[seg_id].data
105
117
 
118
+ def get_bytes(self, segments: List[Segment]):
119
+ seg_id = segments[0].uuid
120
+ return self.loaded_content[seg_id].data.encode('utf-8')
121
+
106
122
  def get_output(self, seg_id: str):
107
123
  if seg_id in self.outputs:
108
124
  return self.outputs[seg_id]
@@ -123,8 +139,8 @@ class TestCaseBase(ABC):
123
139
  - inputs: (optional) List[IOContent]: input content to action
124
140
  - parameters: (optional) Dict: map of action input parameters
125
141
  - in_meta: (optional) Dict: map of metadata as input to action
126
- - in_domains: (optional) List[Domain]: list of domains as input to action
127
- - in_enrichments: (optional) List[Domain]: list of enrichments as input to action
142
+ - join_meta: (optional): List[Dict]: When a List is provided, this enables the JOIN portion of an action.
143
+ When using JOIN, join_meta must match the size of inputs, though the Dict can be empty
128
144
  - did: (optional): str: overrides random DID
129
145
  """
130
146
  if "action" in data:
@@ -144,16 +160,14 @@ class TestCaseBase(ABC):
144
160
 
145
161
  self.inputs = data["inputs"] if "inputs" in data else []
146
162
  self.file_name = data["file_name"] if "file_name" in data else "filename"
147
- self.outputs = data["outputs"] if "outputs" in data else []
148
163
  self.parameters = data["parameters"] if "parameters" in data else {}
149
164
  self.in_meta = data["in_meta"] if "in_meta" in data else {}
150
- self.in_domains = data["in_domains"] if "in_domains" in data else []
151
- self.in_enrichments = data["in_enrichments"] if "in_enrichments" in data else []
152
165
  self.use_did = data["did"] if "did" in data else None
153
166
  self.expected_result_type = None
154
167
  self.err_or_filt_cause = None
155
168
  self.err_or_filt_context = None
156
169
  self.err_or_filt_annotations = None
170
+ self.join_meta = data["join_meta"] if "join_meta" in data else None
157
171
  self.expected_metrics = []
158
172
 
159
173
  def add_metric(self, metric: Metric):
@@ -164,7 +178,7 @@ class TestCaseBase(ABC):
164
178
  A Sets the expected output of the action to an Error Result
165
179
  :param cause: the expected error cause
166
180
  :param context: the expected error context
167
- :param annotations (Optional): Dict: the expected annotations
181
+ :param annotations: Dict: (Optional) the expected annotations
168
182
  """
169
183
  self.expected_result_type = ErrorResult
170
184
  self.err_or_filt_cause = cause
@@ -175,8 +189,8 @@ class TestCaseBase(ABC):
175
189
  """
176
190
  A Sets the expected output of the action to a Filter Result
177
191
  :param cause: the expected filter cause (message)
178
- :param context (Optional): the expected filter context
179
- :param annotations (Optional): Dict: the expected annotations
192
+ :param context: (Optional) the expected filter context
193
+ :param annotations: Dict: (Optional) the expected annotations
180
194
  """
181
195
  self.expected_result_type = FilterResult
182
196
  self.err_or_filt_cause = cause
@@ -193,7 +207,6 @@ class ActionTest(ABC):
193
207
  """
194
208
  self.content_service = InternalContentService()
195
209
  self.did = ""
196
- self.expected_outputs = []
197
210
  self.loaded_inputs = []
198
211
  self.package_name = package_name
199
212
  self.res_path = ""
@@ -204,7 +217,6 @@ class ActionTest(ABC):
204
217
  self.did = str(uuid.uuid4())
205
218
  else:
206
219
  self.did = did
207
- self.expected_outputs = []
208
220
  self.loaded_inputs = []
209
221
  self.res_path = ""
210
222
 
@@ -220,19 +232,12 @@ class ActionTest(ABC):
220
232
 
221
233
  # Load inputs
222
234
  for input_ioc in test_case.inputs:
223
- if len(input_ioc.content_bytes) == 0:
235
+ if not input_ioc.no_content and len(input_ioc.content_bytes) == 0:
224
236
  self.loaded_inputs.append(LoadedContent(self.did, input_ioc, self.load_file(input_ioc)))
225
237
  else:
226
238
  self.loaded_inputs.append(LoadedContent(self.did, input_ioc, None))
227
239
 
228
- # Load expected outputs
229
- for output_ioc in test_case.outputs:
230
- if len(output_ioc.content_bytes) == 0:
231
- self.expected_outputs.append(LoadedContent(self.did, output_ioc, self.load_file(output_ioc)))
232
- else:
233
- self.expected_outputs.append(LoadedContent(self.did, output_ioc, None))
234
-
235
- def make_content_list(self, test_case: TestCaseBase):
240
+ def make_content_list(self):
236
241
  content_list = []
237
242
  for loaded_input in self.loaded_inputs:
238
243
  c = Content(name=loaded_input.name, segments=[loaded_input.segment], media_type=loaded_input.content_type,
@@ -242,34 +247,42 @@ class ActionTest(ABC):
242
247
 
243
248
  return content_list
244
249
 
245
- def make_df_msg(self, test_case: TestCaseBase):
246
- content_list = self.make_content_list(test_case)
250
+ def make_df_msgs(self, test_case: TestCaseBase):
251
+ content_list = self.make_content_list()
247
252
  self.content_service.load(self.loaded_inputs)
248
253
 
249
- return DeltaFileMessage(
250
- metadata=test_case.in_meta,
251
- content_list=content_list,
252
- domains=test_case.in_domains,
253
- enrichments=test_case.in_enrichments)
254
+ delta_file_messages = []
255
+
256
+ if test_case.join_meta is None:
257
+ delta_file_messages.append(DeltaFileMessage(metadata=test_case.in_meta, content_list=content_list))
258
+ else:
259
+ for index, content in enumerate(content_list):
260
+ delta_file_messages.append(DeltaFileMessage(
261
+ metadata=test_case.join_meta[index],
262
+ content_list=[content]))
263
+
264
+ return delta_file_messages
254
265
 
255
266
  def make_context(self, test_case: TestCaseBase):
256
267
  action_name = INGRESS_FLOW + "." + test_case.action.__class__.__name__
268
+ join = {} if test_case.join_meta else None
257
269
  return Context(
258
270
  did=self.did,
259
- action_flow=INGRESS_FLOW,
271
+ delta_file_name=test_case.file_name,
272
+ data_source="DATASRC",
273
+ flow_name=INGRESS_FLOW,
274
+ flow_id="FLOWID",
260
275
  action_name=action_name,
261
- source_filename=test_case.file_name,
262
- ingress_flow=INGRESS_FLOW,
263
- egress_flow=EGRESS_FLOW,
264
- system=SYSTEM,
276
+ action_id="ACTIONID",
277
+ action_version="1.0",
265
278
  hostname=HOSTNAME,
279
+ system_name=SYSTEM,
266
280
  content_service=self.content_service,
267
- collect=None,
268
- collected_dids=None,
281
+ join=join,
269
282
  logger=get_logger())
270
283
 
271
284
  def make_event(self, test_case: TestCaseBase):
272
- return Event(delta_file_messages=[self.make_df_msg(test_case)], context=self.make_context(test_case),
285
+ return Event(delta_file_messages=self.make_df_msgs(test_case), context=self.make_context(test_case),
273
286
  params=test_case.parameters, queue_name="", return_address="")
274
287
 
275
288
  def call_action(self, test_case: TestCaseBase):
@@ -311,37 +324,27 @@ class ActionTest(ABC):
311
324
  else:
312
325
  raise ValueError(f"unknown type: {test_case.expected_result_type}")
313
326
 
314
- def compare_content_details(self, expected: LoadedContent, actual: Content):
327
+ @staticmethod
328
+ def compare_content_details(expected: LoadedContent, actual: Content):
315
329
  assert_equal(expected.content_type, actual.media_type)
316
330
  assert_equal(expected.name, actual.name)
317
331
 
318
- def compare_one_content(self, comparitor: CompareHelper, expected: LoadedContent, actual, index):
332
+ def compare_one_content(self, comparator: CompareHelper, expected: LoadedContent, actual, index):
319
333
  self.compare_content_details(expected, actual)
320
334
  seg_id = actual.segments[0].uuid
321
- comparitor.compare(expected.data, self.content_service.get_output(seg_id), f"Content[{index}]")
322
-
323
- def compare_all_output(self, comparitor: CompareHelper, content: List):
324
- assert_equal_len(self.expected_outputs, content)
325
- for index, expected in enumerate(self.expected_outputs):
326
- self.compare_one_content(comparitor, expected, content[index], index)
327
-
328
- def compare_domains(self, comparitor: CompareHelper, expected_items: List[Dict], results: List[Dict]):
329
- assert_equal_len(expected_items, results)
330
- for index, expected in enumerate(expected_items):
331
- actual = results[index]
332
- assert_equal(expected['name'], actual['name'])
333
- assert_equal(expected['mediaType'], actual['mediaType'])
334
-
335
- expected_value = expected['value']
336
- if type(expected_value) == str:
337
- comparitor.compare(expected_value, actual['value'], f"Domain[{index}]")
338
- elif type(expected_value) == IOContent:
339
- expected_data = self.load_file(expected_value)
340
- comparitor.compare(expected_data, actual['value'], f"Domain[{index}]")
335
+ comparator.compare(expected.data, self.content_service.get_output(seg_id), f"Content[{index}]")
336
+
337
+ def compare_content_list(self, comparator: CompareHelper, expected_outputs: List[IOContent], content: List):
338
+ assert_equal_len(expected_outputs, content)
339
+ for index, expected_ioc in enumerate(expected_outputs):
340
+ if not expected_ioc.no_content and len(expected_ioc.content_bytes) == 0:
341
+ expected = LoadedContent(self.did, expected_ioc, self.load_file(expected_ioc))
341
342
  else:
342
- raise ValueError(f"unknown expected_value type: {type(expected_value)}")
343
+ expected = LoadedContent(self.did, expected_ioc, None)
344
+ self.compare_one_content(comparator, expected, content[index], index)
343
345
 
344
- def compare_one_metric(self, expected: Metric, result: Metric):
346
+ @staticmethod
347
+ def compare_one_metric(expected: Metric, result: Metric):
345
348
  assert expected.name == result.name
346
349
  assert_equal_with_label(expected.value, result.value, expected.name)
347
350
  assert_keys_and_values(expected.tags, result.tags)