cognite-neat 0.88.3__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 (75) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +3 -0
  3. cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
  4. cognite/neat/issues/_base.py +2 -1
  5. cognite/neat/issues/errors/__init__.py +2 -1
  6. cognite/neat/issues/errors/_general.py +7 -0
  7. cognite/neat/issues/warnings/_models.py +1 -1
  8. cognite/neat/issues/warnings/user_modeling.py +1 -1
  9. cognite/neat/rules/_shared.py +49 -6
  10. cognite/neat/rules/analysis/_base.py +1 -1
  11. cognite/neat/rules/exporters/_base.py +7 -18
  12. cognite/neat/rules/exporters/_rules2dms.py +8 -18
  13. cognite/neat/rules/exporters/_rules2excel.py +5 -12
  14. cognite/neat/rules/exporters/_rules2ontology.py +9 -19
  15. cognite/neat/rules/exporters/_rules2yaml.py +3 -6
  16. cognite/neat/rules/importers/_base.py +7 -52
  17. cognite/neat/rules/importers/_dms2rules.py +171 -115
  18. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
  19. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
  20. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
  21. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  22. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  23. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +9 -18
  24. cognite/neat/rules/importers/_rdf/_inference2rules.py +10 -33
  25. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  26. cognite/neat/rules/importers/_rdf/_shared.py +1 -1
  27. cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
  28. cognite/neat/rules/importers/_yaml2rules.py +14 -41
  29. cognite/neat/rules/models/__init__.py +21 -5
  30. cognite/neat/rules/models/_base_input.py +162 -0
  31. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  32. cognite/neat/rules/models/asset/__init__.py +5 -2
  33. cognite/neat/rules/models/asset/_rules.py +2 -20
  34. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  35. cognite/neat/rules/models/asset/_validation.py +1 -1
  36. cognite/neat/rules/models/data_types.py +150 -44
  37. cognite/neat/rules/models/dms/__init__.py +19 -7
  38. cognite/neat/rules/models/dms/_exporter.py +72 -26
  39. cognite/neat/rules/models/dms/_rules.py +42 -155
  40. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  41. cognite/neat/rules/models/dms/_serializer.py +44 -3
  42. cognite/neat/rules/models/dms/_validation.py +3 -4
  43. cognite/neat/rules/models/domain.py +52 -1
  44. cognite/neat/rules/models/entities/__init__.py +63 -0
  45. cognite/neat/rules/models/entities/_constants.py +73 -0
  46. cognite/neat/rules/models/entities/_loaders.py +76 -0
  47. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  48. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  49. cognite/neat/rules/models/entities/_types.py +86 -0
  50. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  51. cognite/neat/rules/models/information/__init__.py +10 -2
  52. cognite/neat/rules/models/information/_rules.py +3 -14
  53. cognite/neat/rules/models/information/_rules_input.py +57 -204
  54. cognite/neat/rules/models/information/_validation.py +1 -1
  55. cognite/neat/rules/transformers/__init__.py +21 -0
  56. cognite/neat/rules/transformers/_base.py +69 -3
  57. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +216 -20
  58. cognite/neat/rules/transformers/_map_onto.py +97 -0
  59. cognite/neat/rules/transformers/_pipelines.py +61 -0
  60. cognite/neat/rules/transformers/_verification.py +136 -0
  61. cognite/neat/store/_provenance.py +10 -1
  62. cognite/neat/utils/cdf/data_classes.py +20 -0
  63. cognite/neat/utils/regex_patterns.py +6 -0
  64. cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
  65. cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
  66. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
  67. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +71 -66
  68. cognite/neat/rules/models/_constants.py +0 -2
  69. cognite/neat/rules/models/_types/__init__.py +0 -19
  70. cognite/neat/rules/models/asset/_converter.py +0 -4
  71. cognite/neat/rules/models/dms/_converter.py +0 -143
  72. /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
  73. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
  74. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
  75. {cognite_neat-0.88.3.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_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 _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,
@@ -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
 
@@ -268,6 +268,10 @@ class ViewApplyDict(CogniteResourceDict[ViewId, ViewApply]):
268
268
  def _as_id(cls, resource: ViewApply) -> ViewId:
269
269
  return resource.as_id()
270
270
 
271
+ @classmethod
272
+ def from_iterable(cls, iterable: Iterable[ViewApply]) -> "ViewApplyDict":
273
+ return cls({view.as_id(): view for view in iterable})
274
+
271
275
 
272
276
  class SpaceApplyDict(CogniteResourceDict[str, SpaceApply]):
273
277
  _RESOURCE = SpaceApply
@@ -276,6 +280,10 @@ class SpaceApplyDict(CogniteResourceDict[str, SpaceApply]):
276
280
  def _as_id(cls, resource: SpaceApply) -> str:
277
281
  return resource.space
278
282
 
283
+ @classmethod
284
+ def from_iterable(cls, iterable: Iterable[SpaceApply]) -> "SpaceApplyDict":
285
+ return cls({space.space: space for space in iterable})
286
+
279
287
 
280
288
  class ContainerApplyDict(CogniteResourceDict[ContainerId, ContainerApply]):
281
289
  _RESOURCE = ContainerApply
@@ -284,6 +292,10 @@ class ContainerApplyDict(CogniteResourceDict[ContainerId, ContainerApply]):
284
292
  def _as_id(cls, resource: ContainerApply) -> ContainerId:
285
293
  return resource.as_id()
286
294
 
295
+ @classmethod
296
+ def from_iterable(cls, iterable: Iterable[ContainerApply]) -> "ContainerApplyDict":
297
+ return cls({container.as_id(): container for container in iterable})
298
+
287
299
 
288
300
  class DataModelApplyDict(CogniteResourceDict[DataModelId, DataModelApply]):
289
301
  _RESOURCE = DataModelApply
@@ -292,6 +304,10 @@ class DataModelApplyDict(CogniteResourceDict[DataModelId, DataModelApply]):
292
304
  def _as_id(cls, resource: DataModelApply) -> DataModelId:
293
305
  return resource.as_id()
294
306
 
307
+ @classmethod
308
+ def from_iterable(cls, iterable: Iterable[DataModelApply]) -> "DataModelApplyDict":
309
+ return cls({data_model.as_id(): data_model for data_model in iterable})
310
+
295
311
 
296
312
  class NodeApplyDict(CogniteResourceDict[NodeId, NodeApply]):
297
313
  _RESOURCE = NodeApply
@@ -299,3 +315,7 @@ class NodeApplyDict(CogniteResourceDict[NodeId, NodeApply]):
299
315
  @classmethod
300
316
  def _as_id(cls, resource: NodeApply) -> NodeId:
301
317
  return resource.as_id()
318
+
319
+ @classmethod
320
+ def from_iterable(cls, iterable: Iterable[NodeApply]) -> "NodeApplyDict":
321
+ return cls({node.as_id(): node for node in iterable})
@@ -19,6 +19,12 @@ PROPERTY_ID_COMPLIANCE_REGEX = r"^(\*)|(?!^(Property|property)$)(^[a-zA-Z][a-zA-
19
19
  VERSION_COMPLIANCE_REGEX = r"^[a-zA-Z0-9]([.a-zA-Z0-9_-]{0,41}[a-zA-Z0-9])?$"
20
20
 
21
21
 
22
+ # This pattern ignores commas inside brackets
23
+ SPLIT_ON_COMMA_PATTERN = re.compile(r",(?![^(]*\))")
24
+ # This pattern ignores equal signs inside brackets
25
+ SPLIT_ON_EQUAL_PATTERN = re.compile(r"=(?![^(]*\))")
26
+
27
+
22
28
  class _Patterns:
23
29
  @cached_property
24
30
  def more_than_one_alphanumeric(self) -> re.Pattern: