cognite-extractor-utils 7.5.3__py3-none-any.whl → 7.5.5__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 +3 -1
- cognite/extractorutils/_inner_util.py +14 -3
- cognite/extractorutils/base.py +14 -15
- cognite/extractorutils/configtools/__init__.py +25 -0
- cognite/extractorutils/configtools/_util.py +7 -9
- cognite/extractorutils/configtools/elements.py +58 -49
- cognite/extractorutils/configtools/loaders.py +29 -26
- cognite/extractorutils/configtools/validators.py +2 -3
- cognite/extractorutils/exceptions.py +1 -4
- cognite/extractorutils/metrics.py +18 -18
- cognite/extractorutils/statestore/_base.py +3 -4
- cognite/extractorutils/statestore/hashing.py +24 -24
- cognite/extractorutils/statestore/watermark.py +17 -14
- cognite/extractorutils/threading.py +4 -4
- cognite/extractorutils/unstable/configuration/exceptions.py +24 -0
- cognite/extractorutils/unstable/configuration/loaders.py +18 -7
- cognite/extractorutils/unstable/configuration/models.py +25 -3
- cognite/extractorutils/unstable/core/_dto.py +10 -0
- cognite/extractorutils/unstable/core/base.py +179 -29
- cognite/extractorutils/unstable/core/errors.py +72 -0
- cognite/extractorutils/unstable/core/restart_policy.py +29 -0
- cognite/extractorutils/unstable/core/runtime.py +170 -26
- cognite/extractorutils/unstable/core/tasks.py +2 -0
- cognite/extractorutils/unstable/scheduling/_scheduler.py +4 -4
- cognite/extractorutils/uploader/__init__.py +14 -0
- cognite/extractorutils/uploader/_base.py +8 -8
- cognite/extractorutils/uploader/assets.py +15 -9
- cognite/extractorutils/uploader/data_modeling.py +13 -13
- cognite/extractorutils/uploader/events.py +9 -9
- cognite/extractorutils/uploader/files.py +144 -38
- cognite/extractorutils/uploader/raw.py +10 -10
- cognite/extractorutils/uploader/time_series.py +56 -58
- cognite/extractorutils/uploader/upload_failure_handler.py +64 -0
- cognite/extractorutils/uploader_extractor.py +11 -11
- cognite/extractorutils/uploader_types.py +4 -12
- cognite/extractorutils/util.py +21 -23
- {cognite_extractor_utils-7.5.3.dist-info → cognite_extractor_utils-7.5.5.dist-info}/METADATA +3 -2
- cognite_extractor_utils-7.5.5.dist-info/RECORD +49 -0
- {cognite_extractor_utils-7.5.3.dist-info → cognite_extractor_utils-7.5.5.dist-info}/WHEEL +1 -1
- cognite/extractorutils/unstable/core/__main__.py +0 -31
- cognite_extractor_utils-7.5.3.dist-info/RECORD +0 -46
- {cognite_extractor_utils-7.5.3.dist-info → cognite_extractor_utils-7.5.5.dist-info}/LICENSE +0 -0
|
@@ -18,15 +18,26 @@ A module containing utilities meant for use inside the extractor-utils package
|
|
|
18
18
|
|
|
19
19
|
import json
|
|
20
20
|
from decimal import Decimal
|
|
21
|
-
from typing import Any
|
|
21
|
+
from typing import Any
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def _resolve_log_level(level: str) -> int:
|
|
25
25
|
return {"NOTSET": 0, "DEBUG": 10, "INFO": 20, "WARNING": 30, "ERROR": 40, "CRITICAL": 50}[level.upper()]
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def resolve_log_level_for_httpx(level: str) -> str:
|
|
29
|
+
return {
|
|
30
|
+
None: "WARNING",
|
|
31
|
+
"INFO": "WARNING",
|
|
32
|
+
"WARNING": "WARNING",
|
|
33
|
+
"ERROR": "ERROR",
|
|
34
|
+
"CRITICAL": "CRITICAL",
|
|
35
|
+
"DEBUG": "DEBUG",
|
|
36
|
+
}.get(level, "WARNING")
|
|
37
|
+
|
|
38
|
+
|
|
28
39
|
class _DecimalEncoder(json.JSONEncoder):
|
|
29
|
-
def default(self, obj: Any) ->
|
|
40
|
+
def default(self, obj: Any) -> dict[str, str]:
|
|
30
41
|
if isinstance(obj, Decimal):
|
|
31
42
|
return {"type": "decimal_encoded", "value": str(obj)}
|
|
32
43
|
return super(_DecimalEncoder, self).default(obj)
|
|
@@ -36,7 +47,7 @@ class _DecimalDecoder(json.JSONDecoder):
|
|
|
36
47
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
37
48
|
json.JSONDecoder.__init__(self, *args, object_hook=self.object_hook, **kwargs)
|
|
38
49
|
|
|
39
|
-
def object_hook(self, obj_dict:
|
|
50
|
+
def object_hook(self, obj_dict: dict[str, str]) -> dict[str, str] | Decimal:
|
|
40
51
|
if obj_dict.get("type") == "decimal_encoded":
|
|
41
52
|
return Decimal(obj_dict["value"])
|
|
42
53
|
return obj_dict
|
cognite/extractorutils/base.py
CHANGED
|
@@ -19,7 +19,7 @@ from dataclasses import is_dataclass
|
|
|
19
19
|
from enum import Enum
|
|
20
20
|
from threading import Thread
|
|
21
21
|
from types import TracebackType
|
|
22
|
-
from typing import Any, Callable,
|
|
22
|
+
from typing import Any, Callable, Generic, Type, TypeVar
|
|
23
23
|
|
|
24
24
|
from dotenv import find_dotenv, load_dotenv
|
|
25
25
|
|
|
@@ -40,6 +40,7 @@ class ReloadConfigAction(Enum):
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
CustomConfigClass = TypeVar("CustomConfigClass", bound=BaseConfig)
|
|
43
|
+
RunHandle = Callable[[CogniteClient, AbstractStateStore, CustomConfigClass, CancellationToken], None]
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
class Extractor(Generic[CustomConfigClass]):
|
|
@@ -68,27 +69,25 @@ class Extractor(Generic[CustomConfigClass]):
|
|
|
68
69
|
heartbeat_waiting_time: Time interval between each heartbeat to the extraction pipeline in seconds.
|
|
69
70
|
"""
|
|
70
71
|
|
|
71
|
-
_config_singleton:
|
|
72
|
-
_statestore_singleton:
|
|
72
|
+
_config_singleton: CustomConfigClass | None = None
|
|
73
|
+
_statestore_singleton: AbstractStateStore | None = None
|
|
73
74
|
|
|
74
75
|
def __init__(
|
|
75
76
|
self,
|
|
76
77
|
*,
|
|
77
78
|
name: str,
|
|
78
79
|
description: str,
|
|
79
|
-
version:
|
|
80
|
-
run_handle:
|
|
81
|
-
Callable[[CogniteClient, AbstractStateStore, CustomConfigClass, CancellationToken], None]
|
|
82
|
-
] = None,
|
|
80
|
+
version: str | None = None,
|
|
81
|
+
run_handle: RunHandle | None = None,
|
|
83
82
|
config_class: Type[CustomConfigClass],
|
|
84
|
-
metrics:
|
|
83
|
+
metrics: BaseMetrics | None = None,
|
|
85
84
|
use_default_state_store: bool = True,
|
|
86
|
-
cancellation_token:
|
|
87
|
-
config_file_path:
|
|
85
|
+
cancellation_token: CancellationToken | None = None,
|
|
86
|
+
config_file_path: str | None = None,
|
|
88
87
|
continuous_extractor: bool = False,
|
|
89
88
|
heartbeat_waiting_time: int = 600,
|
|
90
89
|
handle_interrupts: bool = True,
|
|
91
|
-
reload_config_interval:
|
|
90
|
+
reload_config_interval: int | None = 300,
|
|
92
91
|
reload_config_action: ReloadConfigAction = ReloadConfigAction.DO_NOTHING,
|
|
93
92
|
):
|
|
94
93
|
self.name = name
|
|
@@ -111,7 +110,7 @@ class Extractor(Generic[CustomConfigClass]):
|
|
|
111
110
|
self.cognite_client: CogniteClient
|
|
112
111
|
self.state_store: AbstractStateStore
|
|
113
112
|
self.config: CustomConfigClass
|
|
114
|
-
self.extraction_pipeline:
|
|
113
|
+
self.extraction_pipeline: ExtractionPipeline | None
|
|
115
114
|
self.logger: logging.Logger
|
|
116
115
|
|
|
117
116
|
self.should_be_restarted = False
|
|
@@ -121,7 +120,7 @@ class Extractor(Generic[CustomConfigClass]):
|
|
|
121
120
|
else:
|
|
122
121
|
self.metrics = BaseMetrics(extractor_name=name, extractor_version=self.version)
|
|
123
122
|
|
|
124
|
-
def _initial_load_config(self, override_path:
|
|
123
|
+
def _initial_load_config(self, override_path: str | None = None) -> None:
|
|
125
124
|
"""
|
|
126
125
|
Load a configuration file, either from the specified path, or by a path specified by the user in a command line
|
|
127
126
|
arg. Will quit further execution of no path is given.
|
|
@@ -177,7 +176,7 @@ class Extractor(Generic[CustomConfigClass]):
|
|
|
177
176
|
Either way, the state_store attribute is guaranteed to be set after calling this method.
|
|
178
177
|
"""
|
|
179
178
|
|
|
180
|
-
def recursive_find_state_store(d:
|
|
179
|
+
def recursive_find_state_store(d: dict[str, Any]) -> StateStoreConfig | None:
|
|
181
180
|
for k in d:
|
|
182
181
|
if is_dataclass(d[k]):
|
|
183
182
|
res = recursive_find_state_store(d[k].__dict__)
|
|
@@ -323,7 +322,7 @@ class Extractor(Generic[CustomConfigClass]):
|
|
|
323
322
|
return self
|
|
324
323
|
|
|
325
324
|
def __exit__(
|
|
326
|
-
self, exc_type:
|
|
325
|
+
self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
|
327
326
|
) -> bool:
|
|
328
327
|
"""
|
|
329
328
|
Shuts down the extractor. Makes sure states are preserved, that all uploads of data and metrics are done, etc.
|
|
@@ -107,3 +107,28 @@ from .elements import (
|
|
|
107
107
|
TimeIntervalConfig,
|
|
108
108
|
)
|
|
109
109
|
from .loaders import ConfigResolver, KeyVaultAuthenticationMethod, KeyVaultLoader, load_yaml, load_yaml_dict
|
|
110
|
+
|
|
111
|
+
__all__ = [
|
|
112
|
+
"AuthenticatorConfig",
|
|
113
|
+
"BaseConfig",
|
|
114
|
+
"CastableInt",
|
|
115
|
+
"CertificateConfig",
|
|
116
|
+
"CogniteConfig",
|
|
117
|
+
"ConfigType",
|
|
118
|
+
"ConnectionConfig",
|
|
119
|
+
"EitherIdConfig",
|
|
120
|
+
"FileSizeConfig",
|
|
121
|
+
"LocalStateStoreConfig",
|
|
122
|
+
"LoggingConfig",
|
|
123
|
+
"MetricsConfig",
|
|
124
|
+
"PortNumber",
|
|
125
|
+
"RawDestinationConfig",
|
|
126
|
+
"RawStateStoreConfig",
|
|
127
|
+
"StateStoreConfig",
|
|
128
|
+
"TimeIntervalConfig",
|
|
129
|
+
"ConfigResolver",
|
|
130
|
+
"KeyVaultAuthenticationMethod",
|
|
131
|
+
"KeyVaultLoader",
|
|
132
|
+
"load_yaml",
|
|
133
|
+
"load_yaml_dict",
|
|
134
|
+
]
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import base64
|
|
15
15
|
import re
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import Any, Callable
|
|
17
|
+
from typing import Any, Callable
|
|
18
18
|
|
|
19
19
|
from cryptography.hazmat.primitives import hashes
|
|
20
20
|
from cryptography.hazmat.primitives import serialization as serialization
|
|
@@ -24,7 +24,7 @@ from cryptography.x509 import load_pem_x509_certificate
|
|
|
24
24
|
from cognite.extractorutils.exceptions import InvalidConfigError
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def _to_snake_case(dictionary:
|
|
27
|
+
def _to_snake_case(dictionary: dict[str, Any], case_style: str) -> dict[str, Any]:
|
|
28
28
|
"""
|
|
29
29
|
Ensure that all keys in the dictionary follows the snake casing convention (recursively, so any sub-dictionaries are
|
|
30
30
|
changed too).
|
|
@@ -37,11 +37,11 @@ def _to_snake_case(dictionary: Dict[str, Any], case_style: str) -> Dict[str, Any
|
|
|
37
37
|
An updated dictionary with keys in the given convention.
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
def fix_list(list_:
|
|
40
|
+
def fix_list(list_: list[Any], key_translator: Callable[[str], str]) -> list[Any]:
|
|
41
41
|
if list_ is None:
|
|
42
42
|
return []
|
|
43
43
|
|
|
44
|
-
new_list:
|
|
44
|
+
new_list: list[Any] = [None] * len(list_)
|
|
45
45
|
for i, element in enumerate(list_):
|
|
46
46
|
if isinstance(element, dict):
|
|
47
47
|
new_list[i] = fix_dict(element, key_translator)
|
|
@@ -51,11 +51,11 @@ def _to_snake_case(dictionary: Dict[str, Any], case_style: str) -> Dict[str, Any
|
|
|
51
51
|
new_list[i] = element
|
|
52
52
|
return new_list
|
|
53
53
|
|
|
54
|
-
def fix_dict(dict_:
|
|
54
|
+
def fix_dict(dict_: dict[str, Any], key_translator: Callable[[str], str]) -> dict[str, Any]:
|
|
55
55
|
if dict_ is None:
|
|
56
56
|
return {}
|
|
57
57
|
|
|
58
|
-
new_dict:
|
|
58
|
+
new_dict: dict[str, Any] = {}
|
|
59
59
|
for key in dict_:
|
|
60
60
|
if isinstance(dict_[key], dict):
|
|
61
61
|
new_dict[key_translator(key)] = fix_dict(dict_[key], key_translator)
|
|
@@ -81,9 +81,7 @@ def _to_snake_case(dictionary: Dict[str, Any], case_style: str) -> Dict[str, Any
|
|
|
81
81
|
raise ValueError(f"Invalid case style: {case_style}")
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
def _load_certificate_data(
|
|
85
|
-
cert_path: str | Path, password: Optional[str]
|
|
86
|
-
) -> Union[Tuple[str, str], Tuple[bytes, bytes]]:
|
|
84
|
+
def _load_certificate_data(cert_path: str | Path, password: str | None) -> tuple[str, str] | tuple[bytes, bytes]:
|
|
87
85
|
path = Path(cert_path) if isinstance(cert_path, str) else cert_path
|
|
88
86
|
cert_data = Path(path).read_bytes()
|
|
89
87
|
|
|
@@ -19,7 +19,7 @@ from datetime import timedelta
|
|
|
19
19
|
from enum import Enum
|
|
20
20
|
from logging.handlers import TimedRotatingFileHandler
|
|
21
21
|
from time import sleep
|
|
22
|
-
from typing import Any
|
|
22
|
+
from typing import Any
|
|
23
23
|
from urllib.parse import urljoin, urlparse
|
|
24
24
|
|
|
25
25
|
import yaml
|
|
@@ -32,6 +32,7 @@ from cognite.client.credentials import (
|
|
|
32
32
|
OAuthClientCredentials,
|
|
33
33
|
)
|
|
34
34
|
from cognite.client.data_classes import Asset, DataSet, ExtractionPipeline
|
|
35
|
+
from cognite.extractorutils._inner_util import resolve_log_level_for_httpx
|
|
35
36
|
from cognite.extractorutils.configtools._util import _load_certificate_data
|
|
36
37
|
from cognite.extractorutils.exceptions import InvalidConfigError
|
|
37
38
|
from cognite.extractorutils.metrics import (
|
|
@@ -58,8 +59,8 @@ class CertificateConfig:
|
|
|
58
59
|
"""
|
|
59
60
|
|
|
60
61
|
path: str
|
|
61
|
-
password:
|
|
62
|
-
authority_url:
|
|
62
|
+
password: str | None
|
|
63
|
+
authority_url: str | None = None
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
@dataclass
|
|
@@ -69,15 +70,15 @@ class AuthenticatorConfig:
|
|
|
69
70
|
"""
|
|
70
71
|
|
|
71
72
|
client_id: str
|
|
72
|
-
scopes:
|
|
73
|
-
secret:
|
|
74
|
-
tenant:
|
|
75
|
-
token_url:
|
|
76
|
-
resource:
|
|
77
|
-
audience:
|
|
73
|
+
scopes: list[str]
|
|
74
|
+
secret: str | None = None
|
|
75
|
+
tenant: str | None = None
|
|
76
|
+
token_url: str | None = None
|
|
77
|
+
resource: str | None = None
|
|
78
|
+
audience: str | None = None
|
|
78
79
|
authority: str = "https://login.microsoftonline.com/"
|
|
79
80
|
min_ttl: float = 30 # minimum time to live: refresh token ahead of expiration
|
|
80
|
-
certificate:
|
|
81
|
+
certificate: CertificateConfig | None = None
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
@dataclass
|
|
@@ -87,13 +88,13 @@ class ConnectionConfig:
|
|
|
87
88
|
"""
|
|
88
89
|
|
|
89
90
|
disable_gzip: bool = False
|
|
90
|
-
status_forcelist:
|
|
91
|
+
status_forcelist: list[int] = field(default_factory=lambda: [429, 502, 503, 504])
|
|
91
92
|
max_retries: int = 10
|
|
92
93
|
max_retries_connect: int = 3
|
|
93
94
|
max_retry_backoff: int = 30
|
|
94
95
|
max_connection_pool_size: int = 50
|
|
95
96
|
disable_ssl: bool = False
|
|
96
|
-
proxies:
|
|
97
|
+
proxies: dict[str, str] = field(default_factory=dict)
|
|
97
98
|
|
|
98
99
|
|
|
99
100
|
@dataclass
|
|
@@ -103,8 +104,8 @@ class EitherIdConfig:
|
|
|
103
104
|
An EitherId can only hold one ID type, not both.
|
|
104
105
|
"""
|
|
105
106
|
|
|
106
|
-
id:
|
|
107
|
-
external_id:
|
|
107
|
+
id: int | None
|
|
108
|
+
external_id: str | None
|
|
108
109
|
|
|
109
110
|
@property
|
|
110
111
|
def either_id(self) -> EitherId:
|
|
@@ -128,7 +129,7 @@ class TimeIntervalConfig(yaml.YAMLObject):
|
|
|
128
129
|
return hash(self._interval)
|
|
129
130
|
|
|
130
131
|
@classmethod
|
|
131
|
-
def _parse_expression(cls, expression: str) ->
|
|
132
|
+
def _parse_expression(cls, expression: str) -> tuple[int, str]:
|
|
132
133
|
# First, try to parse pure number and assume seconds (for backwards compatibility)
|
|
133
134
|
try:
|
|
134
135
|
return int(expression), f"{expression}s"
|
|
@@ -188,7 +189,7 @@ class FileSizeConfig(yaml.YAMLObject):
|
|
|
188
189
|
self._bytes, self._expression = FileSizeConfig._parse_expression(expression)
|
|
189
190
|
|
|
190
191
|
@classmethod
|
|
191
|
-
def _parse_expression(cls, expression: str) ->
|
|
192
|
+
def _parse_expression(cls, expression: str) -> tuple[int, str]:
|
|
192
193
|
# First, try to parse pure number and assume bytes
|
|
193
194
|
try:
|
|
194
195
|
return int(expression), f"{expression}s"
|
|
@@ -284,20 +285,20 @@ class CogniteConfig:
|
|
|
284
285
|
|
|
285
286
|
project: str
|
|
286
287
|
idp_authentication: AuthenticatorConfig
|
|
287
|
-
data_set:
|
|
288
|
-
data_set_id:
|
|
289
|
-
data_set_external_id:
|
|
290
|
-
extraction_pipeline:
|
|
288
|
+
data_set: EitherIdConfig | None = None
|
|
289
|
+
data_set_id: int | None = None
|
|
290
|
+
data_set_external_id: str | None = None
|
|
291
|
+
extraction_pipeline: EitherIdConfig | None = None
|
|
291
292
|
timeout: TimeIntervalConfig = TimeIntervalConfig("30s")
|
|
292
293
|
connection: ConnectionConfig = field(default_factory=ConnectionConfig)
|
|
293
|
-
security_categories:
|
|
294
|
+
security_categories: list[int] | None = None
|
|
294
295
|
external_id_prefix: str = ""
|
|
295
296
|
host: str = "https://api.cognitedata.com"
|
|
296
297
|
|
|
297
298
|
def get_cognite_client(
|
|
298
299
|
self,
|
|
299
300
|
client_name: str,
|
|
300
|
-
token_custom_args:
|
|
301
|
+
token_custom_args: dict[str, str] | None = None,
|
|
301
302
|
use_experimental_sdk: bool = False,
|
|
302
303
|
) -> CogniteClient:
|
|
303
304
|
from cognite.client.config import global_config
|
|
@@ -344,7 +345,7 @@ class CogniteConfig:
|
|
|
344
345
|
)
|
|
345
346
|
|
|
346
347
|
elif self.idp_authentication.secret:
|
|
347
|
-
kwargs:
|
|
348
|
+
kwargs: dict[str, Any] = {}
|
|
348
349
|
if self.idp_authentication.token_url:
|
|
349
350
|
_validate_https_url(self.idp_authentication.token_url, "Token URL")
|
|
350
351
|
kwargs["token_url"] = self.idp_authentication.token_url
|
|
@@ -387,7 +388,7 @@ class CogniteConfig:
|
|
|
387
388
|
|
|
388
389
|
return CogniteClient(client_config)
|
|
389
390
|
|
|
390
|
-
def get_data_set(self, cdf_client: CogniteClient) ->
|
|
391
|
+
def get_data_set(self, cdf_client: CogniteClient) -> DataSet | None:
|
|
391
392
|
if self.data_set_external_id:
|
|
392
393
|
logging.getLogger(__name__).warning(
|
|
393
394
|
"Using data-set-external-id is deprecated, please use data-set/external-id instead"
|
|
@@ -406,7 +407,7 @@ class CogniteConfig:
|
|
|
406
407
|
external_id=self.data_set.either_id.external_id,
|
|
407
408
|
)
|
|
408
409
|
|
|
409
|
-
def get_extraction_pipeline(self, cdf_client: CogniteClient) ->
|
|
410
|
+
def get_extraction_pipeline(self, cdf_client: CogniteClient) -> ExtractionPipeline | None:
|
|
410
411
|
if not self.extraction_pipeline:
|
|
411
412
|
return None
|
|
412
413
|
|
|
@@ -438,12 +439,12 @@ class LoggingConfig:
|
|
|
438
439
|
Logging settings, such as log levels and path to log file
|
|
439
440
|
"""
|
|
440
441
|
|
|
441
|
-
console:
|
|
442
|
-
file:
|
|
442
|
+
console: _ConsoleLoggingConfig | None
|
|
443
|
+
file: _FileLoggingConfig | None
|
|
443
444
|
# enables metrics on the number of log messages recorded (per logger and level)
|
|
444
445
|
# In order to collect/see result MetricsConfig should be set as well, so metrics are propagated to
|
|
445
446
|
# Prometheus and/or Cognite
|
|
446
|
-
metrics:
|
|
447
|
+
metrics: bool | None = False
|
|
447
448
|
|
|
448
449
|
def setup_logging(self, suppress_console: bool = False) -> None:
|
|
449
450
|
"""
|
|
@@ -491,15 +492,23 @@ class LoggingConfig:
|
|
|
491
492
|
if root.getEffectiveLevel() > file_handler.level:
|
|
492
493
|
root.setLevel(file_handler.level)
|
|
493
494
|
|
|
495
|
+
log_level = logging.getLevelName(root.getEffectiveLevel())
|
|
496
|
+
httpx_log_level = resolve_log_level_for_httpx(log_level)
|
|
497
|
+
httpx_logger = logging.getLogger("httpx")
|
|
498
|
+
httpx_logger.setLevel(httpx_log_level)
|
|
499
|
+
|
|
500
|
+
http_core_logger = logging.getLogger("httpcore")
|
|
501
|
+
http_core_logger.setLevel(httpx_log_level)
|
|
502
|
+
|
|
494
503
|
|
|
495
504
|
@dataclass
|
|
496
505
|
class _PushGatewayConfig:
|
|
497
506
|
host: str
|
|
498
507
|
job_name: str
|
|
499
|
-
username:
|
|
500
|
-
password:
|
|
508
|
+
username: str | None
|
|
509
|
+
password: str | None
|
|
501
510
|
|
|
502
|
-
clear_after:
|
|
511
|
+
clear_after: TimeIntervalConfig | None
|
|
503
512
|
push_interval: TimeIntervalConfig = TimeIntervalConfig("30s")
|
|
504
513
|
|
|
505
514
|
|
|
@@ -511,9 +520,9 @@ class _PromServerConfig:
|
|
|
511
520
|
@dataclass
|
|
512
521
|
class _CogniteMetricsConfig:
|
|
513
522
|
external_id_prefix: str
|
|
514
|
-
asset_name:
|
|
515
|
-
asset_external_id:
|
|
516
|
-
data_set:
|
|
523
|
+
asset_name: str | None
|
|
524
|
+
asset_external_id: str | None
|
|
525
|
+
data_set: EitherIdConfig | None = None
|
|
517
526
|
|
|
518
527
|
push_interval: TimeIntervalConfig = TimeIntervalConfig("30s")
|
|
519
528
|
|
|
@@ -525,13 +534,13 @@ class MetricsConfig:
|
|
|
525
534
|
Series.
|
|
526
535
|
"""
|
|
527
536
|
|
|
528
|
-
push_gateways:
|
|
529
|
-
cognite:
|
|
530
|
-
server:
|
|
537
|
+
push_gateways: list[_PushGatewayConfig] | None
|
|
538
|
+
cognite: _CogniteMetricsConfig | None
|
|
539
|
+
server: _PromServerConfig | None
|
|
531
540
|
|
|
532
|
-
def start_pushers(self, cdf_client: CogniteClient, cancellation_token:
|
|
533
|
-
self._pushers:
|
|
534
|
-
self._clear_on_stop:
|
|
541
|
+
def start_pushers(self, cdf_client: CogniteClient, cancellation_token: CancellationToken | None = None) -> None:
|
|
542
|
+
self._pushers: list[AbstractMetricsPusher] = []
|
|
543
|
+
self._clear_on_stop: dict[PrometheusPusher, int] = {}
|
|
535
544
|
|
|
536
545
|
push_gateways = self.push_gateways or []
|
|
537
546
|
|
|
@@ -599,9 +608,9 @@ class ConfigType(Enum):
|
|
|
599
608
|
|
|
600
609
|
@dataclass
|
|
601
610
|
class _BaseConfig:
|
|
602
|
-
_file_hash:
|
|
611
|
+
_file_hash: str | None = field(init=False, repr=False, default=None)
|
|
603
612
|
|
|
604
|
-
type:
|
|
613
|
+
type: ConfigType | None
|
|
605
614
|
cognite: CogniteConfig
|
|
606
615
|
|
|
607
616
|
|
|
@@ -611,7 +620,7 @@ class BaseConfig(_BaseConfig):
|
|
|
611
620
|
Basis for an extractor config, containing config version, ``CogniteConfig`` and ``LoggingConfig``
|
|
612
621
|
"""
|
|
613
622
|
|
|
614
|
-
version:
|
|
623
|
+
version: str | int | None
|
|
615
624
|
logger: LoggingConfig
|
|
616
625
|
|
|
617
626
|
|
|
@@ -650,14 +659,14 @@ class StateStoreConfig:
|
|
|
650
659
|
Configuration of the State Store, containing ``LocalStateStoreConfig`` or ``RawStateStoreConfig``
|
|
651
660
|
"""
|
|
652
661
|
|
|
653
|
-
raw:
|
|
654
|
-
local:
|
|
662
|
+
raw: RawStateStoreConfig | None = None
|
|
663
|
+
local: LocalStateStoreConfig | None = None
|
|
655
664
|
|
|
656
665
|
def create_state_store(
|
|
657
666
|
self,
|
|
658
|
-
cdf_client:
|
|
667
|
+
cdf_client: CogniteClient | None = None,
|
|
659
668
|
default_to_local: bool = True,
|
|
660
|
-
cancellation_token:
|
|
669
|
+
cancellation_token: CancellationToken | None = None,
|
|
661
670
|
) -> AbstractStateStore:
|
|
662
671
|
"""
|
|
663
672
|
Create a state store object based on the config.
|
|
@@ -719,8 +728,8 @@ class IgnorePattern:
|
|
|
719
728
|
"""
|
|
720
729
|
|
|
721
730
|
pattern: str
|
|
722
|
-
options:
|
|
723
|
-
flags:
|
|
731
|
+
options: list[RegExpFlag] | None = None
|
|
732
|
+
flags: list[RegExpFlag] | None = None
|
|
724
733
|
|
|
725
734
|
def compile(self) -> re.Pattern[str]:
|
|
726
735
|
"""
|
|
@@ -22,7 +22,7 @@ import sys
|
|
|
22
22
|
from enum import Enum
|
|
23
23
|
from hashlib import sha256
|
|
24
24
|
from pathlib import Path
|
|
25
|
-
from typing import Any, Callable,
|
|
25
|
+
from typing import Any, Callable, Generic, Iterable, TextIO, Type, TypeVar, cast
|
|
26
26
|
|
|
27
27
|
import dacite
|
|
28
28
|
import yaml
|
|
@@ -61,11 +61,11 @@ class KeyVaultLoader:
|
|
|
61
61
|
Class responsible for configuring keyvault for clients using Azure
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
|
-
def __init__(self, config:
|
|
64
|
+
def __init__(self, config: dict | None):
|
|
65
65
|
self.config = config
|
|
66
66
|
|
|
67
|
-
self.credentials:
|
|
68
|
-
self.client:
|
|
67
|
+
self.credentials: TokenCredential | None = None
|
|
68
|
+
self.client: SecretClient | None = None
|
|
69
69
|
|
|
70
70
|
def _init_client(self) -> None:
|
|
71
71
|
from dotenv import find_dotenv, load_dotenv
|
|
@@ -148,10 +148,10 @@ def _env_constructor(_: yaml.SafeLoader, node: yaml.Node) -> bool:
|
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
def _load_yaml_dict_raw(
|
|
151
|
-
source:
|
|
151
|
+
source: TextIO | str,
|
|
152
152
|
expand_envvars: bool = True,
|
|
153
|
-
keyvault_loader:
|
|
154
|
-
) ->
|
|
153
|
+
keyvault_loader: KeyVaultLoader | None = None,
|
|
154
|
+
) -> dict[str, Any]:
|
|
155
155
|
loader = _EnvLoader if expand_envvars else yaml.SafeLoader
|
|
156
156
|
|
|
157
157
|
class SafeLoaderIgnoreUnknown(yaml.SafeLoader):
|
|
@@ -163,6 +163,9 @@ def _load_yaml_dict_raw(
|
|
|
163
163
|
SafeLoaderIgnoreUnknown.add_constructor(None, SafeLoaderIgnoreUnknown.ignore_unknown) # type: ignore
|
|
164
164
|
initial_load = yaml.load(source, Loader=SafeLoaderIgnoreUnknown) # noqa: S506
|
|
165
165
|
|
|
166
|
+
if not isinstance(initial_load, dict):
|
|
167
|
+
raise InvalidConfigError("The root node of the YAML document must be an object")
|
|
168
|
+
|
|
166
169
|
if not isinstance(source, str):
|
|
167
170
|
source.seek(0)
|
|
168
171
|
|
|
@@ -187,12 +190,12 @@ def _load_yaml_dict_raw(
|
|
|
187
190
|
|
|
188
191
|
|
|
189
192
|
def _load_yaml_dict(
|
|
190
|
-
source:
|
|
193
|
+
source: TextIO | str,
|
|
191
194
|
case_style: str = "hyphen",
|
|
192
195
|
expand_envvars: bool = True,
|
|
193
|
-
dict_manipulator: Callable[[
|
|
194
|
-
keyvault_loader:
|
|
195
|
-
) ->
|
|
196
|
+
dict_manipulator: Callable[[dict[str, Any]], dict[str, Any]] = lambda x: x,
|
|
197
|
+
keyvault_loader: KeyVaultLoader | None = None,
|
|
198
|
+
) -> dict[str, Any]:
|
|
196
199
|
config_dict = _load_yaml_dict_raw(source, expand_envvars, keyvault_loader)
|
|
197
200
|
|
|
198
201
|
config_dict = dict_manipulator(config_dict)
|
|
@@ -207,12 +210,12 @@ def _load_yaml_dict(
|
|
|
207
210
|
|
|
208
211
|
|
|
209
212
|
def _load_yaml(
|
|
210
|
-
source:
|
|
213
|
+
source: TextIO | str,
|
|
211
214
|
config_type: Type[CustomConfigClass],
|
|
212
215
|
case_style: str = "hyphen",
|
|
213
216
|
expand_envvars: bool = True,
|
|
214
|
-
dict_manipulator: Callable[[
|
|
215
|
-
keyvault_loader:
|
|
217
|
+
dict_manipulator: Callable[[dict[str, Any]], dict[str, Any]] = lambda x: x,
|
|
218
|
+
keyvault_loader: KeyVaultLoader | None = None,
|
|
216
219
|
) -> CustomConfigClass:
|
|
217
220
|
config_dict = _load_yaml_dict(
|
|
218
221
|
source,
|
|
@@ -264,11 +267,11 @@ def _load_yaml(
|
|
|
264
267
|
|
|
265
268
|
|
|
266
269
|
def load_yaml(
|
|
267
|
-
source:
|
|
270
|
+
source: TextIO | str,
|
|
268
271
|
config_type: Type[CustomConfigClass],
|
|
269
272
|
case_style: str = "hyphen",
|
|
270
273
|
expand_envvars: bool = True,
|
|
271
|
-
keyvault_loader:
|
|
274
|
+
keyvault_loader: KeyVaultLoader | None = None,
|
|
272
275
|
) -> CustomConfigClass:
|
|
273
276
|
"""
|
|
274
277
|
Read a YAML file, and create a config object based on its contents.
|
|
@@ -297,11 +300,11 @@ def load_yaml(
|
|
|
297
300
|
|
|
298
301
|
|
|
299
302
|
def load_yaml_dict(
|
|
300
|
-
source:
|
|
303
|
+
source: TextIO | str,
|
|
301
304
|
case_style: str = "hyphen",
|
|
302
305
|
expand_envvars: bool = True,
|
|
303
|
-
keyvault_loader:
|
|
304
|
-
) ->
|
|
306
|
+
keyvault_loader: KeyVaultLoader | None = None,
|
|
307
|
+
) -> dict[str, Any]:
|
|
305
308
|
"""
|
|
306
309
|
Read a YAML file and return a dictionary from its contents
|
|
307
310
|
|
|
@@ -323,9 +326,9 @@ def load_yaml_dict(
|
|
|
323
326
|
)
|
|
324
327
|
|
|
325
328
|
|
|
326
|
-
def compile_patterns(ignore_patterns:
|
|
329
|
+
def compile_patterns(ignore_patterns: list[str | IgnorePattern]) -> list[re.Pattern[str]]:
|
|
327
330
|
"""
|
|
328
|
-
|
|
331
|
+
list of patterns to compile
|
|
329
332
|
|
|
330
333
|
Args:
|
|
331
334
|
ignore_patterns: A list of strings or IgnorePattern to be compiled.
|
|
@@ -347,17 +350,17 @@ class ConfigResolver(Generic[CustomConfigClass]):
|
|
|
347
350
|
self.config_path = config_path
|
|
348
351
|
self.config_type = config_type
|
|
349
352
|
|
|
350
|
-
self._config:
|
|
351
|
-
self._next_config:
|
|
353
|
+
self._config: CustomConfigClass | None = None
|
|
354
|
+
self._next_config: CustomConfigClass | None = None
|
|
352
355
|
|
|
353
|
-
self._cognite_client:
|
|
356
|
+
self._cognite_client: CogniteClient | None = None
|
|
354
357
|
|
|
355
358
|
def _reload_file(self) -> None:
|
|
356
359
|
with open(self.config_path, "r") as stream:
|
|
357
360
|
self._config_text = stream.read()
|
|
358
361
|
|
|
359
362
|
@property
|
|
360
|
-
def cognite_client(self) ->
|
|
363
|
+
def cognite_client(self) -> CogniteClient | None:
|
|
361
364
|
if self._cognite_client is None and self._config is not None:
|
|
362
365
|
self._cognite_client = self._config.cognite.get_cognite_client("config_resolver")
|
|
363
366
|
return self._cognite_client
|
|
@@ -409,7 +412,7 @@ class ConfigResolver(Generic[CustomConfigClass]):
|
|
|
409
412
|
|
|
410
413
|
return cls(args.config[0], config_type)
|
|
411
414
|
|
|
412
|
-
def _inject_cognite(self, local_part: _BaseConfig, remote_part:
|
|
415
|
+
def _inject_cognite(self, local_part: _BaseConfig, remote_part: dict[str, Any]) -> dict[str, Any]:
|
|
413
416
|
# We can not dump 'local_part.cognite' directly because e.g. 'data_set' may be set remote only...
|
|
414
417
|
remote_part.setdefault("cognite", {})
|
|
415
418
|
remote_part["cognite"]["idp_authentication"] = dataclasses.asdict(local_part.cognite.idp_authentication)
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
|
-
from typing import Union
|
|
4
3
|
|
|
5
4
|
_logger = logging.getLogger(__name__)
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
def matches_patterns(patterns: list[
|
|
7
|
+
def matches_patterns(patterns: list[str | re.Pattern[str]], string: str) -> bool:
|
|
9
8
|
"""
|
|
10
9
|
Check string against list of RegExp patterns.
|
|
11
10
|
|
|
@@ -19,7 +18,7 @@ def matches_patterns(patterns: list[Union[str, re.Pattern[str]]], string: str) -
|
|
|
19
18
|
return any([matches_pattern(pattern, string) for pattern in patterns])
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
def matches_pattern(pattern:
|
|
21
|
+
def matches_pattern(pattern: str | re.Pattern[str], string: str) -> bool:
|
|
23
22
|
"""
|
|
24
23
|
Match pattern against a string.
|
|
25
24
|
|