cognite-neat 0.123.6__py3-none-any.whl → 0.123.8__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.
cognite/neat/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.123.6"
1
+ __version__ = "0.123.8"
2
2
  __engine__ = "^2.0.4"
@@ -1,8 +1,10 @@
1
+ import re
1
2
  import warnings
2
3
  from abc import ABC, abstractmethod
4
+ from collections import defaultdict
3
5
  from collections.abc import Callable, Collection, Iterable, Sequence
4
6
  from dataclasses import dataclass, field
5
- from graphlib import TopologicalSorter
7
+ from graphlib import CycleError, TopologicalSorter
6
8
  from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, TypeVar, cast, overload
7
9
 
8
10
  from cognite.client.data_classes import filters
@@ -56,6 +58,7 @@ from cognite.neat.core._client.data_classes.data_modeling import Component
56
58
  from cognite.neat.core._client.data_classes.schema import DMSSchema
57
59
  from cognite.neat.core._issues.warnings import CDFMaxIterationsWarning
58
60
  from cognite.neat.core._shared import T_ID
61
+ from cognite.neat.core._utils.tarjan import tarjan
59
62
 
60
63
  if TYPE_CHECKING:
61
64
  from cognite.neat.core._client._api_client import NeatClient
@@ -620,6 +623,81 @@ class ViewLoader(DataModelingLoader[ViewId, ViewApply, View, ViewApplyList, View
620
623
  def _create(self, items: Sequence[ViewApply]) -> ViewList:
621
624
  return self._client.data_modeling.views.apply(items)
622
625
 
626
+ def create(self, items: Sequence[ViewApply]) -> ViewList:
627
+ try:
628
+ return self._create(items)
629
+ except CogniteAPIError as e1:
630
+ if self._is_auto_retryable(e1):
631
+ # Fallback to creating one by one if the error is auto-retryable.
632
+ return self._fallback_one_by_one(self._create, items)
633
+ elif self._is_false_not_exists(e1, {item.as_id() for item in items}):
634
+ return self._try_to_recover_coupled(items, e1)
635
+ raise
636
+
637
+ def _try_to_recover_coupled(self, items: Sequence[ViewApply], original_error: CogniteAPIError) -> ViewList:
638
+ """The /models/views endpoint can give faulty 400 about missing views that are part of the request.
639
+
640
+ This method tries to recover from such errors by identifying the strongly connected components in the graph
641
+ defined by the implements and through properties of the views. We then create the components in topological
642
+ order.
643
+
644
+ Args:
645
+ items: The items that failed to create.
646
+ original_error: The original error that was raised. If the problem is not recoverable, this error is raised.
647
+
648
+ Returns:
649
+ The views that were created.
650
+
651
+ """
652
+ views_by_id = {self.get_id(item): item for item in items}
653
+ parents_by_child: dict[ViewId, set[ViewId]] = {
654
+ view_id: {parent for parent in view.implements or [] if parent in views_by_id}
655
+ for view_id, view in views_by_id.items()
656
+ }
657
+ # Check for cycles in the implements graph
658
+ try:
659
+ TopologicalSorter(parents_by_child).static_order()
660
+ except CycleError as e:
661
+ raise CycleError(f"Failed to deploy views. This likely due to a cycle in implements. {e.args[1]}") from None
662
+
663
+ dependencies_by_id: dict[ViewId, set[ViewId]] = defaultdict(set)
664
+ for view_id, view in views_by_id.items():
665
+ dependencies_by_id[view_id].update([parent for parent in view.implements or [] if parent in views_by_id])
666
+ for properties in (view.properties or {}).values():
667
+ if isinstance(properties, ReverseDirectRelationApply):
668
+ if isinstance(properties.through.source, ViewId) and properties.through.source in views_by_id:
669
+ dependencies_by_id[view_id].add(properties.through.source)
670
+
671
+ created = ViewList([])
672
+ for strongly_connected in tarjan(dependencies_by_id):
673
+ to_create = [views_by_id[view_id] for view_id in strongly_connected]
674
+ try:
675
+ created_set = self._client.data_modeling.views.apply(to_create)
676
+ except CogniteAPIError:
677
+ self._client.data_modeling.views.delete(created.as_ids())
678
+ raise original_error from None
679
+ created.extend(created_set)
680
+ return created
681
+
682
+ @staticmethod
683
+ def _is_auto_retryable(e: CogniteAPIError) -> bool:
684
+ return isinstance(e.extra, dict) and "isAutoRetryable" in e.extra and e.extra["isAutoRetryable"]
685
+
686
+ @staticmethod
687
+ def _is_false_not_exists(e: CogniteAPIError, request_views: set[ViewId]) -> bool:
688
+ """This is a bug in the API where it returns a 400 status complaining that a views does not exist,
689
+ even though they are part of the request. This bug is reported to Cognite. The workaround is to do a
690
+ topological sort of the strongly connected views and retry the request.
691
+ """
692
+ if "not exist" not in e.message and 400 <= e.code < 500:
693
+ return False
694
+ results = re.findall(r"'([a-zA-Z0-9_-]+):([a-zA-Z0-9_]+)/([.a-zA-Z0-9_-]+)'", e.message)
695
+ if not results:
696
+ # No view references in the message
697
+ return False
698
+ error_message_views = {ViewId(*result) for result in results}
699
+ return error_message_views.issubset(request_views)
700
+
623
701
  @overload
624
702
  def retrieve(
625
703
  self,
@@ -391,9 +391,9 @@ class _DMSExporter:
391
391
  container.indexes = container.indexes or {}
392
392
  index_property_list = [prop_id for prop_id, _ in sorted(properties, key=lambda x: x[1].order or 0)]
393
393
  index_entity = properties[0][1]
394
- if index_entity.prefix == "inverted" or (index_entity.prefix is Undefined and is_list):
394
+ if index_entity.prefix == "inverted" or (index_entity.prefix == Undefined and is_list):
395
395
  container.indexes[index_name] = InvertedIndex(properties=index_property_list)
396
- elif index_entity.prefix == "btree" or (index_entity.prefix is Undefined and not is_list):
396
+ elif index_entity.prefix == "btree" or (index_entity.prefix == Undefined and not is_list):
397
397
  container.indexes[index_name] = BTreeIndex(
398
398
  properties=index_property_list, cursorable=index_entity.cursorable or False
399
399
  )
@@ -0,0 +1,44 @@
1
+ from typing import TypeVar
2
+
3
+ T = TypeVar("T")
4
+
5
+
6
+ def tarjan(dependencies_by_id: dict[T, set[T]]) -> list[set[T]]:
7
+ """Returns the strongly connected components of the dependency graph
8
+ in topological order.
9
+ Args:
10
+ dependencies_by_id: A dictionary where the keys are ids and the values are sets of ids that the key depends on.
11
+ Returns:
12
+ A list of sets of ids that are strongly connected components in the dependency graph.
13
+ """
14
+
15
+ stack = []
16
+ stack_set = set()
17
+ index: dict[T, int] = {}
18
+ lowlink = {}
19
+ result = []
20
+
21
+ def visit(v: T) -> None:
22
+ index[v] = len(index)
23
+ lowlink[v] = index[v]
24
+ stack.append(v)
25
+ stack_set.add(v)
26
+ for w in dependencies_by_id.get(v, []):
27
+ if w not in index:
28
+ visit(w)
29
+ lowlink[v] = min(lowlink[w], lowlink[v])
30
+ elif w in stack_set:
31
+ lowlink[v] = min(lowlink[v], index[w])
32
+ if lowlink[v] == index[v]:
33
+ scc = set()
34
+ dependency: T | None = None
35
+ while v != dependency:
36
+ dependency = stack.pop()
37
+ scc.add(dependency)
38
+ stack_set.remove(dependency)
39
+ result.append(scc)
40
+
41
+ for view_id in dependencies_by_id.keys():
42
+ if view_id not in index:
43
+ visit(view_id)
44
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognite-neat
3
- Version: 0.123.6
3
+ Version: 0.123.8
4
4
  Summary: Knowledge graph transformation
5
5
  Project-URL: Documentation, https://cognite-neat.readthedocs-hosted.com/
6
6
  Project-URL: Homepage, https://cognite-neat.readthedocs-hosted.com/
@@ -1,5 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=12StS1dzH9_MElqxGvLWrNsxCJl9Hv8A2a9D0E5OD_U,193
2
- cognite/neat/_version.py,sha256=pYgIOIoZmd2M8dmiRwHzXC1Amc9xjpZBXaTTBA_r8-Q,46
2
+ cognite/neat/_version.py,sha256=p4Io2TCN-JUEOMcJwjUduCAyqkMMmElUmdDGzpevtQU,46
3
3
  cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cognite/neat/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  cognite/neat/core/_config.py,sha256=WT1BS8uADcFvGoUYOOfwFOVq_VBl472TisdoA3wLick,280
@@ -9,7 +9,7 @@ cognite/neat/core/_client/__init__.py,sha256=RQ7MwL8mwGqGHokRzsPqO3XStDzmI4-c12_
9
9
  cognite/neat/core/_client/_api_client.py,sha256=CqgG4kEArI9jiKAh82YrRZv_SzeMKA5guIZOvDg2R5I,860
10
10
  cognite/neat/core/_client/testing.py,sha256=rZGd-TFwNtfUqT8LV0u3FT0kHwNrjnvDNZU_Mcd5yx4,1329
11
11
  cognite/neat/core/_client/_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- cognite/neat/core/_client/_api/data_modeling_loaders.py,sha256=YEZP0V_RXenzzFSxDzt4SbVnpzB4VP0fcI-Bab1VLw8,39815
12
+ cognite/neat/core/_client/_api/data_modeling_loaders.py,sha256=6ZiL_btoqMlJF2TtW9q2y8zEWds7QWLn4YlFH72v1Ew,43794
13
13
  cognite/neat/core/_client/_api/neat_instances.py,sha256=8HcQO1sp8zjXdnRBRQ4yeQzt1O906HNSrDDCgrgTe8A,4805
14
14
  cognite/neat/core/_client/_api/schema.py,sha256=lbA8Cka_7K_RjmaxdeqkVkIwKPfWeDvpYvvEOGI07xo,6967
15
15
  cognite/neat/core/_client/_api/statistics.py,sha256=M0JpCHD6WMfggoe-HyXfeigwRCvZJjVF-xNB9CgB4UE,3796
@@ -65,7 +65,7 @@ cognite/neat/core/_data_model/models/mapping/__init__.py,sha256=T68Hf7rhiXa7b03h
65
65
  cognite/neat/core/_data_model/models/mapping/_classic2core.py,sha256=FRDpYP_CX-CfptdFCZmfqsbKCYQ9BQPUbKoifTICe30,1415
66
66
  cognite/neat/core/_data_model/models/mapping/_classic2core.yaml,sha256=ei-nuivNWVW9HmvzDBKIPF6ZdgaMq64XHw_rKm0CMxg,22584
67
67
  cognite/neat/core/_data_model/models/physical/__init__.py,sha256=ONE_xLw1cxfw88rICG_RtbjCYUZm8yS2kBQ4Di3EGnA,987
68
- cognite/neat/core/_data_model/models/physical/_exporter.py,sha256=WHYjWyiKpA7GWO2_vPN3AphpDr8OjzxI5JMyO7b3sYQ,30083
68
+ cognite/neat/core/_data_model/models/physical/_exporter.py,sha256=DPOytV-sIzpGJtfDEfi7G4RWnSCVNRLWe1KzY26ewmc,30083
69
69
  cognite/neat/core/_data_model/models/physical/_unverified.py,sha256=VyI-JULAu6kHJygUclDPH1JYjhf_XcO58tI9BkXORC0,18430
70
70
  cognite/neat/core/_data_model/models/physical/_validation.py,sha256=uPzbcJmFj0g9ATVq86rUsmSGOfQ0iBeIevoKh1lCu98,33179
71
71
  cognite/neat/core/_data_model/models/physical/_verified.py,sha256=Fvy0pOYk03nHDlRz_SECg9ALW3y0LaCZG6XNBduCi9Q,24332
@@ -148,6 +148,7 @@ cognite/neat/core/_utils/graph_transformations_report.py,sha256=ORVH7lw357TPOq4e
148
148
  cognite/neat/core/_utils/io_.py,sha256=D2Mg8sOxfBoDg3fC0jBzaxO3vkXmr0QvZSgYIv6xRkM,386
149
149
  cognite/neat/core/_utils/rdf_.py,sha256=8AALp8H_nXEDSBo6jZ1idyT_x3K4PJT5ZyBEyxPmgxI,10403
150
150
  cognite/neat/core/_utils/spreadsheet.py,sha256=MMI_1zxeHEf9Ggu_-t_ryjj54ky085QIf8eArt5hXEY,5749
151
+ cognite/neat/core/_utils/tarjan.py,sha256=IZvwaIITryGVNbo9Bv5EA9_sW3DyfUNAe7uYyPOCL0g,1357
151
152
  cognite/neat/core/_utils/text.py,sha256=ON4ihfscFJkQqQ-Rj46XXtf-9tAobwXbbfa3wuekSu4,8519
152
153
  cognite/neat/core/_utils/time_.py,sha256=7ayUm0OWZm1JDmy32E4ip8WRr2o0GLwrHwJA8sJ43Z4,357
153
154
  cognite/neat/core/_utils/upload.py,sha256=yR-BvvrWPh0XHoIGByXMEVi3JONzmc5xwXbmEDBdA8U,5860
@@ -185,7 +186,7 @@ cognite/neat/session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4dvc
185
186
  cognite/neat/session/engine/_import.py,sha256=1QxA2_EK613lXYAHKQbZyw2yjo5P9XuiX4Z6_6-WMNQ,169
186
187
  cognite/neat/session/engine/_interface.py,sha256=3W-cYr493c_mW3P5O6MKN1xEQg3cA7NHR_ev3zdF9Vk,533
187
188
  cognite/neat/session/engine/_load.py,sha256=g52uYakQM03VqHt_RDHtpHso1-mFFifH5M4T2ScuH8A,5198
188
- cognite_neat-0.123.6.dist-info/METADATA,sha256=ejxcaEkDxrf0agU1343EbR4eDXUqUwwgku3eEJ90TvE,9171
189
- cognite_neat-0.123.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
190
- cognite_neat-0.123.6.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
191
- cognite_neat-0.123.6.dist-info/RECORD,,
189
+ cognite_neat-0.123.8.dist-info/METADATA,sha256=SxWb1954YW95XT55Z_I9Z9BULuePbOcYuWg8GhfdHLY,9171
190
+ cognite_neat-0.123.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
191
+ cognite_neat-0.123.8.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
192
+ cognite_neat-0.123.8.dist-info/RECORD,,