cognite-neat 0.86.0__py3-none-any.whl → 0.87.3__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-neat might be problematic. Click here for more details.
- cognite/neat/_version.py +1 -1
- cognite/neat/app/api/configuration.py +1 -10
- cognite/neat/app/api/routers/data_exploration.py +1 -1
- cognite/neat/config.py +84 -17
- cognite/neat/constants.py +11 -9
- cognite/neat/graph/extractors/_classic_cdf/_assets.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_events.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_files.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_labels.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_relationships.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_sequences.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +1 -1
- cognite/neat/graph/extractors/_dexpi.py +1 -1
- cognite/neat/graph/extractors/_mock_graph_generator.py +8 -9
- cognite/neat/graph/loaders/__init__.py +5 -2
- cognite/neat/graph/loaders/_base.py +13 -5
- cognite/neat/graph/loaders/_rdf2asset.py +185 -55
- cognite/neat/graph/loaders/_rdf2dms.py +7 -7
- cognite/neat/graph/queries/_base.py +20 -11
- cognite/neat/graph/queries/_construct.py +5 -5
- cognite/neat/graph/queries/_shared.py +21 -7
- cognite/neat/graph/stores/_base.py +16 -4
- cognite/neat/graph/transformers/__init__.py +3 -0
- cognite/neat/graph/transformers/_rdfpath.py +42 -0
- cognite/neat/legacy/graph/extractors/_dexpi.py +0 -5
- cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/legacy/graph/loaders/_asset_loader.py +2 -2
- cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +5 -2
- cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +4 -1
- cognite/neat/legacy/graph/loaders/rdf_to_dms.py +3 -1
- cognite/neat/legacy/graph/stores/_base.py +24 -8
- cognite/neat/legacy/graph/stores/_graphdb_store.py +3 -2
- cognite/neat/legacy/graph/stores/_memory_store.py +3 -3
- cognite/neat/legacy/graph/stores/_oxigraph_store.py +8 -4
- cognite/neat/legacy/graph/stores/_rdf_to_graph.py +5 -3
- cognite/neat/legacy/graph/transformations/query_generator/sparql.py +49 -16
- cognite/neat/legacy/graph/transformations/transformer.py +1 -1
- cognite/neat/legacy/rules/exporters/_rules2dms.py +8 -3
- cognite/neat/legacy/rules/exporters/_rules2graphql.py +1 -1
- cognite/neat/legacy/rules/exporters/_rules2ontology.py +2 -1
- cognite/neat/legacy/rules/exporters/_rules2pydantic_models.py +3 -4
- cognite/neat/legacy/rules/importers/_dms2rules.py +4 -1
- cognite/neat/legacy/rules/importers/_graph2rules.py +3 -3
- cognite/neat/legacy/rules/importers/_owl2rules/_owl2classes.py +1 -1
- cognite/neat/legacy/rules/importers/_owl2rules/_owl2metadata.py +2 -1
- cognite/neat/legacy/rules/importers/_owl2rules/_owl2properties.py +1 -1
- cognite/neat/legacy/rules/models/raw_rules.py +19 -7
- cognite/neat/legacy/rules/models/rules.py +32 -12
- cognite/neat/rules/_shared.py +6 -1
- cognite/neat/rules/analysis/__init__.py +4 -4
- cognite/neat/rules/analysis/_asset.py +143 -0
- cognite/neat/rules/analysis/_base.py +385 -6
- cognite/neat/rules/analysis/_information.py +183 -0
- cognite/neat/rules/exporters/_rules2dms.py +1 -1
- cognite/neat/rules/exporters/_rules2ontology.py +6 -5
- cognite/neat/rules/importers/_dms2rules.py +3 -1
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +2 -8
- cognite/neat/rules/importers/_inference2rules.py +3 -7
- cognite/neat/rules/importers/_owl2rules/_owl2classes.py +1 -1
- cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +2 -1
- cognite/neat/rules/importers/_owl2rules/_owl2properties.py +1 -1
- cognite/neat/rules/issues/spreadsheet.py +35 -0
- cognite/neat/rules/models/_base.py +7 -7
- cognite/neat/rules/models/_rdfpath.py +17 -21
- cognite/neat/rules/models/asset/_rules.py +4 -5
- cognite/neat/rules/models/asset/_validation.py +38 -1
- cognite/neat/rules/models/dms/_converter.py +1 -2
- cognite/neat/rules/models/dms/_exporter.py +7 -3
- cognite/neat/rules/models/dms/_rules.py +3 -0
- cognite/neat/rules/models/dms/_schema.py +5 -4
- cognite/neat/rules/models/domain.py +5 -2
- cognite/neat/rules/models/entities.py +28 -17
- cognite/neat/rules/models/information/_rules.py +10 -8
- cognite/neat/rules/models/information/_rules_input.py +1 -2
- cognite/neat/rules/models/information/_validation.py +2 -2
- cognite/neat/utils/__init__.py +0 -3
- cognite/neat/utils/auth.py +47 -28
- cognite/neat/utils/auxiliary.py +141 -1
- cognite/neat/utils/cdf/__init__.py +0 -0
- cognite/neat/utils/{cdf_classes.py → cdf/data_classes.py} +122 -2
- cognite/neat/utils/{cdf_loaders → cdf/loaders}/_data_modeling.py +37 -0
- cognite/neat/utils/{cdf_loaders → cdf/loaders}/_ingestion.py +2 -1
- cognite/neat/utils/collection_.py +18 -0
- cognite/neat/utils/rdf_.py +165 -0
- cognite/neat/utils/text.py +4 -0
- cognite/neat/utils/time_.py +17 -0
- cognite/neat/utils/upload.py +13 -1
- cognite/neat/workflows/_exceptions.py +5 -5
- cognite/neat/workflows/base.py +1 -1
- cognite/neat/workflows/steps/lib/current/graph_store.py +28 -8
- cognite/neat/workflows/steps/lib/current/rules_validator.py +2 -2
- cognite/neat/workflows/steps/lib/legacy/graph_extractor.py +130 -28
- cognite/neat/workflows/steps/lib/legacy/graph_loader.py +1 -1
- cognite/neat/workflows/steps/lib/legacy/graph_store.py +4 -4
- cognite/neat/workflows/steps/lib/legacy/rules_exporter.py +1 -1
- cognite/neat/workflows/steps/lib/legacy/rules_importer.py +1 -1
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/METADATA +2 -2
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/RECORD +103 -102
- cognite/neat/rules/analysis/_information_rules.py +0 -476
- cognite/neat/utils/cdf.py +0 -59
- cognite/neat/utils/cdf_loaders/data_classes.py +0 -121
- cognite/neat/utils/exceptions.py +0 -41
- cognite/neat/utils/utils.py +0 -429
- /cognite/neat/utils/{cdf_loaders → cdf/loaders}/__init__.py +0 -0
- /cognite/neat/utils/{cdf_loaders → cdf/loaders}/_base.py +0 -0
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/LICENSE +0 -0
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/WHEEL +0 -0
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/entry_points.txt +0 -0
cognite/neat/utils/exceptions.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from pydantic_core import ErrorDetails, PydanticCustomError
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class NeatError(Exception):
|
|
5
|
-
type_: str
|
|
6
|
-
code: int
|
|
7
|
-
description: str
|
|
8
|
-
example: str
|
|
9
|
-
fix: str
|
|
10
|
-
message: str
|
|
11
|
-
|
|
12
|
-
def to_pydantic_custom_error(self):
|
|
13
|
-
return PydanticCustomError(
|
|
14
|
-
self.type_,
|
|
15
|
-
self.message,
|
|
16
|
-
dict(type_=self.type_, code=self.code, description=self.description, example=self.example, fix=self.fix),
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
def to_error_dict(self) -> ErrorDetails:
|
|
20
|
-
return {
|
|
21
|
-
"type": self.type_,
|
|
22
|
-
"loc": (),
|
|
23
|
-
"msg": self.message,
|
|
24
|
-
"input": None,
|
|
25
|
-
"ctx": dict(
|
|
26
|
-
type_=self.type_,
|
|
27
|
-
code=self.code,
|
|
28
|
-
description=self.description,
|
|
29
|
-
example=self.example,
|
|
30
|
-
fix=self.fix,
|
|
31
|
-
),
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class NeatWarning(UserWarning):
|
|
36
|
-
type_: str
|
|
37
|
-
code: int
|
|
38
|
-
description: str
|
|
39
|
-
example: str
|
|
40
|
-
fix: str
|
|
41
|
-
message: str
|
cognite/neat/utils/utils.py
DELETED
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
import hashlib
|
|
2
|
-
import logging
|
|
3
|
-
import re
|
|
4
|
-
import sys
|
|
5
|
-
import time
|
|
6
|
-
from collections import Counter, OrderedDict
|
|
7
|
-
from collections.abc import Iterable
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
from functools import wraps
|
|
10
|
-
from typing import Literal, TypeAlias, cast, overload
|
|
11
|
-
|
|
12
|
-
import pandas as pd
|
|
13
|
-
from cognite.client import ClientConfig, CogniteClient
|
|
14
|
-
from cognite.client.credentials import (
|
|
15
|
-
CredentialProvider,
|
|
16
|
-
OAuthClientCredentials,
|
|
17
|
-
OAuthInteractive,
|
|
18
|
-
Token,
|
|
19
|
-
)
|
|
20
|
-
from cognite.client.exceptions import CogniteDuplicatedError, CogniteReadTimeout
|
|
21
|
-
from pydantic import HttpUrl, TypeAdapter, ValidationError
|
|
22
|
-
from pydantic_core import ErrorDetails
|
|
23
|
-
from pyparsing import Any
|
|
24
|
-
from rdflib import Literal as RdfLiteral
|
|
25
|
-
from rdflib import Namespace
|
|
26
|
-
from rdflib.term import URIRef
|
|
27
|
-
|
|
28
|
-
from cognite.neat import _version
|
|
29
|
-
from cognite.neat.utils.cdf import (
|
|
30
|
-
CogniteClientConfig,
|
|
31
|
-
InteractiveCogniteClient,
|
|
32
|
-
ServiceCogniteClient,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
if sys.version_info >= (3, 11):
|
|
36
|
-
from datetime import UTC
|
|
37
|
-
else:
|
|
38
|
-
from datetime import timezone
|
|
39
|
-
|
|
40
|
-
UTC = timezone.utc
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Triple: TypeAlias = tuple[URIRef, URIRef, RdfLiteral | URIRef]
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def get_cognite_client_from_config(config: ServiceCogniteClient) -> CogniteClient:
|
|
47
|
-
credentials = OAuthClientCredentials(
|
|
48
|
-
token_url=config.token_url,
|
|
49
|
-
client_id=config.client_id,
|
|
50
|
-
client_secret=config.client_secret,
|
|
51
|
-
scopes=config.scopes,
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
return _get_cognite_client(config, credentials)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def get_cognite_client_from_token(config: ServiceCogniteClient) -> CogniteClient:
|
|
58
|
-
credentials = Token(config.client_secret)
|
|
59
|
-
return _get_cognite_client(config, credentials)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def get_cognite_client_interactive(config: InteractiveCogniteClient) -> CogniteClient:
|
|
63
|
-
credentials = OAuthInteractive(
|
|
64
|
-
authority_url=config.authority_url,
|
|
65
|
-
client_id=config.client_id,
|
|
66
|
-
scopes=config.scopes,
|
|
67
|
-
redirect_port=config.redirect_port,
|
|
68
|
-
)
|
|
69
|
-
return _get_cognite_client(config, credentials)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _get_cognite_client(config: CogniteClientConfig, credentials: CredentialProvider) -> CogniteClient:
|
|
73
|
-
logging.info(f"Creating CogniteClient with parameters : {config}")
|
|
74
|
-
|
|
75
|
-
# The client name is used for aggregated logging of Neat Usage
|
|
76
|
-
client_name = f"CogniteNeat:{_version.__version__}"
|
|
77
|
-
return CogniteClient(
|
|
78
|
-
ClientConfig(
|
|
79
|
-
client_name=client_name,
|
|
80
|
-
base_url=config.base_url,
|
|
81
|
-
project=config.project,
|
|
82
|
-
credentials=credentials,
|
|
83
|
-
timeout=config.timeout,
|
|
84
|
-
max_workers=config.max_workers,
|
|
85
|
-
debug=False,
|
|
86
|
-
)
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@overload
|
|
91
|
-
def remove_namespace_from_uri(
|
|
92
|
-
*URI: URIRef | str,
|
|
93
|
-
special_separator: str = "#_",
|
|
94
|
-
validation: Literal["full", "prefix"] = "prefix",
|
|
95
|
-
) -> str: ...
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@overload
|
|
99
|
-
def remove_namespace_from_uri(
|
|
100
|
-
*URI: tuple[URIRef | str, ...],
|
|
101
|
-
special_separator: str = "#_",
|
|
102
|
-
validation: Literal["full", "prefix"] = "prefix",
|
|
103
|
-
) -> tuple[str, ...]: ...
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def remove_namespace_from_uri(
|
|
107
|
-
*URI: URIRef | str | tuple[URIRef | str, ...],
|
|
108
|
-
special_separator: str = "#_",
|
|
109
|
-
validation: Literal["full", "prefix"] = "prefix",
|
|
110
|
-
) -> tuple[str, ...] | str:
|
|
111
|
-
"""Removes namespace from URI
|
|
112
|
-
|
|
113
|
-
Args
|
|
114
|
-
URI: URIRef | str
|
|
115
|
-
URI of an entity
|
|
116
|
-
special_separator : str
|
|
117
|
-
Special separator to use instead of # or / if present in URI
|
|
118
|
-
Set by default to "#_" which covers special client use case
|
|
119
|
-
validation: str
|
|
120
|
-
Validation type to use for URI. If set to "full", URI will be validated using pydantic
|
|
121
|
-
If set to "prefix", only check if URI starts with http or https will be made
|
|
122
|
-
|
|
123
|
-
Returns
|
|
124
|
-
Entities id without namespace
|
|
125
|
-
|
|
126
|
-
Examples:
|
|
127
|
-
|
|
128
|
-
>>> remove_namespace_from_uri("http://www.example.org/index.html#section2")
|
|
129
|
-
'section2'
|
|
130
|
-
>>> remove_namespace_from_uri("http://www.example.org/index.html#section2", "http://www.example.org/index.html#section3")
|
|
131
|
-
('section2', 'section3')
|
|
132
|
-
"""
|
|
133
|
-
if isinstance(URI, str | URIRef):
|
|
134
|
-
uris = (URI,)
|
|
135
|
-
elif isinstance(URI, tuple):
|
|
136
|
-
# Assume that all elements in the tuple are of the same type following type hint
|
|
137
|
-
uris = cast(tuple[URIRef | str, ...], URI)
|
|
138
|
-
else:
|
|
139
|
-
raise TypeError(f"URI must be of type URIRef or str, got {type(URI)}")
|
|
140
|
-
|
|
141
|
-
output = []
|
|
142
|
-
for u in uris:
|
|
143
|
-
if validation == "full":
|
|
144
|
-
try:
|
|
145
|
-
_ = TypeAdapter(HttpUrl).validate_python(u)
|
|
146
|
-
output.append(u.split(special_separator if special_separator in u else "#" if "#" in u else "/")[-1])
|
|
147
|
-
except ValidationError:
|
|
148
|
-
output.append(str(u))
|
|
149
|
-
else:
|
|
150
|
-
if u.lower().startswith("http"):
|
|
151
|
-
output.append(u.split(special_separator if special_separator in u else "#" if "#" in u else "/")[-1])
|
|
152
|
-
else:
|
|
153
|
-
output.append(str(u))
|
|
154
|
-
|
|
155
|
-
return tuple(output) if len(output) > 1 else output[0]
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def get_namespace(URI: URIRef, special_separator: str = "#_") -> str:
|
|
159
|
-
"""Removes namespace from URI
|
|
160
|
-
|
|
161
|
-
Parameters
|
|
162
|
-
----------
|
|
163
|
-
URI : URIRef
|
|
164
|
-
URI of an entity
|
|
165
|
-
special_separator : str
|
|
166
|
-
Special separator to use instead of # or / if present in URI
|
|
167
|
-
Set by default to "#_" which covers special client use case
|
|
168
|
-
|
|
169
|
-
Returns
|
|
170
|
-
-------
|
|
171
|
-
str
|
|
172
|
-
Entity id without namespace
|
|
173
|
-
"""
|
|
174
|
-
if special_separator in URI:
|
|
175
|
-
return URI.split(special_separator)[0] + special_separator
|
|
176
|
-
elif "#" in URI:
|
|
177
|
-
return URI.split("#")[0] + "#"
|
|
178
|
-
else:
|
|
179
|
-
return "/".join(URI.split("/")[:-1]) + "/"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def as_neat_compliant_uri(uri: URIRef) -> URIRef:
|
|
183
|
-
namespace = get_namespace(uri)
|
|
184
|
-
id_ = remove_namespace_from_uri(uri)
|
|
185
|
-
compliant_uri = re.sub(r"[^a-zA-Z0-9-_.]", "", id_)
|
|
186
|
-
return URIRef(f"{namespace}{compliant_uri}")
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def convert_rdflib_content(content: RdfLiteral | URIRef | dict | list) -> Any:
|
|
190
|
-
if isinstance(content, RdfLiteral) or isinstance(content, URIRef):
|
|
191
|
-
return content.toPython()
|
|
192
|
-
elif isinstance(content, dict):
|
|
193
|
-
return {key: convert_rdflib_content(value) for key, value in content.items()}
|
|
194
|
-
elif isinstance(content, list):
|
|
195
|
-
return [convert_rdflib_content(item) for item in content]
|
|
196
|
-
else:
|
|
197
|
-
return content
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def uri_to_short_form(URI: URIRef, prefixes: dict[str, Namespace]) -> str | URIRef:
|
|
201
|
-
"""Returns the short form of a URI if its namespace is present in the prefixes dict,
|
|
202
|
-
otherwise returns the URI itself
|
|
203
|
-
|
|
204
|
-
Args:
|
|
205
|
-
URI: URI to be converted to form prefix:entityName
|
|
206
|
-
prefixes: dict of prefixes
|
|
207
|
-
|
|
208
|
-
Returns:
|
|
209
|
-
shortest form of the URI if its namespace is present in the prefixes dict,
|
|
210
|
-
otherwise returns the URI itself
|
|
211
|
-
"""
|
|
212
|
-
uris: set[str | URIRef] = {URI}
|
|
213
|
-
for prefix, namespace in prefixes.items():
|
|
214
|
-
if URI.startswith(namespace):
|
|
215
|
-
uris.add(f"{prefix}:{URI.replace(namespace, '')}")
|
|
216
|
-
return min(uris, key=len)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def _traverse(hierarchy: dict, graph: dict, names: list[str]) -> dict:
|
|
220
|
-
"""traverse the graph and return the hierarchy"""
|
|
221
|
-
for name in names:
|
|
222
|
-
hierarchy[name] = _traverse({}, graph, graph[name])
|
|
223
|
-
return hierarchy
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def get_generation_order(
|
|
227
|
-
class_linkage: pd.DataFrame,
|
|
228
|
-
parent_col: str = "source_class",
|
|
229
|
-
child_col: str = "target_class",
|
|
230
|
-
) -> dict:
|
|
231
|
-
parent_child_list = class_linkage[[parent_col, child_col]].values.tolist()
|
|
232
|
-
# Build a directed graph and a list of all names that have no parent
|
|
233
|
-
graph: dict[str, set[str]] = {name: set() for tup in parent_child_list for name in tup}
|
|
234
|
-
has_parent = {name: False for tup in parent_child_list for name in tup}
|
|
235
|
-
for parent, child in parent_child_list:
|
|
236
|
-
graph[parent].add(child)
|
|
237
|
-
has_parent[child] = True
|
|
238
|
-
|
|
239
|
-
# All names that have absolutely no parent:
|
|
240
|
-
roots = [name for name, parents in has_parent.items() if not parents]
|
|
241
|
-
|
|
242
|
-
return _traverse({}, graph, roots)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def prettify_generation_order(generation_order: dict, depth: dict | None = None, start=-1) -> dict:
|
|
246
|
-
"""Prettifies generation order dictionary for easier consumption."""
|
|
247
|
-
depth = depth or {}
|
|
248
|
-
for key, value in generation_order.items():
|
|
249
|
-
depth[key] = start + 1
|
|
250
|
-
if isinstance(value, dict):
|
|
251
|
-
prettify_generation_order(value, depth, start=start + 1)
|
|
252
|
-
return OrderedDict(sorted(depth.items(), key=lambda item: item[1]))
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def epoch_now_ms() -> int:
|
|
256
|
-
return int((datetime.now(UTC) - datetime(1970, 1, 1, tzinfo=UTC)).total_seconds() * 1000)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def chunker(sequence, chunk_size):
|
|
260
|
-
return [sequence[i : i + chunk_size] for i in range(0, len(sequence), chunk_size)]
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def datetime_utc_now():
|
|
264
|
-
return datetime.now(UTC)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def retry_decorator(max_retries=2, retry_delay=3, component_name=""):
|
|
268
|
-
def decorator(func):
|
|
269
|
-
@wraps(func)
|
|
270
|
-
def wrapper(*args, **kwargs):
|
|
271
|
-
previous_exception = None
|
|
272
|
-
for attempt in range(max_retries + 1):
|
|
273
|
-
try:
|
|
274
|
-
logging.debug(f"Attempt {attempt + 1} of {max_retries + 1} for {component_name}")
|
|
275
|
-
return func(*args, **kwargs)
|
|
276
|
-
except CogniteReadTimeout as e:
|
|
277
|
-
previous_exception = e
|
|
278
|
-
if attempt < max_retries:
|
|
279
|
-
logging.error(
|
|
280
|
-
f"""CogniteReadTimeout retry attempt {attempt + 1} failed for {component_name} .
|
|
281
|
-
Retrying in {retry_delay} second(s). Error:"""
|
|
282
|
-
)
|
|
283
|
-
logging.error(e)
|
|
284
|
-
time.sleep(retry_delay)
|
|
285
|
-
else:
|
|
286
|
-
raise e
|
|
287
|
-
except CogniteDuplicatedError as e:
|
|
288
|
-
if isinstance(previous_exception, CogniteReadTimeout):
|
|
289
|
-
# if previous exception was CogniteReadTimeout,
|
|
290
|
-
# we can't be sure if the items were created or not
|
|
291
|
-
if len(e.successful) == 0 and len(e.failed) == 0 and len(e.duplicated) >= 0:
|
|
292
|
-
logging.warning(
|
|
293
|
-
f"Duplicate error for {component_name} . All items already exist in CDF. "
|
|
294
|
-
"Suppressing error."
|
|
295
|
-
)
|
|
296
|
-
return
|
|
297
|
-
else:
|
|
298
|
-
# can happend because of eventual consistency. Retry with delay to allow for CDF to catch up
|
|
299
|
-
if attempt < max_retries:
|
|
300
|
-
logging.error(
|
|
301
|
-
f"""CogniteDuplicatedError retry attempt {attempt + 1} failed for {component_name} .
|
|
302
|
-
Retrying in {retry_delay} second(s). Error:"""
|
|
303
|
-
)
|
|
304
|
-
logging.error(e)
|
|
305
|
-
# incerasing delay to allow for CDF to catch up
|
|
306
|
-
time.sleep(retry_delay)
|
|
307
|
-
else:
|
|
308
|
-
raise e
|
|
309
|
-
else:
|
|
310
|
-
# no point in retrying duplicate error if previous exception was not a timeout
|
|
311
|
-
raise e
|
|
312
|
-
|
|
313
|
-
except Exception as e:
|
|
314
|
-
previous_exception = e
|
|
315
|
-
if attempt < max_retries:
|
|
316
|
-
logging.error(
|
|
317
|
-
f"Retry attempt {attempt + 1} failed for {component_name}. "
|
|
318
|
-
f"Retrying in {retry_delay} second(s)."
|
|
319
|
-
)
|
|
320
|
-
logging.error(e)
|
|
321
|
-
time.sleep(retry_delay)
|
|
322
|
-
else:
|
|
323
|
-
raise e
|
|
324
|
-
|
|
325
|
-
return wrapper
|
|
326
|
-
|
|
327
|
-
return decorator
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
def create_sha256_hash(string: str) -> str:
|
|
331
|
-
# Create a SHA-256 hash object
|
|
332
|
-
sha256_hash = hashlib.sha256()
|
|
333
|
-
|
|
334
|
-
# Convert the string to bytes and update the hash object
|
|
335
|
-
sha256_hash.update(string.encode("utf-8"))
|
|
336
|
-
|
|
337
|
-
# Get the hexadecimal representation of the hash
|
|
338
|
-
hash_value = sha256_hash.hexdigest()
|
|
339
|
-
|
|
340
|
-
return hash_value
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
def generate_exception_report(exceptions: list[dict] | list[ErrorDetails] | None, category: str = "") -> str:
|
|
344
|
-
exceptions_as_dict = _order_expectations_by_type(exceptions) if exceptions else {}
|
|
345
|
-
report = ""
|
|
346
|
-
|
|
347
|
-
for exception_type in exceptions_as_dict.keys():
|
|
348
|
-
title = f"# {category}: {exception_type}" if category else ""
|
|
349
|
-
warnings = "\n- " + "\n- ".join(exceptions_as_dict[exception_type])
|
|
350
|
-
report += title + warnings + "\n\n"
|
|
351
|
-
|
|
352
|
-
return report
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def _order_expectations_by_type(
|
|
356
|
-
exceptions: list[dict] | list[ErrorDetails],
|
|
357
|
-
) -> dict[str, list[str]]:
|
|
358
|
-
exception_dict: dict[str, list[str]] = {}
|
|
359
|
-
for exception in exceptions:
|
|
360
|
-
if not isinstance(exception["loc"], str) and isinstance(exception["loc"], Iterable):
|
|
361
|
-
location = f"[{'/'.join(str(e) for e in exception['loc'])}]"
|
|
362
|
-
else:
|
|
363
|
-
location = ""
|
|
364
|
-
|
|
365
|
-
issue = f"{exception['msg']} {location}"
|
|
366
|
-
|
|
367
|
-
if exception_dict.get(exception["type"]) is None:
|
|
368
|
-
exception_dict[exception["type"]] = [issue]
|
|
369
|
-
else:
|
|
370
|
-
exception_dict[exception["type"]].append(issue)
|
|
371
|
-
return exception_dict
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
def remove_none_elements_from_set(s):
|
|
375
|
-
return {x for x in s if x is not None}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
def get_inheritance_path(child: Any, child_parent: dict[Any, list[Any]]) -> list:
|
|
379
|
-
"""Returns the inheritance path for a given child
|
|
380
|
-
|
|
381
|
-
Args:
|
|
382
|
-
child: Child class
|
|
383
|
-
child_parent: Dictionary of child to parent relationship
|
|
384
|
-
|
|
385
|
-
Returns:
|
|
386
|
-
Inheritance path for a given child
|
|
387
|
-
|
|
388
|
-
!!! note "No Circular Inheritance"
|
|
389
|
-
This method assumes that the child_parent dictionary is a tree and does not contain any cycles.
|
|
390
|
-
"""
|
|
391
|
-
path = []
|
|
392
|
-
if child in child_parent:
|
|
393
|
-
path.extend(child_parent[child])
|
|
394
|
-
for parent in child_parent[child]:
|
|
395
|
-
path.extend(get_inheritance_path(parent, child_parent))
|
|
396
|
-
return path
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
def replace_non_alphanumeric_with_underscore(text):
|
|
400
|
-
return re.sub(r"\W+", "_", text)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
def string_to_ideal_type(input_string: str) -> int | bool | float | datetime | str:
|
|
404
|
-
try:
|
|
405
|
-
# Try converting to int
|
|
406
|
-
return int(input_string)
|
|
407
|
-
except ValueError:
|
|
408
|
-
try:
|
|
409
|
-
# Try converting to float
|
|
410
|
-
return float(input_string) # type: ignore
|
|
411
|
-
except ValueError:
|
|
412
|
-
if input_string.lower() == "true":
|
|
413
|
-
# Return True if input is 'true'
|
|
414
|
-
return True
|
|
415
|
-
elif input_string.lower() == "false":
|
|
416
|
-
# Return False if input is 'false'
|
|
417
|
-
return False
|
|
418
|
-
else:
|
|
419
|
-
try:
|
|
420
|
-
# Try converting to datetime
|
|
421
|
-
return datetime.fromisoformat(input_string) # type: ignore
|
|
422
|
-
except ValueError:
|
|
423
|
-
# Return the input string if no conversion is possible
|
|
424
|
-
return input_string
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
def most_occurring_element(list_of_elements: list):
|
|
428
|
-
counts = Counter(list_of_elements)
|
|
429
|
-
return counts.most_common(1)[0][0]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|