cognite-neat 0.87.0__py3-none-any.whl → 0.87.4__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.

Files changed (87) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/app/api/configuration.py +1 -10
  3. cognite/neat/app/api/routers/data_exploration.py +1 -1
  4. cognite/neat/config.py +84 -17
  5. cognite/neat/graph/extractors/_classic_cdf/_assets.py +1 -1
  6. cognite/neat/graph/extractors/_classic_cdf/_events.py +1 -1
  7. cognite/neat/graph/extractors/_classic_cdf/_files.py +1 -1
  8. cognite/neat/graph/extractors/_classic_cdf/_labels.py +1 -1
  9. cognite/neat/graph/extractors/_classic_cdf/_relationships.py +1 -1
  10. cognite/neat/graph/extractors/_classic_cdf/_sequences.py +1 -1
  11. cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +1 -1
  12. cognite/neat/graph/extractors/_dexpi.py +1 -1
  13. cognite/neat/graph/extractors/_mock_graph_generator.py +1 -1
  14. cognite/neat/graph/loaders/_rdf2asset.py +108 -52
  15. cognite/neat/graph/loaders/_rdf2dms.py +6 -6
  16. cognite/neat/graph/queries/_base.py +20 -11
  17. cognite/neat/graph/queries/_construct.py +3 -3
  18. cognite/neat/graph/queries/_shared.py +1 -1
  19. cognite/neat/graph/stores/_base.py +14 -3
  20. cognite/neat/graph/transformers/__init__.py +3 -0
  21. cognite/neat/graph/transformers/_rdfpath.py +42 -0
  22. cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +1 -1
  23. cognite/neat/legacy/graph/loaders/_asset_loader.py +2 -2
  24. cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +5 -2
  25. cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +4 -1
  26. cognite/neat/legacy/graph/loaders/rdf_to_dms.py +3 -1
  27. cognite/neat/legacy/graph/transformations/query_generator/sparql.py +1 -1
  28. cognite/neat/legacy/graph/transformations/transformer.py +1 -1
  29. cognite/neat/legacy/rules/exporters/_rules2dms.py +8 -3
  30. cognite/neat/legacy/rules/exporters/_rules2graphql.py +1 -1
  31. cognite/neat/legacy/rules/exporters/_rules2ontology.py +2 -1
  32. cognite/neat/legacy/rules/exporters/_rules2pydantic_models.py +3 -4
  33. cognite/neat/legacy/rules/importers/_dms2rules.py +4 -1
  34. cognite/neat/legacy/rules/importers/_graph2rules.py +5 -32
  35. cognite/neat/legacy/rules/importers/_owl2rules/_owl2classes.py +1 -1
  36. cognite/neat/legacy/rules/importers/_owl2rules/_owl2metadata.py +2 -1
  37. cognite/neat/legacy/rules/importers/_owl2rules/_owl2properties.py +1 -1
  38. cognite/neat/legacy/rules/models/raw_rules.py +1 -1
  39. cognite/neat/rules/analysis/_asset.py +15 -0
  40. cognite/neat/rules/analysis/_base.py +1 -1
  41. cognite/neat/rules/analysis/_information.py +40 -12
  42. cognite/neat/rules/exporters/_rules2dms.py +1 -1
  43. cognite/neat/rules/exporters/_rules2ontology.py +2 -1
  44. cognite/neat/rules/importers/_dms2rules.py +10 -1
  45. cognite/neat/rules/importers/_inference2rules.py +1 -5
  46. cognite/neat/rules/importers/_owl2rules/_owl2classes.py +1 -1
  47. cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +2 -1
  48. cognite/neat/rules/importers/_owl2rules/_owl2properties.py +1 -1
  49. cognite/neat/rules/issues/spreadsheet.py +35 -0
  50. cognite/neat/rules/models/_rdfpath.py +17 -21
  51. cognite/neat/rules/models/asset/_validation.py +38 -1
  52. cognite/neat/rules/models/dms/_exporter.py +9 -3
  53. cognite/neat/rules/models/dms/_rules.py +1 -0
  54. cognite/neat/rules/models/dms/_rules_input.py +3 -0
  55. cognite/neat/rules/models/dms/_schema.py +5 -4
  56. cognite/neat/rules/models/entities.py +26 -8
  57. cognite/neat/rules/models/information/_validation.py +1 -1
  58. cognite/neat/utils/__init__.py +0 -3
  59. cognite/neat/utils/auth.py +47 -28
  60. cognite/neat/utils/auxiliary.py +141 -1
  61. cognite/neat/utils/cdf/__init__.py +0 -0
  62. cognite/neat/utils/{cdf_classes.py → cdf/data_classes.py} +122 -2
  63. cognite/neat/utils/{cdf_loaders → cdf/loaders}/_data_modeling.py +37 -0
  64. cognite/neat/utils/{cdf_loaders → cdf/loaders}/_ingestion.py +2 -1
  65. cognite/neat/utils/collection_.py +18 -0
  66. cognite/neat/utils/rdf_.py +165 -0
  67. cognite/neat/utils/text.py +4 -0
  68. cognite/neat/utils/time_.py +17 -0
  69. cognite/neat/utils/upload.py +13 -1
  70. cognite/neat/workflows/_exceptions.py +5 -5
  71. cognite/neat/workflows/base.py +1 -1
  72. cognite/neat/workflows/steps/lib/current/rules_validator.py +2 -2
  73. cognite/neat/workflows/steps/lib/legacy/graph_extractor.py +1 -1
  74. cognite/neat/workflows/steps/lib/legacy/graph_loader.py +1 -1
  75. cognite/neat/workflows/steps/lib/legacy/rules_exporter.py +1 -1
  76. cognite/neat/workflows/steps/lib/legacy/rules_importer.py +1 -1
  77. {cognite_neat-0.87.0.dist-info → cognite_neat-0.87.4.dist-info}/METADATA +2 -2
  78. {cognite_neat-0.87.0.dist-info → cognite_neat-0.87.4.dist-info}/RECORD +83 -83
  79. cognite/neat/utils/cdf.py +0 -59
  80. cognite/neat/utils/cdf_loaders/data_classes.py +0 -121
  81. cognite/neat/utils/exceptions.py +0 -41
  82. cognite/neat/utils/utils.py +0 -429
  83. /cognite/neat/utils/{cdf_loaders → cdf/loaders}/__init__.py +0 -0
  84. /cognite/neat/utils/{cdf_loaders → cdf/loaders}/_base.py +0 -0
  85. {cognite_neat-0.87.0.dist-info → cognite_neat-0.87.4.dist-info}/LICENSE +0 -0
  86. {cognite_neat-0.87.0.dist-info → cognite_neat-0.87.4.dist-info}/WHEEL +0 -0
  87. {cognite_neat-0.87.0.dist-info → cognite_neat-0.87.4.dist-info}/entry_points.txt +0 -0
@@ -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]