Skip to content

Commit fb53938

Browse files
Update connection config schema (#444)
* Update connection config schema * feat: cast credentials * fix: use external id * feat: update method * test: test casting * chore: change variable to match * test: comprehensive test of full config-schema * Fix type issue in `KeyValutLoader` (#447) `SecretClient` does not accept `None`s as the `credential`. This is a type issue, that `mypy` only _sometimes_ catches. There is no reason the `credentials` should be a class variable, it's only ever used inside the `_init_client` method, so it's fine to move it to a local variable. * feat: scopes config list * feat: expose connection parameters * fix: update cognite sdk version --------- Co-authored-by: cognite-bulldozer[bot] <51074376+cognite-bulldozer[bot]@users.noreply.github.com>
1 parent a500f54 commit fb53938

File tree

9 files changed

+253
-53
lines changed

9 files changed

+253
-53
lines changed

cognite/extractorutils/unstable/configuration/models.py

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import re
7+
from collections.abc import Iterator
78
from datetime import timedelta
89
from enum import Enum
910
from pathlib import Path
@@ -53,23 +54,44 @@ class ConfigModel(BaseModel):
5354
)
5455

5556

56-
class _ClientCredentialsConfig(ConfigModel):
57-
type: Literal["client-credentials"]
57+
class Scopes(str):
58+
def __init__(self, scopes: str) -> None:
59+
self._scopes = list(scopes.split(" "))
60+
61+
@classmethod
62+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
63+
return core_schema.no_info_after_validator_function(cls, handler(str))
64+
65+
def __eq__(self, other: object) -> bool:
66+
if not isinstance(other, Scopes):
67+
return NotImplemented
68+
return self._scopes == other._scopes
69+
70+
def __hash__(self) -> int:
71+
return hash(self._scopes)
72+
73+
def __iter__(self) -> Iterator[str]:
74+
return iter(self._scopes)
75+
76+
77+
class BaseCredentialsConfig(ConfigModel):
5878
client_id: str
79+
scopes: Scopes
80+
81+
82+
class _ClientCredentialsConfig(BaseCredentialsConfig):
83+
type: Literal["client-credentials"]
5984
client_secret: str
6085
token_url: str
61-
scopes: list[str]
6286
resource: str | None = None
6387
audience: str | None = None
6488

6589

66-
class _ClientCertificateConfig(ConfigModel):
90+
class _ClientCertificateConfig(BaseCredentialsConfig):
6791
type: Literal["client-certificate"]
68-
client_id: str
6992
path: Path
7093
password: str | None = None
7194
authority_url: str
72-
scopes: list[str]
7395

7496

7597
AuthenticationConfig = Annotated[_ClientCredentialsConfig | _ClientCertificateConfig, Field(discriminator="type")]
@@ -191,18 +213,26 @@ def __repr__(self) -> str:
191213
return self._expression
192214

193215

194-
class _ConnectionParameters(ConfigModel):
195-
gzip_compression: bool = False
196-
status_forcelist: list[int] = Field(default_factory=lambda: [429, 502, 503, 504])
197-
max_retries: int = 10
198-
max_retries_connect: int = 3
199-
max_retry_backoff: TimeIntervalConfig = Field(default_factory=lambda: TimeIntervalConfig("30s"))
200-
max_connection_pool_size: int = 50
201-
ssl_verify: bool = True
202-
proxies: dict[str, str] = Field(default_factory=dict)
216+
class RetriesConfig(ConfigModel):
217+
max_retries: int = Field(default=10, ge=-1)
218+
max_backoff: TimeIntervalConfig = Field(default_factory=lambda: TimeIntervalConfig("30s"))
203219
timeout: TimeIntervalConfig = Field(default_factory=lambda: TimeIntervalConfig("30s"))
204220

205221

222+
class SslCertificatesConfig(ConfigModel):
223+
verify: bool = True
224+
allow_list: list[str] | None = None
225+
226+
227+
class ConnectionParameters(ConfigModel):
228+
retries: RetriesConfig = Field(default_factory=RetriesConfig)
229+
ssl_certificates: SslCertificatesConfig = Field(default_factory=SslCertificatesConfig)
230+
231+
232+
class IntegrationConfig(ConfigModel):
233+
external_id: str
234+
235+
206236
class ConnectionConfig(ConfigModel):
207237
"""
208238
Configuration for connecting to a Cognite Data Fusion project.
@@ -216,11 +246,11 @@ class ConnectionConfig(ConfigModel):
216246
project: str
217247
base_url: str
218248

219-
integration: str
249+
integration: IntegrationConfig
220250

221251
authentication: AuthenticationConfig
222252

223-
connection: _ConnectionParameters = Field(default_factory=_ConnectionParameters)
253+
connection: ConnectionParameters = Field(default_factory=ConnectionParameters)
224254

225255
def get_cognite_client(self, client_name: str) -> CogniteClient:
226256
"""
@@ -235,14 +265,9 @@ def get_cognite_client(self, client_name: str) -> CogniteClient:
235265
from cognite.client.config import global_config
236266

237267
global_config.disable_pypi_version_check = True
238-
global_config.disable_gzip = not self.connection.gzip_compression
239-
global_config.status_forcelist = set(self.connection.status_forcelist)
240-
global_config.max_retries = self.connection.max_retries
241-
global_config.max_retries_connect = self.connection.max_retries_connect
242-
global_config.max_retry_backoff = self.connection.max_retry_backoff.seconds
243-
global_config.max_connection_pool_size = self.connection.max_connection_pool_size
244-
global_config.disable_ssl = not self.connection.ssl_verify
245-
global_config.proxies = self.connection.proxies
268+
global_config.max_retries = self.connection.retries.max_retries
269+
global_config.max_retry_backoff = self.connection.retries.max_backoff.seconds
270+
global_config.disable_ssl = not self.connection.ssl_certificates.verify
246271

247272
credential_provider: CredentialProvider
248273
match self.authentication:
@@ -270,7 +295,7 @@ def get_cognite_client(self, client_name: str) -> CogniteClient:
270295
client_id=client_certificate.client_id,
271296
cert_thumbprint=str(thumbprint),
272297
certificate=str(key),
273-
scopes=client_certificate.scopes,
298+
scopes=list(client_certificate.scopes),
274299
)
275300

276301
case _:
@@ -280,7 +305,7 @@ def get_cognite_client(self, client_name: str) -> CogniteClient:
280305
project=self.project,
281306
base_url=self.base_url,
282307
client_name=client_name,
283-
timeout=self.connection.timeout.seconds,
308+
timeout=self.connection.retries.timeout.seconds,
284309
credentials=credential_provider,
285310
)
286311

@@ -315,7 +340,9 @@ def from_environment(cls) -> "ConnectionConfig":
315340
client_id=os.environ["COGNITE_CLIENT_ID"],
316341
client_secret=os.environ["COGNITE_CLIENT_SECRET"],
317342
token_url=os.environ["COGNITE_TOKEN_URL"],
318-
scopes=os.environ["COGNITE_TOKEN_SCOPES"].split(","),
343+
scopes=Scopes(
344+
os.environ["COGNITE_TOKEN_SCOPES"],
345+
),
319346
)
320347
elif "COGNITE_CLIENT_CERTIFICATE_PATH" in os.environ:
321348
auth = _ClientCertificateConfig(
@@ -324,15 +351,17 @@ def from_environment(cls) -> "ConnectionConfig":
324351
path=Path(os.environ["COGNITE_CLIENT_CERTIFICATE_PATH"]),
325352
password=os.environ.get("COGNITE_CLIENT_CERTIFICATE_PATH"),
326353
authority_url=os.environ["COGNITE_AUTHORITY_URL"],
327-
scopes=os.environ["COGNITE_TOKEN_SCOPES"].split(","),
354+
scopes=Scopes(
355+
os.environ["COGNITE_TOKEN_SCOPES"],
356+
),
328357
)
329358
else:
330359
raise KeyError("Missing auth, either COGNITE_CLIENT_SECRET or COGNITE_CLIENT_CERTIFICATE_PATH must be set")
331360

332361
return ConnectionConfig(
333362
project=os.environ["COGNITE_PROJECT"],
334363
base_url=os.environ["COGNITE_BASE_URL"],
335-
integration=os.environ["COGNITE_INTEGRATION"],
364+
integration=IntegrationConfig(external_id=os.environ["COGNITE_INTEGRATION"]),
336365
authentication=auth,
337366
)
338367

cognite/extractorutils/unstable/core/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def _checkin(self) -> None:
224224
res = self.cognite_client.post(
225225
f"/api/v1/projects/{self.cognite_client.config.project}/integrations/checkin",
226226
json={
227-
"externalId": self.connection_config.integration,
227+
"externalId": self.connection_config.integration.external_id,
228228
"taskEvents": task_updates,
229229
"errors": error_updates,
230230
},
@@ -345,7 +345,7 @@ def _report_extractor_info(self) -> None:
345345
self.cognite_client.post(
346346
f"/api/v1/projects/{self.cognite_client.config.project}/integrations/extractorinfo",
347347
json={
348-
"externalId": self.connection_config.integration,
348+
"externalId": self.connection_config.integration.external_id,
349349
"activeConfigRevision": self.current_config_revision,
350350
"extractor": {
351351
"version": self.VERSION,

cognite/extractorutils/unstable/core/runtime.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ def main() -> None:
4949
load_file,
5050
load_from_cdf,
5151
)
52-
from cognite.extractorutils.unstable.configuration.models import ConnectionConfig
52+
from cognite.extractorutils.unstable.configuration.models import ConnectionConfig, ExtractorConfig
5353
from cognite.extractorutils.unstable.core._dto import Error
5454
from cognite.extractorutils.unstable.core.errors import ErrorLevel
5555
from cognite.extractorutils.util import now
5656

5757
from ._messaging import RuntimeMessage
58-
from .base import ConfigRevision, ConfigType, Extractor, FullConfig
58+
from .base import ConfigRevision, Extractor, FullConfig
5959

6060
__all__ = ["ExtractorType", "Runtime"]
6161

@@ -173,7 +173,7 @@ def _try_get_application_config(
173173
self,
174174
args: Namespace,
175175
connection_config: ConnectionConfig,
176-
) -> tuple[ConfigType, ConfigRevision]:
176+
) -> tuple[ExtractorConfig, ConfigRevision]:
177177
current_config_revision: ConfigRevision
178178

179179
if args.local_override:
@@ -194,7 +194,7 @@ def _try_get_application_config(
194194

195195
application_config, current_config_revision = load_from_cdf(
196196
self._cognite_client,
197-
connection_config.integration,
197+
connection_config.integration.external_id,
198198
self._extractor_class.CONFIG_TYPE,
199199
)
200200

@@ -204,7 +204,7 @@ def _safe_get_application_config(
204204
self,
205205
args: Namespace,
206206
connection_config: ConnectionConfig,
207-
) -> tuple[ConfigType, ConfigRevision] | None:
207+
) -> tuple[ExtractorConfig, ConfigRevision] | None:
208208
prev_error: str | None = None
209209

210210
while not self._cancellation_token.is_cancelled:
@@ -233,7 +233,7 @@ def _safe_get_application_config(
233233
self._cognite_client.post(
234234
f"/api/v1/projects/{self._cognite_client.config.project}/odin/checkin",
235235
json={
236-
"externalId": connection_config.integration,
236+
"externalId": connection_config.integration.external_id,
237237
"errors": [error.model_dump()],
238238
},
239239
headers={"cdf-version": "alpha"},

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ classifiers = [
1515
]
1616

1717
dependencies = [
18-
"cognite-sdk>=7.59.0",
18+
"cognite-sdk>=7.75.2",
1919
"prometheus-client>=0.7.0,<=1.0.0",
2020
"arrow>=1.0.0",
2121
"pyyaml>=5.3.0,<7",

tests/test_unstable/conftest.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from cognite.extractorutils.unstable.configuration.models import (
1313
ConnectionConfig,
1414
ExtractorConfig,
15+
IntegrationConfig,
16+
Scopes,
1517
_ClientCredentialsConfig,
1618
)
1719
from cognite.extractorutils.unstable.core.base import Extractor
@@ -75,12 +77,14 @@ def connection_config(extraction_pipeline: str) -> ConnectionConfig:
7577
return ConnectionConfig(
7678
project=os.environ["COGNITE_DEV_PROJECT"],
7779
base_url=os.environ["COGNITE_DEV_BASE_URL"],
78-
integration=extraction_pipeline,
80+
integration=IntegrationConfig(external_id=extraction_pipeline),
7981
authentication=_ClientCredentialsConfig(
8082
type="client-credentials",
8183
client_id=os.environ.get("COGNITE_DEV_CLIENT_ID", os.environ["COGNITE_CLIENT_ID"]),
8284
client_secret=os.environ.get("COGNITE_DEV_CLIENT_SECRET", os.environ["COGNITE_CLIENT_SECRET"]),
83-
scopes=os.environ["COGNITE_DEV_TOKEN_SCOPES"].split(","),
85+
scopes=Scopes(
86+
os.environ["COGNITE_DEV_TOKEN_SCOPES"],
87+
),
8488
token_url=os.environ.get("COGNITE_DEV_TOKEN_URL", os.environ["COGNITE_TOKEN_URL"]),
8589
),
8690
)

tests/test_unstable/test_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_simple_task_report(
7979

8080
# Test that the task run is entered into the history for that task
8181
res = extractor.cognite_client.get(
82-
f"/api/v1/projects/{extractor.cognite_client.config.project}/integrations/history?integration={connection_config.integration}&taskName=TestTask",
82+
f"/api/v1/projects/{extractor.cognite_client.config.project}/integrations/history?integration={connection_config.integration.external_id}&taskName=TestTask",
8383
headers={"cdf-version": "alpha"},
8484
).json()
8585

0 commit comments

Comments
 (0)