cognite-neat 1.0.21__py3-none-any.whl → 1.0.23__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.
@@ -23,7 +23,7 @@ from cognite.neat._data_model.models.dms._view_property import (
23
23
  ViewRequestProperty,
24
24
  )
25
25
  from cognite.neat._data_model.models.dms._views import ViewRequest
26
- from cognite.neat._utils.useful_types import ModusOperandi
26
+ from cognite.neat._utils.useful_types import ModusOperandi, T_Reference
27
27
 
28
28
  # Type aliases for better readability
29
29
  ViewsByReference: TypeAlias = dict[ViewReference, ViewRequest]
@@ -458,9 +458,42 @@ class ValidationResources:
458
458
  view_sets = [self.views_by_container.get(c, set()) for c in containers]
459
459
  return set.intersection(*view_sets)
460
460
 
461
- # =========================================================================
462
- # Methods used for requires constraint validation
463
- # =========================================================================
461
+ @cached_property
462
+ def implements_graph(self) -> nx.DiGraph:
463
+ """Build a weighted directed graph of view implements.
464
+
465
+ Nodes are ViewReferences, edges represent implements.
466
+ An edge A → B means view A implements view B. Order of views in implements is used to set weight of an edge.
467
+
468
+ Includes views from both merged schema and CDF
469
+ """
470
+ graph: nx.DiGraph = nx.DiGraph()
471
+
472
+ for view_ref in self.cdf.views:
473
+ graph.add_node(view_ref)
474
+ for view_ref in self.merged.views:
475
+ graph.add_node(view_ref)
476
+
477
+ # Add edges for implements
478
+ for view_ref in graph.nodes():
479
+ view = self.select_view(view_ref)
480
+ if not view or not view.implements:
481
+ continue
482
+
483
+ # Adding weight to preserve order of implements
484
+ for i, implement in enumerate(view.implements):
485
+ graph.add_edge(view_ref, implement, weight=i + 1)
486
+
487
+ return graph
488
+
489
+ @cached_property
490
+ def implements_cycles(self) -> list[list[ViewReference]]:
491
+ """Find all cycles in the implements graph.
492
+ Returns:
493
+ List of lists, where each list contains the ordered Views involved in forming the implements cycle.
494
+ """
495
+
496
+ return self.graph_cycles(self.implements_graph)
464
497
 
465
498
  @cached_property
466
499
  def requires_constraint_graph(self) -> nx.DiGraph:
@@ -524,15 +557,15 @@ class ValidationResources:
524
557
  return False
525
558
 
526
559
  @cached_property
527
- def requires_constraint_cycles(self) -> list[set[ContainerReference]]:
528
- """Find all cycles in the requires constraint graph using Tarjan's algorithm.
529
-
530
- Uses strongly connected components (SCC) to identify cycles efficiently.
531
- An SCC with more than one node indicates a cycle.
532
-
560
+ def requires_constraint_cycles(self) -> list[list[ContainerReference]]:
561
+ """Find all cycles in the requires constraint graph.
533
562
  Returns:
534
- List of sets, where each set contains the containers involved in a cycle.
563
+ List of lists, where each list contains the ordered containers involved in forming the requires cycle.
535
564
  """
536
- sccs = nx.strongly_connected_components(self.requires_constraint_graph)
537
- # Only SCCs with more than one node represent cycles
538
- return [scc for scc in sccs if len(scc) > 1]
565
+
566
+ return self.graph_cycles(self.requires_constraint_graph)
567
+
568
+ @staticmethod
569
+ def graph_cycles(graph: nx.DiGraph) -> list[list[T_Reference]]:
570
+ """Returns cycles in the graph otherwise empty list"""
571
+ return [candidate for candidate in nx.simple_cycles(graph) if len(candidate) > 1]
@@ -26,6 +26,7 @@ from ._containers import (
26
26
  ExternalContainerDoesNotExist,
27
27
  ExternalContainerPropertyDoesNotExist,
28
28
  RequiredContainerDoesNotExist,
29
+ RequiresConstraintCycle,
29
30
  )
30
31
  from ._limits import (
31
32
  ContainerPropertyCountIsOutOfLimits,
@@ -54,6 +55,7 @@ __all__ = [
54
55
  "ExternalContainerPropertyDoesNotExist",
55
56
  "ImplementedViewNotExisting",
56
57
  "RequiredContainerDoesNotExist",
58
+ "RequiresConstraintCycle",
57
59
  "ReverseConnectionContainerMissing",
58
60
  "ReverseConnectionContainerPropertyMissing",
59
61
  "ReverseConnectionContainerPropertyWrongType",
@@ -197,3 +197,41 @@ class RequiredContainerDoesNotExist(DataModelValidator):
197
197
  )
198
198
 
199
199
  return errors
200
+
201
+
202
+ class RequiresConstraintCycle(DataModelValidator):
203
+ """
204
+ Validates that requires constraints between containers do not form cycles.
205
+
206
+ ## What it does
207
+ This validator checks if the requires constraints between containers form a cycle.
208
+ For example, if container A requires B, B requires C, and C requires A, this forms
209
+ a cycle.
210
+
211
+ ## Why is this bad?
212
+ Cycles in requires constraints will be rejected by the CDF API. The deployment
213
+ of the data model will fail if any such cycle exists.
214
+
215
+ ## Example
216
+ Container `my_space:OrderContainer` requires `my_space:CustomerContainer`, which
217
+ requires `my_space:OrderContainer`. This creates a cycle and will be rejected.
218
+ """
219
+
220
+ code = f"{BASE_CODE}-005"
221
+ issue_type = ConsistencyError
222
+ alpha = True # Still in development
223
+
224
+ def run(self) -> list[ConsistencyError]:
225
+ errors: list[ConsistencyError] = []
226
+
227
+ for cycle in self.validation_resources.requires_constraint_cycles:
228
+ cycle_str = " -> ".join(str(c) for c in cycle) + f" -> {cycle[0]!s}"
229
+ errors.append(
230
+ ConsistencyError(
231
+ message=f"Requires constraints form a cycle: {cycle_str}",
232
+ fix="Remove one of the requires constraints to break the cycle",
233
+ code=self.code,
234
+ )
235
+ )
236
+
237
+ return errors
@@ -124,3 +124,41 @@ class ImplementedViewNotExisting(DataModelValidator):
124
124
  )
125
125
 
126
126
  return errors
127
+
128
+
129
+ class CyclicImplements(DataModelValidator):
130
+ """Validates that view implements are not forming a cycle (i.e. cyclic graph of implements)
131
+
132
+ ## What it does
133
+ Runs graph analysis of the implements graph finding every cycle within the graph
134
+
135
+ ## Why is this bad?
136
+ You will not be able to deploy the data model to CDF, since cyclic implements are impossible to resolve
137
+ in terms of inheritance of properties.
138
+
139
+ ## Example
140
+ Say we have following views: A, B, and C, where A implements B, B implements C, and C implements A. This forms
141
+ the cyclic graph of implements A->B->C->A.
142
+
143
+ """
144
+
145
+ code = f"{BASE_CODE}-003"
146
+ issue_type = ConsistencyError
147
+ alpha = True
148
+
149
+ def run(self) -> list[ConsistencyError]:
150
+ errors: list[ConsistencyError] = []
151
+
152
+ for cycle in self.validation_resources.implements_cycles:
153
+ errors.append(
154
+ ConsistencyError(
155
+ message=(
156
+ "Detected cycle of view implements "
157
+ f"{'->'.join([str(view) for view in cycle]) + f'->{cycle[0]}'}"
158
+ ),
159
+ fix="Inspect involved views and make necessary fix",
160
+ code=self.code,
161
+ )
162
+ )
163
+
164
+ return errors
cognite/neat/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "1.0.21"
1
+ __version__ = "1.0.23"
2
2
  __engine__ = "^2.0.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognite-neat
3
- Version: 1.0.21
3
+ Version: 1.0.23
4
4
  Summary: Knowledge graph transformation
5
5
  Author: Nikola Vasiljevic, Anders Albert
6
6
  Author-email: Nikola Vasiljevic <nikola.vasiljevic@cognite.com>, Anders Albert <anders.albert@cognite.com>
@@ -55,10 +55,10 @@ Requires-Dist: lxml>=5.3.0,<6.0.0 ; extra == 'lxml'
55
55
  Requires-Dist: oxrdflib>=0.4.0,<0.5.0 ; extra == 'oxi'
56
56
  Requires-Dist: pyoxigraph>=0.4.3,<0.5.0 ; extra == 'oxi'
57
57
  Requires-Python: >=3.10
58
- Project-URL: Changelog, https://github.com/cognitedata/neat/releases
59
58
  Project-URL: Documentation, https://cognite-neat.readthedocs-hosted.com/
60
- Project-URL: GitHub, https://github.com/cognitedata/neat
61
59
  Project-URL: Homepage, https://cognite-neat.readthedocs-hosted.com/
60
+ Project-URL: GitHub, https://github.com/cognitedata/neat
61
+ Project-URL: Changelog, https://github.com/cognitedata/neat/releases
62
62
  Provides-Extra: docs
63
63
  Provides-Extra: google
64
64
  Provides-Extra: lxml
@@ -15,7 +15,7 @@ cognite/neat/_client/statistics_api.py,sha256=HcYb2nNC9M_iaI1xyjjLn2Cz1tcyu7BJea
15
15
  cognite/neat/_client/views_api.py,sha256=Qzk_wiLtaWszxCQFDBoWCH1yDc4GOEJsVOcL061rcK0,5639
16
16
  cognite/neat/_config.py,sha256=ZvCkcaRVAvH4-ClvinoWaLWhRJpRByqdvncGFsf5gLk,9886
17
17
  cognite/neat/_data_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- cognite/neat/_data_model/_analysis.py,sha256=lPiXF0td4y5TudrR1KPH5OUZ_YNBz8tO6cLg3RnJwEg,22413
18
+ cognite/neat/_data_model/_analysis.py,sha256=dN-udKm_5oD3217O4B_QIps2Hx4v50-Pu2fR0bQNQg0,23504
19
19
  cognite/neat/_data_model/_constants.py,sha256=E2axzdYjsIy7lTHsjW91wsv6r-pUwko8g6K8C_oRnxk,1707
20
20
  cognite/neat/_data_model/_identifiers.py,sha256=lDLvMvYDgRNFgk5GmxWzOUunG7M3synAciNjzJI0m_o,1913
21
21
  cognite/neat/_data_model/_shared.py,sha256=H0gFqa8tKFNWuvdat5jL6OwySjCw3aQkLPY3wtb9Wrw,1302
@@ -74,15 +74,15 @@ cognite/neat/_data_model/models/entities/_data_types.py,sha256=DfdEWGek7gODro-_0
74
74
  cognite/neat/_data_model/models/entities/_identifiers.py,sha256=a7ojJKY1ErZgUANHscEwkctX4RJ7bWEEWOQt5g5Tsdk,1915
75
75
  cognite/neat/_data_model/models/entities/_parser.py,sha256=zef_pSDZYMZrJl4IKreFDR577KutfhtN1xpH3Ayjt2o,7669
76
76
  cognite/neat/_data_model/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
- cognite/neat/_data_model/validation/dms/__init__.py,sha256=kKD18-Bg_G-w11Cs7Wv_TKV0C_q62Pm2RKLpOz27ar4,2642
77
+ cognite/neat/_data_model/validation/dms/__init__.py,sha256=STb0mYmyD-E6CfhjaXNOgWM1LvYSm59m6-Khu9cEhNI,2702
78
78
  cognite/neat/_data_model/validation/dms/_ai_readiness.py,sha256=bffMQJ5pqumU5P3KaEdQP67OO5eMKqzN2BAWbUjG6KE,16143
79
79
  cognite/neat/_data_model/validation/dms/_base.py,sha256=G_gMPTgKwyBW62UcCkKIBVHWp9ufAZPJ2p7o69_dJI0,820
80
80
  cognite/neat/_data_model/validation/dms/_connections.py,sha256=-kUXf2_3V50ckxwXRwJoTHsKkS5zxiBKkkkHg8Dm4WI,30353
81
81
  cognite/neat/_data_model/validation/dms/_consistency.py,sha256=IKSUoRQfQQcsymviESW9VuTFX7jsZMXfsObeZHPdov4,2435
82
- cognite/neat/_data_model/validation/dms/_containers.py,sha256=5Lka1Cg-SaP9Ued0cku0leG1Sjx76JQ9XcBZK_dtfuM,8520
82
+ cognite/neat/_data_model/validation/dms/_containers.py,sha256=8pVnmeX6G9tQaGzzwRB_40y7TYUm4guaNbRiFgoGILU,9895
83
83
  cognite/neat/_data_model/validation/dms/_limits.py,sha256=U7z8sN-kAyJsF5hYHPNBBg25Fvz1F8njhzYVSQOIiOU,14779
84
84
  cognite/neat/_data_model/validation/dms/_orchestrator.py,sha256=qiuUSUmNhekFyBARUUO2yhG-X9AeU_LL49UrJ65JXFA,2964
85
- cognite/neat/_data_model/validation/dms/_views.py,sha256=Q0x7jdG69-AVc93VrwdZ1_rFHpq-I-OG98puM4lcweE,5068
85
+ cognite/neat/_data_model/validation/dms/_views.py,sha256=pRdnj5ZBnHNnbLKleXGbipteGma8_l5AYsDIfqgAil4,6345
86
86
  cognite/neat/_exceptions.py,sha256=mO19TEecZYDNqSvzuc6JmCLFQ70eniT1-Gb0AEbgbzE,2090
87
87
  cognite/neat/_issues.py,sha256=wH1mnkrpBsHUkQMGUHFLUIQWQlfJ_qMfdF7q0d9wNhY,1871
88
88
  cognite/neat/_session/__init__.py,sha256=owqW5Mml2DSZx1AvPvwNRTBngfhBNrQ6EH-7CKL7Jp0,61
@@ -321,9 +321,9 @@ cognite/neat/_v0/session/_template.py,sha256=BNcvrW5y7LWzRM1XFxZkfR1Nc7e8UgjBClH
321
321
  cognite/neat/_v0/session/_to.py,sha256=AnsRSDDdfFyYwSgi0Z-904X7WdLtPfLlR0x1xsu_jAo,19447
322
322
  cognite/neat/_v0/session/_wizard.py,sha256=baPJgXAAF3d1bn4nbIzon1gWfJOeS5T43UXRDJEnD3c,1490
323
323
  cognite/neat/_v0/session/exceptions.py,sha256=jv52D-SjxGfgqaHR8vnpzo0SOJETIuwbyffSWAxSDJw,3495
324
- cognite/neat/_version.py,sha256=0Bv-euqjHzX0kdwCv6YRDD5BAUEFBhkf7f6PaEKGdd0,45
324
+ cognite/neat/_version.py,sha256=rj_LKbqpGZavJZekkd20Oo3-5Wi6i3pBDjeRkCt22zY,45
325
325
  cognite/neat/legacy.py,sha256=eI2ecxOV8ilGHyLZlN54ve_abtoK34oXognkFv3yvF0,219
326
326
  cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
327
- cognite_neat-1.0.21.dist-info/WHEEL,sha256=XjEbIc5-wIORjWaafhI6vBtlxDBp7S9KiujWF1EM7Ak,79
328
- cognite_neat-1.0.21.dist-info/METADATA,sha256=QpA63SYiPB_pttmYhdDpmx7AbJl98Sd8V-wIbjoWCwI,6689
329
- cognite_neat-1.0.21.dist-info/RECORD,,
327
+ cognite_neat-1.0.23.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
328
+ cognite_neat-1.0.23.dist-info/METADATA,sha256=hByYgnOhvq27boiIAHGRyCwddaWgqPxcP_jX8TytSMs,6689
329
+ cognite_neat-1.0.23.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.25
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any