cognite-neat 0.88.3__py3-none-any.whl → 0.90.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 (84) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +6 -3
  3. cognite/neat/graph/extractors/__init__.py +2 -0
  4. cognite/neat/graph/extractors/_classic_cdf/_base.py +2 -2
  5. cognite/neat/graph/extractors/_dms.py +158 -0
  6. cognite/neat/graph/extractors/_mock_graph_generator.py +50 -9
  7. cognite/neat/graph/loaders/_rdf2dms.py +16 -13
  8. cognite/neat/graph/models.py +1 -0
  9. cognite/neat/graph/queries/_base.py +4 -2
  10. cognite/neat/issues/_base.py +3 -1
  11. cognite/neat/issues/errors/__init__.py +2 -1
  12. cognite/neat/issues/errors/_general.py +7 -0
  13. cognite/neat/issues/warnings/__init__.py +2 -0
  14. cognite/neat/issues/warnings/_models.py +1 -1
  15. cognite/neat/issues/warnings/_properties.py +12 -0
  16. cognite/neat/issues/warnings/user_modeling.py +1 -1
  17. cognite/neat/rules/_shared.py +49 -6
  18. cognite/neat/rules/analysis/_base.py +1 -1
  19. cognite/neat/rules/exporters/_base.py +7 -18
  20. cognite/neat/rules/exporters/_rules2dms.py +8 -18
  21. cognite/neat/rules/exporters/_rules2excel.py +5 -12
  22. cognite/neat/rules/exporters/_rules2ontology.py +9 -19
  23. cognite/neat/rules/exporters/_rules2yaml.py +3 -6
  24. cognite/neat/rules/importers/_base.py +7 -52
  25. cognite/neat/rules/importers/_dms2rules.py +172 -116
  26. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
  27. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
  28. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
  29. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  30. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  31. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +9 -18
  32. cognite/neat/rules/importers/_rdf/_inference2rules.py +37 -65
  33. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  34. cognite/neat/rules/importers/_rdf/_shared.py +1 -1
  35. cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
  36. cognite/neat/rules/importers/_yaml2rules.py +14 -41
  37. cognite/neat/rules/models/__init__.py +21 -5
  38. cognite/neat/rules/models/_base_input.py +162 -0
  39. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  40. cognite/neat/rules/models/asset/__init__.py +5 -2
  41. cognite/neat/rules/models/asset/_rules.py +2 -20
  42. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  43. cognite/neat/rules/models/asset/_validation.py +1 -1
  44. cognite/neat/rules/models/data_types.py +150 -44
  45. cognite/neat/rules/models/dms/__init__.py +19 -7
  46. cognite/neat/rules/models/dms/_exporter.py +82 -39
  47. cognite/neat/rules/models/dms/_rules.py +42 -155
  48. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  49. cognite/neat/rules/models/dms/_serializer.py +44 -3
  50. cognite/neat/rules/models/dms/_validation.py +3 -4
  51. cognite/neat/rules/models/domain.py +52 -1
  52. cognite/neat/rules/models/entities/__init__.py +63 -0
  53. cognite/neat/rules/models/entities/_constants.py +73 -0
  54. cognite/neat/rules/models/entities/_loaders.py +76 -0
  55. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  56. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  57. cognite/neat/rules/models/entities/_types.py +86 -0
  58. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  59. cognite/neat/rules/models/information/__init__.py +10 -2
  60. cognite/neat/rules/models/information/_rules.py +3 -14
  61. cognite/neat/rules/models/information/_rules_input.py +57 -204
  62. cognite/neat/rules/models/information/_validation.py +1 -1
  63. cognite/neat/rules/transformers/__init__.py +21 -0
  64. cognite/neat/rules/transformers/_base.py +69 -3
  65. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +226 -21
  66. cognite/neat/rules/transformers/_map_onto.py +97 -0
  67. cognite/neat/rules/transformers/_pipelines.py +61 -0
  68. cognite/neat/rules/transformers/_verification.py +136 -0
  69. cognite/neat/store/_base.py +2 -2
  70. cognite/neat/store/_provenance.py +10 -1
  71. cognite/neat/utils/cdf/data_classes.py +20 -0
  72. cognite/neat/utils/regex_patterns.py +6 -0
  73. cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
  74. cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
  75. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/METADATA +1 -1
  76. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/RECORD +80 -74
  77. cognite/neat/rules/models/_constants.py +0 -2
  78. cognite/neat/rules/models/_types/__init__.py +0 -19
  79. cognite/neat/rules/models/asset/_converter.py +0 -4
  80. cognite/neat/rules/models/dms/_converter.py +0 -143
  81. /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
  82. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/LICENSE +0 -0
  83. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/WHEEL +0 -0
  84. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/entry_points.txt +0 -0
@@ -1,40 +1,117 @@
1
1
  import re
2
+ import warnings
3
+ from abc import ABC, abstractmethod
2
4
  from collections import Counter, defaultdict
3
5
  from collections.abc import Collection
4
6
  from datetime import date, datetime
5
- from typing import TYPE_CHECKING, Literal
7
+ from typing import Literal, TypeVar, cast
6
8
 
7
9
  from cognite.client.data_classes import data_modeling as dms
8
-
9
- from cognite.neat.rules.models import data_types
10
- from cognite.neat.rules.models._base import (
10
+ from rdflib import Namespace
11
+
12
+ from cognite.neat.constants import DMS_CONTAINER_PROPERTY_SIZE_LIMIT
13
+ from cognite.neat.issues.warnings.user_modeling import ParentInDifferentSpaceWarning
14
+ from cognite.neat.rules._shared import JustRules, OutRules, VerifiedRules
15
+ from cognite.neat.rules.models import (
16
+ AssetRules,
17
+ DMSRules,
18
+ DomainRules,
11
19
  ExtensionCategory,
20
+ InformationRules,
12
21
  SchemaCompleteness,
13
22
  SheetList,
23
+ data_types,
14
24
  )
15
- from cognite.neat.rules.models._constants import DMS_CONTAINER_PROPERTY_SIZE_LIMIT
16
25
  from cognite.neat.rules.models.data_types import DataType
17
- from cognite.neat.rules.models.domain import DomainRules
26
+ from cognite.neat.rules.models.dms import DMSMetadata, DMSProperty, DMSView
18
27
  from cognite.neat.rules.models.entities import (
19
28
  AssetEntity,
20
29
  AssetFields,
21
30
  ClassEntity,
22
31
  ContainerEntity,
23
32
  DMSUnknownEntity,
33
+ EdgeEntity,
24
34
  EntityTypes,
25
35
  MultiValueTypeInfo,
26
36
  ReferenceEntity,
27
37
  RelationshipEntity,
38
+ ReverseConnectionEntity,
28
39
  UnknownEntity,
29
40
  ViewEntity,
30
- ViewPropertyEntity,
31
41
  )
42
+ from cognite.neat.rules.models.information import InformationClass, InformationMetadata, InformationProperty
43
+
44
+ from ._base import RulesTransformer
45
+
46
+ T_VerifiedInRules = TypeVar("T_VerifiedInRules", bound=VerifiedRules)
47
+ T_VerifiedOutRules = TypeVar("T_VerifiedOutRules", bound=VerifiedRules)
48
+
49
+
50
+ class ConversionTransformer(RulesTransformer[T_VerifiedInRules, T_VerifiedOutRules], ABC):
51
+ """Base class for all conversion transformers."""
52
+
53
+ def transform(self, rules: T_VerifiedInRules | OutRules[T_VerifiedInRules]) -> JustRules[T_VerifiedOutRules]:
54
+ out = self._transform(self._to_rules(rules))
55
+ return JustRules(out)
56
+
57
+ @abstractmethod
58
+ def _transform(self, rules: T_VerifiedInRules) -> T_VerifiedOutRules:
59
+ raise NotImplementedError()
60
+
61
+
62
+ class InformationToDMS(ConversionTransformer[InformationRules, DMSRules]):
63
+ """Converts InformationRules to DMSRules."""
64
+
65
+ def __init__(self, ignore_undefined_value_types: bool = False):
66
+ self.ignore_undefined_value_types = ignore_undefined_value_types
67
+
68
+ def _transform(self, rules: InformationRules) -> DMSRules:
69
+ return _InformationRulesConverter(rules).as_dms_rules(self.ignore_undefined_value_types)
70
+
71
+
72
+ class InformationToAsset(ConversionTransformer[InformationRules, AssetRules]):
73
+ """Converts InformationRules to AssetRules."""
74
+
75
+ def _transform(self, rules: InformationRules) -> AssetRules:
76
+ return _InformationRulesConverter(rules).as_asset_architect_rules()
77
+
32
78
 
33
- from ._rules import InformationClass, InformationMetadata, InformationProperty, InformationRules
79
+ class AssetToInformation(ConversionTransformer[AssetRules, InformationRules]):
80
+ """Converts AssetRules to InformationRules."""
34
81
 
35
- if TYPE_CHECKING:
36
- from cognite.neat.rules.models.asset._rules import AssetRules
37
- from cognite.neat.rules.models.dms._rules import DMSMetadata, DMSProperty, DMSRules
82
+ def _transform(self, rules: AssetRules) -> InformationRules:
83
+ return InformationRules.model_validate(rules.model_dump())
84
+
85
+
86
+ class DMSToInformation(ConversionTransformer[DMSRules, InformationRules]):
87
+ """Converts DMSRules to InformationRules."""
88
+
89
+ def _transform(self, rules: DMSRules) -> InformationRules:
90
+ return _DMSRulesConverter(rules).as_information_rules()
91
+
92
+
93
+ class ConvertToRules(ConversionTransformer[VerifiedRules, VerifiedRules]):
94
+ """Converts any rules to any rules."""
95
+
96
+ def __init__(self, out_cls: type[VerifiedRules]):
97
+ self._out_cls = out_cls
98
+
99
+ def _transform(self, rules: VerifiedRules) -> VerifiedRules:
100
+ if isinstance(rules, self._out_cls):
101
+ return rules
102
+ if isinstance(rules, InformationRules) and self._out_cls is DMSRules:
103
+ return InformationToDMS().transform(rules).rules
104
+ if isinstance(rules, InformationRules) and self._out_cls is AssetRules:
105
+ return InformationToAsset().transform(rules).rules
106
+ if isinstance(rules, AssetRules) and self._out_cls is InformationRules:
107
+ return AssetToInformation().transform(rules).rules
108
+ if isinstance(rules, AssetRules) and self._out_cls is DMSRules:
109
+ return InformationToDMS().transform(AssetToInformation().transform(rules)).rules
110
+ if isinstance(rules, DMSRules) and self._out_cls is InformationRules:
111
+ return DMSToInformation().transform(rules).rules
112
+ if isinstance(rules, DMSRules) and self._out_cls is AssetRules:
113
+ return InformationToAsset().transform(DMSToInformation().transform(rules)).rules
114
+ raise ValueError(f"Unsupported conversion from {type(rules)} to {self._out_cls}")
38
115
 
39
116
 
40
117
  class _InformationRulesConverter:
@@ -79,7 +156,7 @@ class _InformationRulesConverter:
79
156
  prefixes=self.rules.prefixes,
80
157
  )
81
158
 
82
- def as_dms_rules(self) -> "DMSRules":
159
+ def as_dms_rules(self, ignore_undefined_value_types: bool = False) -> "DMSRules":
83
160
  from cognite.neat.rules.models.dms._rules import (
84
161
  DMSContainer,
85
162
  DMSProperty,
@@ -95,6 +172,8 @@ class _InformationRulesConverter:
95
172
  properties_by_class: dict[ClassEntity, list[DMSProperty]] = defaultdict(list)
96
173
  referenced_containers: dict[ContainerEntity, Counter[ClassEntity]] = defaultdict(Counter)
97
174
  for prop in self.rules.properties:
175
+ if ignore_undefined_value_types and isinstance(prop.value_type, UnknownEntity):
176
+ continue
98
177
  dms_property = self._as_dms_property(prop, default_space, default_version)
99
178
  properties_by_class[prop.class_].append(dms_property)
100
179
  if dms_property.container:
@@ -112,8 +191,10 @@ class _InformationRulesConverter:
112
191
  for cls_ in self.rules.classes
113
192
  ]
114
193
 
115
- last_dms_rules = self.rules.last.as_dms_rules() if self.rules.last else None
116
- ref_dms_rules = self.rules.reference.as_dms_rules() if self.rules.reference else None
194
+ last_dms_rules = _InformationRulesConverter(self.rules.last).as_dms_rules() if self.rules.last else None
195
+ ref_dms_rules = (
196
+ _InformationRulesConverter(self.rules.reference).as_dms_rules() if self.rules.reference else None
197
+ )
117
198
 
118
199
  class_by_entity = {cls_.class_: cls_ for cls_ in self.rules.classes}
119
200
  if self.rules.last:
@@ -197,7 +278,7 @@ class _InformationRulesConverter:
197
278
  from cognite.neat.rules.models.dms._rules import DMSProperty
198
279
 
199
280
  # returns property type, which can be ObjectProperty or DatatypeProperty
200
- value_type: DataType | ViewEntity | ViewPropertyEntity | DMSUnknownEntity
281
+ value_type: DataType | ViewEntity | DMSUnknownEntity
201
282
  if isinstance(prop.value_type, DataType):
202
283
  value_type = prop.value_type
203
284
  elif isinstance(prop.value_type, UnknownEntity):
@@ -209,17 +290,22 @@ class _InformationRulesConverter:
209
290
  else:
210
291
  raise ValueError(f"Unsupported value type: {prop.value_type.type_}")
211
292
 
212
- relation: Literal["direct", "edge", "reverse"] | None = None
213
- if isinstance(value_type, ViewEntity | ViewPropertyEntity):
214
- relation = "edge" if prop.is_list else "direct"
293
+ connection: Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None = None
294
+ if isinstance(value_type, ViewEntity):
295
+ # Default connection type.
296
+ connection = EdgeEntity() if prop.is_list else "direct"
297
+
298
+ # defaulting to direct connection
299
+ elif isinstance(value_type, DMSUnknownEntity):
300
+ connection = "direct"
215
301
 
216
302
  container: ContainerEntity | None = None
217
303
  container_property: str | None = None
218
304
  is_list: bool | None = prop.is_list
219
305
  nullable: bool | None = not prop.is_mandatory
220
- if relation == "edge":
306
+ if isinstance(connection, EdgeEntity):
221
307
  nullable = None
222
- elif relation == "direct":
308
+ elif connection == "direct":
223
309
  nullable = True
224
310
  container, container_property = self._get_container(prop, default_space)
225
311
  else:
@@ -232,7 +318,7 @@ class _InformationRulesConverter:
232
318
  value_type=value_type,
233
319
  nullable=nullable,
234
320
  is_list=is_list,
235
- connection=relation,
321
+ connection=connection,
236
322
  default=prop.default,
237
323
  reference=prop.reference,
238
324
  container=container,
@@ -326,3 +412,122 @@ class _InformationRulesConverter:
326
412
  return data_types.DateTime()
327
413
 
328
414
  return data_types.String()
415
+
416
+
417
+ class _DMSRulesConverter:
418
+ def __init__(self, dms: DMSRules):
419
+ self.dms = dms
420
+
421
+ def as_domain_rules(self) -> "DomainRules":
422
+ raise NotImplementedError("DomainRules not implemented yet")
423
+
424
+ def as_information_rules(
425
+ self,
426
+ ) -> "InformationRules":
427
+ from cognite.neat.rules.models.information._rules import (
428
+ InformationClass,
429
+ InformationProperty,
430
+ InformationRules,
431
+ )
432
+
433
+ dms = self.dms.metadata
434
+
435
+ metadata = self._convert_metadata_to_info(dms)
436
+
437
+ classes = [
438
+ InformationClass(
439
+ # we do not want a version in class as we use URI for the class
440
+ class_=ClassEntity(prefix=view.class_.prefix, suffix=view.class_.suffix),
441
+ description=view.description,
442
+ parent=[
443
+ # we do not want a version in class as we use URI for the class
444
+ implemented_view.as_class(skip_version=True)
445
+ # We only want parents in the same namespace, parent in a different namespace is a reference
446
+ for implemented_view in view.implements or []
447
+ if implemented_view.prefix == view.class_.prefix
448
+ ],
449
+ reference=self._get_class_reference(view),
450
+ )
451
+ for view in self.dms.views
452
+ ]
453
+
454
+ properties: list[InformationProperty] = []
455
+ value_type: DataType | ClassEntity | str
456
+ for property_ in self.dms.properties:
457
+ if isinstance(property_.value_type, DataType):
458
+ value_type = property_.value_type
459
+ elif isinstance(property_.value_type, ViewEntity):
460
+ value_type = ClassEntity(
461
+ prefix=property_.value_type.prefix,
462
+ suffix=property_.value_type.suffix,
463
+ )
464
+ elif isinstance(property_.value_type, DMSUnknownEntity):
465
+ value_type = UnknownEntity()
466
+ else:
467
+ raise ValueError(f"Unsupported value type: {property_.value_type.type_}")
468
+
469
+ properties.append(
470
+ InformationProperty(
471
+ # Removing version
472
+ class_=ClassEntity(suffix=property_.class_.suffix, prefix=property_.class_.prefix),
473
+ property_=property_.view_property,
474
+ value_type=value_type,
475
+ description=property_.description,
476
+ min_count=0 if property_.nullable or property_.nullable is None else 1,
477
+ max_count=float("inf") if property_.is_list or property_.nullable is None else 1,
478
+ reference=self._get_property_reference(property_),
479
+ )
480
+ )
481
+
482
+ return InformationRules(
483
+ metadata=metadata,
484
+ properties=SheetList[InformationProperty](data=properties),
485
+ classes=SheetList[InformationClass](data=classes),
486
+ last=_DMSRulesConverter(self.dms.last).as_information_rules() if self.dms.last else None,
487
+ reference=_DMSRulesConverter(self.dms.reference).as_information_rules() if self.dms.reference else None,
488
+ )
489
+
490
+ @classmethod
491
+ def _convert_metadata_to_info(cls, metadata: DMSMetadata) -> "InformationMetadata":
492
+ from cognite.neat.rules.models.information._rules import InformationMetadata
493
+
494
+ prefix = metadata.space
495
+ return InformationMetadata(
496
+ schema_=metadata.schema_,
497
+ data_model_type=metadata.data_model_type,
498
+ extension=metadata.extension,
499
+ prefix=prefix,
500
+ namespace=Namespace(f"https://purl.orgl/neat/{prefix}/"),
501
+ version=metadata.version,
502
+ description=metadata.description,
503
+ name=metadata.name or metadata.external_id,
504
+ creator=metadata.creator,
505
+ created=metadata.created,
506
+ updated=metadata.updated,
507
+ )
508
+
509
+ @classmethod
510
+ def _get_class_reference(cls, view: DMSView) -> ReferenceEntity | None:
511
+ parents_other_namespace = [parent for parent in view.implements or [] if parent.prefix != view.class_.prefix]
512
+ if len(parents_other_namespace) == 0:
513
+ return None
514
+ if len(parents_other_namespace) > 1:
515
+ warnings.warn(
516
+ ParentInDifferentSpaceWarning(view.view.as_id()),
517
+ stacklevel=2,
518
+ )
519
+ other_parent = parents_other_namespace[0]
520
+
521
+ return ReferenceEntity(prefix=other_parent.prefix, suffix=other_parent.suffix)
522
+
523
+ @classmethod
524
+ def _get_property_reference(cls, property_: DMSProperty) -> ReferenceEntity | None:
525
+ has_container_other_namespace = property_.container and property_.container.prefix != property_.class_.prefix
526
+ if not has_container_other_namespace:
527
+ return None
528
+ container = cast(ContainerEntity, property_.container)
529
+ return ReferenceEntity(
530
+ prefix=container.prefix,
531
+ suffix=container.suffix,
532
+ property=property_.container_property,
533
+ )
@@ -0,0 +1,97 @@
1
+ from abc import ABC
2
+ from collections import defaultdict
3
+
4
+ from cognite.neat.rules._shared import JustRules, OutRules
5
+ from cognite.neat.rules.models import DMSRules
6
+ from cognite.neat.rules.models.dms import DMSProperty
7
+ from cognite.neat.rules.models.entities import ReferenceEntity
8
+
9
+ from ._base import RulesTransformer
10
+
11
+
12
+ class MapOntoTransformers(RulesTransformer[DMSRules, DMSRules], ABC):
13
+ """Base class for transformers that map one rule onto another."""
14
+
15
+ ...
16
+
17
+
18
+ class MapOneToOne(MapOntoTransformers):
19
+ """Takes transform data models and makes it into an extension of the reference data model.
20
+
21
+ Note this transformer mutates the input rules.
22
+
23
+ The argument view_extension_mapping is a dictionary where the keys are views of this data model,
24
+ and each value is the view of the reference data model that the view should extend. For example:
25
+
26
+ ```python
27
+ view_extension_mapping = {"Pump": "Asset"}
28
+ ```
29
+
30
+ This would make the view "Pump" in this data model extend the view "Asset" in the reference data model.
31
+ Note that all the keys in the dictionary must be external ids of views in this data model,
32
+ and all the values must be external ids of views in the reference data model.
33
+
34
+ Args:
35
+ reference: The reference data model
36
+ view_extension_mapping: A dictionary mapping views in this data model to views in the reference data model
37
+ default_extension: The default view in the reference data model that views in this
38
+ data model should extend if no mapping is provided.
39
+
40
+ """
41
+
42
+ def __init__(
43
+ self, reference: DMSRules, view_extension_mapping: dict[str, str], default_extension: str | None = None
44
+ ) -> None:
45
+ self.reference = reference
46
+ self.view_extension_mapping = view_extension_mapping
47
+ self.default_extension = default_extension
48
+
49
+ def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
50
+ solution: DMSRules = self._to_rules(rules)
51
+ if solution.reference is not None:
52
+ raise ValueError("Reference already exists")
53
+ solution.reference = self.reference
54
+ view_by_external_id = {view.view.external_id: view for view in solution.views}
55
+ ref_view_by_external_id = {view.view.external_id: view for view in self.reference.views}
56
+
57
+ if invalid_views := set(self.view_extension_mapping.keys()) - set(view_by_external_id.keys()):
58
+ raise ValueError(f"Views are not in this dat model {invalid_views}")
59
+ if invalid_views := set(self.view_extension_mapping.values()) - set(ref_view_by_external_id.keys()):
60
+ raise ValueError(f"Views are not in the reference data model {invalid_views}")
61
+ if self.default_extension and self.default_extension not in ref_view_by_external_id:
62
+ raise ValueError(f"Default extension view not in the reference data model {self.default_extension}")
63
+
64
+ properties_by_view_external_id: dict[str, dict[str, DMSProperty]] = defaultdict(dict)
65
+ for prop in solution.properties:
66
+ properties_by_view_external_id[prop.view.external_id][prop.view_property] = prop
67
+
68
+ ref_properties_by_view_external_id: dict[str, dict[str, DMSProperty]] = defaultdict(dict)
69
+ for prop in self.reference.properties:
70
+ ref_properties_by_view_external_id[prop.view.external_id][prop.view_property] = prop
71
+
72
+ for view_external_id, view in view_by_external_id.items():
73
+ if view_external_id in self.view_extension_mapping:
74
+ ref_external_id = self.view_extension_mapping[view_external_id]
75
+ elif self.default_extension:
76
+ ref_external_id = self.default_extension
77
+ else:
78
+ continue
79
+
80
+ ref_view = ref_view_by_external_id[ref_external_id]
81
+ shared_properties = set(properties_by_view_external_id[view_external_id].keys()) & set(
82
+ ref_properties_by_view_external_id[ref_external_id].keys()
83
+ )
84
+ if shared_properties:
85
+ if view.implements is None:
86
+ view.implements = [ref_view.view]
87
+ elif isinstance(view.implements, list) and ref_view.view not in view.implements:
88
+ view.implements.append(ref_view.view)
89
+ for prop_name in shared_properties:
90
+ prop = properties_by_view_external_id[view_external_id][prop_name]
91
+ ref_prop = ref_properties_by_view_external_id[ref_external_id][prop_name]
92
+ if ref_prop.container and ref_prop.container_property:
93
+ prop.container = ref_prop.container
94
+ prop.container_property = ref_prop.container_property
95
+ prop.reference = ReferenceEntity.from_entity(ref_prop.view, ref_prop.view_property)
96
+
97
+ return JustRules(solution)
@@ -0,0 +1,61 @@
1
+ from collections.abc import Iterable
2
+
3
+ from cognite.neat.issues.errors import NeatValueError
4
+ from cognite.neat.rules._shared import InputRules, MaybeRules, VerifiedRules
5
+ from cognite.neat.rules.importers import BaseImporter
6
+ from cognite.neat.rules.models import VERIFIED_RULES_BY_ROLE, RoleTypes
7
+
8
+ from ._base import RulesPipeline, RulesTransformer
9
+ from ._converters import ConvertToRules
10
+ from ._verification import VerifyAnyRules
11
+
12
+
13
+ class ImporterPipeline(RulesPipeline[InputRules, VerifiedRules]):
14
+ """This is a standard pipeline that verifies, convert and return the rules from the importer."""
15
+
16
+ def __init__(
17
+ self,
18
+ importer: BaseImporter[InputRules],
19
+ items: Iterable[RulesTransformer[InputRules, VerifiedRules]] | None = None,
20
+ ) -> None:
21
+ super().__init__(items or [])
22
+ self._importer = importer
23
+
24
+ @classmethod
25
+ def _create_pipeline(cls, importer: BaseImporter[InputRules], role: RoleTypes | None = None) -> "ImporterPipeline":
26
+ items: list[RulesTransformer] = [VerifyAnyRules(errors="continue")]
27
+ if role is not None:
28
+ out_cls = VERIFIED_RULES_BY_ROLE[role]
29
+ items.append(ConvertToRules(out_cls))
30
+ return cls(importer, items)
31
+
32
+ @classmethod
33
+ def try_verify(cls, importer: BaseImporter, role: RoleTypes | None = None) -> MaybeRules[VerifiedRules]:
34
+ """This is a standard pipeline that verifies, convert and return the rules from the importer.
35
+
36
+ Args:
37
+ importer: The importer to use.
38
+ role: The type of rules to convert to. If None, the rules will not be converted.
39
+
40
+ Returns:
41
+ The verified rules.
42
+ """
43
+ return cls._create_pipeline(importer, role).try_execute()
44
+
45
+ @classmethod
46
+ def verify(cls, importer: BaseImporter, role: RoleTypes | None = None) -> VerifiedRules:
47
+ """Verify the rules."""
48
+ return cls._create_pipeline(importer, role).execute()
49
+
50
+ def try_execute(self) -> MaybeRules[VerifiedRules]:
51
+ """Try to execute the pipeline from importer to rules."""
52
+ rules = self._importer.to_rules()
53
+ return self.try_transform(rules)
54
+
55
+ def execute(self) -> VerifiedRules:
56
+ """Execute the pipeline from importer to rules."""
57
+ rules = self._importer.to_rules()
58
+ out = self.transform(rules).get_rules()
59
+ if out is None:
60
+ raise NeatValueError("Failed to convert rules")
61
+ return out
@@ -0,0 +1,136 @@
1
+ import warnings
2
+ from abc import ABC
3
+ from collections.abc import Iterator
4
+ from contextlib import contextmanager
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import ValidationError
8
+
9
+ from cognite.neat.issues import IssueList, NeatError, NeatWarning
10
+ from cognite.neat.issues.errors import NeatTypeError
11
+ from cognite.neat.rules._shared import (
12
+ InputRules,
13
+ MaybeRules,
14
+ OutRules,
15
+ ReadRules,
16
+ T_InputRules,
17
+ T_VerifiedRules,
18
+ VerifiedRules,
19
+ )
20
+ from cognite.neat.rules.models import (
21
+ AssetInputRules,
22
+ AssetRules,
23
+ DMSInputRules,
24
+ DMSRules,
25
+ DomainInputRules,
26
+ DomainRules,
27
+ InformationInputRules,
28
+ InformationRules,
29
+ )
30
+
31
+ from ._base import RulesTransformer
32
+
33
+
34
+ class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], ABC):
35
+ """Base class for all verification transformers."""
36
+
37
+ _rules_cls: type[T_VerifiedRules]
38
+
39
+ def __init__(self, errors: Literal["raise", "continue"]) -> None:
40
+ self.errors = errors
41
+
42
+ def transform(self, rules: T_InputRules | OutRules[T_InputRules]) -> MaybeRules[T_VerifiedRules]:
43
+ issues = IssueList()
44
+ in_: T_InputRules = self._to_rules(rules)
45
+ error_args: dict[str, Any] = {}
46
+ if isinstance(rules, ReadRules):
47
+ error_args = rules.read_context
48
+ verified_rules: T_VerifiedRules | None = None
49
+ with _handle_issues(issues, NeatError, NeatWarning, error_args) as future:
50
+ rules_cls = self._get_rules_cls(in_)
51
+ verified_rules = rules_cls.model_validate(in_.dump()) # type: ignore[assignment]
52
+
53
+ if (future.result == "failure" or issues.has_errors or verified_rules is None) and self.errors == "raise":
54
+ raise issues.as_errors()
55
+ return MaybeRules[T_VerifiedRules](
56
+ rules=verified_rules,
57
+ issues=issues,
58
+ )
59
+
60
+ def _get_rules_cls(self, in_: T_InputRules) -> type[T_VerifiedRules]:
61
+ return self._rules_cls
62
+
63
+
64
+ class VerifyDMSRules(VerificationTransformer[DMSInputRules, DMSRules]):
65
+ """Class to verify DMS rules."""
66
+
67
+ _rules_cls = DMSRules
68
+
69
+
70
+ class VerifyInformationRules(VerificationTransformer[InformationInputRules, InformationRules]):
71
+ """Class to verify Information rules."""
72
+
73
+ _rules_cls = InformationRules
74
+
75
+
76
+ class VerifyAssetRules(VerificationTransformer[AssetInputRules, AssetRules]):
77
+ """Class to verify Asset rules."""
78
+
79
+ _rules_cls = AssetRules
80
+
81
+
82
+ class VerifyAnyRules(VerificationTransformer[InputRules, VerifiedRules]):
83
+ """Class to verify arbitrary rules"""
84
+
85
+ def _get_rules_cls(self, in_: InputRules) -> type[VerifiedRules]:
86
+ if isinstance(in_, InformationInputRules):
87
+ return InformationRules
88
+ elif isinstance(in_, DMSInputRules):
89
+ return DMSRules
90
+ elif isinstance(in_, AssetInputRules):
91
+ return AssetRules
92
+ elif isinstance(in_, DomainInputRules):
93
+ return DomainRules
94
+ else:
95
+ raise NeatTypeError(f"Unsupported rules type: {type(in_)}")
96
+
97
+
98
+ class _FutureResult:
99
+ def __init__(self) -> None:
100
+ self._result: Literal["success", "failure", "pending"] = "pending"
101
+
102
+ @property
103
+ def result(self) -> Literal["success", "failure", "pending"]:
104
+ return self._result
105
+
106
+
107
+ @contextmanager
108
+ def _handle_issues(
109
+ issues: IssueList,
110
+ error_cls: type[NeatError] = NeatError,
111
+ warning_cls: type[NeatWarning] = NeatWarning,
112
+ error_args: dict[str, Any] | None = None,
113
+ ) -> Iterator[_FutureResult]:
114
+ """This is an internal help function to handle issues and warnings.
115
+
116
+ Args:
117
+ issues: The issues list to append to.
118
+ error_cls: The class used to convert errors to issues.
119
+ warning_cls: The class used to convert warnings to issues.
120
+
121
+ Returns:
122
+ FutureResult: A future result object that can be used to check the result of the context manager.
123
+ """
124
+ with warnings.catch_warnings(record=True) as warning_logger:
125
+ warnings.simplefilter("always")
126
+ future_result = _FutureResult()
127
+ try:
128
+ yield future_result
129
+ except ValidationError as e:
130
+ issues.extend(error_cls.from_pydantic_errors(e.errors(), **(error_args or {})))
131
+ future_result._result = "failure"
132
+ else:
133
+ future_result._result = "success"
134
+ finally:
135
+ if warning_logger:
136
+ issues.extend([warning_cls.from_warning(warning) for warning in warning_logger]) # type: ignore[misc]
@@ -12,7 +12,7 @@ from rdflib.plugins.stores.sparqlstore import SPARQLUpdateStore
12
12
  from cognite.neat.constants import DEFAULT_NAMESPACE
13
13
  from cognite.neat.graph._shared import MIMETypes
14
14
  from cognite.neat.graph.extractors import RdfFileExtractor, TripleExtractors
15
- from cognite.neat.graph.models import Triple
15
+ from cognite.neat.graph.models import InstanceType, Triple
16
16
  from cognite.neat.graph.queries import Queries
17
17
  from cognite.neat.graph.transformers import Transformers
18
18
  from cognite.neat.rules.analysis import InformationAnalysis
@@ -173,7 +173,7 @@ class NeatGraphStore:
173
173
  )
174
174
  )
175
175
 
176
- def read(self, class_: str) -> Iterable[tuple[str, dict[str, list[str]]]]:
176
+ def read(self, class_: str) -> Iterable[tuple[str, dict[str | InstanceType, list[str]]]]:
177
177
  """Read instances for given view from the graph store."""
178
178
 
179
179
  if not self.rules:
@@ -69,6 +69,10 @@ class Change(FrozenNeatObject):
69
69
  activity: Activity
70
70
  entity: Entity
71
71
  description: str
72
+ # triples that were added to the graph store
73
+ addition: list[tuple[URIRef, URIRef, URIRef | Literal]] | None = None
74
+ # triples that were removed from the graph store
75
+ subtraction: list[tuple[URIRef, URIRef, URIRef | Literal]] | None = None
72
76
 
73
77
  def as_triples(self):
74
78
  return self.agent.as_triples() + self.activity.as_triples() + self.entity.as_triples()
@@ -77,7 +81,12 @@ class Change(FrozenNeatObject):
77
81
  def record(cls, activity: str, start: datetime, end: datetime, description: str):
78
82
  """User friendly method to record a change that occurred in the graph store."""
79
83
  agent = Agent()
80
- activity = Activity(used=activity, was_associated_with=agent, started_at_time=start, ended_at_time=end)
84
+ activity = Activity(
85
+ used=activity,
86
+ was_associated_with=agent,
87
+ started_at_time=start,
88
+ ended_at_time=end,
89
+ )
81
90
  entity = Entity(was_generated_by=activity, was_attributed_to=agent)
82
91
  return cls(agent, activity, entity, description)
83
92