cognite-extractor-utils 7.5.14__py3-none-any.whl → 7.7.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 cognite-extractor-utils might be problematic. Click here for more details.
- cognite/extractorutils/__init__.py +1 -1
- cognite/extractorutils/_inner_util.py +1 -1
- cognite/extractorutils/base.py +120 -40
- cognite/extractorutils/configtools/__init__.py +4 -5
- cognite/extractorutils/configtools/_util.py +3 -2
- cognite/extractorutils/configtools/elements.py +206 -33
- cognite/extractorutils/configtools/loaders.py +68 -16
- cognite/extractorutils/configtools/validators.py +5 -1
- cognite/extractorutils/exceptions.py +11 -2
- cognite/extractorutils/metrics.py +17 -12
- cognite/extractorutils/statestore/__init__.py +77 -3
- cognite/extractorutils/statestore/_base.py +7 -3
- cognite/extractorutils/statestore/hashing.py +129 -15
- cognite/extractorutils/statestore/watermark.py +77 -87
- cognite/extractorutils/threading.py +30 -4
- cognite/extractorutils/unstable/__init__.py +5 -5
- cognite/extractorutils/unstable/configuration/__init__.py +3 -0
- cognite/extractorutils/unstable/configuration/exceptions.py +13 -2
- cognite/extractorutils/unstable/configuration/loaders.py +78 -13
- cognite/extractorutils/unstable/configuration/models.py +121 -7
- cognite/extractorutils/unstable/core/__init__.py +5 -0
- cognite/extractorutils/unstable/core/_dto.py +5 -3
- cognite/extractorutils/unstable/core/base.py +113 -4
- cognite/extractorutils/unstable/core/errors.py +41 -0
- cognite/extractorutils/unstable/core/logger.py +149 -0
- cognite/extractorutils/unstable/core/restart_policy.py +16 -2
- cognite/extractorutils/unstable/core/runtime.py +44 -6
- cognite/extractorutils/unstable/core/tasks.py +53 -1
- cognite/extractorutils/unstable/scheduling/__init__.py +13 -0
- cognite/extractorutils/unstable/scheduling/_scheduler.py +1 -1
- cognite/extractorutils/uploader/__init__.py +9 -5
- cognite/extractorutils/uploader/_base.py +4 -5
- cognite/extractorutils/uploader/assets.py +13 -8
- cognite/extractorutils/uploader/data_modeling.py +37 -2
- cognite/extractorutils/uploader/events.py +14 -9
- cognite/extractorutils/uploader/files.py +80 -21
- cognite/extractorutils/uploader/raw.py +12 -7
- cognite/extractorutils/uploader/time_series.py +370 -94
- cognite/extractorutils/uploader/upload_failure_handler.py +35 -2
- cognite/extractorutils/uploader_extractor.py +47 -9
- cognite/extractorutils/uploader_types.py +26 -1
- cognite/extractorutils/util.py +76 -23
- {cognite_extractor_utils-7.5.14.dist-info → cognite_extractor_utils-7.7.0.dist-info}/METADATA +1 -1
- cognite_extractor_utils-7.7.0.dist-info/RECORD +50 -0
- cognite_extractor_utils-7.5.14.dist-info/RECORD +0 -50
- {cognite_extractor_utils-7.5.14.dist-info → cognite_extractor_utils-7.7.0.dist-info}/WHEEL +0 -0
- {cognite_extractor_utils-7.5.14.dist-info → cognite_extractor_utils-7.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a mechanism to handle file upload failures by logging details to a newline delimited JSON file.
|
|
3
|
+
"""
|
|
4
|
+
|
|
1
5
|
from collections.abc import Iterator
|
|
2
|
-
from datetime import datetime
|
|
6
|
+
from datetime import datetime, timezone
|
|
3
7
|
|
|
4
8
|
import jsonlines
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
class FileErrorMapping:
|
|
12
|
+
"""
|
|
13
|
+
A class to represent a mapping of file name to its error reason.
|
|
14
|
+
"""
|
|
15
|
+
|
|
8
16
|
def __init__(self, file_name: str, error_reason: str) -> None:
|
|
9
17
|
self.file_name = file_name
|
|
10
18
|
self.error_reason = error_reason
|
|
11
19
|
|
|
12
20
|
def __iter__(self) -> Iterator[list[str]]:
|
|
21
|
+
"""
|
|
22
|
+
Returns an single-item iterator containing the file name and error reason.
|
|
23
|
+
"""
|
|
13
24
|
return iter([[self.file_name, self.error_reason]])
|
|
14
25
|
|
|
15
26
|
|
|
16
27
|
class FileFailureManager:
|
|
28
|
+
"""
|
|
29
|
+
A class to manage file upload failures by logging them to a newline delimited JSON file.
|
|
30
|
+
"""
|
|
31
|
+
|
|
17
32
|
MAX_QUEUE_SIZE = 500
|
|
18
33
|
START_TIME_KEY = "start_time"
|
|
19
34
|
FILE_REASON_MAP_KEY = "file_error_reason_map"
|
|
@@ -22,7 +37,7 @@ class FileFailureManager:
|
|
|
22
37
|
self.failure_logs: dict[str, str] = {}
|
|
23
38
|
|
|
24
39
|
self.path_to_failure_log: str = self._pre_process_file_extension(path_to_file)
|
|
25
|
-
self.start_time = start_time or str(datetime.now())
|
|
40
|
+
self.start_time = start_time or str(datetime.now(tz=timezone.utc))
|
|
26
41
|
self._initialize_failure_logs()
|
|
27
42
|
|
|
28
43
|
def _pre_process_file_extension(self, path_to_file: str | None) -> str:
|
|
@@ -34,13 +49,28 @@ class FileFailureManager:
|
|
|
34
49
|
self.failure_logs = {}
|
|
35
50
|
|
|
36
51
|
def __len__(self) -> int:
|
|
52
|
+
"""
|
|
53
|
+
Returns the number of failure logs currently stored.
|
|
54
|
+
"""
|
|
37
55
|
return len(self.failure_logs)
|
|
38
56
|
|
|
39
57
|
def clear(self) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Clears the queue of failure logs.
|
|
60
|
+
"""
|
|
40
61
|
self.failure_logs.clear()
|
|
41
62
|
self._initialize_failure_logs()
|
|
42
63
|
|
|
43
64
|
def add(self, file_name: str, error_reason: str) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Adds a file name and its error reason to the failure logs.
|
|
67
|
+
|
|
68
|
+
If the number of logs exceeds the maximum queue size, it writes the logs to a file.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
file_name: The name of the file that failed to upload.
|
|
72
|
+
error_reason: The reason for the failure.
|
|
73
|
+
"""
|
|
44
74
|
error_file_object = FileErrorMapping(file_name=file_name, error_reason=error_reason)
|
|
45
75
|
error_file_dict = dict(error_file_object)
|
|
46
76
|
|
|
@@ -50,6 +80,9 @@ class FileFailureManager:
|
|
|
50
80
|
self.write_to_file()
|
|
51
81
|
|
|
52
82
|
def write_to_file(self) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Flushes the current failure logs to a newline delimited JSON file and clears the queue.
|
|
85
|
+
"""
|
|
53
86
|
if len(self) == 0:
|
|
54
87
|
return
|
|
55
88
|
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DEPRECATED. Use the normal base class and instantiate the upload queues manually.
|
|
3
|
+
|
|
4
|
+
A module containing a version of the Extractor class with pre-defined upload queues.
|
|
5
|
+
"""
|
|
6
|
+
|
|
1
7
|
# Copyright 2022 Cognite AS
|
|
2
8
|
#
|
|
3
9
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -12,12 +18,8 @@
|
|
|
12
18
|
# See the License for the specific language governing permissions and
|
|
13
19
|
# limitations under the License.
|
|
14
20
|
|
|
15
|
-
"""
|
|
16
|
-
A module containing a slightly more advanced base extractor class, sorting a generic output into upload queues.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
21
|
from collections.abc import Callable, Iterable
|
|
20
|
-
from dataclasses import dataclass
|
|
22
|
+
from dataclasses import dataclass, field
|
|
21
23
|
from types import TracebackType
|
|
22
24
|
from typing import Any, TypeVar
|
|
23
25
|
|
|
@@ -29,20 +31,33 @@ from cognite.extractorutils.configtools import BaseConfig, TimeIntervalConfig
|
|
|
29
31
|
from cognite.extractorutils.metrics import BaseMetrics
|
|
30
32
|
from cognite.extractorutils.statestore import AbstractStateStore
|
|
31
33
|
from cognite.extractorutils.threading import CancellationToken
|
|
32
|
-
from cognite.extractorutils.uploader import
|
|
33
|
-
|
|
34
|
+
from cognite.extractorutils.uploader import (
|
|
35
|
+
CDMTimeSeriesUploadQueue,
|
|
36
|
+
EventUploadQueue,
|
|
37
|
+
RawUploadQueue,
|
|
38
|
+
TimeSeriesUploadQueue,
|
|
39
|
+
)
|
|
40
|
+
from cognite.extractorutils.uploader_types import CdfTypes, Event, InsertCDMDatapoints, InsertDatapoints, RawRow
|
|
34
41
|
|
|
35
42
|
|
|
36
43
|
@dataclass
|
|
37
44
|
class QueueConfigClass:
|
|
45
|
+
"""
|
|
46
|
+
Configuration for several upload queues.
|
|
47
|
+
"""
|
|
48
|
+
|
|
38
49
|
event_size: int = 10_000
|
|
39
50
|
raw_size: int = 50_000
|
|
40
51
|
timeseries_size: int = 1_000_000
|
|
41
|
-
upload_interval: TimeIntervalConfig = TimeIntervalConfig("1m")
|
|
52
|
+
upload_interval: TimeIntervalConfig = field(default_factory=lambda: TimeIntervalConfig("1m"))
|
|
42
53
|
|
|
43
54
|
|
|
44
55
|
@dataclass
|
|
45
56
|
class UploaderExtractorConfig(BaseConfig):
|
|
57
|
+
"""
|
|
58
|
+
Base configuration for the UploaderExtractor.
|
|
59
|
+
"""
|
|
60
|
+
|
|
46
61
|
queues: QueueConfigClass | None
|
|
47
62
|
|
|
48
63
|
|
|
@@ -108,6 +123,13 @@ class UploaderExtractor(Extractor[UploaderExtractorConfigClass]):
|
|
|
108
123
|
self.middleware = middleware if isinstance(middleware, list) else []
|
|
109
124
|
|
|
110
125
|
def handle_output(self, output: CdfTypes) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Handle the output of the extractor and sort it into appropriate upload queues.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
output: The output from the extractor, which can be an Event, RawRow, InsertDatapoints, or an iterable of
|
|
131
|
+
these types.
|
|
132
|
+
"""
|
|
111
133
|
list_output = [output] if not isinstance(output, Iterable) else output
|
|
112
134
|
peekable_output = peekable(list_output)
|
|
113
135
|
|
|
@@ -136,6 +158,10 @@ class UploaderExtractor(Extractor[UploaderExtractorConfigClass]):
|
|
|
136
158
|
self.time_series_queue.add_to_upload_queue(
|
|
137
159
|
id=dp.id, external_id=dp.external_id, datapoints=dp.datapoints
|
|
138
160
|
)
|
|
161
|
+
elif isinstance(peek, InsertCDMDatapoints):
|
|
162
|
+
for dp in peekable_output:
|
|
163
|
+
if isinstance(dp, InsertCDMDatapoints):
|
|
164
|
+
self.cdm_time_series_queue.add_to_upload_queue(instance_id=dp.instance_id, datapoints=dp.datapoints)
|
|
139
165
|
else:
|
|
140
166
|
raise ValueError(f"Unexpected type: {type(peek)}")
|
|
141
167
|
|
|
@@ -145,6 +171,9 @@ class UploaderExtractor(Extractor[UploaderExtractorConfigClass]):
|
|
|
145
171
|
return item
|
|
146
172
|
|
|
147
173
|
def __enter__(self) -> "UploaderExtractor":
|
|
174
|
+
"""
|
|
175
|
+
Initializes the upload queues and returns the extractor instance.
|
|
176
|
+
"""
|
|
148
177
|
super().__enter__()
|
|
149
178
|
|
|
150
179
|
queue_config = self.config.queues if self.config.queues else QueueConfigClass()
|
|
@@ -167,13 +196,22 @@ class UploaderExtractor(Extractor[UploaderExtractorConfigClass]):
|
|
|
167
196
|
trigger_log_level="INFO",
|
|
168
197
|
create_missing=True,
|
|
169
198
|
).__enter__()
|
|
170
|
-
|
|
199
|
+
self.cdm_time_series_queue = CDMTimeSeriesUploadQueue(
|
|
200
|
+
self.cognite_client,
|
|
201
|
+
max_queue_size=queue_config.timeseries_size,
|
|
202
|
+
max_upload_interval=queue_config.upload_interval.seconds,
|
|
203
|
+
trigger_log_level="INFO",
|
|
204
|
+
).__enter__()
|
|
171
205
|
return self
|
|
172
206
|
|
|
173
207
|
def __exit__(
|
|
174
208
|
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
|
175
209
|
) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Waits for the upload queues and exits the extractor context.
|
|
212
|
+
"""
|
|
176
213
|
self.event_queue.__exit__(exc_type, exc_val, exc_tb)
|
|
177
214
|
self.raw_queue.__exit__(exc_type, exc_val, exc_tb)
|
|
178
215
|
self.time_series_queue.__exit__(exc_type, exc_val, exc_tb)
|
|
216
|
+
self.cdm_time_series_queue.__exit__(exc_type, exc_val, exc_tb)
|
|
179
217
|
return super().__exit__(exc_type, exc_val, exc_tb)
|
|
@@ -1,19 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DEPRECATED: This module is deprecated and will be removed in a future release.
|
|
3
|
+
|
|
4
|
+
These types are used in the UploaderExtractor, as well as the REST and MQTT extensions for the extractorutils library.
|
|
5
|
+
"""
|
|
6
|
+
|
|
1
7
|
from collections.abc import Iterable
|
|
2
8
|
from typing import TypeAlias
|
|
3
9
|
|
|
4
10
|
from cognite.client.data_classes import Event as _Event
|
|
5
11
|
from cognite.client.data_classes import Row as _Row
|
|
12
|
+
from cognite.client.data_classes.data_modeling import NodeId
|
|
6
13
|
from cognite.extractorutils.uploader.time_series import DataPoint
|
|
7
14
|
|
|
8
15
|
|
|
9
16
|
class InsertDatapoints:
|
|
10
|
-
|
|
17
|
+
"""
|
|
18
|
+
A class representing a batch of datapoints to be inserted into a time series.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, *, id: int | None = None, external_id: str | None = None, datapoints: list[DataPoint]): # noqa: A002
|
|
11
22
|
self.id = id
|
|
12
23
|
self.external_id = external_id
|
|
13
24
|
self.datapoints = datapoints
|
|
14
25
|
|
|
15
26
|
|
|
27
|
+
class InsertCDMDatapoints:
|
|
28
|
+
"""
|
|
29
|
+
A class representing a batch of datapoints to be inserted into a cdm time series.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, *, instance_id: NodeId, datapoints: list[DataPoint]):
|
|
33
|
+
self.instance_id = instance_id
|
|
34
|
+
self.datapoints = datapoints
|
|
35
|
+
|
|
36
|
+
|
|
16
37
|
class RawRow:
|
|
38
|
+
"""
|
|
39
|
+
A class representing a row of data to be inserted into a RAW table.
|
|
40
|
+
"""
|
|
41
|
+
|
|
17
42
|
def __init__(self, db_name: str, table_name: str, row: _Row | Iterable[_Row]):
|
|
18
43
|
self.db_name = db_name
|
|
19
44
|
self.table_name = table_name
|
cognite/extractorutils/util.py
CHANGED
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
|
-
|
|
17
|
-
extractors.
|
|
16
|
+
This module contains miscellaneous functions and classes.
|
|
18
17
|
"""
|
|
19
18
|
|
|
20
19
|
import io
|
|
@@ -79,8 +78,9 @@ def ensure_assets(cdf_client: CogniteClient, assets: Iterable[Asset]) -> None:
|
|
|
79
78
|
|
|
80
79
|
class EitherId:
|
|
81
80
|
"""
|
|
82
|
-
Class representing an ID in CDF, which can either be an external or internal ID.
|
|
83
|
-
|
|
81
|
+
Class representing an ID in CDF, which can either be an external or internal ID.
|
|
82
|
+
|
|
83
|
+
An EitherId can only hold one ID type, not both.
|
|
84
84
|
|
|
85
85
|
Args:
|
|
86
86
|
id: Internal ID
|
|
@@ -111,7 +111,7 @@ class EitherId:
|
|
|
111
111
|
|
|
112
112
|
def type(self) -> str:
|
|
113
113
|
"""
|
|
114
|
-
Get the type of the ID
|
|
114
|
+
Get the type of the ID.
|
|
115
115
|
|
|
116
116
|
Returns:
|
|
117
117
|
'id' if the EitherId represents an internal ID, 'externalId' if the EitherId represents an external ID
|
|
@@ -120,7 +120,7 @@ class EitherId:
|
|
|
120
120
|
|
|
121
121
|
def content(self) -> int | str:
|
|
122
122
|
"""
|
|
123
|
-
Get the value of the ID
|
|
123
|
+
Get the value of the ID.
|
|
124
124
|
|
|
125
125
|
Returns:
|
|
126
126
|
The ID
|
|
@@ -129,7 +129,7 @@ class EitherId:
|
|
|
129
129
|
|
|
130
130
|
def __eq__(self, other: Any) -> bool:
|
|
131
131
|
"""
|
|
132
|
-
Compare with another object. Only returns true if other is an EitherId with the same type and content
|
|
132
|
+
Compare with another object. Only returns true if other is an EitherId with the same type and content.
|
|
133
133
|
|
|
134
134
|
Args:
|
|
135
135
|
other: Another object
|
|
@@ -144,7 +144,7 @@ class EitherId:
|
|
|
144
144
|
|
|
145
145
|
def __hash__(self) -> int:
|
|
146
146
|
"""
|
|
147
|
-
Returns a hash of the internal or external ID
|
|
147
|
+
Returns a hash of the internal or external ID.
|
|
148
148
|
|
|
149
149
|
Returns:
|
|
150
150
|
Hash code of ID
|
|
@@ -186,6 +186,7 @@ def add_extraction_pipeline(
|
|
|
186
186
|
extraction_pipeline_ext_id: External ID of the extraction pipeline
|
|
187
187
|
cognite_client: Client to use when communicating with CDF
|
|
188
188
|
heartbeat_waiting_time: Target interval between heartbeats, in seconds
|
|
189
|
+
added_message: Message to add to the extraction pipeline run status message.
|
|
189
190
|
|
|
190
191
|
Usage:
|
|
191
192
|
If you have a function named "extract_data(*args, **kwargs)" and want to connect it to an extraction
|
|
@@ -200,7 +201,6 @@ def add_extraction_pipeline(
|
|
|
200
201
|
def extract_data(*args, **kwargs):
|
|
201
202
|
<INSERT FUNCTION BODY>
|
|
202
203
|
"""
|
|
203
|
-
|
|
204
204
|
# TODO 1. Consider refactoring this decorator to share methods with the Extractor context manager in .base.py
|
|
205
205
|
# as they serve a similar purpose
|
|
206
206
|
|
|
@@ -226,10 +226,10 @@ def add_extraction_pipeline(
|
|
|
226
226
|
|
|
227
227
|
def _report_error(exception: Exception) -> None:
|
|
228
228
|
"""
|
|
229
|
-
Called on an unsuccessful exit of the extractor
|
|
229
|
+
Called on an unsuccessful exit of the extractor.
|
|
230
230
|
"""
|
|
231
231
|
message = (
|
|
232
|
-
f"Exception for function '{input_function.__name__}'. {added_message}:\n
|
|
232
|
+
f"Exception for function '{input_function.__name__}'. {added_message}:\n{str(exception)[:1000]}"
|
|
233
233
|
)
|
|
234
234
|
cognite_client.extraction_pipelines.runs.create(
|
|
235
235
|
ExtractionPipelineRun(
|
|
@@ -276,8 +276,9 @@ def add_extraction_pipeline(
|
|
|
276
276
|
|
|
277
277
|
def throttled_loop(target_time: int, cancellation_token: CancellationToken) -> Generator[None, None, None]:
|
|
278
278
|
"""
|
|
279
|
-
A loop generator that automatically sleeps until each iteration has taken the desired amount of time.
|
|
280
|
-
|
|
279
|
+
A loop generator that automatically sleeps until each iteration has taken the desired amount of time.
|
|
280
|
+
|
|
281
|
+
Useful for when you want to avoid overloading a source system with requests.
|
|
281
282
|
|
|
282
283
|
Example:
|
|
283
284
|
This example will throttle printing to only print every 10th second:
|
|
@@ -418,8 +419,9 @@ def requests_exceptions(
|
|
|
418
419
|
status_codes: list[int] | None = None,
|
|
419
420
|
) -> dict[type[Exception], Callable[[Any], bool]]:
|
|
420
421
|
"""
|
|
421
|
-
Retry exceptions from using the ``requests`` library.
|
|
422
|
-
|
|
422
|
+
Retry exceptions from using the ``requests`` library.
|
|
423
|
+
|
|
424
|
+
This will retry all connection and HTTP errors matching the given status codes.
|
|
423
425
|
|
|
424
426
|
Example:
|
|
425
427
|
|
|
@@ -452,8 +454,9 @@ def httpx_exceptions(
|
|
|
452
454
|
status_codes: list[int] | None = None,
|
|
453
455
|
) -> dict[type[Exception], Callable[[Any], bool]]:
|
|
454
456
|
"""
|
|
455
|
-
Retry exceptions from using the ``httpx`` library.
|
|
456
|
-
|
|
457
|
+
Retry exceptions from using the ``httpx`` library.
|
|
458
|
+
|
|
459
|
+
This will retry all connection and HTTP errors matching the given status codes.
|
|
457
460
|
|
|
458
461
|
Example:
|
|
459
462
|
|
|
@@ -486,8 +489,9 @@ def cognite_exceptions(
|
|
|
486
489
|
status_codes: list[int] | None = None,
|
|
487
490
|
) -> dict[type[Exception], Callable[[Any], bool]]:
|
|
488
491
|
"""
|
|
489
|
-
Retry exceptions from using the Cognite SDK.
|
|
490
|
-
|
|
492
|
+
Retry exceptions from using the Cognite SDK.
|
|
493
|
+
|
|
494
|
+
This will retry all connection and HTTP errors matching the given status codes.
|
|
491
495
|
|
|
492
496
|
Example:
|
|
493
497
|
|
|
@@ -508,22 +512,42 @@ def cognite_exceptions(
|
|
|
508
512
|
|
|
509
513
|
|
|
510
514
|
def datetime_to_timestamp(dt: datetime) -> int:
|
|
515
|
+
"""
|
|
516
|
+
Convert a datetime object to a timestamp in milliseconds since 1970-01-01 00:00:00 UTC.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
dt: The datetime object to convert. It should be timezone-aware.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
The timestamp in milliseconds.
|
|
523
|
+
"""
|
|
511
524
|
return int(dt.timestamp() * 1000)
|
|
512
525
|
|
|
513
526
|
|
|
514
527
|
def timestamp_to_datetime(ts: int) -> datetime:
|
|
528
|
+
"""
|
|
529
|
+
Convert a timestamp in milliseconds since 1970-01-01 00:00:00 UTC to a datetime object.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
ts: The timestamp in milliseconds.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
A datetime object representing the timestamp in UTC.
|
|
536
|
+
"""
|
|
515
537
|
return datetime.fromtimestamp(ts / 1000, tz=timezone.utc)
|
|
516
538
|
|
|
517
539
|
|
|
518
540
|
def now() -> int:
|
|
519
541
|
"""
|
|
520
|
-
Current time in CDF format (
|
|
542
|
+
Current time in CDF format (milliseconds since 1970-01-01 00:00:00 UTC).
|
|
521
543
|
"""
|
|
522
544
|
return int(time() * 1000)
|
|
523
545
|
|
|
524
546
|
|
|
525
547
|
def truncate_byte_len(item: str, ln: int) -> str:
|
|
526
|
-
"""
|
|
548
|
+
"""
|
|
549
|
+
Safely truncate an arbitrary utf-8 string.
|
|
550
|
+
|
|
527
551
|
Used to sanitize metadata.
|
|
528
552
|
|
|
529
553
|
Args:
|
|
@@ -533,7 +557,6 @@ def truncate_byte_len(item: str, ln: int) -> str:
|
|
|
533
557
|
Returns:
|
|
534
558
|
str: truncated string
|
|
535
559
|
"""
|
|
536
|
-
|
|
537
560
|
bts = item.encode("utf-8")
|
|
538
561
|
if len(bts) <= ln:
|
|
539
562
|
return item
|
|
@@ -570,7 +593,21 @@ def truncate_byte_len(item: str, ln: int) -> str:
|
|
|
570
593
|
|
|
571
594
|
|
|
572
595
|
class BufferedReadWithLength(io.BufferedReader):
|
|
573
|
-
|
|
596
|
+
"""
|
|
597
|
+
A BufferedReader that also has a length attribute.
|
|
598
|
+
|
|
599
|
+
Some libraries (like requests) checks streams for a ``len`` attribute to use for the content-length header when
|
|
600
|
+
uploading files. Using this class allows these libraries to work with streams that have a known length without
|
|
601
|
+
seeking to the end of the stream to find its length.
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
raw: The raw IO object to read from.
|
|
605
|
+
buffer_size: The size of the buffer to use.
|
|
606
|
+
len: The length of the stream in bytes.
|
|
607
|
+
on_close: A callable that will be called when the stream is closed. This can be used to clean up resources.
|
|
608
|
+
"""
|
|
609
|
+
|
|
610
|
+
def __init__(self, raw: RawIOBase, buffer_size: int, len: int, on_close: Callable[[], None] | None = None) -> None: # noqa: A002
|
|
574
611
|
super().__init__(raw, buffer_size)
|
|
575
612
|
# Do not remove even if it appears to be unused. :P
|
|
576
613
|
# Requests uses this to add the content-length header, which is necessary for writing to files in azure clusters
|
|
@@ -578,6 +615,9 @@ class BufferedReadWithLength(io.BufferedReader):
|
|
|
578
615
|
self.on_close = on_close
|
|
579
616
|
|
|
580
617
|
def close(self) -> None:
|
|
618
|
+
"""
|
|
619
|
+
Close the stream and call the on_close callback if it is set.
|
|
620
|
+
"""
|
|
581
621
|
if self.on_close:
|
|
582
622
|
self.on_close()
|
|
583
623
|
return super().close()
|
|
@@ -589,6 +629,19 @@ def iterable_to_stream(
|
|
|
589
629
|
buffer_size: int = io.DEFAULT_BUFFER_SIZE,
|
|
590
630
|
on_close: Callable[[], None] | None = None,
|
|
591
631
|
) -> BufferedReadWithLength:
|
|
632
|
+
"""
|
|
633
|
+
Convert an iterable of bytes into a stream that can be read from.
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
iterator: An iterable that yields bytes. This can be a generator or any other iterable.
|
|
637
|
+
file_size_bytes: The total size of the file in bytes. This is used to set the length of the stream.
|
|
638
|
+
buffer_size: The size of the buffer to use when reading from the stream.
|
|
639
|
+
on_close: A callable that will be called when the stream is closed. This can be used to clean up resources.
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
A BufferedReader that can be read from, with a known length.
|
|
643
|
+
"""
|
|
644
|
+
|
|
592
645
|
class ChunkIteratorStream(io.RawIOBase):
|
|
593
646
|
def __init__(self) -> None:
|
|
594
647
|
self.last_chunk = None
|
{cognite_extractor_utils-7.5.14.dist-info → cognite_extractor_utils-7.7.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognite-extractor-utils
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.7.0
|
|
4
4
|
Summary: Utilities for easier development of extractors for CDF
|
|
5
5
|
Project-URL: repository, https://github.com/cognitedata/python-extractor-utils
|
|
6
6
|
Author-email: Mathias Lohne <mathias.lohne@cognite.com>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
cognite/extractorutils/__init__.py,sha256=Wn0kPh1z4ahQxBmba_sEQKfDuquoNoSIok9yk7kN5g4,764
|
|
2
|
+
cognite/extractorutils/_inner_util.py,sha256=v0SvTyFqwjWkJLGoYh2-i5jry3I43BFoRkhj2MjMSQ0,1780
|
|
3
|
+
cognite/extractorutils/base.py,sha256=Kabgxd269K_aw-P5EdCTP45mrFaIA4YBj5SZjlBVRnY,18605
|
|
4
|
+
cognite/extractorutils/exceptions.py,sha256=VAGAO5sH6-2DgJZGCMeoCojdRDf9o3g1r3__8REFv-Y,1361
|
|
5
|
+
cognite/extractorutils/metrics.py,sha256=gLoDWQlXNOOVLr4uQzw_l7RE85l4ljHweakzFbrx6-Y,15621
|
|
6
|
+
cognite/extractorutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
cognite/extractorutils/threading.py,sha256=GybbefZkxIqxUgxQHAiGZyEcMz-Cc3k1peILcDiG1G8,4555
|
|
8
|
+
cognite/extractorutils/uploader_extractor.py,sha256=ZPy-tc9F9fOG6GS2oOdWodolbcFa-4gHXIY8IWaWraA,9015
|
|
9
|
+
cognite/extractorutils/uploader_types.py,sha256=86wPnkEBE3_3r__4KHjxcF0unqdUXHaNrUU1bi0bOjY,1648
|
|
10
|
+
cognite/extractorutils/util.py,sha256=hs0jKwQZO3Owvrld2wzA8HUDjz4uAAcjnJ8hfLYqHv4,22887
|
|
11
|
+
cognite/extractorutils/configtools/__init__.py,sha256=oK0VmZ5hBEuTRwJebVPl_3zlbf6P_Cf5G3VU7u6Shic,3533
|
|
12
|
+
cognite/extractorutils/configtools/_util.py,sha256=NVGaUjCwUCKFMFOKZxco02q6jwdVU4vHYk67i6R_Zxc,4829
|
|
13
|
+
cognite/extractorutils/configtools/elements.py,sha256=a-k7Hm0IP1Q_0SLWakB_ZF5nKKACerckEJmCCR4n7hc,31696
|
|
14
|
+
cognite/extractorutils/configtools/loaders.py,sha256=1o_1j7yGZxSuqPNB0xz_6m_SZ73Jga477aKy61Uyvfs,20021
|
|
15
|
+
cognite/extractorutils/configtools/validators.py,sha256=Gaq0DkWqFdfG6M0ySAqCzXVtyF4DVxiOyn0SQyCOfeo,1089
|
|
16
|
+
cognite/extractorutils/statestore/__init__.py,sha256=tkXba5SNBkQa559ZcMe0acY7zjwy4lPI9rWjwdbiRlo,2759
|
|
17
|
+
cognite/extractorutils/statestore/_base.py,sha256=zEMPAPB3RJfJHQUyOuepF0mTgJ2y7WT0YQM6irj1YQg,2797
|
|
18
|
+
cognite/extractorutils/statestore/hashing.py,sha256=woINWriOozUNcPCWl6zc4R4TjLQx1bBrkpKAVOJPXyk,12345
|
|
19
|
+
cognite/extractorutils/statestore/watermark.py,sha256=T_-xAizUGs1PFeS1tngDnupaYqOWtPHIV__ohQjo9gw,16779
|
|
20
|
+
cognite/extractorutils/unstable/__init__.py,sha256=xqEgk3O1OW47ArWvzzyB4dy83Y78ESV2F8mQegpPz74,417
|
|
21
|
+
cognite/extractorutils/unstable/configuration/__init__.py,sha256=9ycCHyh7Zr7fZ_I9bwHKdywJOfZUYGDBYLU_94Uyq_4,81
|
|
22
|
+
cognite/extractorutils/unstable/configuration/exceptions.py,sha256=MnCX3EixlMi6xI3_QU0sl-JUNiPcxt6NFy8cpJwLWQY,819
|
|
23
|
+
cognite/extractorutils/unstable/configuration/loaders.py,sha256=2Q2P9yGfsBUk1VwxmTACKJUiH2CGL0p3vOFA3jjMuxY,6145
|
|
24
|
+
cognite/extractorutils/unstable/configuration/models.py,sha256=KK1Y5CeWlLPpqF4YHcXCN8WwKsgu4HOReLc5J_1wfHI,13300
|
|
25
|
+
cognite/extractorutils/unstable/core/__init__.py,sha256=f0LREaOHQig5hL59jfDn9FYfUIR_e1cgqDCIqTJV_VI,213
|
|
26
|
+
cognite/extractorutils/unstable/core/_dto.py,sha256=hxrdBZIlbQsYH182fl6tqmL6vpwE7cJACzXbV-ARc0Q,1296
|
|
27
|
+
cognite/extractorutils/unstable/core/_messaging.py,sha256=D9rOW8fijryXffbm90d8VTf2vy5FmwVGU-H0O-cn-EI,68
|
|
28
|
+
cognite/extractorutils/unstable/core/base.py,sha256=y9UHfg6ggkcQS7HKBpii38URJqzkdOvjcLhM_GUSikY,16235
|
|
29
|
+
cognite/extractorutils/unstable/core/errors.py,sha256=BNMOYNPOhWvEzfFZxfjOWG4lr9x5zzAa180H0NTNglY,3536
|
|
30
|
+
cognite/extractorutils/unstable/core/logger.py,sha256=WNEgIiuBUNGe7v68B0y31H641Ez7UdJwptjXDnD22wo,10183
|
|
31
|
+
cognite/extractorutils/unstable/core/restart_policy.py,sha256=UakVHntfffAGeye_tV0NltfR8U8xsdL8KS5OAB3BNkk,1279
|
|
32
|
+
cognite/extractorutils/unstable/core/runtime.py,sha256=qu-z9KAjbtnDRg9mFl4cBJlA-acXMhOwQXIbydSykOY,12451
|
|
33
|
+
cognite/extractorutils/unstable/core/tasks.py,sha256=8R0kvKTydpnGaGLlgvhp8_uaq9JAVxRpy5KLjDR-npE,5017
|
|
34
|
+
cognite/extractorutils/unstable/scheduling/__init__.py,sha256=NGVNw-dq7eYwm8jLwb8ChPTSyfAnAKMzi3_QDsh46yw,809
|
|
35
|
+
cognite/extractorutils/unstable/scheduling/_scheduler.py,sha256=xfAKAI_pyahdFo7zPOmvAQ_16MFE46zFIYSopzwpLag,3741
|
|
36
|
+
cognite/extractorutils/unstable/scheduling/_schedules.py,sha256=y0NVeXYZOFcAyzBgAe8jqK0W-SZL5m99UwXAacGzqIw,677
|
|
37
|
+
cognite/extractorutils/uploader/__init__.py,sha256=U6feg43wlrB3K5lhGXBSDJJWOdtuHIse0G1ZsP3MdxM,3458
|
|
38
|
+
cognite/extractorutils/uploader/_base.py,sha256=ZcCVAC7rCe2klCapbKUg4q7p_9KqQg890MvUmHt6j1M,5230
|
|
39
|
+
cognite/extractorutils/uploader/_metrics.py,sha256=J2LJXb19L_SLSJ_voNIQHYLp0pjxUKevpH1q_xKX6Hk,3247
|
|
40
|
+
cognite/extractorutils/uploader/assets.py,sha256=Dio6m-KFZ4EgFfNKET36K3vL-O6Mp9aHVEC3GPfo6J8,5753
|
|
41
|
+
cognite/extractorutils/uploader/data_modeling.py,sha256=ncSbUsybirOg7WqPqhJKe5n-5avRHkcZ6aF54nHJaSQ,5123
|
|
42
|
+
cognite/extractorutils/uploader/events.py,sha256=YSU0wRfN0dLKMmhmyVrZ1afJcX7kT0wxDLgQ7gBpoco,5676
|
|
43
|
+
cognite/extractorutils/uploader/files.py,sha256=NIPg6CpUp2y5fRMDHQXt1igSxyJkdN-K1ANGTv0r98E,28680
|
|
44
|
+
cognite/extractorutils/uploader/raw.py,sha256=KBvlBBwKNfaoY9RgOsyKq7ylHjQCxhIznlQ-lQS_XM4,6754
|
|
45
|
+
cognite/extractorutils/uploader/time_series.py,sha256=LV_z2XLiY3oyQwnkgTF4QkPSrvIQ40C1mbDYLh86NW8,37324
|
|
46
|
+
cognite/extractorutils/uploader/upload_failure_handler.py,sha256=nXw9IVcOxxuywd_17ybXijdpo0omtY0Bkb9fT_fWYFM,3048
|
|
47
|
+
cognite_extractor_utils-7.7.0.dist-info/METADATA,sha256=_HrMZjtxJ_fnHfguavXWF6pi8-QI8eZRD1re5jM0NMg,4888
|
|
48
|
+
cognite_extractor_utils-7.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
49
|
+
cognite_extractor_utils-7.7.0.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
50
|
+
cognite_extractor_utils-7.7.0.dist-info/RECORD,,
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
cognite/extractorutils/__init__.py,sha256=CoUfXp9Vt0lIxThUC3H7MjFYpg5gS32akEvDoPirSV8,765
|
|
2
|
-
cognite/extractorutils/_inner_util.py,sha256=ZMZIBwFpSD39RzLZq_OJlrFtVt8NFOtU4ObzAG8vTB4,1779
|
|
3
|
-
cognite/extractorutils/base.py,sha256=eGz3sHHsLJWgPYTVw8xJ-7PJUK7GCK8K61CMpU5R3WU,16414
|
|
4
|
-
cognite/extractorutils/exceptions.py,sha256=4qreRiTwZH9lyLIKR67TP02MUxN9oYhCd2vFTb5bRME,1125
|
|
5
|
-
cognite/extractorutils/metrics.py,sha256=dBxtfJkTWcVbVpqM5VYmP4U3cO7iultz7k036Um4Tx0,15406
|
|
6
|
-
cognite/extractorutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
cognite/extractorutils/threading.py,sha256=bO0EdGje42I3JOjyo3JKjqCjRk9W4ZCyKC2jCjKYrvw,3584
|
|
8
|
-
cognite/extractorutils/uploader_extractor.py,sha256=MFS4GG9hPNaZ8EB3pP_bJNVi0r1y_wDYq7MrjwbpwO4,7639
|
|
9
|
-
cognite/extractorutils/uploader_types.py,sha256=lePcXPrXVcQYtvnF9Uv12gHaeOIGfYKxIjwy-ez5p7E,892
|
|
10
|
-
cognite/extractorutils/util.py,sha256=ZAx688yXV85xmWuAASlZ3e9yY5pvyR8H0r4rhnPdgF4,21052
|
|
11
|
-
cognite/extractorutils/configtools/__init__.py,sha256=LSa7gVws_mrLCMRTKGs1zfiG6-IM0HzyOF6x0EfllSo,3616
|
|
12
|
-
cognite/extractorutils/configtools/_util.py,sha256=VMxXXmPvNPf3Jjwknqm7i-Bp-z_ORN0DFKjBHgBsWA0,4773
|
|
13
|
-
cognite/extractorutils/configtools/elements.py,sha256=n2Nbl-fZnuMmyduWXh0KfOYibUVZQnCwUeT6OQTnJ6U,26760
|
|
14
|
-
cognite/extractorutils/configtools/loaders.py,sha256=Iqyn1i050j8vDa2Bb7yJgW2I-riXgdbdFpGX9TRLU84,18374
|
|
15
|
-
cognite/extractorutils/configtools/validators.py,sha256=xug3GOMIO4NOdyyvXtYlpKyq9wuDtGf7-xqIefD5bIo,1016
|
|
16
|
-
cognite/extractorutils/statestore/__init__.py,sha256=hV3r11FUXkH6-60Ct6zLSROMNVrEeiE3Shmkf28Q-co,359
|
|
17
|
-
cognite/extractorutils/statestore/_base.py,sha256=mWdFk4EZl886V6uXRj4O2sv2_ANJ3Sigmgeql-XEsmc,2675
|
|
18
|
-
cognite/extractorutils/statestore/hashing.py,sha256=fVm2ifLvMqiozar6t5Sa1lMjPGqpRF56JpYNro8pQng,8000
|
|
19
|
-
cognite/extractorutils/statestore/watermark.py,sha256=CDQW0QkdBSp_dYFVcOEQCqKNQZw9EqjOWmeh-xOcqfo,16656
|
|
20
|
-
cognite/extractorutils/unstable/__init__.py,sha256=L6nqJHjylpk67CE-PbXJyb_TBI4yjhEYEz9J9WShDfM,341
|
|
21
|
-
cognite/extractorutils/unstable/configuration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
cognite/extractorutils/unstable/configuration/exceptions.py,sha256=2-0jUp9IFwzk2QTQzWLoGgW1KOApk9UmPmLwtwRUBaE,591
|
|
23
|
-
cognite/extractorutils/unstable/configuration/loaders.py,sha256=cb7dvEvIypSv5k81qSFhs95bHROTPhGx5quHOviIb7g,3863
|
|
24
|
-
cognite/extractorutils/unstable/configuration/models.py,sha256=YYGXxSwct04J9A6NDlW0kpKHvlvtPxuLeMHgq5KwH-g,9567
|
|
25
|
-
cognite/extractorutils/unstable/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
cognite/extractorutils/unstable/core/_dto.py,sha256=tvvy39cvf-QT28GWz5FpqxQ5vAVk0t69JoPPhpWlweY,1293
|
|
27
|
-
cognite/extractorutils/unstable/core/_messaging.py,sha256=D9rOW8fijryXffbm90d8VTf2vy5FmwVGU-H0O-cn-EI,68
|
|
28
|
-
cognite/extractorutils/unstable/core/base.py,sha256=eOtz4nNR3bv-vgQ-ma5yaUGpNN4nNNB5MSrtRRqy8tI,12249
|
|
29
|
-
cognite/extractorutils/unstable/core/errors.py,sha256=oTRB5Alt-rM90wHfQQM0idjATpv0BrKtoAIOvtu7P-k,2115
|
|
30
|
-
cognite/extractorutils/unstable/core/logger.py,sha256=MLkOZ6ofFKBpm7UAVy7l_RoUEm9ipLdkkay4mxzJJH0,3765
|
|
31
|
-
cognite/extractorutils/unstable/core/restart_policy.py,sha256=4FUohTXeC74mnq36Q16PQ2i9jPI-WuUZm8XwClFSnYk,631
|
|
32
|
-
cognite/extractorutils/unstable/core/runtime.py,sha256=RdRRBplPyHxyM6xujAl1e3phjbUA3Fk18x-pPFQB6e0,11067
|
|
33
|
-
cognite/extractorutils/unstable/core/tasks.py,sha256=XvakIwp_zCV7XaWUgYT2z3LCGoQTljY0xdVJWFfm-ng,3158
|
|
34
|
-
cognite/extractorutils/unstable/scheduling/__init__.py,sha256=L90_rCZNHvti-PInne0r7W9edIkifctELjiaxEoQiSc,67
|
|
35
|
-
cognite/extractorutils/unstable/scheduling/_scheduler.py,sha256=jxr5gICz0nrLFr7PfplEWi1nrO5uW1NS-F9CH9v3eHs,3721
|
|
36
|
-
cognite/extractorutils/unstable/scheduling/_schedules.py,sha256=y0NVeXYZOFcAyzBgAe8jqK0W-SZL5m99UwXAacGzqIw,677
|
|
37
|
-
cognite/extractorutils/uploader/__init__.py,sha256=MgyvZojwLE-oUCZ0VALISd2rUCqShlyozxhzAKX5uj4,3396
|
|
38
|
-
cognite/extractorutils/uploader/_base.py,sha256=WO8dftb7J9alEIuC4iOd8X3WliD1ODgjge6IFNimsxc,5300
|
|
39
|
-
cognite/extractorutils/uploader/_metrics.py,sha256=J2LJXb19L_SLSJ_voNIQHYLp0pjxUKevpH1q_xKX6Hk,3247
|
|
40
|
-
cognite/extractorutils/uploader/assets.py,sha256=a6LKbGjWh29Lj0PnL1bdhP88e27FKh8xfTFTsOBUOew,5713
|
|
41
|
-
cognite/extractorutils/uploader/data_modeling.py,sha256=h4uRnppU2dVBquJz0Uy2SKNy5A1Gq-g7ZAlERu5vjIU,3614
|
|
42
|
-
cognite/extractorutils/uploader/events.py,sha256=soU8TslPFIA7Ck4ATR_a7MHGitztRcn0cEt7LIbMPsE,5643
|
|
43
|
-
cognite/extractorutils/uploader/files.py,sha256=ag3TUrImJdAUiOSXugagD03-9WGlFpIyd31z3WMJU20,27180
|
|
44
|
-
cognite/extractorutils/uploader/raw.py,sha256=8duMk9uVJPK3O6C0nzwnvrl0UzBtJs_T_L1la0Vo89k,6713
|
|
45
|
-
cognite/extractorutils/uploader/time_series.py,sha256=3HaKfWcSOTKFmB6VwpRwioOykkRa6qmruSSsZu8Njdk,26507
|
|
46
|
-
cognite/extractorutils/uploader/upload_failure_handler.py,sha256=wbUSXUP26rbmK2NGdQMzcWQwNOh2sXYOnlj0A_sk1n8,2003
|
|
47
|
-
cognite_extractor_utils-7.5.14.dist-info/METADATA,sha256=fOEVjTfL3MwM-AYOc64C0pVx4yaay5mvyXLcqLiTEFI,4889
|
|
48
|
-
cognite_extractor_utils-7.5.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
49
|
-
cognite_extractor_utils-7.5.14.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
50
|
-
cognite_extractor_utils-7.5.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|