cognite-neat 0.98.0__py3-none-any.whl → 0.99.1__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 (103) hide show
  1. cognite/neat/_client/__init__.py +4 -0
  2. cognite/neat/_client/_api/data_modeling_loaders.py +585 -0
  3. cognite/neat/_client/_api/schema.py +111 -0
  4. cognite/neat/_client/_api_client.py +17 -0
  5. cognite/neat/_client/data_classes/__init__.py +0 -0
  6. cognite/neat/{_utils/cdf/data_classes.py → _client/data_classes/data_modeling.py} +8 -135
  7. cognite/neat/_client/data_classes/schema.py +495 -0
  8. cognite/neat/_constants.py +27 -4
  9. cognite/neat/_graph/_shared.py +14 -15
  10. cognite/neat/_graph/extractors/_classic_cdf/_assets.py +14 -154
  11. cognite/neat/_graph/extractors/_classic_cdf/_base.py +154 -7
  12. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +25 -14
  13. cognite/neat/_graph/extractors/_classic_cdf/_data_sets.py +17 -92
  14. cognite/neat/_graph/extractors/_classic_cdf/_events.py +13 -162
  15. cognite/neat/_graph/extractors/_classic_cdf/_files.py +15 -179
  16. cognite/neat/_graph/extractors/_classic_cdf/_labels.py +32 -100
  17. cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +27 -178
  18. cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +14 -139
  19. cognite/neat/_graph/extractors/_classic_cdf/_timeseries.py +15 -173
  20. cognite/neat/_graph/extractors/_rdf_file.py +6 -7
  21. cognite/neat/_graph/loaders/_rdf2dms.py +2 -2
  22. cognite/neat/_graph/queries/_base.py +17 -1
  23. cognite/neat/_graph/transformers/_classic_cdf.py +74 -147
  24. cognite/neat/_graph/transformers/_prune_graph.py +1 -1
  25. cognite/neat/_graph/transformers/_rdfpath.py +1 -1
  26. cognite/neat/_issues/_base.py +26 -17
  27. cognite/neat/_issues/errors/__init__.py +4 -2
  28. cognite/neat/_issues/errors/_external.py +7 -0
  29. cognite/neat/_issues/errors/_properties.py +2 -7
  30. cognite/neat/_issues/errors/_resources.py +1 -1
  31. cognite/neat/_issues/warnings/__init__.py +8 -0
  32. cognite/neat/_issues/warnings/_external.py +16 -0
  33. cognite/neat/_issues/warnings/_properties.py +16 -0
  34. cognite/neat/_issues/warnings/_resources.py +26 -2
  35. cognite/neat/_issues/warnings/user_modeling.py +4 -4
  36. cognite/neat/_rules/_constants.py +8 -11
  37. cognite/neat/_rules/analysis/_base.py +8 -4
  38. cognite/neat/_rules/exporters/_base.py +3 -4
  39. cognite/neat/_rules/exporters/_rules2dms.py +33 -46
  40. cognite/neat/_rules/importers/__init__.py +1 -3
  41. cognite/neat/_rules/importers/_base.py +1 -1
  42. cognite/neat/_rules/importers/_dms2rules.py +6 -29
  43. cognite/neat/_rules/importers/_rdf/__init__.py +5 -0
  44. cognite/neat/_rules/importers/_rdf/_base.py +34 -11
  45. cognite/neat/_rules/importers/_rdf/_imf2rules.py +91 -0
  46. cognite/neat/_rules/importers/_rdf/_inference2rules.py +43 -35
  47. cognite/neat/_rules/importers/_rdf/_owl2rules.py +80 -0
  48. cognite/neat/_rules/importers/_rdf/_shared.py +138 -441
  49. cognite/neat/_rules/models/__init__.py +1 -1
  50. cognite/neat/_rules/models/_base_rules.py +22 -12
  51. cognite/neat/_rules/models/dms/__init__.py +4 -2
  52. cognite/neat/_rules/models/dms/_exporter.py +45 -48
  53. cognite/neat/_rules/models/dms/_rules.py +20 -17
  54. cognite/neat/_rules/models/dms/_rules_input.py +52 -8
  55. cognite/neat/_rules/models/dms/_validation.py +391 -119
  56. cognite/neat/_rules/models/entities/_single_value.py +32 -4
  57. cognite/neat/_rules/models/information/__init__.py +2 -0
  58. cognite/neat/_rules/models/information/_rules.py +0 -67
  59. cognite/neat/_rules/models/information/_validation.py +9 -9
  60. cognite/neat/_rules/models/mapping/__init__.py +2 -3
  61. cognite/neat/_rules/models/mapping/_classic2core.py +36 -146
  62. cognite/neat/_rules/models/mapping/_classic2core.yaml +343 -0
  63. cognite/neat/_rules/transformers/__init__.py +2 -2
  64. cognite/neat/_rules/transformers/_converters.py +110 -11
  65. cognite/neat/_rules/transformers/_mapping.py +105 -30
  66. cognite/neat/_rules/transformers/_pipelines.py +1 -1
  67. cognite/neat/_rules/transformers/_verification.py +31 -3
  68. cognite/neat/_session/_base.py +24 -8
  69. cognite/neat/_session/_drop.py +35 -0
  70. cognite/neat/_session/_inspect.py +17 -5
  71. cognite/neat/_session/_mapping.py +39 -0
  72. cognite/neat/_session/_prepare.py +219 -23
  73. cognite/neat/_session/_read.py +49 -12
  74. cognite/neat/_session/_to.py +8 -5
  75. cognite/neat/_session/exceptions.py +4 -0
  76. cognite/neat/_store/_base.py +27 -24
  77. cognite/neat/_utils/rdf_.py +34 -5
  78. cognite/neat/_version.py +1 -1
  79. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +5 -88
  80. cognite/neat/_workflows/steps/lib/current/rules_importer.py +3 -14
  81. cognite/neat/_workflows/steps/lib/current/rules_validator.py +6 -7
  82. {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.1.dist-info}/METADATA +3 -3
  83. {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.1.dist-info}/RECORD +87 -92
  84. cognite/neat/_rules/importers/_rdf/_imf2rules/__init__.py +0 -3
  85. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2classes.py +0 -86
  86. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2metadata.py +0 -29
  87. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2properties.py +0 -130
  88. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2rules.py +0 -154
  89. cognite/neat/_rules/importers/_rdf/_owl2rules/__init__.py +0 -3
  90. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2classes.py +0 -58
  91. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2metadata.py +0 -65
  92. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2properties.py +0 -59
  93. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2rules.py +0 -39
  94. cognite/neat/_rules/models/dms/_schema.py +0 -1101
  95. cognite/neat/_rules/models/mapping/_base.py +0 -131
  96. cognite/neat/_utils/cdf/loaders/__init__.py +0 -25
  97. cognite/neat/_utils/cdf/loaders/_base.py +0 -54
  98. cognite/neat/_utils/cdf/loaders/_data_modeling.py +0 -339
  99. cognite/neat/_utils/cdf/loaders/_ingestion.py +0 -167
  100. /cognite/neat/{_utils/cdf → _client/_api}/__init__.py +0 -0
  101. {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.1.dist-info}/LICENSE +0 -0
  102. {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.1.dist-info}/WHEEL +0 -0
  103. {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.1.dist-info}/entry_points.txt +0 -0
@@ -1,13 +1,16 @@
1
+ import warnings
1
2
  from abc import ABC
2
3
  from collections import defaultdict
4
+ from functools import cached_property
5
+ from typing import Any, ClassVar, Literal
3
6
 
7
+ from cognite.neat._issues.errors import NeatValueError
8
+ from cognite.neat._issues.warnings import NeatValueWarning, PropertyOverwritingWarning
4
9
  from cognite.neat._rules._shared import JustRules, OutRules
5
- from cognite.neat._rules.models import DMSRules, InformationRules
6
- from cognite.neat._rules.models._base_rules import ClassRef
7
- from cognite.neat._rules.models.dms import DMSProperty
8
- from cognite.neat._rules.models.entities import ClassEntity
9
- from cognite.neat._rules.models.information import InformationClass
10
- from cognite.neat._rules.models.mapping import RuleMapping
10
+ from cognite.neat._rules.models import DMSRules, SheetList
11
+ from cognite.neat._rules.models.data_types import Enum
12
+ from cognite.neat._rules.models.dms import DMSEnum, DMSProperty, DMSView
13
+ from cognite.neat._rules.models.entities import ViewEntity
11
14
 
12
15
  from ._base import RulesTransformer
13
16
 
@@ -96,7 +99,7 @@ class MapOneToOne(MapOntoTransformers):
96
99
  return JustRules(solution)
97
100
 
98
101
 
99
- class RuleMapper(RulesTransformer[InformationRules, InformationRules]):
102
+ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
100
103
  """Maps properties and classes using the given mapping.
101
104
 
102
105
  **Note**: This transformer mutates the input rules.
@@ -106,30 +109,102 @@ class RuleMapper(RulesTransformer[InformationRules, InformationRules]):
106
109
 
107
110
  """
108
111
 
109
- def __init__(self, mapping: RuleMapping) -> None:
112
+ _mapping_fields: ClassVar[frozenset[str]] = frozenset(
113
+ ["connection", "value_type", "nullable", "immutable", "is_list", "default", "index", "constraint"]
114
+ )
115
+
116
+ def __init__(self, mapping: DMSRules, data_type_conflict: Literal["overwrite"] = "overwrite") -> None:
110
117
  self.mapping = mapping
118
+ self.data_type_conflict = data_type_conflict
119
+
120
+ @cached_property
121
+ def _view_by_entity_id(self) -> dict[str, DMSView]:
122
+ return {view.view.external_id: view for view in self.mapping.views}
111
123
 
112
- def transform(self, rules: InformationRules | OutRules[InformationRules]) -> JustRules[InformationRules]:
124
+ @cached_property
125
+ def _property_by_view_property(self) -> dict[tuple[str, str], DMSProperty]:
126
+ return {(prop.view.external_id, prop.view_property): prop for prop in self.mapping.properties}
127
+
128
+ def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
129
+ if self.data_type_conflict != "overwrite":
130
+ raise NeatValueError(f"Invalid data_type_conflict: {self.data_type_conflict}")
113
131
  input_rules = self._to_rules(rules)
132
+ new_rules = input_rules.model_copy(deep=True)
133
+ new_rules.metadata.version += "_mapped"
114
134
 
115
- destination_by_source = self.mapping.properties.as_destination_by_source()
116
- destination_cls_by_source = self.mapping.classes.as_destination_by_source()
117
- used_destination_classes: set[ClassEntity] = set()
118
- for prop in input_rules.properties:
119
- if destination_prop := destination_by_source.get(prop.as_reference()):
120
- prop.class_ = destination_prop.class_
121
- prop.property_ = destination_prop.property_
122
- used_destination_classes.add(destination_prop.class_)
123
- elif destination_cls := destination_cls_by_source.get(ClassRef(Class=prop.class_)):
124
- # If the property is not in the mapping, but the class is,
125
- # then we should map the class to the destination
126
- prop.class_ = destination_cls.class_
127
-
128
- for cls_ in input_rules.classes:
129
- if destination_cls := destination_cls_by_source.get(cls_.as_reference()):
130
- cls_.class_ = destination_cls.class_
131
- existing_classes = {cls_.class_ for cls_ in input_rules.classes}
132
- for new_cls in used_destination_classes - existing_classes:
133
- input_rules.classes.append(InformationClass(class_=new_cls))
134
-
135
- return JustRules(input_rules)
135
+ for view in new_rules.views:
136
+ if mapping_view := self._view_by_entity_id.get(view.view.external_id):
137
+ view.implements = mapping_view.implements
138
+
139
+ for prop in new_rules.properties:
140
+ key = (prop.view.external_id, prop.view_property)
141
+ if key not in self._property_by_view_property:
142
+ continue
143
+ mapping_prop = self._property_by_view_property[key]
144
+ to_overwrite, conflicts = self._find_overwrites(prop, mapping_prop)
145
+ if conflicts and self.data_type_conflict == "overwrite":
146
+ warnings.warn(
147
+ PropertyOverwritingWarning(prop.view.as_id(), "view", prop.view_property, tuple(conflicts)),
148
+ stacklevel=2,
149
+ )
150
+ elif conflicts:
151
+ raise NeatValueError(f"Conflicting properties for {prop.view}.{prop.view_property}: {conflicts}")
152
+ for field_name, value in to_overwrite.items():
153
+ setattr(prop, field_name, value)
154
+ prop.container = mapping_prop.container
155
+ prop.container_property = mapping_prop.container_property
156
+
157
+ # Add missing views used as value types
158
+ existing_views = {view.view for view in new_rules.views}
159
+ new_value_types = {
160
+ prop.value_type
161
+ for prop in new_rules.properties
162
+ if isinstance(prop.value_type, ViewEntity) and prop.value_type not in existing_views
163
+ }
164
+ for new_value_type in new_value_types:
165
+ if mapping_view := self._view_by_entity_id.get(new_value_type.external_id):
166
+ new_rules.views.append(mapping_view)
167
+ else:
168
+ warnings.warn(NeatValueWarning(f"View {new_value_type} not found in mapping"), stacklevel=2)
169
+
170
+ # Add missing enums
171
+ existing_enum_collections = {item.collection for item in new_rules.enum or []}
172
+ new_enums = {
173
+ prop.value_type.collection
174
+ for prop in new_rules.properties
175
+ if isinstance(prop.value_type, Enum) and prop.value_type.collection not in existing_enum_collections
176
+ }
177
+ if new_enums:
178
+ new_rules.enum = new_rules.enum or SheetList[DMSEnum]([])
179
+ for item in self.mapping.enum or []:
180
+ if item.collection in new_enums:
181
+ new_rules.enum.append(item)
182
+
183
+ return JustRules(new_rules)
184
+
185
+ def _find_overwrites(self, prop: DMSProperty, mapping_prop: DMSProperty) -> tuple[dict[str, Any], list[str]]:
186
+ """Finds the properties that need to be overwritten and returns them.
187
+
188
+ In addition, conflicting properties are returned. Note that overwriting properties that are
189
+ originally None is not considered a conflict. Thus, you can have properties to overwrite but no
190
+ conflicts.
191
+
192
+ Args:
193
+ prop: The property to compare.
194
+ mapping_prop: The property to compare against.
195
+
196
+ Returns:
197
+ A tuple with the properties to overwrite and the conflicting properties.
198
+
199
+ """
200
+ to_overwrite: dict[str, Any] = {}
201
+ conflicts: list[str] = []
202
+ for field_name in self._mapping_fields:
203
+ mapping_value = getattr(mapping_prop, field_name)
204
+ source_value = getattr(prop, field_name)
205
+ if mapping_value != source_value:
206
+ to_overwrite[field_name] = mapping_value
207
+ if source_value is not None:
208
+ # These are used for warnings so we use the alias to make it more readable for the user
209
+ conflicts.append(mapping_prop.model_fields[field_name].alias or field_name)
210
+ return to_overwrite, conflicts
@@ -23,7 +23,7 @@ class ImporterPipeline(RulesPipeline[InputRules, VerifiedRules]):
23
23
 
24
24
  @classmethod
25
25
  def _create_pipeline(cls, importer: BaseImporter[InputRules], role: RoleTypes | None = None) -> "ImporterPipeline":
26
- items: list[RulesTransformer] = [VerifyAnyRules(errors="continue")]
26
+ items: list[RulesTransformer] = [VerifyAnyRules(errors="continue", validate=True)]
27
27
  if role is not None:
28
28
  out_cls = VERIFIED_RULES_BY_ROLE[role]
29
29
  items.append(ConvertToRules(out_cls))
@@ -1,7 +1,7 @@
1
1
  from abc import ABC
2
2
  from typing import Any, Literal
3
3
 
4
- from cognite.neat._issues import IssueList, NeatError, NeatWarning, catch_issues
4
+ from cognite.neat._issues import IssueList, MultiValueError, NeatError, NeatWarning, catch_issues
5
5
  from cognite.neat._issues.errors import NeatTypeError
6
6
  from cognite.neat._rules._shared import (
7
7
  InputRules,
@@ -18,6 +18,8 @@ from cognite.neat._rules.models import (
18
18
  InformationInputRules,
19
19
  InformationRules,
20
20
  )
21
+ from cognite.neat._rules.models.dms import DMSValidation
22
+ from cognite.neat._rules.models.information import InformationValidation
21
23
 
22
24
  from ._base import RulesTransformer
23
25
 
@@ -26,9 +28,11 @@ class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], A
26
28
  """Base class for all verification transformers."""
27
29
 
28
30
  _rules_cls: type[T_VerifiedRules]
31
+ _validation_cls: type
29
32
 
30
- def __init__(self, errors: Literal["raise", "continue"]) -> None:
33
+ def __init__(self, errors: Literal["raise", "continue"], validate: bool = True) -> None:
31
34
  self.errors = errors
35
+ self.validate = validate
32
36
 
33
37
  def transform(self, rules: T_InputRules | OutRules[T_InputRules]) -> MaybeRules[T_VerifiedRules]:
34
38
  issues = IssueList()
@@ -39,7 +43,18 @@ class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], A
39
43
  verified_rules: T_VerifiedRules | None = None
40
44
  with catch_issues(issues, NeatError, NeatWarning, error_args) as future:
41
45
  rules_cls = self._get_rules_cls(in_)
42
- verified_rules = rules_cls.model_validate(in_.dump()) # type: ignore[assignment]
46
+ dumped = in_.dump()
47
+ verified_rules = rules_cls.model_validate(dumped) # type: ignore[assignment]
48
+ if self.validate:
49
+ validation_cls = self._get_validation_cls(verified_rules) # type: ignore[arg-type]
50
+ validation_issues = validation_cls(verified_rules).validate()
51
+ # We need to trigger warnings are raise exceptions such that they are caught by the context manager
52
+ # and processed with the read context
53
+ if validation_issues.warnings:
54
+ validation_issues.trigger_warnings()
55
+ if validation_issues.has_errors:
56
+ verified_rules = None
57
+ raise MultiValueError(validation_issues.errors)
43
58
 
44
59
  if (future.result == "failure" or issues.has_errors or verified_rules is None) and self.errors == "raise":
45
60
  raise issues.as_errors()
@@ -51,17 +66,22 @@ class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], A
51
66
  def _get_rules_cls(self, in_: T_InputRules) -> type[T_VerifiedRules]:
52
67
  return self._rules_cls
53
68
 
69
+ def _get_validation_cls(self, rules: T_VerifiedRules) -> type:
70
+ return self._validation_cls
71
+
54
72
 
55
73
  class VerifyDMSRules(VerificationTransformer[DMSInputRules, DMSRules]):
56
74
  """Class to verify DMS rules."""
57
75
 
58
76
  _rules_cls = DMSRules
77
+ _validation_cls = DMSValidation
59
78
 
60
79
 
61
80
  class VerifyInformationRules(VerificationTransformer[InformationInputRules, InformationRules]):
62
81
  """Class to verify Information rules."""
63
82
 
64
83
  _rules_cls = InformationRules
84
+ _validation_cls = InformationValidation
65
85
 
66
86
 
67
87
  class VerifyAnyRules(VerificationTransformer[InputRules, VerifiedRules]):
@@ -74,3 +94,11 @@ class VerifyAnyRules(VerificationTransformer[InputRules, VerifiedRules]):
74
94
  return DMSRules
75
95
  else:
76
96
  raise NeatTypeError(f"Unsupported rules type: {type(in_)}")
97
+
98
+ def _get_validation_cls(self, rules: T_VerifiedRules) -> type:
99
+ if isinstance(rules, InformationRules):
100
+ return InformationValidation
101
+ elif isinstance(rules, DMSRules):
102
+ return DMSValidation
103
+ else:
104
+ raise NeatTypeError(f"Unsupported rules type: {type(rules)}")
@@ -5,11 +5,14 @@ from cognite.client import CogniteClient
5
5
  from cognite.client import data_modeling as dm
6
6
 
7
7
  from cognite.neat import _version
8
+ from cognite.neat._client import NeatClient
8
9
  from cognite.neat._issues import IssueList, catch_issues
9
10
  from cognite.neat._issues.errors import RegexViolationError
10
11
  from cognite.neat._rules import importers
11
12
  from cognite.neat._rules._shared import ReadRules, VerifiedRules
12
13
  from cognite.neat._rules.models import DMSRules
14
+ from cognite.neat._rules.models.dms import DMSValidation
15
+ from cognite.neat._rules.models.information import InformationValidation
13
16
  from cognite.neat._rules.models.information._rules import InformationRules
14
17
  from cognite.neat._rules.models.information._rules_input import InformationInputRules
15
18
  from cognite.neat._rules.transformers import ConvertToRules, VerifyAnyRules
@@ -18,10 +21,11 @@ from cognite.neat._store._provenance import (
18
21
  INSTANCES_ENTITY,
19
22
  Change,
20
23
  )
21
- from cognite.neat._utils.auth import _CLIENT_NAME
22
24
 
23
25
  from ._collector import _COLLECTOR, Collector
26
+ from ._drop import DropAPI
24
27
  from ._inspect import InspectAPI
28
+ from ._mapping import MappingAPI
25
29
  from ._prepare import PrepareAPI
26
30
  from ._read import ReadAPI
27
31
  from ._set import SetAPI
@@ -41,19 +45,19 @@ class NeatSession:
41
45
  verbose: bool = True,
42
46
  load_engine: Literal["newest", "cache", "skip"] = "cache",
43
47
  ) -> None:
44
- self._client = client
48
+ self._client = NeatClient(client) if client else None
45
49
  self._verbose = verbose
46
50
  self._state = SessionState(store_type=storage)
47
- self.read = ReadAPI(self._state, client, verbose)
48
- self.to = ToAPI(self._state, client, verbose)
49
- self.prepare = PrepareAPI(self._state, verbose)
51
+ self.read = ReadAPI(self._state, self._client, verbose)
52
+ self.to = ToAPI(self._state, self._client, verbose)
53
+ self.prepare = PrepareAPI(self._client, self._state, verbose)
50
54
  self.show = ShowAPI(self._state)
51
55
  self.set = SetAPI(self._state, verbose)
52
56
  self.inspect = InspectAPI(self._state)
57
+ self.mapping = MappingAPI(self._state)
58
+ self.drop = DropAPI(self._state)
53
59
  self.opt = OptAPI()
54
60
  self.opt._display()
55
- if self._client is not None and self._client._config is not None:
56
- self._client._config.client_name = _CLIENT_NAME
57
61
  if load_engine != "skip" and (engine_version := load_neat_engine(client, load_engine)):
58
62
  print(f"Neat Engine {engine_version} loaded.")
59
63
 
@@ -63,9 +67,21 @@ class NeatSession:
63
67
 
64
68
  def verify(self) -> IssueList:
65
69
  source_id, last_unverified_rule = self._state.data_model.last_unverified_rule
66
- transformer = VerifyAnyRules("continue")
70
+ transformer = VerifyAnyRules("continue", validate=False)
67
71
  start = datetime.now(timezone.utc)
68
72
  output = transformer.try_transform(last_unverified_rule)
73
+ if isinstance(output.rules, DMSRules):
74
+ issues = DMSValidation(output.rules, self._client).validate()
75
+ elif isinstance(output.rules, InformationRules):
76
+ issues = InformationValidation(output.rules).validate()
77
+ else:
78
+ raise NeatSessionError("Unsupported rule type")
79
+ if issues.has_errors:
80
+ # This is up for discussion, but I think we should not return rules that
81
+ # only pass the verification but not the validation.
82
+ output.rules = None
83
+ output.issues.extend(issues)
84
+
69
85
  end = datetime.now(timezone.utc)
70
86
 
71
87
  if output.rules:
@@ -0,0 +1,35 @@
1
+ from rdflib import URIRef
2
+
3
+ from ._state import SessionState
4
+ from .exceptions import session_class_wrapper
5
+
6
+ try:
7
+ from rich import print
8
+ except ImportError:
9
+ ...
10
+
11
+
12
+ @session_class_wrapper
13
+ class DropAPI:
14
+ def __init__(self, state: SessionState):
15
+ self._state = state
16
+
17
+ def instances(self, type: str | list[str]) -> None:
18
+ """Drop instances from the session.
19
+
20
+ Args:
21
+ type: The type of instances to drop.
22
+ """
23
+ type_list = type if isinstance(type, list) else [type]
24
+ uri_type_type = dict((v, k) for k, v in self._state.instances.store.queries.types.items())
25
+ selected_uri_by_type: dict[URIRef, str] = {}
26
+ for type_item in type_list:
27
+ if type_item not in uri_type_type:
28
+ print(f"Type {type_item} not found.")
29
+ selected_uri_by_type[uri_type_type[type_item]] = type_item
30
+
31
+ result = self._state.instances.store.queries.drop_types(list(selected_uri_by_type.keys()))
32
+
33
+ for type_uri, count in result.items():
34
+ print(f"Dropped {count} instances of type {selected_uri_by_type[type_uri]}")
35
+ return None
@@ -11,6 +11,13 @@ from cognite.neat._utils.upload import UploadResult, UploadResultCore, UploadRes
11
11
  from ._state import SessionState
12
12
  from .exceptions import session_class_wrapper
13
13
 
14
+ try:
15
+ from rich.markdown import Markdown as RichMarkdown
16
+
17
+ RICH_AVAILABLE = True
18
+ except ImportError:
19
+ RICH_AVAILABLE = False
20
+
14
21
 
15
22
  @session_class_wrapper
16
23
  class InspectAPI:
@@ -61,14 +68,19 @@ class InspectIssues:
61
68
  closest_match = set(difflib.get_close_matches(search, unique_types))
62
69
  issues = IssueList([issue for issue in issues if type(issue).__name__ in closest_match])
63
70
 
71
+ issue_str = "\n".join(
72
+ [f" * **{type(issue).__name__}**: {issue.as_message(include_type=False)}" for issue in issues]
73
+ )
74
+ markdown_str = f"### {len(issues)} issues found\n\n{issue_str}"
75
+
64
76
  if IN_NOTEBOOK:
65
77
  from IPython.display import Markdown, display
66
78
 
67
- issue_str = "\n".join(
68
- [f" * **{type(issue).__name__}**: {issue.as_message(include_type=False)}" for issue in issues]
69
- )
70
- message = f"### {len(issues)} issues found\n\n{issue_str}"
71
- display(Markdown(message))
79
+ display(Markdown(markdown_str))
80
+ elif RICH_AVAILABLE:
81
+ from rich import print
82
+
83
+ print(RichMarkdown(markdown_str))
72
84
 
73
85
  if return_dataframe:
74
86
  return issues.to_pandas()
@@ -0,0 +1,39 @@
1
+ from datetime import datetime, timezone
2
+
3
+ from cognite.neat._rules.models.mapping import load_classic_to_core_mapping
4
+ from cognite.neat._rules.transformers import RuleMapper
5
+ from cognite.neat._store._provenance import Change
6
+
7
+ from ._state import SessionState
8
+ from .exceptions import session_class_wrapper
9
+
10
+
11
+ @session_class_wrapper
12
+ class MappingAPI:
13
+ def __init__(self, state: SessionState):
14
+ self._state = state
15
+
16
+ def classic_to_core(self, org_name: str) -> None:
17
+ """Map classic types to core types.
18
+
19
+ Note this automatically creates an extended CogniteCore model.
20
+
21
+ """
22
+ source_id, rules = self._state.data_model.last_verified_dms_rules
23
+
24
+ start = datetime.now(timezone.utc)
25
+ transformer = RuleMapper(load_classic_to_core_mapping(org_name, rules.metadata.space, rules.metadata.version))
26
+ output = transformer.transform(rules)
27
+ end = datetime.now(timezone.utc)
28
+
29
+ change = Change.from_rules_activity(
30
+ output,
31
+ transformer.agent,
32
+ start,
33
+ end,
34
+ "Mapping classic to core",
35
+ self._state.data_model.provenance.source_entity(source_id)
36
+ or self._state.data_model.provenance.target_entity(source_id),
37
+ )
38
+
39
+ self._state.data_model.write(output.rules, change)