cognite-neat 0.88.2__py3-none-any.whl → 0.89.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 (129) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +3 -0
  3. cognite/neat/graph/__init__.py +0 -3
  4. cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
  5. cognite/neat/graph/loaders/_base.py +3 -3
  6. cognite/neat/graph/loaders/_rdf2asset.py +24 -25
  7. cognite/neat/graph/loaders/_rdf2dms.py +20 -15
  8. cognite/neat/issues/__init__.py +1 -3
  9. cognite/neat/issues/_base.py +261 -71
  10. cognite/neat/issues/errors/__init__.py +73 -0
  11. cognite/neat/issues/errors/_external.py +67 -0
  12. cognite/neat/issues/errors/_general.py +35 -0
  13. cognite/neat/issues/errors/_properties.py +62 -0
  14. cognite/neat/issues/errors/_resources.py +111 -0
  15. cognite/neat/issues/errors/_workflow.py +36 -0
  16. cognite/neat/issues/formatters.py +1 -1
  17. cognite/neat/issues/warnings/__init__.py +66 -0
  18. cognite/neat/issues/warnings/_external.py +40 -0
  19. cognite/neat/issues/warnings/_general.py +29 -0
  20. cognite/neat/issues/warnings/_models.py +92 -0
  21. cognite/neat/issues/warnings/_properties.py +44 -0
  22. cognite/neat/issues/warnings/_resources.py +55 -0
  23. cognite/neat/issues/warnings/user_modeling.py +113 -0
  24. cognite/neat/rules/_shared.py +53 -2
  25. cognite/neat/rules/analysis/_base.py +1 -1
  26. cognite/neat/rules/exporters/_base.py +7 -18
  27. cognite/neat/rules/exporters/_rules2dms.py +17 -20
  28. cognite/neat/rules/exporters/_rules2excel.py +9 -16
  29. cognite/neat/rules/exporters/_rules2ontology.py +77 -64
  30. cognite/neat/rules/exporters/_rules2yaml.py +6 -9
  31. cognite/neat/rules/exporters/_validation.py +11 -96
  32. cognite/neat/rules/importers/_base.py +9 -58
  33. cognite/neat/rules/importers/_dms2rules.py +188 -135
  34. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +48 -35
  35. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +36 -45
  36. cognite/neat/rules/importers/_dtdl2rules/spec.py +7 -0
  37. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +8 -4
  38. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  39. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  40. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +12 -19
  41. cognite/neat/rules/importers/_rdf/_inference2rules.py +14 -37
  42. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +1 -0
  43. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -0
  44. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  45. cognite/neat/rules/importers/_rdf/_shared.py +4 -4
  46. cognite/neat/rules/importers/_spreadsheet2rules.py +46 -97
  47. cognite/neat/rules/importers/_yaml2rules.py +32 -58
  48. cognite/neat/rules/models/__init__.py +21 -5
  49. cognite/neat/rules/models/_base_input.py +162 -0
  50. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  51. cognite/neat/rules/models/_rdfpath.py +4 -4
  52. cognite/neat/rules/models/{_types/_field.py → _types.py} +5 -10
  53. cognite/neat/rules/models/asset/__init__.py +5 -2
  54. cognite/neat/rules/models/asset/_rules.py +3 -23
  55. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  56. cognite/neat/rules/models/asset/_validation.py +14 -10
  57. cognite/neat/rules/models/data_types.py +150 -44
  58. cognite/neat/rules/models/dms/__init__.py +19 -7
  59. cognite/neat/rules/models/dms/_exporter.py +102 -34
  60. cognite/neat/rules/models/dms/_rules.py +65 -162
  61. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  62. cognite/neat/rules/models/dms/_schema.py +87 -78
  63. cognite/neat/rules/models/dms/_serializer.py +44 -3
  64. cognite/neat/rules/models/dms/_validation.py +106 -68
  65. cognite/neat/rules/models/domain.py +52 -1
  66. cognite/neat/rules/models/entities/__init__.py +63 -0
  67. cognite/neat/rules/models/entities/_constants.py +73 -0
  68. cognite/neat/rules/models/entities/_loaders.py +76 -0
  69. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  70. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  71. cognite/neat/rules/models/entities/_types.py +86 -0
  72. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  73. cognite/neat/rules/models/information/__init__.py +10 -2
  74. cognite/neat/rules/models/information/_rules.py +10 -22
  75. cognite/neat/rules/models/information/_rules_input.py +57 -204
  76. cognite/neat/rules/models/information/_validation.py +48 -25
  77. cognite/neat/rules/transformers/__init__.py +21 -0
  78. cognite/neat/rules/transformers/_base.py +81 -0
  79. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +217 -21
  80. cognite/neat/rules/transformers/_map_onto.py +97 -0
  81. cognite/neat/rules/transformers/_pipelines.py +61 -0
  82. cognite/neat/rules/transformers/_verification.py +136 -0
  83. cognite/neat/{graph/stores → store}/_provenance.py +10 -1
  84. cognite/neat/utils/auxiliary.py +2 -35
  85. cognite/neat/utils/cdf/data_classes.py +20 -0
  86. cognite/neat/utils/regex_patterns.py +6 -0
  87. cognite/neat/utils/text.py +17 -0
  88. cognite/neat/workflows/base.py +4 -4
  89. cognite/neat/workflows/cdf_store.py +3 -3
  90. cognite/neat/workflows/steps/data_contracts.py +1 -1
  91. cognite/neat/workflows/steps/lib/current/graph_extractor.py +3 -3
  92. cognite/neat/workflows/steps/lib/current/graph_loader.py +2 -2
  93. cognite/neat/workflows/steps/lib/current/graph_store.py +1 -1
  94. cognite/neat/workflows/steps/lib/current/rules_exporter.py +116 -47
  95. cognite/neat/workflows/steps/lib/current/rules_importer.py +30 -28
  96. cognite/neat/workflows/steps/lib/current/rules_validator.py +5 -6
  97. cognite/neat/workflows/steps/lib/io/io_steps.py +5 -5
  98. cognite/neat/workflows/steps_registry.py +4 -5
  99. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
  100. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +105 -106
  101. cognite/neat/exceptions.py +0 -145
  102. cognite/neat/graph/exceptions.py +0 -90
  103. cognite/neat/issues/errors/external.py +0 -21
  104. cognite/neat/issues/errors/properties.py +0 -75
  105. cognite/neat/issues/errors/resources.py +0 -123
  106. cognite/neat/issues/errors/schema.py +0 -0
  107. cognite/neat/issues/neat_warnings/__init__.py +0 -2
  108. cognite/neat/issues/neat_warnings/identifier.py +0 -27
  109. cognite/neat/issues/neat_warnings/models.py +0 -22
  110. cognite/neat/issues/neat_warnings/properties.py +0 -77
  111. cognite/neat/issues/neat_warnings/resources.py +0 -125
  112. cognite/neat/rules/issues/__init__.py +0 -22
  113. cognite/neat/rules/issues/base.py +0 -63
  114. cognite/neat/rules/issues/dms.py +0 -549
  115. cognite/neat/rules/issues/fileread.py +0 -197
  116. cognite/neat/rules/issues/ontology.py +0 -298
  117. cognite/neat/rules/issues/spreadsheet.py +0 -563
  118. cognite/neat/rules/issues/spreadsheet_file.py +0 -151
  119. cognite/neat/rules/issues/tables.py +0 -72
  120. cognite/neat/rules/models/_constants.py +0 -1
  121. cognite/neat/rules/models/_types/__init__.py +0 -19
  122. cognite/neat/rules/models/asset/_converter.py +0 -4
  123. cognite/neat/rules/models/dms/_converter.py +0 -145
  124. cognite/neat/workflows/_exceptions.py +0 -41
  125. /cognite/neat/{graph/stores → store}/__init__.py +0 -0
  126. /cognite/neat/{graph/stores → store}/_base.py +0 -0
  127. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
  128. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
  129. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/entry_points.txt +0 -0
@@ -1,40 +1,114 @@
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_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 _transform(self, rules: InformationRules) -> DMSRules:
66
+ return _InformationRulesConverter(rules).as_dms_rules()
67
+
68
+
69
+ class InformationToAsset(ConversionTransformer[InformationRules, AssetRules]):
70
+ """Converts InformationRules to AssetRules."""
71
+
72
+ def _transform(self, rules: InformationRules) -> AssetRules:
73
+ return _InformationRulesConverter(rules).as_asset_architect_rules()
74
+
32
75
 
33
- from ._rules import InformationClass, InformationMetadata, InformationProperty, InformationRules
76
+ class AssetToInformation(ConversionTransformer[AssetRules, InformationRules]):
77
+ """Converts AssetRules to InformationRules."""
34
78
 
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
79
+ def _transform(self, rules: AssetRules) -> InformationRules:
80
+ return InformationRules.model_validate(rules.model_dump())
81
+
82
+
83
+ class DMSToInformation(ConversionTransformer[DMSRules, InformationRules]):
84
+ """Converts DMSRules to InformationRules."""
85
+
86
+ def _transform(self, rules: DMSRules) -> InformationRules:
87
+ return _DMSRulesConverter(rules).as_information_rules()
88
+
89
+
90
+ class ConvertToRules(ConversionTransformer[VerifiedRules, VerifiedRules]):
91
+ """Converts any rules to any rules."""
92
+
93
+ def __init__(self, out_cls: type[VerifiedRules]):
94
+ self._out_cls = out_cls
95
+
96
+ def _transform(self, rules: VerifiedRules) -> VerifiedRules:
97
+ if isinstance(rules, self._out_cls):
98
+ return rules
99
+ if isinstance(rules, InformationRules) and self._out_cls is DMSRules:
100
+ return InformationToDMS().transform(rules).rules
101
+ if isinstance(rules, InformationRules) and self._out_cls is AssetRules:
102
+ return InformationToAsset().transform(rules).rules
103
+ if isinstance(rules, AssetRules) and self._out_cls is InformationRules:
104
+ return AssetToInformation().transform(rules).rules
105
+ if isinstance(rules, AssetRules) and self._out_cls is DMSRules:
106
+ return InformationToDMS().transform(AssetToInformation().transform(rules)).rules
107
+ if isinstance(rules, DMSRules) and self._out_cls is InformationRules:
108
+ return DMSToInformation().transform(rules).rules
109
+ if isinstance(rules, DMSRules) and self._out_cls is AssetRules:
110
+ return InformationToAsset().transform(DMSToInformation().transform(rules)).rules
111
+ raise ValueError(f"Unsupported conversion from {type(rules)} to {self._out_cls}")
38
112
 
39
113
 
40
114
  class _InformationRulesConverter:
@@ -112,8 +186,10 @@ class _InformationRulesConverter:
112
186
  for cls_ in self.rules.classes
113
187
  ]
114
188
 
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
189
+ last_dms_rules = _InformationRulesConverter(self.rules.last).as_dms_rules() if self.rules.last else None
190
+ ref_dms_rules = (
191
+ _InformationRulesConverter(self.rules.reference).as_dms_rules() if self.rules.reference else None
192
+ )
117
193
 
118
194
  class_by_entity = {cls_.class_: cls_ for cls_ in self.rules.classes}
119
195
  if self.rules.last:
@@ -197,7 +273,7 @@ class _InformationRulesConverter:
197
273
  from cognite.neat.rules.models.dms._rules import DMSProperty
198
274
 
199
275
  # returns property type, which can be ObjectProperty or DatatypeProperty
200
- value_type: DataType | ViewEntity | ViewPropertyEntity | DMSUnknownEntity
276
+ value_type: DataType | ViewEntity | DMSUnknownEntity
201
277
  if isinstance(prop.value_type, DataType):
202
278
  value_type = prop.value_type
203
279
  elif isinstance(prop.value_type, UnknownEntity):
@@ -209,17 +285,18 @@ class _InformationRulesConverter:
209
285
  else:
210
286
  raise ValueError(f"Unsupported value type: {prop.value_type.type_}")
211
287
 
212
- relation: Literal["direct", "edge", "reverse"] | None = None
213
- if isinstance(value_type, ViewEntity | ViewPropertyEntity):
214
- relation = "edge" if prop.is_list else "direct"
288
+ connection: Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None = None
289
+ if isinstance(value_type, ViewEntity):
290
+ # Default connection type.
291
+ connection = EdgeEntity() if prop.is_list else "direct"
215
292
 
216
293
  container: ContainerEntity | None = None
217
294
  container_property: str | None = None
218
295
  is_list: bool | None = prop.is_list
219
296
  nullable: bool | None = not prop.is_mandatory
220
- if relation == "edge":
297
+ if isinstance(connection, EdgeEntity):
221
298
  nullable = None
222
- elif relation == "direct":
299
+ elif connection == "direct":
223
300
  nullable = True
224
301
  container, container_property = self._get_container(prop, default_space)
225
302
  else:
@@ -232,7 +309,7 @@ class _InformationRulesConverter:
232
309
  value_type=value_type,
233
310
  nullable=nullable,
234
311
  is_list=is_list,
235
- connection=relation,
312
+ connection=connection,
236
313
  default=prop.default,
237
314
  reference=prop.reference,
238
315
  container=container,
@@ -266,7 +343,7 @@ class _InformationRulesConverter:
266
343
  else:
267
344
  container_entity = prop.class_.as_container_entity(default_space)
268
345
 
269
- while self.property_count_by_container[container_entity] >= DMS_CONTAINER_SIZE_LIMIT:
346
+ while self.property_count_by_container[container_entity] >= DMS_CONTAINER_PROPERTY_SIZE_LIMIT:
270
347
  container_entity.suffix = self._bump_suffix(container_entity.suffix)
271
348
 
272
349
  self.property_count_by_container[container_entity] += 1
@@ -326,3 +403,122 @@ class _InformationRulesConverter:
326
403
  return data_types.DateTime()
327
404
 
328
405
  return data_types.String()
406
+
407
+
408
+ class _DMSRulesConverter:
409
+ def __init__(self, dms: DMSRules):
410
+ self.dms = dms
411
+
412
+ def as_domain_rules(self) -> "DomainRules":
413
+ raise NotImplementedError("DomainRules not implemented yet")
414
+
415
+ def as_information_rules(
416
+ self,
417
+ ) -> "InformationRules":
418
+ from cognite.neat.rules.models.information._rules import (
419
+ InformationClass,
420
+ InformationProperty,
421
+ InformationRules,
422
+ )
423
+
424
+ dms = self.dms.metadata
425
+
426
+ metadata = self._convert_metadata_to_info(dms)
427
+
428
+ classes = [
429
+ InformationClass(
430
+ # we do not want a version in class as we use URI for the class
431
+ class_=ClassEntity(prefix=view.class_.prefix, suffix=view.class_.suffix),
432
+ description=view.description,
433
+ parent=[
434
+ # we do not want a version in class as we use URI for the class
435
+ implemented_view.as_class(skip_version=True)
436
+ # We only want parents in the same namespace, parent in a different namespace is a reference
437
+ for implemented_view in view.implements or []
438
+ if implemented_view.prefix == view.class_.prefix
439
+ ],
440
+ reference=self._get_class_reference(view),
441
+ )
442
+ for view in self.dms.views
443
+ ]
444
+
445
+ properties: list[InformationProperty] = []
446
+ value_type: DataType | ClassEntity | str
447
+ for property_ in self.dms.properties:
448
+ if isinstance(property_.value_type, DataType):
449
+ value_type = property_.value_type
450
+ elif isinstance(property_.value_type, ViewEntity):
451
+ value_type = ClassEntity(
452
+ prefix=property_.value_type.prefix,
453
+ suffix=property_.value_type.suffix,
454
+ )
455
+ elif isinstance(property_.value_type, DMSUnknownEntity):
456
+ value_type = UnknownEntity()
457
+ else:
458
+ raise ValueError(f"Unsupported value type: {property_.value_type.type_}")
459
+
460
+ properties.append(
461
+ InformationProperty(
462
+ # Removing version
463
+ class_=ClassEntity(suffix=property_.class_.suffix, prefix=property_.class_.prefix),
464
+ property_=property_.view_property,
465
+ value_type=value_type,
466
+ description=property_.description,
467
+ min_count=0 if property_.nullable or property_.nullable is None else 1,
468
+ max_count=float("inf") if property_.is_list or property_.nullable is None else 1,
469
+ reference=self._get_property_reference(property_),
470
+ )
471
+ )
472
+
473
+ return InformationRules(
474
+ metadata=metadata,
475
+ properties=SheetList[InformationProperty](data=properties),
476
+ classes=SheetList[InformationClass](data=classes),
477
+ last=_DMSRulesConverter(self.dms.last).as_information_rules() if self.dms.last else None,
478
+ reference=_DMSRulesConverter(self.dms.reference).as_information_rules() if self.dms.reference else None,
479
+ )
480
+
481
+ @classmethod
482
+ def _convert_metadata_to_info(cls, metadata: DMSMetadata) -> "InformationMetadata":
483
+ from cognite.neat.rules.models.information._rules import InformationMetadata
484
+
485
+ prefix = metadata.space
486
+ return InformationMetadata(
487
+ schema_=metadata.schema_,
488
+ data_model_type=metadata.data_model_type,
489
+ extension=metadata.extension,
490
+ prefix=prefix,
491
+ namespace=Namespace(f"https://purl.orgl/neat/{prefix}/"),
492
+ version=metadata.version,
493
+ description=metadata.description,
494
+ name=metadata.name or metadata.external_id,
495
+ creator=metadata.creator,
496
+ created=metadata.created,
497
+ updated=metadata.updated,
498
+ )
499
+
500
+ @classmethod
501
+ def _get_class_reference(cls, view: DMSView) -> ReferenceEntity | None:
502
+ parents_other_namespace = [parent for parent in view.implements or [] if parent.prefix != view.class_.prefix]
503
+ if len(parents_other_namespace) == 0:
504
+ return None
505
+ if len(parents_other_namespace) > 1:
506
+ warnings.warn(
507
+ ParentInDifferentSpaceWarning(view.view.as_id()),
508
+ stacklevel=2,
509
+ )
510
+ other_parent = parents_other_namespace[0]
511
+
512
+ return ReferenceEntity(prefix=other_parent.prefix, suffix=other_parent.suffix)
513
+
514
+ @classmethod
515
+ def _get_property_reference(cls, property_: DMSProperty) -> ReferenceEntity | None:
516
+ has_container_other_namespace = property_.container and property_.container.prefix != property_.class_.prefix
517
+ if not has_container_other_namespace:
518
+ return None
519
+ container = cast(ContainerEntity, property_.container)
520
+ return ReferenceEntity(
521
+ prefix=container.prefix,
522
+ suffix=container.suffix,
523
+ property=property_.container_property,
524
+ )
@@ -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]
@@ -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
 
@@ -3,15 +3,14 @@ import importlib
3
3
  import inspect
4
4
  import logging
5
5
  import time
6
- from collections.abc import Callable, Iterable
6
+ from collections.abc import Callable
7
7
  from datetime import datetime
8
8
  from functools import wraps
9
9
  from types import ModuleType
10
10
 
11
11
  from cognite.client.exceptions import CogniteDuplicatedError, CogniteReadTimeout
12
- from pydantic_core import ErrorDetails
13
12
 
14
- from cognite.neat.exceptions import NeatImportError
13
+ from cognite.neat.issues.errors import NeatImportError
15
14
 
16
15
 
17
16
  def local_import(module: str, extra: str) -> ModuleType:
@@ -119,38 +118,6 @@ def create_sha256_hash(string: str) -> str:
119
118
  return hash_value
120
119
 
121
120
 
122
- # Will likely be removed with legacy code
123
- def generate_exception_report(exceptions: list[dict] | list[ErrorDetails] | None, category: str = "") -> str:
124
- exceptions_as_dict = _order_expectations_by_type(exceptions) if exceptions else {}
125
- report = ""
126
-
127
- for exception_type in exceptions_as_dict.keys():
128
- title = f"# {category}: {exception_type}" if category else ""
129
- warnings = "\n- " + "\n- ".join(exceptions_as_dict[exception_type])
130
- report += title + warnings + "\n\n"
131
-
132
- return report
133
-
134
-
135
- def _order_expectations_by_type(
136
- exceptions: list[dict] | list[ErrorDetails],
137
- ) -> dict[str, list[str]]:
138
- exception_dict: dict[str, list[str]] = {}
139
- for exception in exceptions:
140
- if not isinstance(exception["loc"], str) and isinstance(exception["loc"], Iterable):
141
- location = f"[{'/'.join(str(e) for e in exception['loc'])}]"
142
- else:
143
- location = ""
144
-
145
- issue = f"{exception['msg']} {location}"
146
-
147
- if exception_dict.get(exception["type"]) is None:
148
- exception_dict[exception["type"]] = [issue]
149
- else:
150
- exception_dict[exception["type"]].append(issue)
151
- return exception_dict
152
-
153
-
154
121
  def string_to_ideal_type(input_string: str) -> int | bool | float | datetime | str:
155
122
  try:
156
123
  # Try converting to int