cognite-neat 0.103.1__py3-none-any.whl → 0.104.0__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 (61) hide show
  1. cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
  2. cognite/neat/_graph/transformers/_base.py +109 -1
  3. cognite/neat/_graph/transformers/_classic_cdf.py +4 -0
  4. cognite/neat/_graph/transformers/_prune_graph.py +103 -47
  5. cognite/neat/_graph/transformers/_rdfpath.py +41 -17
  6. cognite/neat/_graph/transformers/_value_type.py +119 -154
  7. cognite/neat/_issues/_base.py +35 -8
  8. cognite/neat/_issues/warnings/_resources.py +1 -1
  9. cognite/neat/_rules/_shared.py +18 -34
  10. cognite/neat/_rules/exporters/_base.py +28 -2
  11. cognite/neat/_rules/exporters/_rules2dms.py +4 -0
  12. cognite/neat/_rules/exporters/_rules2excel.py +11 -0
  13. cognite/neat/_rules/exporters/_rules2instance_template.py +4 -0
  14. cognite/neat/_rules/exporters/_rules2ontology.py +13 -1
  15. cognite/neat/_rules/exporters/_rules2yaml.py +4 -0
  16. cognite/neat/_rules/importers/_base.py +9 -0
  17. cognite/neat/_rules/importers/_dms2rules.py +17 -5
  18. cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +5 -2
  19. cognite/neat/_rules/importers/_rdf/_base.py +10 -8
  20. cognite/neat/_rules/importers/_rdf/_imf2rules.py +4 -0
  21. cognite/neat/_rules/importers/_rdf/_inference2rules.py +7 -0
  22. cognite/neat/_rules/importers/_rdf/_owl2rules.py +4 -0
  23. cognite/neat/_rules/importers/_spreadsheet2rules.py +17 -8
  24. cognite/neat/_rules/importers/_yaml2rules.py +21 -7
  25. cognite/neat/_rules/models/_base_input.py +1 -1
  26. cognite/neat/_rules/models/_base_rules.py +5 -0
  27. cognite/neat/_rules/models/dms/_rules.py +4 -0
  28. cognite/neat/_rules/models/dms/_rules_input.py +9 -0
  29. cognite/neat/_rules/models/information/_rules.py +4 -0
  30. cognite/neat/_rules/models/information/_rules_input.py +9 -0
  31. cognite/neat/_rules/models/mapping/_classic2core.py +2 -5
  32. cognite/neat/_rules/transformers/__init__.py +5 -4
  33. cognite/neat/_rules/transformers/_base.py +41 -65
  34. cognite/neat/_rules/transformers/_converters.py +149 -62
  35. cognite/neat/_rules/transformers/_mapping.py +17 -12
  36. cognite/neat/_rules/transformers/_verification.py +50 -37
  37. cognite/neat/_session/_base.py +32 -121
  38. cognite/neat/_session/_inspect.py +3 -3
  39. cognite/neat/_session/_mapping.py +17 -105
  40. cognite/neat/_session/_prepare.py +36 -254
  41. cognite/neat/_session/_read.py +11 -130
  42. cognite/neat/_session/_set.py +6 -30
  43. cognite/neat/_session/_show.py +40 -21
  44. cognite/neat/_session/_state.py +49 -107
  45. cognite/neat/_session/_to.py +42 -31
  46. cognite/neat/_shared.py +23 -2
  47. cognite/neat/_store/_provenance.py +3 -82
  48. cognite/neat/_store/_rules_store.py +372 -10
  49. cognite/neat/_store/exceptions.py +23 -0
  50. cognite/neat/_utils/graph_transformations_report.py +36 -0
  51. cognite/neat/_utils/rdf_.py +8 -0
  52. cognite/neat/_utils/spreadsheet.py +5 -4
  53. cognite/neat/_version.py +1 -1
  54. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +7 -7
  55. cognite/neat/_workflows/steps/lib/current/rules_importer.py +24 -99
  56. {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/METADATA +1 -1
  57. {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/RECORD +60 -59
  58. cognite/neat/_rules/transformers/_pipelines.py +0 -70
  59. {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/LICENSE +0 -0
  60. {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/WHEEL +0 -0
  61. {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,7 @@
1
+ import dataclasses
1
2
  import re
2
3
  import warnings
3
- from abc import ABC, abstractmethod
4
+ from abc import ABC
4
5
  from collections import Counter, defaultdict
5
6
  from collections.abc import Collection, Mapping
6
7
  from datetime import date, datetime
@@ -9,11 +10,12 @@ from typing import ClassVar, Literal, TypeVar, cast, overload
9
10
  from cognite.client.data_classes import data_modeling as dms
10
11
  from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier, ViewId
11
12
 
13
+ from cognite.neat._client import NeatClient
14
+ from cognite.neat._client.data_classes.data_modeling import ContainerApplyDict, ViewApplyDict
12
15
  from cognite.neat._constants import (
13
16
  COGNITE_MODELS,
14
17
  DMS_CONTAINER_PROPERTY_SIZE_LIMIT,
15
18
  )
16
- from cognite.neat._issues._base import IssueList
17
19
  from cognite.neat._issues.errors import NeatValueError
18
20
  from cognite.neat._issues.warnings import NeatValueWarning
19
21
  from cognite.neat._issues.warnings._models import (
@@ -21,13 +23,13 @@ from cognite.neat._issues.warnings._models import (
21
23
  SolutionModelBuildOnTopOfCDMWarning,
22
24
  )
23
25
  from cognite.neat._rules._shared import (
24
- InputRules,
25
- JustRules,
26
- OutRules,
26
+ ReadInputRules,
27
27
  ReadRules,
28
+ T_InputRules,
28
29
  VerifiedRules,
29
30
  )
30
31
  from cognite.neat._rules.analysis import DMSAnalysis
32
+ from cognite.neat._rules.importers import DMSImporter
31
33
  from cognite.neat._rules.models import (
32
34
  DMSInputRules,
33
35
  DMSRules,
@@ -36,7 +38,7 @@ from cognite.neat._rules.models import (
36
38
  data_types,
37
39
  )
38
40
  from cognite.neat._rules.models.data_types import AnyURI, DataType, String
39
- from cognite.neat._rules.models.dms import DMSMetadata, DMSProperty, DMSView
41
+ from cognite.neat._rules.models.dms import DMSMetadata, DMSProperty, DMSValidation, DMSView
40
42
  from cognite.neat._rules.models.dms._rules import DMSContainer
41
43
  from cognite.neat._rules.models.entities import (
42
44
  ClassEntity,
@@ -60,40 +62,35 @@ from cognite.neat._utils.collection_ import remove_list_elements
60
62
  from cognite.neat._utils.text import to_camel
61
63
 
62
64
  from ._base import RulesTransformer
65
+ from ._verification import VerifyDMSRules
63
66
 
64
67
  T_VerifiedInRules = TypeVar("T_VerifiedInRules", bound=VerifiedRules)
65
68
  T_VerifiedOutRules = TypeVar("T_VerifiedOutRules", bound=VerifiedRules)
66
- T_InputInRules = TypeVar("T_InputInRules", bound=InputRules)
67
- T_InputOutRules = TypeVar("T_InputOutRules", bound=InputRules)
69
+ T_InputInRules = TypeVar("T_InputInRules", bound=ReadInputRules)
70
+ T_InputOutRules = TypeVar("T_InputOutRules", bound=ReadInputRules)
68
71
 
69
72
 
70
73
  class ConversionTransformer(RulesTransformer[T_VerifiedInRules, T_VerifiedOutRules], ABC):
71
74
  """Base class for all conversion transformers."""
72
75
 
73
- def transform(self, rules: T_VerifiedInRules | OutRules[T_VerifiedInRules]) -> JustRules[T_VerifiedOutRules]:
74
- out = self._transform(self._to_rules(rules))
75
- return JustRules(out)
76
+ ...
76
77
 
77
- @abstractmethod
78
- def _transform(self, rules: T_VerifiedInRules) -> T_VerifiedOutRules:
79
- raise NotImplementedError()
80
78
 
81
-
82
- class ToCompliantEntities(RulesTransformer[InformationInputRules, InformationInputRules]): # type: ignore[misc]
79
+ class ToCompliantEntities(RulesTransformer[ReadRules[InformationInputRules], ReadRules[InformationInputRules]]): # type: ignore[misc]
83
80
  """Converts input rules to rules with compliant entity IDs that match regex patters used
84
81
  by DMS schema components."""
85
82
 
86
- def transform(
87
- self, rules: InformationInputRules | OutRules[InformationInputRules]
88
- ) -> ReadRules[InformationInputRules]:
89
- return ReadRules(self._transform(self._to_rules(rules)), IssueList(), {})
90
-
91
- def _transform(self, rules: InformationInputRules) -> InformationInputRules:
92
- rules.classes = self._fix_classes(rules.classes)
93
- rules.properties = self._fix_properties(rules.properties)
94
- rules.metadata.version += "_dms_compliant"
83
+ @property
84
+ def description(self) -> str:
85
+ return "Ensures externalIDs are compliant with CDF"
95
86
 
96
- return rules
87
+ def transform(self, rules: ReadRules[InformationInputRules]) -> ReadRules[InformationInputRules]:
88
+ if rules.rules is None:
89
+ return rules
90
+ copy: InformationInputRules = dataclasses.replace(rules.rules)
91
+ copy.classes = self._fix_classes(copy.classes)
92
+ copy.properties = self._fix_properties(copy.properties)
93
+ return ReadRules(copy, rules.read_context)
97
94
 
98
95
  @classmethod
99
96
  def _fix_entity(cls, entity: str) -> str:
@@ -179,46 +176,47 @@ class ToCompliantEntities(RulesTransformer[InformationInputRules, InformationInp
179
176
  return fixed_definitions
180
177
 
181
178
 
182
- class PrefixEntities(RulesTransformer[InputRules, InputRules]): # type: ignore[misc]
179
+ class PrefixEntities(RulesTransformer[ReadRules[T_InputRules], ReadRules[T_InputRules]]): # type: ignore[type-var]
183
180
  """Prefixes all entities with a given prefix."""
184
181
 
185
182
  def __init__(self, prefix: str) -> None:
186
183
  self._prefix = prefix
187
184
 
188
- def transform(self, rules: InputRules | OutRules[InputRules]) -> ReadRules[InputRules]:
189
- return ReadRules(self._transform(self._to_rules(rules)), IssueList(), {})
185
+ @property
186
+ def description(self) -> str:
187
+ return f"Prefixes all views with {self._prefix!r}"
190
188
 
191
- def _transform(self, rules: InputRules) -> InputRules:
192
- rules.metadata.version += f"_prefixed_{self._prefix}"
193
-
194
- if isinstance(rules, InformationInputRules):
195
- # Todo Make Not mutate input class
189
+ def transform(self, rules: ReadRules[T_InputRules]) -> ReadRules[T_InputRules]:
190
+ in_ = rules.rules
191
+ if in_ is None:
192
+ return rules
193
+ copy: T_InputRules = dataclasses.replace(in_)
194
+ if isinstance(copy, InformationInputRules):
196
195
  prefixed_by_class: dict[str, str] = {}
197
- for cls in rules.classes:
196
+ for cls in copy.classes:
198
197
  prefixed = str(self._with_prefix(cls.class_))
199
198
  prefixed_by_class[str(cls.class_)] = prefixed
200
199
  cls.class_ = prefixed
201
- for prop in rules.properties:
200
+ for prop in copy.properties:
202
201
  prop.class_ = self._with_prefix(prop.class_)
203
202
  if str(prop.value_type) in prefixed_by_class:
204
203
  prop.value_type = prefixed_by_class[str(prop.value_type)]
205
- return rules
206
- elif isinstance(rules, DMSInputRules):
207
- # Todo not mutate input class new_dms = copy.deepcopy(rules)
204
+ return ReadRules(copy, rules.read_context) # type: ignore[arg-type]
205
+ elif isinstance(copy, DMSInputRules):
208
206
  prefixed_by_view: dict[str, str] = {}
209
- for view in rules.views:
207
+ for view in copy.views:
210
208
  prefixed = str(self._with_prefix(view.view))
211
209
  prefixed_by_view[str(view.view)] = prefixed
212
210
  view.view = prefixed
213
- for dms_prop in rules.properties:
211
+ for dms_prop in copy.properties:
214
212
  dms_prop.view = self._with_prefix(dms_prop.view)
215
213
  if str(dms_prop.value_type) in prefixed_by_view:
216
214
  dms_prop.value_type = prefixed_by_view[str(dms_prop.value_type)]
217
- if rules.containers:
218
- for container in rules.containers:
215
+ if copy.containers:
216
+ for container in copy.containers:
219
217
  container.container = self._with_prefix(container.container)
220
- return rules
221
- raise NeatValueError(f"Unsupported rules type: {type(rules)}")
218
+ return ReadRules(copy, rules.read_context)
219
+ raise NeatValueError(f"Unsupported rules type: {type(copy)}")
222
220
 
223
221
  @overload
224
222
  def _with_prefix(self, raw: str) -> str: ...
@@ -254,14 +252,14 @@ class InformationToDMS(ConversionTransformer[InformationRules, DMSRules]):
254
252
  self.ignore_undefined_value_types = ignore_undefined_value_types
255
253
  self.mode = mode
256
254
 
257
- def _transform(self, rules: InformationRules) -> DMSRules:
255
+ def transform(self, rules: InformationRules) -> DMSRules:
258
256
  return _InformationRulesConverter(rules).as_dms_rules(self.ignore_undefined_value_types, self.mode)
259
257
 
260
258
 
261
259
  class DMSToInformation(ConversionTransformer[DMSRules, InformationRules]):
262
260
  """Converts DMSRules to InformationRules."""
263
261
 
264
- def _transform(self, rules: DMSRules) -> InformationRules:
262
+ def transform(self, rules: DMSRules) -> InformationRules:
265
263
  return _DMSRulesConverter(rules).as_information_rules()
266
264
 
267
265
 
@@ -271,13 +269,13 @@ class ConvertToRules(ConversionTransformer[VerifiedRules, VerifiedRules]):
271
269
  def __init__(self, out_cls: type[VerifiedRules]):
272
270
  self._out_cls = out_cls
273
271
 
274
- def _transform(self, rules: VerifiedRules) -> VerifiedRules:
272
+ def transform(self, rules: VerifiedRules) -> VerifiedRules:
275
273
  if isinstance(rules, self._out_cls):
276
274
  return rules
277
275
  if isinstance(rules, InformationRules) and self._out_cls is DMSRules:
278
- return InformationToDMS().transform(rules).rules
276
+ return InformationToDMS().transform(rules)
279
277
  if isinstance(rules, DMSRules) and self._out_cls is InformationRules:
280
- return DMSToInformation().transform(rules).rules
278
+ return DMSToInformation().transform(rules)
281
279
  raise ValueError(f"Unsupported conversion from {type(rules)} to {self._out_cls}")
282
280
 
283
281
 
@@ -288,16 +286,20 @@ class SetIDDMSModel(RulesTransformer[DMSRules, DMSRules]):
288
286
  def __init__(self, new_id: DataModelId | tuple[str, str, str]):
289
287
  self.new_id = DataModelId.load(new_id)
290
288
 
291
- def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
289
+ @property
290
+ def description(self) -> str:
291
+ return f"Sets the Data Model ID to {self.new_id.as_tuple()}"
292
+
293
+ def transform(self, rules: DMSRules) -> DMSRules:
292
294
  if self.new_id.version is None:
293
295
  raise NeatValueError("Version is required when setting a new Data Model ID")
294
- dump = self._to_rules(rules).dump()
296
+ dump = rules.dump()
295
297
  dump["metadata"]["space"] = self.new_id.space
296
298
  dump["metadata"]["external_id"] = self.new_id.external_id
297
299
  dump["metadata"]["version"] = self.new_id.version
298
300
  # Serialize and deserialize to set the new space and external_id
299
301
  # as the default values for the new model.
300
- return JustRules(DMSRules.model_validate(DMSInputRules.load(dump).dump()))
302
+ return DMSRules.model_validate(DMSInputRules.load(dump).dump())
301
303
 
302
304
 
303
305
  class ToExtension(RulesTransformer[DMSRules, DMSRules]):
@@ -322,9 +324,9 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
322
324
  self.move_connections = move_connections
323
325
  self.include = include
324
326
 
325
- def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
327
+ def transform(self, rules: DMSRules) -> DMSRules:
326
328
  # Copy to ensure immutability
327
- reference_model = self._to_rules(rules)
329
+ reference_model = rules
328
330
  reference_model_id = reference_model.metadata.as_data_model_id()
329
331
 
330
332
  # if model is solution then we need to get correct space for views and containers
@@ -365,7 +367,7 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
365
367
  def _has_views_in_multiple_space(self, rules: DMSRules) -> bool:
366
368
  return any(view.view.space != rules.metadata.space for view in rules.views)
367
369
 
368
- def _to_solution(self, reference_rules: DMSRules, remove_views_in_other_space: bool = True) -> JustRules[DMSRules]:
370
+ def _to_solution(self, reference_rules: DMSRules, remove_views_in_other_space: bool = True) -> DMSRules:
369
371
  """For creation of solution data model / rules specifically for mapping over existing containers."""
370
372
 
371
373
  dump = reference_rules.dump()
@@ -420,9 +422,9 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
420
422
  solution_model.containers = new_containers
421
423
  solution_model.properties.extend(new_properties)
422
424
 
423
- return JustRules(solution_model)
425
+ return solution_model
424
426
 
425
- def _to_enterprise(self, reference_model: DMSRules) -> JustRules[DMSRules]:
427
+ def _to_enterprise(self, reference_model: DMSRules) -> DMSRules:
426
428
  dump = reference_model.dump()
427
429
 
428
430
  # This will create reference model components in the enterprise model space
@@ -454,7 +456,7 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
454
456
 
455
457
  enterprise_properties.extend(enterprise_connections)
456
458
 
457
- return JustRules(enterprise_model)
459
+ return enterprise_model
458
460
 
459
461
  @staticmethod
460
462
  def _expand_properties(rules: DMSRules) -> DMSRules:
@@ -558,6 +560,17 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
558
560
 
559
561
  return new_properties
560
562
 
563
+ @property
564
+ def description(self) -> str:
565
+ if self.type_ == "enterprise":
566
+ return f"Prepared data model {self.new_model_id} to be enterprise data model."
567
+ elif self.type_ == "solution":
568
+ return f"Prepared data model {self.new_model_id} to be solution data model."
569
+ elif self.type_ == "data_product":
570
+ return f"Prepared data model {self.new_model_id} to be data product model."
571
+ else:
572
+ return f"Unsupported data model type: {self.type_}"
573
+
561
574
 
562
575
  class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
563
576
  _ASSET_VIEW = ViewId("cdf_cdm", "CogniteAsset", "v1")
@@ -605,8 +618,8 @@ class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
605
618
  )
606
619
  self.drop_external_ids = {external_id for external_id in drop if external_id not in self._VIEW_BY_COLLECTION}
607
620
 
608
- def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
609
- verified = self._to_rules(rules)
621
+ def transform(self, rules: DMSRules) -> DMSRules:
622
+ verified = rules
610
623
  if verified.metadata.as_data_model_id() not in COGNITE_MODELS:
611
624
  raise NeatValueError(f"Can only reduce Cognite Data Models, not {verified.metadata.as_data_model_id()}")
612
625
 
@@ -630,13 +643,87 @@ class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
630
643
 
631
644
  new_model.properties = new_properties
632
645
 
633
- return JustRules(new_model)
646
+ return new_model
634
647
 
635
648
  def _is_asset_3D_property(self, prop: DMSProperty) -> bool:
636
649
  if "3D" not in self.drop_collection:
637
650
  return False
638
651
  return prop.view.as_id() == self._ASSET_VIEW and prop.view_property == "object3D"
639
652
 
653
+ @property
654
+ def description(self) -> str:
655
+ return f"Removed {len(self.drop_external_ids) + len(self.drop_collection)} views from data model"
656
+
657
+
658
+ class IncludeReferenced(RulesTransformer[DMSRules, DMSRules]):
659
+ def __init__(self, client: NeatClient, include_properties: bool = False) -> None:
660
+ self._client = client
661
+ self.include_properties = include_properties
662
+
663
+ def transform(self, rules: DMSRules) -> DMSRules:
664
+ dms_rules = rules
665
+ view_ids, container_ids = DMSValidation(dms_rules, self._client).imported_views_and_containers_ids()
666
+ if not (view_ids or container_ids):
667
+ warnings.warn(
668
+ NeatValueWarning(
669
+ f"Data model {dms_rules.metadata.as_data_model_id()} does not have any "
670
+ "referenced views or containers."
671
+ "that is not already included in the data model."
672
+ ),
673
+ stacklevel=2,
674
+ )
675
+ return dms_rules
676
+
677
+ schema = self._client.schema.retrieve([v.as_id() for v in view_ids], [c.as_id() for c in container_ids])
678
+ copy_ = dms_rules.model_copy(deep=True)
679
+ # Sorting to ensure deterministic order
680
+ schema.containers = ContainerApplyDict(sorted(schema.containers.items(), key=lambda x: x[0].as_tuple()))
681
+ schema.views = ViewApplyDict(sorted(schema.views.items(), key=lambda x: x[0].as_tuple()))
682
+ importer = DMSImporter(schema)
683
+
684
+ imported = importer.to_rules()
685
+ if imported.rules is None:
686
+ raise NeatValueError("Could not import the referenced views and containers.")
687
+
688
+ verified = VerifyDMSRules(validate=False).transform(imported)
689
+ if copy_.containers is None:
690
+ copy_.containers = verified.containers
691
+ else:
692
+ existing_containers = {c.container for c in copy_.containers}
693
+ copy_.containers.extend([c for c in verified.containers or [] if c.container not in existing_containers])
694
+ existing_views = {v.view for v in copy_.views}
695
+ copy_.views.extend([v for v in verified.views if v.view not in existing_views])
696
+ if self.include_properties:
697
+ existing_properties = {(p.view, p.view_property) for p in copy_.properties}
698
+ copy_.properties.extend(
699
+ [p for p in verified.properties if (p.view, p.view_property) not in existing_properties]
700
+ )
701
+
702
+ return copy_
703
+
704
+ @property
705
+ def description(self) -> str:
706
+ return "Included referenced views and containers in the data model."
707
+
708
+
709
+ class AddClassImplements(RulesTransformer[InformationRules, InformationRules]):
710
+ def __init__(self, implements: str, suffix: str):
711
+ self.implements = implements
712
+ self.suffix = suffix
713
+
714
+ def transform(self, rules: InformationRules) -> InformationRules:
715
+ info_rules = rules
716
+ output = info_rules.model_copy(deep=True)
717
+ for class_ in output.classes:
718
+ if class_.class_.suffix.endswith(self.suffix):
719
+ class_.implements = [ClassEntity(prefix=class_.class_.prefix, suffix=self.implements)]
720
+ output.metadata.version = f"{output.metadata.version}.implements_{self.implements}"
721
+ return output
722
+
723
+ @property
724
+ def description(self) -> str:
725
+ return f"Added implements property to classes with suffix {self.suffix}"
726
+
640
727
 
641
728
  class _InformationRulesConverter:
642
729
  _edge_properties: ClassVar[frozenset[str]] = frozenset({"endNode", "end_node", "startNode", "start_node"})
@@ -9,7 +9,6 @@ from cognite.client import data_modeling as dm
9
9
  from cognite.neat._client import NeatClient
10
10
  from cognite.neat._issues.errors import CDFMissingClientError, NeatValueError, ResourceNotFoundError
11
11
  from cognite.neat._issues.warnings import NeatValueWarning, PropertyOverwritingWarning
12
- from cognite.neat._rules._shared import JustRules, OutRules
13
12
  from cognite.neat._rules.models import DMSRules, SheetList
14
13
  from cognite.neat._rules.models.data_types import Enum
15
14
  from cognite.neat._rules.models.dms import DMSEnum, DMSProperty, DMSView
@@ -55,8 +54,8 @@ class MapOneToOne(MapOntoTransformers):
55
54
  self.view_extension_mapping = view_extension_mapping
56
55
  self.default_extension = default_extension
57
56
 
58
- def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
59
- solution: DMSRules = self._to_rules(rules)
57
+ def transform(self, rules: DMSRules) -> DMSRules:
58
+ solution: DMSRules = rules
60
59
  view_by_external_id = {view.view.external_id: view for view in solution.views}
61
60
  ref_view_by_external_id = {view.view.external_id: view for view in self.reference.views}
62
61
 
@@ -99,7 +98,7 @@ class MapOneToOne(MapOntoTransformers):
99
98
  prop.container = ref_prop.container
100
99
  prop.container_property = ref_prop.container_property
101
100
 
102
- return JustRules(solution)
101
+ return solution
103
102
 
104
103
 
105
104
  class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
@@ -128,12 +127,11 @@ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
128
127
  def _property_by_view_property(self) -> dict[tuple[str, str], DMSProperty]:
129
128
  return {(prop.view.external_id, prop.view_property): prop for prop in self.mapping.properties}
130
129
 
131
- def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
130
+ def transform(self, rules: DMSRules) -> DMSRules:
132
131
  if self.data_type_conflict != "overwrite":
133
132
  raise NeatValueError(f"Invalid data_type_conflict: {self.data_type_conflict}")
134
- input_rules = self._to_rules(rules)
133
+ input_rules = rules
135
134
  new_rules = input_rules.model_copy(deep=True)
136
- new_rules.metadata.version += "_mapped"
137
135
 
138
136
  for view in new_rules.views:
139
137
  if mapping_view := self._view_by_entity_id.get(view.view.external_id):
@@ -183,7 +181,7 @@ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
183
181
  if item.collection in new_enums:
184
182
  new_rules.enum.append(item)
185
183
 
186
- return JustRules(new_rules)
184
+ return new_rules
187
185
 
188
186
  def _find_overwrites(self, prop: DMSProperty, mapping_prop: DMSProperty) -> tuple[dict[str, Any], list[str]]:
189
187
  """Finds the properties that need to be overwritten and returns them.
@@ -212,6 +210,10 @@ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
212
210
  conflicts.append(mapping_prop.model_fields[field_name].alias or field_name)
213
211
  return to_overwrite, conflicts
214
212
 
213
+ @property
214
+ def description(self) -> str:
215
+ return f"Mapping to {self.mapping.metadata.as_data_model_id()!r}."
216
+
215
217
 
216
218
  class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
217
219
  """Looks up all view properties that map to the same container property,
@@ -221,10 +223,9 @@ class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
221
223
  def __init__(self, client: NeatClient | None = None) -> None:
222
224
  self._client = client
223
225
 
224
- def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
225
- input_rules = self._to_rules(rules)
226
+ def transform(self, rules: DMSRules) -> DMSRules:
227
+ input_rules = rules
226
228
  new_rules = input_rules.model_copy(deep=True)
227
- new_rules.metadata.version += "_as_parent_name"
228
229
 
229
230
  path_by_view = self._inheritance_path_by_view(new_rules)
230
231
  view_by_container_property = self._view_by_container_properties(new_rules)
@@ -240,7 +241,7 @@ class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
240
241
  ):
241
242
  prop.view_property = parent_name
242
243
 
243
- return JustRules(new_rules)
244
+ return new_rules
244
245
 
245
246
  # Todo: Move into Probe class. Note this means that the Probe class must take a NeatClient as an argument.
246
247
  def _inheritance_path_by_view(self, rules: DMSRules) -> dict[ViewEntity, list[ViewEntity]]:
@@ -338,3 +339,7 @@ class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
338
339
  _, prop_name = min(view_properties, key=lambda prop: len(path_by_view[prop[0]]))
339
340
  parent_name_by_container_property[(container, container_property)] = prop_name
340
341
  return parent_name_by_container_property
342
+
343
+ @property
344
+ def description(self) -> str:
345
+ return "Renaming property names to parent name"
@@ -1,14 +1,11 @@
1
1
  from abc import ABC
2
- from typing import Any, Literal
3
2
 
3
+ from cognite.neat._client import NeatClient
4
4
  from cognite.neat._issues import IssueList, MultiValueError, NeatError, NeatWarning, catch_issues
5
- from cognite.neat._issues.errors import NeatTypeError
5
+ from cognite.neat._issues.errors import NeatTypeError, NeatValueError
6
6
  from cognite.neat._rules._shared import (
7
- InputRules,
8
- MaybeRules,
9
- OutRules,
10
7
  ReadRules,
11
- T_InputRules,
8
+ T_ReadInputRules,
12
9
  T_VerifiedRules,
13
10
  VerifiedRules,
14
11
  )
@@ -24,78 +21,94 @@ from cognite.neat._rules.models.information import InformationValidation
24
21
  from ._base import RulesTransformer
25
22
 
26
23
 
27
- class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], ABC):
24
+ class VerificationTransformer(RulesTransformer[T_ReadInputRules, T_VerifiedRules], ABC):
28
25
  """Base class for all verification transformers."""
29
26
 
30
27
  _rules_cls: type[T_VerifiedRules]
31
28
  _validation_cls: type
32
29
 
33
- def __init__(self, errors: Literal["raise", "continue"], validate: bool = True) -> None:
34
- self.errors = errors
30
+ def __init__(self, validate: bool = True, client: NeatClient | None = None) -> None:
35
31
  self.validate = validate
32
+ self._client = client
36
33
 
37
- def transform(self, rules: T_InputRules | OutRules[T_InputRules]) -> MaybeRules[T_VerifiedRules]:
38
- issues = IssueList()
39
- in_: T_InputRules = self._to_rules(rules)
40
- error_args: dict[str, Any] = {}
41
- if isinstance(rules, ReadRules):
42
- error_args = rules.read_context
34
+ def transform(self, rules: T_ReadInputRules) -> T_VerifiedRules:
35
+ in_ = rules.rules
36
+ if in_ is None:
37
+ raise NeatValueError("Cannot verify rules. The reading of the rules failed.")
38
+ error_args = rules.read_context
43
39
  verified_rules: T_VerifiedRules | None = None
44
- with catch_issues(issues, NeatError, NeatWarning, error_args) as future:
45
- rules_cls = self._get_rules_cls(in_)
40
+ # We need to catch issues as we use the error args to provide extra context for the errors/warnings
41
+ # For example, which row in the spreadsheet the error occurred o
42
+ issues = IssueList()
43
+ with catch_issues(issues, NeatError, NeatWarning, error_args) as _:
44
+ rules_cls = self._get_rules_cls(rules)
46
45
  dumped = in_.dump()
47
46
  verified_rules = rules_cls.model_validate(dumped) # type: ignore[assignment]
48
47
  if self.validate:
49
48
  validation_cls = self._get_validation_cls(verified_rules) # type: ignore[arg-type]
50
- validation_issues = validation_cls(verified_rules).validate()
51
- # We need to trigger warnings are raise exceptions such that they are caught by the context manager
52
- # and processed with the read context
53
- if validation_issues.warnings:
54
- validation_issues.trigger_warnings()
49
+ if issubclass(validation_cls, DMSValidation):
50
+ validation_issues = DMSValidation(verified_rules, self._client).validate() # type: ignore[arg-type]
51
+ elif issubclass(validation_cls, InformationValidation):
52
+ validation_issues = InformationValidation(verified_rules).validate() # type: ignore[arg-type]
53
+ else:
54
+ raise NeatValueError("Unsupported rule type")
55
+
56
+ # Need to trigger and raise such that the catch_issues can add the extra context
57
+ validation_issues.trigger_warnings()
55
58
  if validation_issues.has_errors:
56
- verified_rules = None
57
59
  raise MultiValueError(validation_issues.errors)
58
60
 
59
- if (future.result == "failure" or issues.has_errors or verified_rules is None) and self.errors == "raise":
60
- raise issues.as_errors()
61
- return MaybeRules[T_VerifiedRules](
62
- rules=verified_rules,
63
- issues=issues,
64
- )
61
+ # Raise issues which is expected to be handled outside of this method
62
+ issues.trigger_warnings()
63
+ if issues.has_errors:
64
+ raise MultiValueError(issues.errors)
65
+ if verified_rules is None:
66
+ raise NeatValueError("Rules were not verified")
67
+ return verified_rules
65
68
 
66
- def _get_rules_cls(self, in_: T_InputRules) -> type[T_VerifiedRules]:
69
+ def _get_rules_cls(self, in_: T_ReadInputRules) -> type[T_VerifiedRules]:
67
70
  return self._rules_cls
68
71
 
69
72
  def _get_validation_cls(self, rules: T_VerifiedRules) -> type:
70
73
  return self._validation_cls
71
74
 
75
+ @property
76
+ def description(self) -> str:
77
+ return "Verify rules"
78
+
72
79
 
73
- class VerifyDMSRules(VerificationTransformer[DMSInputRules, DMSRules]):
80
+ class VerifyDMSRules(VerificationTransformer[ReadRules[DMSInputRules], DMSRules]):
74
81
  """Class to verify DMS rules."""
75
82
 
76
83
  _rules_cls = DMSRules
77
84
  _validation_cls = DMSValidation
78
85
 
86
+ def transform(self, rules: ReadRules[DMSInputRules]) -> DMSRules:
87
+ return super().transform(rules)
79
88
 
80
- class VerifyInformationRules(VerificationTransformer[InformationInputRules, InformationRules]):
89
+
90
+ class VerifyInformationRules(VerificationTransformer[ReadRules[InformationInputRules], InformationRules]):
81
91
  """Class to verify Information rules."""
82
92
 
83
93
  _rules_cls = InformationRules
84
94
  _validation_cls = InformationValidation
85
95
 
96
+ def transform(self, rules: ReadRules[InformationInputRules]) -> InformationRules:
97
+ return super().transform(rules)
98
+
86
99
 
87
- class VerifyAnyRules(VerificationTransformer[InputRules, VerifiedRules]):
100
+ class VerifyAnyRules(VerificationTransformer[T_ReadInputRules, VerifiedRules]):
88
101
  """Class to verify arbitrary rules"""
89
102
 
90
- def _get_rules_cls(self, in_: InputRules) -> type[VerifiedRules]:
91
- if isinstance(in_, InformationInputRules):
103
+ def _get_rules_cls(self, in_: T_ReadInputRules) -> type[VerifiedRules]:
104
+ if isinstance(in_.rules, InformationInputRules):
92
105
  return InformationRules
93
- elif isinstance(in_, DMSInputRules):
106
+ elif isinstance(in_.rules, DMSInputRules):
94
107
  return DMSRules
95
108
  else:
96
109
  raise NeatTypeError(f"Unsupported rules type: {type(in_)}")
97
110
 
98
- def _get_validation_cls(self, rules: T_VerifiedRules) -> type:
111
+ def _get_validation_cls(self, rules: VerifiedRules) -> type:
99
112
  if isinstance(rules, InformationRules):
100
113
  return InformationValidation
101
114
  elif isinstance(rules, DMSRules):