cognite-extractor-utils 7.5.8__tar.gz → 7.5.10__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 cognite-extractor-utils might be problematic. Click here for more details.
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/PKG-INFO +2 -2
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/__init__.py +1 -1
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/configtools/__init__.py +1 -1
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/core/base.py +49 -41
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/core/errors.py +17 -2
- cognite_extractor_utils-7.5.10/cognite/extractorutils/unstable/core/logger.py +149 -0
- cognite_extractor_utils-7.5.10/cognite/extractorutils/unstable/core/tasks.py +116 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/scheduling/_scheduler.py +1 -1
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/files.py +14 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/pyproject.toml +2 -2
- cognite_extractor_utils-7.5.8/cognite/extractorutils/unstable/core/tasks.py +0 -31
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/.gitignore +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/LICENSE +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/README.md +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/_inner_util.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/base.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/configtools/_util.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/configtools/elements.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/configtools/loaders.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/configtools/validators.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/exceptions.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/metrics.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/py.typed +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/statestore/__init__.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/statestore/_base.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/statestore/hashing.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/statestore/watermark.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/threading.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/__init__.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/configuration/__init__.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/configuration/exceptions.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/configuration/loaders.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/configuration/models.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/core/__init__.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/core/_dto.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/core/_messaging.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/core/restart_policy.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/core/runtime.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/scheduling/__init__.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/unstable/scheduling/_schedules.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/__init__.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/_base.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/_metrics.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/assets.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/data_modeling.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/events.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/raw.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/time_series.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader/upload_failure_handler.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader_extractor.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/uploader_types.py +0 -0
- {cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognite-extractor-utils
|
|
3
|
-
Version: 7.5.
|
|
3
|
+
Version: 7.5.10
|
|
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>
|
|
@@ -14,7 +14,7 @@ Requires-Dist: azure-identity>=1.14.0
|
|
|
14
14
|
Requires-Dist: azure-keyvault-secrets>=4.7.0
|
|
15
15
|
Requires-Dist: cognite-sdk>=7.59.0
|
|
16
16
|
Requires-Dist: croniter>=6.0.0
|
|
17
|
-
Requires-Dist: dacite
|
|
17
|
+
Requires-Dist: dacite<1.9.0,>=1.6.0
|
|
18
18
|
Requires-Dist: decorator>=5.1.1
|
|
19
19
|
Requires-Dist: httpx<1,>=0.27.0
|
|
20
20
|
Requires-Dist: jsonlines>=4.0.0
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
Module containing tools for loading and verifying config files, and a YAML loader to automatically serialize these
|
|
17
17
|
dataclasses from a config file.
|
|
18
18
|
|
|
19
|
-
Configs are described as ``dataclass
|
|
19
|
+
Configs are described as ``dataclass`` es, and use the ``BaseConfig`` class as a superclass to get a few things
|
|
20
20
|
built-in: config version, Cognite project and logging. Use type hints to specify types, use the ``Optional`` type to
|
|
21
21
|
specify that a config parameter is optional, and give the attribute a value to give it a default.
|
|
22
22
|
|
|
@@ -2,11 +2,10 @@ import logging
|
|
|
2
2
|
import logging.config
|
|
3
3
|
import time
|
|
4
4
|
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
-
from
|
|
5
|
+
from functools import partial
|
|
6
6
|
from logging.handlers import TimedRotatingFileHandler
|
|
7
7
|
from multiprocessing import Queue
|
|
8
8
|
from threading import RLock, Thread
|
|
9
|
-
from traceback import format_exception
|
|
10
9
|
from types import TracebackType
|
|
11
10
|
from typing import Generic, Literal, TypeVar
|
|
12
11
|
|
|
@@ -25,8 +24,9 @@ from cognite.extractorutils.unstable.core._dto import Error as DtoError
|
|
|
25
24
|
from cognite.extractorutils.unstable.core._dto import TaskUpdate
|
|
26
25
|
from cognite.extractorutils.unstable.core._messaging import RuntimeMessage
|
|
27
26
|
from cognite.extractorutils.unstable.core.errors import Error, ErrorLevel
|
|
27
|
+
from cognite.extractorutils.unstable.core.logger import CogniteLogger
|
|
28
28
|
from cognite.extractorutils.unstable.core.restart_policy import WHEN_CONTINUOUS_TASKS_CRASHES, RestartPolicy
|
|
29
|
-
from cognite.extractorutils.unstable.core.tasks import ContinuousTask, ScheduledTask, StartupTask, Task
|
|
29
|
+
from cognite.extractorutils.unstable.core.tasks import ContinuousTask, ScheduledTask, StartupTask, Task, TaskContext
|
|
30
30
|
from cognite.extractorutils.unstable.scheduling import TaskScheduler
|
|
31
31
|
from cognite.extractorutils.util import now
|
|
32
32
|
|
|
@@ -51,7 +51,7 @@ class FullConfig(Generic[_T]):
|
|
|
51
51
|
self.current_config_revision = current_config_revision
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
class Extractor(Generic[ConfigType]):
|
|
54
|
+
class Extractor(Generic[ConfigType], CogniteLogger):
|
|
55
55
|
NAME: str
|
|
56
56
|
EXTERNAL_ID: str
|
|
57
57
|
DESCRIPTION: str
|
|
@@ -62,6 +62,8 @@ class Extractor(Generic[ConfigType]):
|
|
|
62
62
|
RESTART_POLICY: RestartPolicy = WHEN_CONTINUOUS_TASKS_CRASHES
|
|
63
63
|
|
|
64
64
|
def __init__(self, config: FullConfig[ConfigType]) -> None:
|
|
65
|
+
self._logger = logging.getLogger(f"{self.EXTERNAL_ID}.main")
|
|
66
|
+
|
|
65
67
|
self.cancellation_token = CancellationToken()
|
|
66
68
|
self.cancellation_token.cancel_on_interrupt()
|
|
67
69
|
|
|
@@ -80,10 +82,6 @@ class Extractor(Generic[ConfigType]):
|
|
|
80
82
|
self._task_updates: list[TaskUpdate] = []
|
|
81
83
|
self._errors: dict[str, Error] = {}
|
|
82
84
|
|
|
83
|
-
self.logger = logging.getLogger(f"{self.EXTERNAL_ID}.main")
|
|
84
|
-
|
|
85
|
-
self._current_task: ContextVar[str | None] = ContextVar("current_task", default=None)
|
|
86
|
-
|
|
87
85
|
self.__init_tasks__()
|
|
88
86
|
|
|
89
87
|
def _setup_logging(self) -> None:
|
|
@@ -175,36 +173,34 @@ class Extractor(Generic[ConfigType]):
|
|
|
175
173
|
def _run_checkin(self) -> None:
|
|
176
174
|
while not self.cancellation_token.is_cancelled:
|
|
177
175
|
try:
|
|
178
|
-
self.
|
|
176
|
+
self._logger.debug("Running checkin")
|
|
179
177
|
self._checkin()
|
|
180
178
|
except Exception:
|
|
181
|
-
self.
|
|
179
|
+
self._logger.exception("Error during checkin")
|
|
182
180
|
self.cancellation_token.wait(10)
|
|
183
181
|
|
|
184
182
|
def _report_error(self, error: Error) -> None:
|
|
185
183
|
with self._checkin_lock:
|
|
186
184
|
self._errors[error.external_id] = error
|
|
187
185
|
|
|
188
|
-
def
|
|
186
|
+
def _new_error(
|
|
189
187
|
self,
|
|
190
188
|
level: ErrorLevel,
|
|
191
189
|
description: str,
|
|
192
|
-
details: str | None = None,
|
|
193
190
|
*,
|
|
194
|
-
|
|
191
|
+
details: str | None = None,
|
|
192
|
+
task_name: str | None = None,
|
|
195
193
|
) -> Error:
|
|
196
|
-
task_name = self._current_task.get()
|
|
197
|
-
|
|
198
194
|
return Error(
|
|
199
195
|
level=level,
|
|
200
196
|
description=description,
|
|
201
197
|
details=details,
|
|
202
198
|
extractor=self,
|
|
203
|
-
task_name=
|
|
199
|
+
task_name=task_name,
|
|
204
200
|
)
|
|
205
201
|
|
|
206
202
|
def restart(self) -> None:
|
|
207
|
-
self.
|
|
203
|
+
self._logger.info("Restarting extractor")
|
|
208
204
|
if self._runtime_messages:
|
|
209
205
|
self._runtime_messages.put(RuntimeMessage.RESTART)
|
|
210
206
|
self.cancellation_token.cancel()
|
|
@@ -217,7 +213,7 @@ class Extractor(Generic[ConfigType]):
|
|
|
217
213
|
# Store this for later, since we'll override it with the wrapped version
|
|
218
214
|
target = task.target
|
|
219
215
|
|
|
220
|
-
def run_task() -> None:
|
|
216
|
+
def run_task(task_context: TaskContext) -> None:
|
|
221
217
|
"""
|
|
222
218
|
A wrapped version of the task's target, with tracking and error handling
|
|
223
219
|
"""
|
|
@@ -227,33 +223,22 @@ class Extractor(Generic[ConfigType]):
|
|
|
227
223
|
TaskUpdate(type="started", name=task.name, timestamp=now()),
|
|
228
224
|
)
|
|
229
225
|
|
|
230
|
-
context_token: Token[str | None] | None = None
|
|
231
|
-
|
|
232
226
|
try:
|
|
233
|
-
# Set the current task context var, used to track that we're in a task for error reporting
|
|
234
|
-
context_token = self._current_task.set(task.name)
|
|
235
|
-
|
|
236
227
|
# Run task
|
|
237
|
-
target()
|
|
228
|
+
target(task_context)
|
|
238
229
|
|
|
239
230
|
except Exception as e:
|
|
240
|
-
self.logger.exception(f"Unexpected error in {task.name}")
|
|
241
|
-
|
|
242
231
|
# Task crashed, record it as a fatal error
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
)
|
|
232
|
+
task_context.exception(
|
|
233
|
+
f"Task {task.name} crashed unexpectedly",
|
|
234
|
+
e,
|
|
235
|
+
level=ErrorLevel.fatal,
|
|
236
|
+
)
|
|
248
237
|
|
|
249
238
|
if self.__class__.RESTART_POLICY(task, e):
|
|
250
239
|
self.restart()
|
|
251
240
|
|
|
252
241
|
finally:
|
|
253
|
-
# Unset the current task
|
|
254
|
-
if context_token is not None:
|
|
255
|
-
self._current_task.reset(context_token)
|
|
256
|
-
|
|
257
242
|
# Record task end
|
|
258
243
|
with self._checkin_lock:
|
|
259
244
|
self._task_updates.append(
|
|
@@ -265,7 +250,16 @@ class Extractor(Generic[ConfigType]):
|
|
|
265
250
|
|
|
266
251
|
match task:
|
|
267
252
|
case ScheduledTask() as t:
|
|
268
|
-
self._scheduler.schedule_task(
|
|
253
|
+
self._scheduler.schedule_task(
|
|
254
|
+
name=t.name,
|
|
255
|
+
schedule=t.schedule,
|
|
256
|
+
task=lambda: t.target(
|
|
257
|
+
TaskContext(
|
|
258
|
+
task=task,
|
|
259
|
+
extractor=self,
|
|
260
|
+
)
|
|
261
|
+
),
|
|
262
|
+
)
|
|
269
263
|
|
|
270
264
|
def _report_extractor_info(self) -> None:
|
|
271
265
|
self.cognite_client.post(
|
|
@@ -281,6 +275,8 @@ class Extractor(Generic[ConfigType]):
|
|
|
281
275
|
{
|
|
282
276
|
"name": t.name,
|
|
283
277
|
"type": "continuous" if isinstance(t, ContinuousTask) else "batch",
|
|
278
|
+
"action": True if isinstance(t, ScheduledTask) else False,
|
|
279
|
+
"description": t.description,
|
|
284
280
|
}
|
|
285
281
|
for t in self._tasks
|
|
286
282
|
],
|
|
@@ -310,7 +306,7 @@ class Extractor(Generic[ConfigType]):
|
|
|
310
306
|
with self._checkin_lock:
|
|
311
307
|
self._checkin()
|
|
312
308
|
|
|
313
|
-
self.
|
|
309
|
+
self._logger.info("Shutting down extractor")
|
|
314
310
|
return exc_val is None
|
|
315
311
|
|
|
316
312
|
def run(self) -> None:
|
|
@@ -333,15 +329,27 @@ class Extractor(Generic[ConfigType]):
|
|
|
333
329
|
case _:
|
|
334
330
|
assert_never(task)
|
|
335
331
|
|
|
336
|
-
self.
|
|
332
|
+
self._logger.info("Starting extractor")
|
|
337
333
|
if startup:
|
|
338
334
|
with ThreadPoolExecutor() as pool:
|
|
339
335
|
for task in startup:
|
|
340
|
-
pool.submit(
|
|
341
|
-
|
|
336
|
+
pool.submit(
|
|
337
|
+
partial(
|
|
338
|
+
task.target,
|
|
339
|
+
TaskContext(
|
|
340
|
+
task=task,
|
|
341
|
+
extractor=self,
|
|
342
|
+
),
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
self._logger.info("Startup done")
|
|
342
346
|
|
|
343
347
|
for task in continuous:
|
|
344
|
-
Thread(
|
|
348
|
+
Thread(
|
|
349
|
+
name=pascalize(task.name),
|
|
350
|
+
target=task.target,
|
|
351
|
+
args=(TaskContext(task=task, extractor=self),),
|
|
352
|
+
).start()
|
|
345
353
|
|
|
346
354
|
if has_scheduled:
|
|
347
355
|
self._scheduler.run()
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import logging
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from types import TracebackType
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
from uuid import uuid4
|
|
5
6
|
|
|
7
|
+
from typing_extensions import assert_never
|
|
8
|
+
|
|
6
9
|
from cognite.extractorutils.util import now
|
|
7
10
|
|
|
8
|
-
if
|
|
11
|
+
if TYPE_CHECKING:
|
|
9
12
|
from .base import Extractor
|
|
10
13
|
|
|
11
14
|
__all__ = ["Error", "ErrorLevel"]
|
|
@@ -16,6 +19,18 @@ class ErrorLevel(Enum):
|
|
|
16
19
|
error = "error"
|
|
17
20
|
fatal = "fatal"
|
|
18
21
|
|
|
22
|
+
@property
|
|
23
|
+
def log_level(self) -> int:
|
|
24
|
+
match self:
|
|
25
|
+
case ErrorLevel.warning:
|
|
26
|
+
return logging.WARNING
|
|
27
|
+
case ErrorLevel.error:
|
|
28
|
+
return logging.ERROR
|
|
29
|
+
case ErrorLevel.fatal:
|
|
30
|
+
return logging.CRITICAL
|
|
31
|
+
case _:
|
|
32
|
+
assert_never(self)
|
|
33
|
+
|
|
19
34
|
|
|
20
35
|
class Error:
|
|
21
36
|
def __init__(
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from logging import Logger, getLogger
|
|
3
|
+
from traceback import format_exception
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from typing_extensions import assert_never
|
|
7
|
+
|
|
8
|
+
from cognite.extractorutils.unstable.core.errors import Error, ErrorLevel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CogniteLogger(ABC):
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self._logger: Logger = getLogger()
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def _new_error(
|
|
17
|
+
self,
|
|
18
|
+
level: ErrorLevel,
|
|
19
|
+
description: str,
|
|
20
|
+
*,
|
|
21
|
+
details: str | None = None,
|
|
22
|
+
task_name: str | None = None,
|
|
23
|
+
) -> Error:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def debug(self, message: str) -> None:
|
|
27
|
+
self._logger.debug(message)
|
|
28
|
+
|
|
29
|
+
def info(self, message: str) -> None:
|
|
30
|
+
self._logger.info(message)
|
|
31
|
+
|
|
32
|
+
def begin_warning(
|
|
33
|
+
self,
|
|
34
|
+
message: str,
|
|
35
|
+
*,
|
|
36
|
+
details: str | None = None,
|
|
37
|
+
auto_log: bool = True,
|
|
38
|
+
) -> Error:
|
|
39
|
+
if auto_log:
|
|
40
|
+
self._logger.warning(message)
|
|
41
|
+
return self._new_error(
|
|
42
|
+
level=ErrorLevel.warning,
|
|
43
|
+
description=message,
|
|
44
|
+
details=details,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def begin_error(
|
|
48
|
+
self,
|
|
49
|
+
message: str,
|
|
50
|
+
*,
|
|
51
|
+
details: str | None = None,
|
|
52
|
+
auto_log: bool = True,
|
|
53
|
+
) -> Error:
|
|
54
|
+
if auto_log:
|
|
55
|
+
self._logger.error(message)
|
|
56
|
+
return self._new_error(
|
|
57
|
+
level=ErrorLevel.error,
|
|
58
|
+
description=message,
|
|
59
|
+
details=details,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def begin_fatal(
|
|
63
|
+
self,
|
|
64
|
+
message: str,
|
|
65
|
+
*,
|
|
66
|
+
details: str | None = None,
|
|
67
|
+
auto_log: bool = True,
|
|
68
|
+
) -> Error:
|
|
69
|
+
if auto_log:
|
|
70
|
+
self._logger.critical(message)
|
|
71
|
+
return self._new_error(
|
|
72
|
+
level=ErrorLevel.fatal,
|
|
73
|
+
description=message,
|
|
74
|
+
details=details,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def warning(
|
|
78
|
+
self,
|
|
79
|
+
message: str,
|
|
80
|
+
*,
|
|
81
|
+
details: str | None = None,
|
|
82
|
+
auto_log: bool = True,
|
|
83
|
+
) -> None:
|
|
84
|
+
if auto_log:
|
|
85
|
+
self._logger.warning(message)
|
|
86
|
+
self._new_error(
|
|
87
|
+
level=ErrorLevel.warning,
|
|
88
|
+
description=message,
|
|
89
|
+
details=details,
|
|
90
|
+
).instant()
|
|
91
|
+
|
|
92
|
+
def error(
|
|
93
|
+
self,
|
|
94
|
+
message: str,
|
|
95
|
+
*,
|
|
96
|
+
details: str | None = None,
|
|
97
|
+
auto_log: bool = True,
|
|
98
|
+
) -> None:
|
|
99
|
+
if auto_log:
|
|
100
|
+
self._logger.error(message)
|
|
101
|
+
self._new_error(
|
|
102
|
+
level=ErrorLevel.error,
|
|
103
|
+
description=message,
|
|
104
|
+
details=details,
|
|
105
|
+
).instant()
|
|
106
|
+
|
|
107
|
+
def fatal(
|
|
108
|
+
self,
|
|
109
|
+
message: str,
|
|
110
|
+
*,
|
|
111
|
+
details: str | None = None,
|
|
112
|
+
auto_log: bool = True,
|
|
113
|
+
) -> None:
|
|
114
|
+
if auto_log:
|
|
115
|
+
self._logger.critical(message)
|
|
116
|
+
self._new_error(
|
|
117
|
+
level=ErrorLevel.fatal,
|
|
118
|
+
description=message,
|
|
119
|
+
details=details,
|
|
120
|
+
).instant()
|
|
121
|
+
|
|
122
|
+
def exception(
|
|
123
|
+
self,
|
|
124
|
+
message: str,
|
|
125
|
+
exception: Exception,
|
|
126
|
+
*,
|
|
127
|
+
level: ErrorLevel = ErrorLevel.error,
|
|
128
|
+
include_details: Literal["stack_trace"] | Literal["exception_message"] | bool = "exception_message",
|
|
129
|
+
auto_log: bool = True,
|
|
130
|
+
) -> None:
|
|
131
|
+
if auto_log:
|
|
132
|
+
self._logger.log(level=level.log_level, msg=message, exc_info=exception)
|
|
133
|
+
|
|
134
|
+
details: str | None
|
|
135
|
+
match include_details:
|
|
136
|
+
case "stack_trace":
|
|
137
|
+
details = "".join(format_exception(exception))
|
|
138
|
+
case "exception_message" | True:
|
|
139
|
+
details = str(exception)
|
|
140
|
+
case False:
|
|
141
|
+
details = None
|
|
142
|
+
case _:
|
|
143
|
+
assert_never(include_details)
|
|
144
|
+
|
|
145
|
+
self._new_error(
|
|
146
|
+
level=level,
|
|
147
|
+
description=message,
|
|
148
|
+
details=details,
|
|
149
|
+
).instant()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from cognite.extractorutils.unstable.configuration.models import (
|
|
6
|
+
CronConfig,
|
|
7
|
+
IntervalConfig,
|
|
8
|
+
ScheduleConfig,
|
|
9
|
+
TimeIntervalConfig,
|
|
10
|
+
)
|
|
11
|
+
from cognite.extractorutils.unstable.core.errors import Error, ErrorLevel
|
|
12
|
+
from cognite.extractorutils.unstable.core.logger import CogniteLogger
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from cognite.extractorutils.unstable.core.base import Extractor
|
|
16
|
+
|
|
17
|
+
__all__ = ["ScheduledTask", "ContinuousTask", "StartupTask", "Task", "TaskContext"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TaskContext(CogniteLogger):
|
|
21
|
+
def __init__(self, task: "Task", extractor: "Extractor"):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self._task = task
|
|
24
|
+
self._extractor = extractor
|
|
25
|
+
|
|
26
|
+
self._logger = logging.getLogger(f"{self._extractor.EXTERNAL_ID}.{self._task.name.replace(' ', '')}")
|
|
27
|
+
|
|
28
|
+
def _new_error(
|
|
29
|
+
self,
|
|
30
|
+
level: ErrorLevel,
|
|
31
|
+
description: str,
|
|
32
|
+
*,
|
|
33
|
+
details: str | None = None,
|
|
34
|
+
task_name: str | None = None,
|
|
35
|
+
) -> Error:
|
|
36
|
+
return self._extractor._new_error(
|
|
37
|
+
level=level,
|
|
38
|
+
description=description,
|
|
39
|
+
details=details,
|
|
40
|
+
task_name=self._task.name,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
TaskTarget = Callable[[TaskContext], None]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _Task:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
name: str,
|
|
52
|
+
target: TaskTarget,
|
|
53
|
+
description: str | None = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
self.name = name
|
|
56
|
+
self.target = target
|
|
57
|
+
self.description = description
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ScheduledTask(_Task):
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
*,
|
|
64
|
+
name: str,
|
|
65
|
+
target: TaskTarget,
|
|
66
|
+
description: str | None = None,
|
|
67
|
+
schedule: ScheduleConfig,
|
|
68
|
+
):
|
|
69
|
+
super().__init__(name=name, target=target, description=description)
|
|
70
|
+
self.schedule = schedule
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_interval(
|
|
74
|
+
cls, *, interval: str, name: str, target: TaskTarget, description: str | None = None
|
|
75
|
+
) -> "ScheduledTask":
|
|
76
|
+
return ScheduledTask(
|
|
77
|
+
name=name,
|
|
78
|
+
target=target,
|
|
79
|
+
description=description,
|
|
80
|
+
schedule=IntervalConfig(type="interval", expression=TimeIntervalConfig(interval)),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def from_cron(cls, *, cron: str, name: str, target: TaskTarget, description: str | None = None) -> "ScheduledTask":
|
|
85
|
+
return ScheduledTask(
|
|
86
|
+
name=name,
|
|
87
|
+
target=target,
|
|
88
|
+
description=description,
|
|
89
|
+
schedule=CronConfig(type="cron", expression=cron),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ContinuousTask(_Task):
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
*,
|
|
97
|
+
name: str,
|
|
98
|
+
target: TaskTarget,
|
|
99
|
+
description: str | None = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
super().__init__(name=name, target=target, description=description)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class StartupTask(_Task):
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
*,
|
|
108
|
+
name: str,
|
|
109
|
+
target: TaskTarget,
|
|
110
|
+
description: str | None = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
super().__init__(name=name, target=target, description=description)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Making a type union to help with exhaustion checks in matches
|
|
116
|
+
Task = ScheduledTask | ContinuousTask | StartupTask
|
|
@@ -71,7 +71,7 @@ class TaskScheduler:
|
|
|
71
71
|
with self._running_lock:
|
|
72
72
|
self._running.remove(job)
|
|
73
73
|
|
|
74
|
-
Thread(target=wrap, name=f"{pascalize(job.name)}").start()
|
|
74
|
+
Thread(target=wrap, name=f"{pascalize(job.name.replace(' ', '_'))}").start()
|
|
75
75
|
return True
|
|
76
76
|
|
|
77
77
|
def trigger(self, name: str) -> bool:
|
|
@@ -25,6 +25,7 @@ from typing import (
|
|
|
25
25
|
)
|
|
26
26
|
from urllib.parse import ParseResult, urlparse
|
|
27
27
|
|
|
28
|
+
from h11._util import LocalProtocolError
|
|
28
29
|
from httpx import URL, Client, Headers, Request, StreamConsumed, SyncByteStream
|
|
29
30
|
from requests.utils import super_len
|
|
30
31
|
|
|
@@ -481,6 +482,19 @@ class IOFileUploadQueue(AbstractUploadQueue):
|
|
|
481
482
|
try:
|
|
482
483
|
upload_file(read_file, file_meta)
|
|
483
484
|
|
|
485
|
+
except LocalProtocolError as e:
|
|
486
|
+
error_message = str(e)
|
|
487
|
+
if (
|
|
488
|
+
"Too much data for declared Content" in error_message
|
|
489
|
+
or "Too little data for declared Content-Length" in error_message
|
|
490
|
+
):
|
|
491
|
+
self.logger.error(
|
|
492
|
+
f"Content Length Mismatch while uploading file: {file_meta} error message: {error_message}"
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
self.add_entry_failure_logger(file_name=str(file_meta.name), error=e)
|
|
496
|
+
self.errors.append(e)
|
|
497
|
+
|
|
484
498
|
except Exception as e:
|
|
485
499
|
self.logger.exception(
|
|
486
500
|
f"Unexpected error while uploading file: {file_meta.external_id} {file_meta.name}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cognite-extractor-utils"
|
|
3
|
-
version = "7.5.
|
|
3
|
+
version = "7.5.10"
|
|
4
4
|
description = "Utilities for easier development of extractors for CDF"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Mathias Lohne", email = "mathias.lohne@cognite.com"}
|
|
@@ -19,7 +19,7 @@ dependencies = [
|
|
|
19
19
|
"prometheus-client>=0.7.0,<=1.0.0",
|
|
20
20
|
"arrow>=1.0.0",
|
|
21
21
|
"pyyaml>=5.3.0,<7",
|
|
22
|
-
"dacite>=1.6.0",
|
|
22
|
+
"dacite>=1.6.0,<1.9.0",
|
|
23
23
|
"psutil>=6.0.0",
|
|
24
24
|
"decorator>=5.1.1",
|
|
25
25
|
"more-itertools>=10.0.0",
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from collections.abc import Callable
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
from cognite.extractorutils.unstable.configuration.models import ScheduleConfig
|
|
6
|
-
|
|
7
|
-
__all__ = ["ScheduledTask", "ContinuousTask", "StartupTask", "Task"]
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@dataclass
|
|
11
|
-
class _Task(ABC):
|
|
12
|
-
name: str
|
|
13
|
-
target: Callable[[], None]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class ScheduledTask(_Task):
|
|
18
|
-
schedule: ScheduleConfig
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@dataclass
|
|
22
|
-
class ContinuousTask(_Task):
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class StartupTask(_Task):
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# Making a type union to help with exhaustion checks in matches
|
|
31
|
-
Task = ScheduledTask | ContinuousTask | StartupTask
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/metrics.py
RENAMED
|
File without changes
|
{cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/threading.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cognite_extractor_utils-7.5.8 → cognite_extractor_utils-7.5.10}/cognite/extractorutils/util.py
RENAMED
|
File without changes
|