cognite-extractor-utils 7.5.4__py3-none-any.whl → 7.5.6__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.

Files changed (42) hide show
  1. cognite/extractorutils/__init__.py +3 -1
  2. cognite/extractorutils/_inner_util.py +14 -3
  3. cognite/extractorutils/base.py +14 -15
  4. cognite/extractorutils/configtools/__init__.py +25 -0
  5. cognite/extractorutils/configtools/_util.py +7 -9
  6. cognite/extractorutils/configtools/elements.py +58 -49
  7. cognite/extractorutils/configtools/loaders.py +29 -26
  8. cognite/extractorutils/configtools/validators.py +2 -3
  9. cognite/extractorutils/exceptions.py +1 -4
  10. cognite/extractorutils/metrics.py +18 -18
  11. cognite/extractorutils/statestore/_base.py +3 -4
  12. cognite/extractorutils/statestore/hashing.py +24 -24
  13. cognite/extractorutils/statestore/watermark.py +17 -14
  14. cognite/extractorutils/threading.py +4 -4
  15. cognite/extractorutils/unstable/configuration/exceptions.py +24 -0
  16. cognite/extractorutils/unstable/configuration/loaders.py +18 -7
  17. cognite/extractorutils/unstable/configuration/models.py +25 -3
  18. cognite/extractorutils/unstable/core/_dto.py +10 -0
  19. cognite/extractorutils/unstable/core/base.py +179 -29
  20. cognite/extractorutils/unstable/core/errors.py +72 -0
  21. cognite/extractorutils/unstable/core/restart_policy.py +29 -0
  22. cognite/extractorutils/unstable/core/runtime.py +170 -26
  23. cognite/extractorutils/unstable/core/tasks.py +2 -0
  24. cognite/extractorutils/unstable/scheduling/_scheduler.py +4 -4
  25. cognite/extractorutils/uploader/__init__.py +14 -0
  26. cognite/extractorutils/uploader/_base.py +8 -8
  27. cognite/extractorutils/uploader/assets.py +15 -9
  28. cognite/extractorutils/uploader/data_modeling.py +13 -13
  29. cognite/extractorutils/uploader/events.py +9 -9
  30. cognite/extractorutils/uploader/files.py +153 -46
  31. cognite/extractorutils/uploader/raw.py +10 -10
  32. cognite/extractorutils/uploader/time_series.py +56 -58
  33. cognite/extractorutils/uploader/upload_failure_handler.py +64 -0
  34. cognite/extractorutils/uploader_extractor.py +11 -11
  35. cognite/extractorutils/uploader_types.py +4 -12
  36. cognite/extractorutils/util.py +21 -23
  37. {cognite_extractor_utils-7.5.4.dist-info → cognite_extractor_utils-7.5.6.dist-info}/METADATA +4 -3
  38. cognite_extractor_utils-7.5.6.dist-info/RECORD +49 -0
  39. {cognite_extractor_utils-7.5.4.dist-info → cognite_extractor_utils-7.5.6.dist-info}/WHEEL +1 -1
  40. cognite/extractorutils/unstable/core/__main__.py +0 -31
  41. cognite_extractor_utils-7.5.4.dist-info/RECORD +0 -46
  42. {cognite_extractor_utils-7.5.4.dist-info → cognite_extractor_utils-7.5.6.dist-info}/LICENSE +0 -0
@@ -16,5 +16,7 @@
16
16
  Cognite extractor utils is a Python package that simplifies the development of new extractors.
17
17
  """
18
18
 
19
- __version__ = "7.5.4"
19
+ __version__ = "7.5.6"
20
20
  from .base import Extractor
21
+
22
+ __all__ = ["Extractor"]
@@ -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, Dict, Union
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) -> Dict[str, str]:
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: Dict[str, str]) -> Union[Dict[str, str], Decimal]:
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
@@ -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, Dict, Generic, Optional, Type, TypeVar
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: Optional[CustomConfigClass] = None
72
- _statestore_singleton: Optional[AbstractStateStore] = None
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: Optional[str] = None,
80
- run_handle: Optional[
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: Optional[BaseMetrics] = None,
83
+ metrics: BaseMetrics | None = None,
85
84
  use_default_state_store: bool = True,
86
- cancellation_token: Optional[CancellationToken] = None,
87
- config_file_path: Optional[str] = None,
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: Optional[int] = 300,
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: Optional[ExtractionPipeline]
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: Optional[str] = None) -> None:
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: Dict[str, Any]) -> Optional[StateStoreConfig]:
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: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
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, Dict, List, Optional, Tuple, Union
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: Dict[str, Any], case_style: str) -> Dict[str, Any]:
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_: List[Any], key_translator: Callable[[str], str]) -> List[Any]:
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: List[Any] = [None] * len(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_: Dict[str, Any], key_translator: Callable[[str], str]) -> Dict[str, Any]:
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: Dict[str, Any] = {}
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, Dict, List, Optional, Tuple, Union
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: Optional[str]
62
- authority_url: Optional[str] = None
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: List[str]
73
- secret: Optional[str] = None
74
- tenant: Optional[str] = None
75
- token_url: Optional[str] = None
76
- resource: Optional[str] = None
77
- audience: Optional[str] = None
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: Optional[CertificateConfig] = None
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: List[int] = field(default_factory=lambda: [429, 502, 503, 504])
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: Dict[str, str] = field(default_factory=dict)
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: Optional[int]
107
- external_id: Optional[str]
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) -> Tuple[int, 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) -> Tuple[int, 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: Optional[EitherIdConfig] = None
288
- data_set_id: Optional[int] = None
289
- data_set_external_id: Optional[str] = None
290
- extraction_pipeline: Optional[EitherIdConfig] = None
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: Optional[List[int]] = None
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: Optional[Dict[str, str]] = None,
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: Dict[str, Any] = {}
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) -> Optional[DataSet]:
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) -> Optional[ExtractionPipeline]:
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: Optional[_ConsoleLoggingConfig]
442
- file: Optional[_FileLoggingConfig]
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: Optional[bool] = False
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: Optional[str]
500
- password: Optional[str]
508
+ username: str | None
509
+ password: str | None
501
510
 
502
- clear_after: Optional[TimeIntervalConfig]
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: Optional[str]
515
- asset_external_id: Optional[str]
516
- data_set: Optional[EitherIdConfig] = None
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: Optional[List[_PushGatewayConfig]]
529
- cognite: Optional[_CogniteMetricsConfig]
530
- server: Optional[_PromServerConfig]
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: Optional[CancellationToken] = None) -> None:
533
- self._pushers: List[AbstractMetricsPusher] = []
534
- self._clear_on_stop: Dict[PrometheusPusher, int] = {}
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: Optional[str] = field(init=False, repr=False, default=None)
611
+ _file_hash: str | None = field(init=False, repr=False, default=None)
603
612
 
604
- type: Optional[ConfigType]
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: Optional[Union[str, int]]
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: Optional[RawStateStoreConfig] = None
654
- local: Optional[LocalStateStoreConfig] = None
662
+ raw: RawStateStoreConfig | None = None
663
+ local: LocalStateStoreConfig | None = None
655
664
 
656
665
  def create_state_store(
657
666
  self,
658
- cdf_client: Optional[CogniteClient] = None,
667
+ cdf_client: CogniteClient | None = None,
659
668
  default_to_local: bool = True,
660
- cancellation_token: Optional[CancellationToken] = None,
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: Optional[list[RegExpFlag]] = None
723
- flags: Optional[list[RegExpFlag]] = None
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, Dict, Generic, Iterable, List, Optional, TextIO, Type, TypeVar, Union, cast
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: Optional[dict]):
64
+ def __init__(self, config: dict | None):
65
65
  self.config = config
66
66
 
67
- self.credentials: Optional[TokenCredential] = None
68
- self.client: Optional[SecretClient] = None
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: Union[TextIO, str],
151
+ source: TextIO | str,
152
152
  expand_envvars: bool = True,
153
- keyvault_loader: Optional[KeyVaultLoader] = None,
154
- ) -> Dict[str, Any]:
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: Union[TextIO, str],
193
+ source: TextIO | str,
191
194
  case_style: str = "hyphen",
192
195
  expand_envvars: bool = True,
193
- dict_manipulator: Callable[[Dict[str, Any]], Dict[str, Any]] = lambda x: x,
194
- keyvault_loader: Optional[KeyVaultLoader] = None,
195
- ) -> Dict[str, Any]:
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: Union[TextIO, str],
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[[Dict[str, Any]], Dict[str, Any]] = lambda x: x,
215
- keyvault_loader: Optional[KeyVaultLoader] = None,
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: Union[TextIO, str],
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: Optional[KeyVaultLoader] = None,
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: Union[TextIO, str],
303
+ source: TextIO | str,
301
304
  case_style: str = "hyphen",
302
305
  expand_envvars: bool = True,
303
- keyvault_loader: Optional[KeyVaultLoader] = None,
304
- ) -> Dict[str, Any]:
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: List[Union[str, IgnorePattern]]) -> list[re.Pattern[str]]:
329
+ def compile_patterns(ignore_patterns: list[str | IgnorePattern]) -> list[re.Pattern[str]]:
327
330
  """
328
- List of patterns to compile
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: Optional[CustomConfigClass] = None
351
- self._next_config: Optional[CustomConfigClass] = None
353
+ self._config: CustomConfigClass | None = None
354
+ self._next_config: CustomConfigClass | None = None
352
355
 
353
- self._cognite_client: Optional[CogniteClient] = None
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) -> Optional[CogniteClient]:
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: Dict[str, Any]) -> Dict[str, Any]:
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[Union[str, re.Pattern[str]]], string: str) -> bool:
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: Union[str, re.Pattern[str]], string: str) -> bool:
21
+ def matches_pattern(pattern: str | re.Pattern[str], string: str) -> bool:
23
22
  """
24
23
  Match pattern against a string.
25
24