deltafi 2.0rc1705024454242__py3-none-any.whl → 2.4.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/__init__.py +1 -1
- deltafi/action.py +36 -20
- deltafi/actioneventqueue.py +1 -1
- deltafi/actiontype.py +3 -1
- deltafi/domain.py +73 -63
- deltafi/exception.py +1 -11
- deltafi/genericmodel.py +4 -2
- deltafi/input.py +1 -1
- deltafi/logger.py +4 -4
- deltafi/metric.py +2 -2
- deltafi/plugin.py +198 -50
- deltafi/result.py +95 -31
- deltafi/storage.py +6 -1
- deltafi/test_kit/__init__.py +1 -1
- deltafi/test_kit/assertions.py +10 -2
- deltafi/test_kit/compare_helpers.py +251 -8
- deltafi/test_kit/constants.py +1 -1
- deltafi/test_kit/egress.py +54 -0
- deltafi/test_kit/framework.py +146 -112
- deltafi/test_kit/timed_ingress.py +101 -0
- deltafi/test_kit/transform.py +45 -15
- {deltafi-2.0rc1705024454242.dist-info → deltafi-2.4.0.dist-info}/METADATA +13 -12
- deltafi-2.4.0.dist-info/RECORD +24 -0
- {deltafi-2.0rc1705024454242.dist-info → deltafi-2.4.0.dist-info}/WHEEL +1 -1
- deltafi-2.0rc1705024454242.dist-info/RECORD +0 -22
deltafi/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
deltafi/action.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -20,13 +20,23 @@ 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
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
class Join(ABC):
|
|
31
|
+
def join(self, transform_inputs: List[TransformInput]):
|
|
32
|
+
all_content = []
|
|
33
|
+
all_metadata = {}
|
|
34
|
+
for transform_input in transform_inputs:
|
|
35
|
+
all_content += transform_input.content
|
|
36
|
+
all_metadata.update(transform_input.metadata)
|
|
37
|
+
return TransformInput(content=all_content, metadata=all_metadata)
|
|
38
|
+
|
|
39
|
+
|
|
30
40
|
class Action(ABC):
|
|
31
41
|
def __init__(self, action_type: ActionType, description: str, valid_result_types: tuple):
|
|
32
42
|
self.action_type = action_type
|
|
@@ -38,8 +48,8 @@ class Action(ABC):
|
|
|
38
48
|
def build_input(self, context: Context, delta_file_message: DeltaFileMessage):
|
|
39
49
|
pass
|
|
40
50
|
|
|
41
|
-
def
|
|
42
|
-
raise RuntimeError(f"
|
|
51
|
+
def execute_join_action(self, event):
|
|
52
|
+
raise RuntimeError(f"Join is not supported for {self.__class__.__name__}")
|
|
43
53
|
|
|
44
54
|
@abstractmethod
|
|
45
55
|
def execute(self, context: Context, action_input: Any, params: BaseModel):
|
|
@@ -48,22 +58,25 @@ class Action(ABC):
|
|
|
48
58
|
def execute_action(self, event):
|
|
49
59
|
if event.delta_file_messages is None or not len(event.delta_file_messages):
|
|
50
60
|
raise RuntimeError(f"Received event with no delta file messages for did {event.context.did}")
|
|
51
|
-
|
|
52
|
-
|
|
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))
|
|
61
|
+
if event.context.join is not None:
|
|
62
|
+
result = self.execute_join_action(event)
|
|
56
63
|
else:
|
|
57
|
-
result = self.execute(
|
|
58
|
-
|
|
64
|
+
result = self.execute(
|
|
65
|
+
event.context,
|
|
66
|
+
self.build_input(event.context, event.delta_file_messages[0]),
|
|
67
|
+
self.param_class().model_validate(event.params))
|
|
59
68
|
|
|
60
69
|
self.validate_type(result)
|
|
61
70
|
return result
|
|
62
71
|
|
|
63
72
|
@staticmethod
|
|
64
|
-
def param_class(
|
|
73
|
+
def param_class():
|
|
65
74
|
"""Factory method to create and return an empty GenericModel instance.
|
|
66
75
|
|
|
76
|
+
All action parameter classes must inherit pydantic.BaseModel.
|
|
77
|
+
Use of complex types in custom action parameter classes must specify
|
|
78
|
+
the internal types when defined. E.g., dict[str, str], or List[str]
|
|
79
|
+
|
|
67
80
|
Returns
|
|
68
81
|
-------
|
|
69
82
|
GenericModel
|
|
@@ -110,18 +123,21 @@ class TimedIngressAction(Action, ABC):
|
|
|
110
123
|
|
|
111
124
|
class TransformAction(Action, ABC):
|
|
112
125
|
def __init__(self, description: str):
|
|
113
|
-
super().__init__(ActionType.TRANSFORM, description,
|
|
126
|
+
super().__init__(ActionType.TRANSFORM, description,
|
|
127
|
+
(TransformResult, TransformResults, ErrorResult, FilterResult))
|
|
114
128
|
|
|
115
129
|
def build_input(self, context: Context, delta_file_message: DeltaFileMessage):
|
|
116
130
|
return TransformInput(content=delta_file_message.content_list, metadata=delta_file_message.metadata)
|
|
117
131
|
|
|
118
|
-
def
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
def execute_join_action(self, event):
|
|
133
|
+
if isinstance(self, Join):
|
|
134
|
+
return self.execute(
|
|
135
|
+
event.context,
|
|
136
|
+
self.join([self.build_input(event.context, delta_file_message)
|
|
137
|
+
for delta_file_message in event.delta_file_messages]),
|
|
138
|
+
self.param_class().model_validate(event.params))
|
|
139
|
+
else:
|
|
140
|
+
super().execute_join_action(event)
|
|
125
141
|
|
|
126
142
|
@abstractmethod
|
|
127
143
|
def transform(self, context: Context, params: BaseModel, transform_input: TransformInput):
|
deltafi/actioneventqueue.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
deltafi/actiontype.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -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"
|
deltafi/domain.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -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
|
|
|
@@ -40,61 +41,88 @@ class ActionExecution(NamedTuple):
|
|
|
40
41
|
|
|
41
42
|
class Context(NamedTuple):
|
|
42
43
|
did: str
|
|
43
|
-
|
|
44
|
+
delta_file_name: str
|
|
45
|
+
data_source: str
|
|
46
|
+
flow_name: str
|
|
47
|
+
flow_id: str
|
|
44
48
|
action_name: str
|
|
45
|
-
|
|
46
|
-
ingress_flow: str
|
|
47
|
-
egress_flow: str
|
|
48
|
-
system: str
|
|
49
|
+
action_version: str
|
|
49
50
|
hostname: str
|
|
51
|
+
system_name: str
|
|
50
52
|
content_service: ContentService
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
join: dict = None
|
|
54
|
+
joined_dids: List[str] = None
|
|
53
55
|
memo: str = None
|
|
54
56
|
logger: Logger = None
|
|
57
|
+
saved_content: List = []
|
|
55
58
|
|
|
56
59
|
@classmethod
|
|
57
|
-
def create(cls, context: dict,
|
|
60
|
+
def create(cls, context: dict, content_service: ContentService, logger: Logger):
|
|
58
61
|
did = context['did']
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
action_name = action_name_parts[1]
|
|
62
|
-
if 'sourceFilename' in context:
|
|
63
|
-
source_filename = context['sourceFilename']
|
|
62
|
+
if 'deltaFileName' in context:
|
|
63
|
+
delta_file_name = context['deltaFileName']
|
|
64
64
|
else:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
egress_flow = context['egressFlow']
|
|
65
|
+
delta_file_name = None
|
|
66
|
+
if 'dataSource' in context:
|
|
67
|
+
data_source = context['dataSource']
|
|
69
68
|
else:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
collect = context['collect']
|
|
69
|
+
data_source = None
|
|
70
|
+
if 'flowName' in context:
|
|
71
|
+
flow_name = context['flowName']
|
|
74
72
|
else:
|
|
75
|
-
|
|
76
|
-
if '
|
|
77
|
-
|
|
73
|
+
flow_name = None
|
|
74
|
+
if 'flowId' in context:
|
|
75
|
+
flow_id = context['flowId']
|
|
78
76
|
else:
|
|
79
|
-
|
|
77
|
+
flow_id = None
|
|
78
|
+
if 'actionName' in context:
|
|
79
|
+
action_name = context['actionName']
|
|
80
|
+
else:
|
|
81
|
+
action_name = None
|
|
82
|
+
if 'actionVersion' in context:
|
|
83
|
+
action_version = context['actionVersion']
|
|
84
|
+
else:
|
|
85
|
+
action_version = None
|
|
86
|
+
if 'hostname' in context:
|
|
87
|
+
hostname = context['hostname']
|
|
88
|
+
else:
|
|
89
|
+
hostname = None
|
|
90
|
+
if 'systemName' in context:
|
|
91
|
+
system_name = context['systemName']
|
|
92
|
+
else:
|
|
93
|
+
system_name = None
|
|
94
|
+
if 'join' in context:
|
|
95
|
+
join = context['join']
|
|
96
|
+
else:
|
|
97
|
+
join = None
|
|
98
|
+
if 'joinedDids' in context:
|
|
99
|
+
joined_dids = context['joinedDids']
|
|
100
|
+
else:
|
|
101
|
+
joined_dids = None
|
|
80
102
|
if 'memo' in context:
|
|
81
103
|
memo = context['memo']
|
|
82
104
|
else:
|
|
83
105
|
memo = None
|
|
106
|
+
|
|
84
107
|
return Context(did=did,
|
|
85
|
-
|
|
108
|
+
delta_file_name=delta_file_name,
|
|
109
|
+
data_source=data_source,
|
|
110
|
+
flow_name=flow_name,
|
|
111
|
+
flow_id=flow_id,
|
|
86
112
|
action_name=action_name,
|
|
87
|
-
|
|
88
|
-
ingress_flow=ingress_flow,
|
|
89
|
-
egress_flow=egress_flow,
|
|
90
|
-
system=system,
|
|
113
|
+
action_version=action_version,
|
|
91
114
|
hostname=hostname,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
115
|
+
system_name=system_name,
|
|
116
|
+
join=join,
|
|
117
|
+
joined_dids=joined_dids,
|
|
95
118
|
memo=memo,
|
|
119
|
+
content_service=content_service,
|
|
120
|
+
saved_content=[],
|
|
96
121
|
logger=logger)
|
|
97
122
|
|
|
123
|
+
def child_context(self):
|
|
124
|
+
return self._replace(did=str(uuid4()))
|
|
125
|
+
|
|
98
126
|
|
|
99
127
|
class Content:
|
|
100
128
|
"""
|
|
@@ -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.
|
|
@@ -264,6 +291,12 @@ class Content:
|
|
|
264
291
|
"""
|
|
265
292
|
self.segments.extend(other_content.segments)
|
|
266
293
|
|
|
294
|
+
def get_segment_names(self):
|
|
295
|
+
segment_names = {}
|
|
296
|
+
for seg in self.segments:
|
|
297
|
+
segment_names[seg.id()] = seg
|
|
298
|
+
return segment_names
|
|
299
|
+
|
|
267
300
|
def __eq__(self, other):
|
|
268
301
|
if isinstance(other, Content):
|
|
269
302
|
return (self.name == other.name and
|
|
@@ -296,41 +329,17 @@ class Content:
|
|
|
296
329
|
content_service=content_service)
|
|
297
330
|
|
|
298
331
|
|
|
299
|
-
class Domain(NamedTuple):
|
|
300
|
-
name: str
|
|
301
|
-
value: str
|
|
302
|
-
media_type: str
|
|
303
|
-
|
|
304
|
-
@classmethod
|
|
305
|
-
def from_dict(cls, domain: dict):
|
|
306
|
-
name = domain['name']
|
|
307
|
-
if 'value' in domain:
|
|
308
|
-
value = domain['value']
|
|
309
|
-
else:
|
|
310
|
-
value = None
|
|
311
|
-
media_type = domain['mediaType']
|
|
312
|
-
return Domain(name=name,
|
|
313
|
-
value=value,
|
|
314
|
-
media_type=media_type)
|
|
315
|
-
|
|
316
|
-
|
|
317
332
|
class DeltaFileMessage(NamedTuple):
|
|
318
333
|
metadata: Dict[str, str]
|
|
319
334
|
content_list: List[Content]
|
|
320
|
-
domains: List[Domain]
|
|
321
|
-
enrichments: List[Domain]
|
|
322
335
|
|
|
323
336
|
@classmethod
|
|
324
337
|
def from_dict(cls, delta_file_message: dict, content_service: ContentService):
|
|
325
338
|
metadata = delta_file_message['metadata']
|
|
326
339
|
content_list = [Content.from_dict(content, content_service) for content in delta_file_message['contentList']]
|
|
327
|
-
domains = [Domain.from_dict(domain) for domain in delta_file_message['domains']] if 'domains' in delta_file_message else []
|
|
328
|
-
enrichments = [Domain.from_dict(domain) for domain in delta_file_message['enrichments']] if 'enrichments' in delta_file_message else []
|
|
329
340
|
|
|
330
341
|
return DeltaFileMessage(metadata=metadata,
|
|
331
|
-
content_list=content_list
|
|
332
|
-
domains=domains,
|
|
333
|
-
enrichments=enrichments)
|
|
342
|
+
content_list=content_list)
|
|
334
343
|
|
|
335
344
|
|
|
336
345
|
class Event(NamedTuple):
|
|
@@ -341,9 +350,10 @@ class Event(NamedTuple):
|
|
|
341
350
|
return_address: str
|
|
342
351
|
|
|
343
352
|
@classmethod
|
|
344
|
-
def create(cls, event: dict,
|
|
345
|
-
delta_file_messages = [DeltaFileMessage.from_dict(delta_file_message, content_service) for delta_file_message in
|
|
346
|
-
|
|
353
|
+
def create(cls, event: dict, content_service: ContentService, logger: Logger):
|
|
354
|
+
delta_file_messages = [DeltaFileMessage.from_dict(delta_file_message, content_service) for delta_file_message in
|
|
355
|
+
event['deltaFileMessages']]
|
|
356
|
+
context = Context.create(event['actionContext'], content_service, logger)
|
|
347
357
|
params = event['actionParams']
|
|
348
358
|
queue_name = None
|
|
349
359
|
if 'queueName' in event:
|
deltafi/exception.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -23,16 +23,6 @@ class ExpectedContentException(RuntimeError):
|
|
|
23
23
|
self.size = size
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class MissingDomainException(RuntimeError):
|
|
27
|
-
def __init__(self, name):
|
|
28
|
-
self.name = name
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class MissingEnrichmentException(RuntimeError):
|
|
32
|
-
def __init__(self, name):
|
|
33
|
-
self.name = name
|
|
34
|
-
|
|
35
|
-
|
|
36
26
|
class MissingMetadataException(RuntimeError):
|
|
37
27
|
def __init__(self, key):
|
|
38
28
|
self.key = key
|
deltafi/genericmodel.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
|
|
23
23
|
Provides an empty subclass of pydantic.BaseModel.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
All action parameter classes must inherit pydantic.BaseModel.
|
|
26
|
+
|
|
27
|
+
Starting Pydantic v2, the BaseModel cannot be directly instantiated. This class provides for instantiation of GenericModel objects that inherit from BaseModel.
|
|
26
28
|
|
|
27
29
|
This class does not define fields for validation or any other purpose.
|
|
28
30
|
"""
|
deltafi/input.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
deltafi/logger.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
|
|
19
19
|
import logging
|
|
20
20
|
import sys
|
|
21
|
-
from datetime import datetime
|
|
21
|
+
from datetime import datetime, UTC
|
|
22
22
|
|
|
23
23
|
import json_logging
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def get_logger(name: str = None) -> logging.Logger:
|
|
27
27
|
logger = logging.getLogger(name)
|
|
28
|
-
logger.setLevel(logging.
|
|
28
|
+
logger.setLevel(logging.INFO)
|
|
29
29
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
30
30
|
logger.propagate = False
|
|
31
31
|
|
|
@@ -42,7 +42,7 @@ def _sanitize_log_msg(record):
|
|
|
42
42
|
class JSONLogFormatter(json_logging.JSONLogFormatter):
|
|
43
43
|
|
|
44
44
|
def _format_log_object(self, record, request_util):
|
|
45
|
-
utcnow = datetime.
|
|
45
|
+
utcnow = datetime.now(UTC)
|
|
46
46
|
|
|
47
47
|
json_log_object = {
|
|
48
48
|
'timestamp': json_logging.util.iso_time_format(utcnow),
|
deltafi/metric.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# DeltaFi - Data transformation and enrichment platform
|
|
3
3
|
#
|
|
4
|
-
# Copyright 2021-
|
|
4
|
+
# Copyright 2021-2025 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
5
|
#
|
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
7
|
# you may not use this file except in compliance with the License.
|
|
@@ -22,7 +22,7 @@ from typing import Dict, NamedTuple
|
|
|
22
22
|
class Metric(NamedTuple):
|
|
23
23
|
name: str
|
|
24
24
|
value: int
|
|
25
|
-
tags: Dict[str, str]
|
|
25
|
+
tags: Dict[str, str] = {}
|
|
26
26
|
|
|
27
27
|
def json(self):
|
|
28
28
|
return {
|