cognite-extractor-utils 7.5.7__py3-none-any.whl → 7.5.9__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 +4 -3
- cognite/extractorutils/configtools/__init__.py +1 -1
- cognite/extractorutils/configtools/_util.py +2 -1
- cognite/extractorutils/configtools/elements.py +1 -1
- cognite/extractorutils/configtools/loaders.py +10 -9
- cognite/extractorutils/exceptions.py +1 -1
- cognite/extractorutils/metrics.py +7 -6
- cognite/extractorutils/statestore/hashing.py +6 -6
- cognite/extractorutils/statestore/watermark.py +13 -13
- cognite/extractorutils/threading.py +1 -1
- cognite/extractorutils/unstable/configuration/exceptions.py +2 -5
- cognite/extractorutils/unstable/configuration/loaders.py +8 -8
- cognite/extractorutils/unstable/configuration/models.py +12 -12
- cognite/extractorutils/unstable/core/base.py +55 -50
- cognite/extractorutils/unstable/core/errors.py +18 -3
- cognite/extractorutils/unstable/core/logger.py +149 -0
- cognite/extractorutils/unstable/core/restart_policy.py +1 -1
- cognite/extractorutils/unstable/core/runtime.py +10 -55
- cognite/extractorutils/unstable/core/tasks.py +99 -14
- cognite/extractorutils/unstable/scheduling/_scheduler.py +2 -2
- cognite/extractorutils/uploader/_base.py +2 -1
- cognite/extractorutils/uploader/assets.py +3 -2
- cognite/extractorutils/uploader/data_modeling.py +3 -2
- cognite/extractorutils/uploader/events.py +2 -2
- cognite/extractorutils/uploader/files.py +13 -18
- cognite/extractorutils/uploader/raw.py +3 -2
- cognite/extractorutils/uploader/time_series.py +9 -8
- cognite/extractorutils/uploader/upload_failure_handler.py +2 -2
- cognite/extractorutils/uploader_extractor.py +7 -6
- cognite/extractorutils/uploader_types.py +2 -1
- cognite/extractorutils/util.py +9 -8
- {cognite_extractor_utils-7.5.7.dist-info → cognite_extractor_utils-7.5.9.dist-info}/METADATA +30 -36
- cognite_extractor_utils-7.5.9.dist-info/RECORD +50 -0
- {cognite_extractor_utils-7.5.7.dist-info → cognite_extractor_utils-7.5.9.dist-info}/WHEEL +1 -1
- cognite_extractor_utils-7.5.7.dist-info/RECORD +0 -49
- {cognite_extractor_utils-7.5.7.dist-info → cognite_extractor_utils-7.5.9.dist-info/licenses}/LICENSE +0 -0
|
@@ -2,13 +2,12 @@ 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
|
-
from typing import Generic, Literal,
|
|
10
|
+
from typing import Generic, Literal, TypeVar
|
|
12
11
|
|
|
13
12
|
from humps import pascalize
|
|
14
13
|
from typing_extensions import Self, assert_never
|
|
@@ -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
|
|
|
@@ -45,32 +45,31 @@ class FullConfig(Generic[_T]):
|
|
|
45
45
|
connection_config: ConnectionConfig,
|
|
46
46
|
application_config: _T,
|
|
47
47
|
current_config_revision: ConfigRevision,
|
|
48
|
-
newest_config_revision: ConfigRevision,
|
|
49
48
|
) -> None:
|
|
50
49
|
self.connection_config = connection_config
|
|
51
50
|
self.application_config = application_config
|
|
52
51
|
self.current_config_revision = current_config_revision
|
|
53
|
-
self.newest_config_revision = newest_config_revision
|
|
54
52
|
|
|
55
53
|
|
|
56
|
-
class Extractor(Generic[ConfigType]):
|
|
54
|
+
class Extractor(Generic[ConfigType], CogniteLogger):
|
|
57
55
|
NAME: str
|
|
58
56
|
EXTERNAL_ID: str
|
|
59
57
|
DESCRIPTION: str
|
|
60
58
|
VERSION: str
|
|
61
59
|
|
|
62
|
-
CONFIG_TYPE:
|
|
60
|
+
CONFIG_TYPE: type[ConfigType]
|
|
63
61
|
|
|
64
62
|
RESTART_POLICY: RestartPolicy = WHEN_CONTINUOUS_TASKS_CRASHES
|
|
65
63
|
|
|
66
64
|
def __init__(self, config: FullConfig[ConfigType]) -> None:
|
|
65
|
+
self._logger = logging.getLogger(f"{self.EXTERNAL_ID}.main")
|
|
66
|
+
|
|
67
67
|
self.cancellation_token = CancellationToken()
|
|
68
68
|
self.cancellation_token.cancel_on_interrupt()
|
|
69
69
|
|
|
70
70
|
self.connection_config = config.connection_config
|
|
71
71
|
self.application_config = config.application_config
|
|
72
72
|
self.current_config_revision = config.current_config_revision
|
|
73
|
-
self.newest_config_revision = config.newest_config_revision
|
|
74
73
|
|
|
75
74
|
self.cognite_client = self.connection_config.get_cognite_client(f"{self.EXTERNAL_ID}-{self.VERSION}")
|
|
76
75
|
|
|
@@ -83,10 +82,6 @@ class Extractor(Generic[ConfigType]):
|
|
|
83
82
|
self._task_updates: list[TaskUpdate] = []
|
|
84
83
|
self._errors: dict[str, Error] = {}
|
|
85
84
|
|
|
86
|
-
self.logger = logging.getLogger(f"{self.EXTERNAL_ID}.main")
|
|
87
|
-
|
|
88
|
-
self._current_task: ContextVar[str | None] = ContextVar("current_task", default=None)
|
|
89
|
-
|
|
90
85
|
self.__init_tasks__()
|
|
91
86
|
|
|
92
87
|
def _setup_logging(self) -> None:
|
|
@@ -158,7 +153,7 @@ class Extractor(Generic[ConfigType]):
|
|
|
158
153
|
self._errors.clear()
|
|
159
154
|
|
|
160
155
|
res = self.cognite_client.post(
|
|
161
|
-
f"/api/v1/projects/{self.cognite_client.config.project}/
|
|
156
|
+
f"/api/v1/projects/{self.cognite_client.config.project}/integrations/checkin",
|
|
162
157
|
json={
|
|
163
158
|
"externalId": self.connection_config.integration,
|
|
164
159
|
"taskEvents": task_updates,
|
|
@@ -171,43 +166,41 @@ class Extractor(Generic[ConfigType]):
|
|
|
171
166
|
if (
|
|
172
167
|
new_config_revision
|
|
173
168
|
and self.current_config_revision != "local"
|
|
174
|
-
and new_config_revision > self.
|
|
169
|
+
and new_config_revision > self.current_config_revision
|
|
175
170
|
):
|
|
176
171
|
self.restart()
|
|
177
172
|
|
|
178
173
|
def _run_checkin(self) -> None:
|
|
179
174
|
while not self.cancellation_token.is_cancelled:
|
|
180
175
|
try:
|
|
181
|
-
self.
|
|
176
|
+
self._logger.debug("Running checkin")
|
|
182
177
|
self._checkin()
|
|
183
178
|
except Exception:
|
|
184
|
-
self.
|
|
179
|
+
self._logger.exception("Error during checkin")
|
|
185
180
|
self.cancellation_token.wait(10)
|
|
186
181
|
|
|
187
182
|
def _report_error(self, error: Error) -> None:
|
|
188
183
|
with self._checkin_lock:
|
|
189
184
|
self._errors[error.external_id] = error
|
|
190
185
|
|
|
191
|
-
def
|
|
186
|
+
def _new_error(
|
|
192
187
|
self,
|
|
193
188
|
level: ErrorLevel,
|
|
194
189
|
description: str,
|
|
195
|
-
details: str | None = None,
|
|
196
190
|
*,
|
|
197
|
-
|
|
191
|
+
details: str | None = None,
|
|
192
|
+
task_name: str | None = None,
|
|
198
193
|
) -> Error:
|
|
199
|
-
task_name = self._current_task.get()
|
|
200
|
-
|
|
201
194
|
return Error(
|
|
202
195
|
level=level,
|
|
203
196
|
description=description,
|
|
204
197
|
details=details,
|
|
205
198
|
extractor=self,
|
|
206
|
-
task_name=
|
|
199
|
+
task_name=task_name,
|
|
207
200
|
)
|
|
208
201
|
|
|
209
202
|
def restart(self) -> None:
|
|
210
|
-
self.
|
|
203
|
+
self._logger.info("Restarting extractor")
|
|
211
204
|
if self._runtime_messages:
|
|
212
205
|
self._runtime_messages.put(RuntimeMessage.RESTART)
|
|
213
206
|
self.cancellation_token.cancel()
|
|
@@ -220,7 +213,7 @@ class Extractor(Generic[ConfigType]):
|
|
|
220
213
|
# Store this for later, since we'll override it with the wrapped version
|
|
221
214
|
target = task.target
|
|
222
215
|
|
|
223
|
-
def run_task() -> None:
|
|
216
|
+
def run_task(task_context: TaskContext) -> None:
|
|
224
217
|
"""
|
|
225
218
|
A wrapped version of the task's target, with tracking and error handling
|
|
226
219
|
"""
|
|
@@ -230,33 +223,22 @@ class Extractor(Generic[ConfigType]):
|
|
|
230
223
|
TaskUpdate(type="started", name=task.name, timestamp=now()),
|
|
231
224
|
)
|
|
232
225
|
|
|
233
|
-
context_token: Token[str | None] | None = None
|
|
234
|
-
|
|
235
226
|
try:
|
|
236
|
-
# Set the current task context var, used to track that we're in a task for error reporting
|
|
237
|
-
context_token = self._current_task.set(task.name)
|
|
238
|
-
|
|
239
227
|
# Run task
|
|
240
|
-
target()
|
|
228
|
+
target(task_context)
|
|
241
229
|
|
|
242
230
|
except Exception as e:
|
|
243
|
-
self.logger.exception(f"Unexpected error in {task.name}")
|
|
244
|
-
|
|
245
231
|
# Task crashed, record it as a fatal error
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)
|
|
232
|
+
task_context.exception(
|
|
233
|
+
f"Task {task.name} crashed unexpectedly",
|
|
234
|
+
e,
|
|
235
|
+
level=ErrorLevel.fatal,
|
|
236
|
+
)
|
|
251
237
|
|
|
252
238
|
if self.__class__.RESTART_POLICY(task, e):
|
|
253
239
|
self.restart()
|
|
254
240
|
|
|
255
241
|
finally:
|
|
256
|
-
# Unset the current task
|
|
257
|
-
if context_token is not None:
|
|
258
|
-
self._current_task.reset(context_token)
|
|
259
|
-
|
|
260
242
|
# Record task end
|
|
261
243
|
with self._checkin_lock:
|
|
262
244
|
self._task_updates.append(
|
|
@@ -268,11 +250,20 @@ class Extractor(Generic[ConfigType]):
|
|
|
268
250
|
|
|
269
251
|
match task:
|
|
270
252
|
case ScheduledTask() as t:
|
|
271
|
-
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
|
+
)
|
|
272
263
|
|
|
273
264
|
def _report_extractor_info(self) -> None:
|
|
274
265
|
self.cognite_client.post(
|
|
275
|
-
f"/api/v1/projects/{self.cognite_client.config.project}/
|
|
266
|
+
f"/api/v1/projects/{self.cognite_client.config.project}/integrations/extractorinfo",
|
|
276
267
|
json={
|
|
277
268
|
"externalId": self.connection_config.integration,
|
|
278
269
|
"activeConfigRevision": self.current_config_revision,
|
|
@@ -284,6 +275,8 @@ class Extractor(Generic[ConfigType]):
|
|
|
284
275
|
{
|
|
285
276
|
"name": t.name,
|
|
286
277
|
"type": "continuous" if isinstance(t, ContinuousTask) else "batch",
|
|
278
|
+
"action": True if isinstance(t, ScheduledTask) else False,
|
|
279
|
+
"description": t.description,
|
|
287
280
|
}
|
|
288
281
|
for t in self._tasks
|
|
289
282
|
],
|
|
@@ -305,7 +298,7 @@ class Extractor(Generic[ConfigType]):
|
|
|
305
298
|
|
|
306
299
|
def __exit__(
|
|
307
300
|
self,
|
|
308
|
-
exc_type:
|
|
301
|
+
exc_type: type[BaseException] | None,
|
|
309
302
|
exc_val: BaseException | None,
|
|
310
303
|
exc_tb: TracebackType | None,
|
|
311
304
|
) -> bool:
|
|
@@ -313,7 +306,7 @@ class Extractor(Generic[ConfigType]):
|
|
|
313
306
|
with self._checkin_lock:
|
|
314
307
|
self._checkin()
|
|
315
308
|
|
|
316
|
-
self.
|
|
309
|
+
self._logger.info("Shutting down extractor")
|
|
317
310
|
return exc_val is None
|
|
318
311
|
|
|
319
312
|
def run(self) -> None:
|
|
@@ -336,15 +329,27 @@ class Extractor(Generic[ConfigType]):
|
|
|
336
329
|
case _:
|
|
337
330
|
assert_never(task)
|
|
338
331
|
|
|
339
|
-
self.
|
|
332
|
+
self._logger.info("Starting extractor")
|
|
340
333
|
if startup:
|
|
341
334
|
with ThreadPoolExecutor() as pool:
|
|
342
335
|
for task in startup:
|
|
343
|
-
pool.submit(
|
|
344
|
-
|
|
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")
|
|
345
346
|
|
|
346
347
|
for task in continuous:
|
|
347
|
-
Thread(
|
|
348
|
+
Thread(
|
|
349
|
+
name=pascalize(task.name),
|
|
350
|
+
target=task.target,
|
|
351
|
+
args=(TaskContext(task=task, extractor=self),),
|
|
352
|
+
).start()
|
|
348
353
|
|
|
349
354
|
if has_scheduled:
|
|
350
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__(
|
|
@@ -64,7 +79,7 @@ class Error:
|
|
|
64
79
|
|
|
65
80
|
def __exit__(
|
|
66
81
|
self,
|
|
67
|
-
exc_type:
|
|
82
|
+
exc_type: type[BaseException] | None,
|
|
68
83
|
exc_val: BaseException | None,
|
|
69
84
|
exc_tb: TracebackType | None,
|
|
70
85
|
) -> bool:
|
|
@@ -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()
|
|
@@ -5,8 +5,7 @@ import time
|
|
|
5
5
|
from argparse import ArgumentParser, Namespace
|
|
6
6
|
from multiprocessing import Process, Queue
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Generic,
|
|
9
|
-
from uuid import uuid4
|
|
8
|
+
from typing import Any, Generic, TypeVar
|
|
10
9
|
|
|
11
10
|
from requests.exceptions import ConnectionError
|
|
12
11
|
from typing_extensions import assert_never
|
|
@@ -17,7 +16,6 @@ from cognite.extractorutils.unstable.configuration.exceptions import InvalidConf
|
|
|
17
16
|
from cognite.extractorutils.unstable.configuration.loaders import load_file, load_from_cdf
|
|
18
17
|
from cognite.extractorutils.unstable.configuration.models import ConnectionConfig
|
|
19
18
|
from cognite.extractorutils.unstable.core._dto import Error
|
|
20
|
-
from cognite.extractorutils.util import now
|
|
21
19
|
|
|
22
20
|
from ._messaging import RuntimeMessage
|
|
23
21
|
from .base import ConfigRevision, ConfigType, Extractor, FullConfig
|
|
@@ -30,7 +28,7 @@ ExtractorType = TypeVar("ExtractorType", bound=Extractor)
|
|
|
30
28
|
class Runtime(Generic[ExtractorType]):
|
|
31
29
|
def __init__(
|
|
32
30
|
self,
|
|
33
|
-
extractor:
|
|
31
|
+
extractor: type[ExtractorType],
|
|
34
32
|
) -> None:
|
|
35
33
|
self._extractor_class = extractor
|
|
36
34
|
self._cancellation_token = CancellationToken()
|
|
@@ -127,15 +125,13 @@ class Runtime(Generic[ExtractorType]):
|
|
|
127
125
|
self,
|
|
128
126
|
args: Namespace,
|
|
129
127
|
connection_config: ConnectionConfig,
|
|
130
|
-
) -> tuple[ConfigType, ConfigRevision
|
|
128
|
+
) -> tuple[ConfigType, ConfigRevision]:
|
|
131
129
|
current_config_revision: ConfigRevision
|
|
132
|
-
newest_config_revision: ConfigRevision
|
|
133
130
|
|
|
134
131
|
if args.local_override:
|
|
135
132
|
self.logger.info("Loading local application config")
|
|
136
133
|
|
|
137
134
|
current_config_revision = "local"
|
|
138
|
-
newest_config_revision = "local"
|
|
139
135
|
try:
|
|
140
136
|
application_config = load_file(args.local_override[0], self._extractor_class.CONFIG_TYPE)
|
|
141
137
|
except InvalidConfigError as e:
|
|
@@ -153,50 +149,12 @@ class Runtime(Generic[ExtractorType]):
|
|
|
153
149
|
|
|
154
150
|
errors: list[Error] = []
|
|
155
151
|
|
|
156
|
-
revision: int | None = None
|
|
157
152
|
try:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
self._extractor_class.CONFIG_TYPE,
|
|
164
|
-
revision=revision,
|
|
165
|
-
)
|
|
166
|
-
break
|
|
167
|
-
|
|
168
|
-
except InvalidConfigError as e:
|
|
169
|
-
if e.attempted_revision is None:
|
|
170
|
-
# Should never happen, attempted_revision is set in every handler in load_from_cdf, but it's
|
|
171
|
-
# needed for type checks to pass
|
|
172
|
-
raise e
|
|
173
|
-
|
|
174
|
-
self.logger.error(f"Revision {e.attempted_revision} is invalid: {e.message}")
|
|
175
|
-
|
|
176
|
-
t = now()
|
|
177
|
-
errors.append(
|
|
178
|
-
Error(
|
|
179
|
-
external_id=str(uuid4()),
|
|
180
|
-
level="error",
|
|
181
|
-
description=f"Revision {e.attempted_revision} is invalid",
|
|
182
|
-
details=e.message,
|
|
183
|
-
start_time=t,
|
|
184
|
-
end_time=t,
|
|
185
|
-
task=None,
|
|
186
|
-
)
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
if revision is None:
|
|
190
|
-
revision = e.attempted_revision - 1
|
|
191
|
-
newest_config_revision = e.attempted_revision
|
|
192
|
-
else:
|
|
193
|
-
revision -= 1
|
|
194
|
-
|
|
195
|
-
if revision > 0:
|
|
196
|
-
self.logger.info(f"Falling back to revision {revision}")
|
|
197
|
-
else:
|
|
198
|
-
self.logger.critical("No more revisions to fall back to")
|
|
199
|
-
raise e
|
|
153
|
+
application_config, current_config_revision = load_from_cdf(
|
|
154
|
+
client,
|
|
155
|
+
connection_config.integration,
|
|
156
|
+
self._extractor_class.CONFIG_TYPE,
|
|
157
|
+
)
|
|
200
158
|
|
|
201
159
|
finally:
|
|
202
160
|
if errors:
|
|
@@ -209,7 +167,7 @@ class Runtime(Generic[ExtractorType]):
|
|
|
209
167
|
headers={"cdf-version": "alpha"},
|
|
210
168
|
)
|
|
211
169
|
|
|
212
|
-
return application_config, current_config_revision
|
|
170
|
+
return application_config, current_config_revision
|
|
213
171
|
|
|
214
172
|
def _verify_connection_config(self, connection_config: ConnectionConfig) -> bool:
|
|
215
173
|
client = connection_config.get_cognite_client(
|
|
@@ -281,9 +239,7 @@ class Runtime(Generic[ExtractorType]):
|
|
|
281
239
|
application_config: Any
|
|
282
240
|
while not self._cancellation_token.is_cancelled:
|
|
283
241
|
try:
|
|
284
|
-
application_config, current_config_revision
|
|
285
|
-
args, connection_config
|
|
286
|
-
)
|
|
242
|
+
application_config, current_config_revision = self._get_application_config(args, connection_config)
|
|
287
243
|
|
|
288
244
|
except InvalidConfigError:
|
|
289
245
|
self.logger.critical("Could not get a valid application config file. Shutting down")
|
|
@@ -295,7 +251,6 @@ class Runtime(Generic[ExtractorType]):
|
|
|
295
251
|
connection_config=connection_config,
|
|
296
252
|
application_config=application_config,
|
|
297
253
|
current_config_revision=current_config_revision,
|
|
298
|
-
newest_config_revision=newest_config_revision,
|
|
299
254
|
)
|
|
300
255
|
)
|
|
301
256
|
process.join()
|