cognite-neat 0.107.0__py3-none-any.whl → 0.109.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 (69) hide show
  1. cognite/neat/_constants.py +35 -1
  2. cognite/neat/_graph/_shared.py +4 -0
  3. cognite/neat/_graph/extractors/_classic_cdf/_base.py +115 -14
  4. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +87 -6
  5. cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +48 -12
  6. cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +19 -1
  7. cognite/neat/_graph/extractors/_dms.py +162 -47
  8. cognite/neat/_graph/extractors/_dms_graph.py +54 -4
  9. cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
  10. cognite/neat/_graph/extractors/_rdf_file.py +3 -2
  11. cognite/neat/_graph/loaders/__init__.py +1 -3
  12. cognite/neat/_graph/loaders/_rdf2dms.py +20 -10
  13. cognite/neat/_graph/queries/_base.py +144 -84
  14. cognite/neat/_graph/queries/_construct.py +1 -1
  15. cognite/neat/_graph/transformers/__init__.py +3 -1
  16. cognite/neat/_graph/transformers/_base.py +4 -4
  17. cognite/neat/_graph/transformers/_classic_cdf.py +13 -13
  18. cognite/neat/_graph/transformers/_prune_graph.py +3 -3
  19. cognite/neat/_graph/transformers/_rdfpath.py +3 -4
  20. cognite/neat/_graph/transformers/_value_type.py +71 -13
  21. cognite/neat/_issues/errors/__init__.py +2 -0
  22. cognite/neat/_issues/errors/_external.py +8 -0
  23. cognite/neat/_issues/errors/_resources.py +1 -1
  24. cognite/neat/_issues/warnings/__init__.py +0 -2
  25. cognite/neat/_issues/warnings/_models.py +1 -1
  26. cognite/neat/_issues/warnings/_properties.py +0 -8
  27. cognite/neat/_issues/warnings/_resources.py +1 -1
  28. cognite/neat/_rules/catalog/classic_model.xlsx +0 -0
  29. cognite/neat/_rules/exporters/_rules2instance_template.py +3 -3
  30. cognite/neat/_rules/exporters/_rules2yaml.py +1 -1
  31. cognite/neat/_rules/importers/__init__.py +3 -1
  32. cognite/neat/_rules/importers/_dtdl2rules/spec.py +1 -2
  33. cognite/neat/_rules/importers/_rdf/__init__.py +2 -2
  34. cognite/neat/_rules/importers/_rdf/_base.py +2 -2
  35. cognite/neat/_rules/importers/_rdf/_inference2rules.py +310 -26
  36. cognite/neat/_rules/models/_base_rules.py +22 -11
  37. cognite/neat/_rules/models/dms/_exporter.py +5 -4
  38. cognite/neat/_rules/models/dms/_rules.py +1 -8
  39. cognite/neat/_rules/models/dms/_rules_input.py +4 -0
  40. cognite/neat/_rules/models/information/_rules_input.py +5 -0
  41. cognite/neat/_rules/transformers/__init__.py +10 -3
  42. cognite/neat/_rules/transformers/_base.py +6 -1
  43. cognite/neat/_rules/transformers/_converters.py +530 -364
  44. cognite/neat/_rules/transformers/_mapping.py +4 -4
  45. cognite/neat/_session/_base.py +100 -47
  46. cognite/neat/_session/_create.py +133 -0
  47. cognite/neat/_session/_drop.py +60 -2
  48. cognite/neat/_session/_fix.py +28 -0
  49. cognite/neat/_session/_inspect.py +22 -7
  50. cognite/neat/_session/_mapping.py +8 -8
  51. cognite/neat/_session/_prepare.py +3 -247
  52. cognite/neat/_session/_read.py +138 -17
  53. cognite/neat/_session/_set.py +50 -1
  54. cognite/neat/_session/_show.py +16 -43
  55. cognite/neat/_session/_state.py +53 -52
  56. cognite/neat/_session/_to.py +11 -4
  57. cognite/neat/_session/_wizard.py +1 -1
  58. cognite/neat/_session/exceptions.py +8 -1
  59. cognite/neat/_store/_graph_store.py +301 -146
  60. cognite/neat/_store/_provenance.py +36 -20
  61. cognite/neat/_store/_rules_store.py +253 -267
  62. cognite/neat/_store/exceptions.py +40 -4
  63. cognite/neat/_utils/auth.py +5 -3
  64. cognite/neat/_version.py +1 -1
  65. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/METADATA +1 -1
  66. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/RECORD +69 -67
  67. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/LICENSE +0 -0
  68. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/WHEEL +0 -0
  69. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/entry_points.txt +0 -0
@@ -13,10 +13,10 @@ from cognite.neat._rules.models.data_types import Enum
13
13
  from cognite.neat._rules.models.dms import DMSContainer, DMSEnum, DMSProperty
14
14
  from cognite.neat._rules.models.entities import ClassEntity, ContainerEntity, ViewEntity
15
15
 
16
- from ._base import RulesTransformer
16
+ from ._base import VerifiedRulesTransformer
17
17
 
18
18
 
19
- class MapOntoTransformers(RulesTransformer[DMSRules, DMSRules], ABC):
19
+ class MapOntoTransformers(VerifiedRulesTransformer[DMSRules, DMSRules], ABC):
20
20
  """Base class for transformers that map one rule onto another."""
21
21
 
22
22
  ...
@@ -100,7 +100,7 @@ class MapOneToOne(MapOntoTransformers):
100
100
  return solution
101
101
 
102
102
 
103
- class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
103
+ class RuleMapper(VerifiedRulesTransformer[DMSRules, DMSRules]):
104
104
  """Maps properties and classes using the given mapping.
105
105
 
106
106
  Args:
@@ -232,7 +232,7 @@ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
232
232
  return f"Mapping to {self.mapping.metadata.as_data_model_id()!r}."
233
233
 
234
234
 
235
- class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
235
+ class AsParentPropertyId(VerifiedRulesTransformer[DMSRules, DMSRules]):
236
236
  """Looks up all view properties that map to the same container property,
237
237
  and changes the child view property id to match the parent property id.
238
238
  """
@@ -1,3 +1,4 @@
1
+ from pathlib import Path
1
2
  from typing import Literal
2
3
 
3
4
  from cognite.client import CogniteClient
@@ -7,14 +8,23 @@ from cognite.neat import _version
7
8
  from cognite.neat._client import NeatClient
8
9
  from cognite.neat._issues import IssueList
9
10
  from cognite.neat._issues.errors import RegexViolationError
11
+ from cognite.neat._issues.errors._general import NeatImportError
10
12
  from cognite.neat._rules import importers
11
- from cognite.neat._rules.models._base_input import InputRules
13
+ from cognite.neat._rules.models import DMSRules
12
14
  from cognite.neat._rules.models.information._rules import InformationRules
13
- from cognite.neat._rules.transformers import ConvertToRules, InformationToDMS, VerifyAnyRules
14
- from cognite.neat._rules.transformers._converters import ConversionTransformer
15
+ from cognite.neat._rules.transformers import (
16
+ InformationToDMS,
17
+ MergeDMSRules,
18
+ MergeInformationRules,
19
+ VerifyInformationRules,
20
+ )
21
+ from cognite.neat._store._rules_store import RulesEntity
22
+ from cognite.neat._utils.auxiliary import local_import
15
23
 
16
24
  from ._collector import _COLLECTOR, Collector
25
+ from ._create import CreateAPI
17
26
  from ._drop import DropAPI
27
+ from ._fix import FixAPI
18
28
  from ._inspect import InspectAPI
19
29
  from ._mapping import MappingAPI
20
30
  from ._prepare import PrepareAPI
@@ -24,7 +34,7 @@ from ._show import ShowAPI
24
34
  from ._state import SessionState
25
35
  from ._to import ToAPI
26
36
  from .engine import load_neat_engine
27
- from .exceptions import NeatSessionError, session_class_wrapper
37
+ from .exceptions import session_class_wrapper
28
38
 
29
39
 
30
40
  @session_class_wrapper
@@ -70,25 +80,42 @@ class NeatSession:
70
80
  def __init__(
71
81
  self,
72
82
  client: CogniteClient | None = None,
73
- storage: Literal["memory", "oxigraph"] = "memory",
83
+ storage: Literal["memory", "oxigraph"] | None = None,
84
+ storage_path: str | None = None,
74
85
  verbose: bool = True,
75
86
  load_engine: Literal["newest", "cache", "skip"] = "cache",
76
87
  ) -> None:
77
88
  self._verbose = verbose
78
- self._state = SessionState(store_type=storage, client=NeatClient(client) if client else None)
89
+ self._state = SessionState(
90
+ store_type=storage or self._select_most_performant_store(),
91
+ storage_path=Path(storage_path) if storage_path else None,
92
+ client=NeatClient(client) if client else None,
93
+ )
79
94
  self.read = ReadAPI(self._state, verbose)
80
95
  self.to = ToAPI(self._state, verbose)
96
+ self.fix = FixAPI(self._state, verbose)
81
97
  self.prepare = PrepareAPI(self._state, verbose)
82
98
  self.show = ShowAPI(self._state)
83
99
  self.set = SetAPI(self._state, verbose)
84
100
  self.inspect = InspectAPI(self._state)
85
101
  self.mapping = MappingAPI(self._state)
86
102
  self.drop = DropAPI(self._state)
103
+ self.create = CreateAPI(self._state)
87
104
  self.opt = OptAPI()
88
105
  self.opt._display()
89
106
  if load_engine != "skip" and (engine_version := load_neat_engine(client, load_engine)):
90
107
  print(f"Neat Engine {engine_version} loaded.")
91
108
 
109
+ def _select_most_performant_store(self) -> Literal["memory", "oxigraph"]:
110
+ """Select the most performant store based on the current environment."""
111
+
112
+ try:
113
+ local_import("pyoxigraph", "oxi")
114
+ local_import("oxrdflib", "oxi")
115
+ return "oxigraph"
116
+ except NeatImportError:
117
+ return "memory"
118
+
92
119
  @property
93
120
  def version(self) -> str:
94
121
  """Get the current version of neat.
@@ -118,46 +145,27 @@ class NeatSession:
118
145
  neat.verify()
119
146
  ```
120
147
  """
121
- transformer = VerifyAnyRules(validate=True, client=self._state.client) # type: ignore[var-annotated]
122
- issues = self._state.rule_transform(transformer)
123
- if not issues.has_errors:
124
- rules = self._state.rule_store.last_verified_rule
125
- if isinstance(rules, InformationRules):
126
- self._state.instances.store.add_rules(rules)
127
-
128
- if issues:
129
- print("You can inspect the issues with the .inspect.issues(...) method.")
130
- return issues
148
+ print("This action has no effect. Neat no longer supports unverified data models.")
149
+ return IssueList()
131
150
 
132
- def convert(self, target: Literal["dms", "information"]) -> IssueList:
151
+ def convert(self, reserved_properties: Literal["error", "warning"] = "warning") -> IssueList:
133
152
  """Converts the last verified data model to the target type.
134
153
 
135
154
  Args:
136
- target: The target type to convert the data model to.
155
+ reserved_properties: What to do with reserved properties. Can be "error" or "warning".
137
156
 
138
157
  Example:
139
158
  Convert to DMS rules
140
159
  ```python
141
- neat.convert(target="dms")
142
- ```
143
-
144
- Example:
145
- Convert to Information rules
146
- ```python
147
- neat.convert(target="information")
160
+ neat.convert()
148
161
  ```
149
162
  """
150
- converter: ConversionTransformer
151
- if target == "dms":
152
- converter = InformationToDMS()
153
- elif target == "information":
154
- converter = ConvertToRules(InformationRules)
155
- else:
156
- raise NeatSessionError(f"Target {target} not supported.")
163
+ converter = InformationToDMS(reserved_properties=reserved_properties)
164
+
157
165
  issues = self._state.rule_transform(converter)
158
166
 
159
167
  if self._verbose and not issues.has_errors:
160
- print(f"Rules converted to {target}")
168
+ print("Rules converted to dms.")
161
169
  else:
162
170
  print("Conversion failed.")
163
171
  if issues:
@@ -174,13 +182,11 @@ class NeatSession:
174
182
  "NeatInferredDataModel",
175
183
  "v1",
176
184
  ),
177
- max_number_of_instance: int = 100,
178
185
  ) -> IssueList:
179
186
  """Data model inference from instances.
180
187
 
181
188
  Args:
182
189
  model_id: The ID of the inferred data model.
183
- max_number_of_instance: The maximum number of instances to use for inference.
184
190
 
185
191
  Example:
186
192
  Infer a data model after reading a source file
@@ -191,6 +197,12 @@ class NeatSession:
191
197
  neat.infer()
192
198
  ```
193
199
  """
200
+ return self._infer_subclasses(model_id)
201
+
202
+ def _previous_inference(
203
+ self, model_id: dm.DataModelId | tuple[str, str, str], max_number_of_instance: int = 100
204
+ ) -> IssueList:
205
+ # Temporary keeping the old inference method in case we need to revert back
194
206
  model_id = dm.DataModelId.load(model_id)
195
207
  importer = importers.InferenceImporter.from_graph_store(
196
208
  store=self._state.instances.store,
@@ -199,29 +211,70 @@ class NeatSession:
199
211
  )
200
212
  return self._state.rule_import(importer)
201
213
 
214
+ def _infer_subclasses(
215
+ self,
216
+ model_id: dm.DataModelId | tuple[str, str, str] = (
217
+ "neat_space",
218
+ "NeatInferredDataModel",
219
+ "v1",
220
+ ),
221
+ ) -> IssueList:
222
+ """Infer data model from instances."""
223
+ last_entity: RulesEntity | None = None
224
+ if self._state.rule_store.provenance:
225
+ last_entity = self._state.rule_store.provenance[-1].target_entity
226
+
227
+ # Note that this importer behaves as a transformer in the rule store when there is an existing rules.
228
+ # We are essentially transforming the last entity's information rules into a new set of information rules.
229
+ importer = importers.SubclassInferenceImporter(
230
+ issue_list=IssueList(),
231
+ graph=self._state.instances.store.graph(),
232
+ rules=last_entity.information if last_entity is not None else None,
233
+ data_model_id=dm.DataModelId.load(model_id) if last_entity is None else None,
234
+ )
235
+
236
+ def action() -> tuple[InformationRules, DMSRules | None]:
237
+ unverified_information = importer.to_rules()
238
+ extra_info = VerifyInformationRules().transform(unverified_information)
239
+ if not last_entity:
240
+ return extra_info, None
241
+ merged_info = MergeInformationRules(extra_info).transform(last_entity.information)
242
+ if not last_entity.dms:
243
+ return merged_info, None
244
+ extra_dms = InformationToDMS(reserved_properties="warning").transform(extra_info)
245
+ merged_dms = MergeDMSRules(extra_dms).transform(last_entity.dms)
246
+ return merged_info, merged_dms
247
+
248
+ return self._state.rule_store.do_activity(action, importer)
249
+
202
250
  def _repr_html_(self) -> str:
203
251
  state = self._state
204
- if (
205
- not state.instances.has_store
206
- and not state.rule_store.has_unverified_rules
207
- and not state.rule_store.has_verified_rules
208
- ):
252
+ if state.instances.empty and state.rule_store.empty:
209
253
  return "<strong>Empty session</strong>. Get started by reading something with the <em>.read</em> attribute."
210
254
 
211
255
  output = []
212
256
 
213
- if state.rule_store.has_unverified_rules and not state.rule_store.has_verified_rules:
214
- rules: InputRules = state.rule_store.last_unverified_rule
215
- output.append(f"<H2>Unverified Data Model</H2><br />{rules._repr_html_()}") # type: ignore
257
+ if state.rule_store.provenance:
258
+ last_entity = state.rule_store.provenance[-1].target_entity
259
+ if last_entity.dms:
260
+ html = last_entity.dms._repr_html_()
261
+ else:
262
+ html = last_entity.information._repr_html_()
263
+ output.append(f"<H2>Data Model</H2><br />{html}") # type: ignore
216
264
 
217
- if state.rule_store.has_verified_rules:
218
- output.append(f"<H2>Verified Data Model</H2><br />{state.rule_store.last_verified_rule._repr_html_()}") # type: ignore
219
-
220
- if state.instances.has_store:
265
+ if not state.instances.empty:
221
266
  output.append(f"<H2>Instances</H2> {state.instances.store._repr_html_()}")
222
267
 
223
268
  return "<br />".join(output)
224
269
 
270
+ def close(self) -> None:
271
+ """Close the session and release resources."""
272
+ self._state.instances.store.dataset.close()
273
+
274
+ def __del__(self) -> None:
275
+ """Called by garbage collector"""
276
+ self.close()
277
+
225
278
 
226
279
  @session_class_wrapper
227
280
  class OptAPI:
@@ -0,0 +1,133 @@
1
+ from typing import Literal
2
+
3
+ from cognite.client.data_classes.data_modeling import DataModelIdentifier
4
+
5
+ from cognite.neat._issues import IssueList
6
+ from cognite.neat._rules.models.dms import DMSValidation
7
+ from cognite.neat._rules.transformers import (
8
+ IncludeReferenced,
9
+ ToDataProductModel,
10
+ ToEnterpriseModel,
11
+ ToSolutionModel,
12
+ VerifiedRulesTransformer,
13
+ )
14
+
15
+ from ._state import SessionState
16
+ from .exceptions import NeatSessionError, session_class_wrapper
17
+
18
+
19
+ @session_class_wrapper
20
+ class CreateAPI:
21
+ """
22
+ Create new data model based on the given data.
23
+ """
24
+
25
+ def __init__(self, state: SessionState):
26
+ self._state = state
27
+
28
+ def enterprise_model(
29
+ self,
30
+ data_model_id: DataModelIdentifier,
31
+ org_name: str = "My",
32
+ dummy_property: str = "GUID",
33
+ ) -> IssueList:
34
+ """Uses the current data model as a basis to create enterprise data model
35
+
36
+ Args:
37
+ data_model_id: The enterprise data model id that is being created
38
+ org_name: Organization name to use for the views in the enterprise data model.
39
+ dummy_property: The dummy property to use as placeholder for the views in the new data model.
40
+
41
+ !!! note "Enterprise Data Model Creation"
42
+
43
+ Always create an enterprise data model from a Cognite Data Model as this will
44
+ assure all the Cognite Data Fusion applications to run smoothly, such as
45
+ - Search
46
+ - Atlas AI
47
+ - ...
48
+
49
+ !!! note "Move Connections"
50
+
51
+ If you want to move the connections to the new data model, set the move_connections
52
+ to True. This will move the connections to the new data model and use new model
53
+ views as the source and target views.
54
+
55
+ """
56
+ return self._state.rule_transform(
57
+ ToEnterpriseModel(
58
+ new_model_id=data_model_id,
59
+ org_name=org_name,
60
+ dummy_property=dummy_property,
61
+ move_connections=True,
62
+ )
63
+ )
64
+
65
+ def solution_model(
66
+ self,
67
+ data_model_id: DataModelIdentifier,
68
+ direct_property: str = "enterprise",
69
+ view_prefix: str = "Enterprise",
70
+ ) -> IssueList:
71
+ """Uses the current data model as a basis to create solution data model
72
+
73
+ Args:
74
+ data_model_id: The solution data model id that is being created.
75
+ direct_property: The property to use for the direct connection between the views in the solution data model
76
+ and the enterprise data model.
77
+ view_prefix: The prefix to use for the views in the enterprise data model.
78
+
79
+ !!! note "Solution Data Model Mode"
80
+
81
+ The read-only solution model will only be able to read from the existing containers
82
+ from the enterprise data model, therefore the solution data model will not have
83
+ containers in the solution data model space. Meaning the solution data model views
84
+ will be read-only.
85
+
86
+ The write mode will have additional containers in the solution data model space,
87
+ allowing in addition to read through the solution model views, also writing to
88
+ the containers in the solution data model space.
89
+
90
+ """
91
+ return self._state.rule_transform(
92
+ ToSolutionModel(
93
+ new_model_id=data_model_id,
94
+ properties="connection",
95
+ direct_property=direct_property,
96
+ view_prefix=view_prefix,
97
+ )
98
+ )
99
+
100
+ def data_product_model(
101
+ self,
102
+ data_model_id: DataModelIdentifier,
103
+ include: Literal["same-space", "all"] = "same-space",
104
+ ) -> None:
105
+ """Uses the current data model as a basis to create data product data model.
106
+
107
+ A data product model is a data model that ONLY maps to containers and do not use implements. This is
108
+ typically used for defining the data in a data product.
109
+
110
+ Args:
111
+ data_model_id: The data product data model id that is being created.
112
+ include: The views to include in the data product data model. Can be either "same-space" or "all".
113
+ If you set same-space, only the properties of the views in the same space as the data model
114
+ will be included.
115
+ """
116
+
117
+ view_ids, container_ids = DMSValidation(
118
+ self._state.rule_store.last_verified_dms_rules
119
+ ).imported_views_and_containers_ids()
120
+ transformers: list[VerifiedRulesTransformer] = []
121
+ client = self._state.client
122
+ if (view_ids or container_ids) and client is None:
123
+ raise NeatSessionError(
124
+ "No client provided. You are referencing unknown views and containers in your data model, "
125
+ "NEAT needs a client to lookup the definitions. "
126
+ "Please set the client in the session, NeatSession(client=client)."
127
+ )
128
+ elif (view_ids or container_ids) and client:
129
+ transformers.append(IncludeReferenced(client, include_properties=True))
130
+
131
+ transformers.append(ToDataProductModel(new_model_id=data_model_id, include=include))
132
+
133
+ self._state.rule_transform(*transformers)
@@ -1,7 +1,16 @@
1
+ import difflib
2
+ from collections.abc import Collection
3
+ from typing import Literal
4
+
5
+ from cognite.client.utils.useful_types import SequenceNotStr
1
6
  from rdflib import URIRef
2
7
 
8
+ from cognite.neat._constants import COGNITE_MODELS
9
+ from cognite.neat._issues import IssueList
10
+ from cognite.neat._rules.transformers import DropModelViews
11
+
3
12
  from ._state import SessionState
4
- from .exceptions import session_class_wrapper
13
+ from .exceptions import NeatSessionError, session_class_wrapper
5
14
 
6
15
  try:
7
16
  from rich import print
@@ -17,6 +26,7 @@ class DropAPI:
17
26
 
18
27
  def __init__(self, state: SessionState):
19
28
  self._state = state
29
+ self.data_model = DropDataModelAPI(state)
20
30
 
21
31
  def instances(self, type: str | list[str]) -> None:
22
32
  """Drop instances from the session.
@@ -31,7 +41,11 @@ class DropAPI:
31
41
  ```
32
42
  """
33
43
  type_list = type if isinstance(type, list) else [type]
34
- uri_type_type = dict((v, k) for k, v in self._state.instances.store.queries.types.items())
44
+
45
+ # Temporary solution until we agree on the form of specifying named graphs
46
+ # it will default to the default named graph
47
+ named_graph = self._state.instances.store.default_named_graph
48
+ uri_type_type = dict((v, k) for k, v in self._state.instances.store.queries.types(named_graph).items())
35
49
  selected_uri_by_type: dict[URIRef, str] = {}
36
50
  for type_item in type_list:
37
51
  if type_item not in uri_type_type:
@@ -43,3 +57,47 @@ class DropAPI:
43
57
  for type_uri, count in result.items():
44
58
  print(f"Dropped {count} instances of type {selected_uri_by_type[type_uri]}")
45
59
  return None
60
+
61
+
62
+ @session_class_wrapper
63
+ class DropDataModelAPI:
64
+ def __init__(self, state: SessionState):
65
+ self._state = state
66
+
67
+ def views(
68
+ self,
69
+ view_external_id: str | SequenceNotStr[str] | None = None,
70
+ group: Literal["3D", "Annotation", "BaseViews"]
71
+ | Collection[Literal["3D", "Annotation", "BaseViews"]]
72
+ | None = None,
73
+ ) -> IssueList:
74
+ """Drops views from the data model.
75
+ Args:
76
+ view_external_id: The externalId of the view to drop.
77
+ group: Only applies to CogniteCore model. This is a shorthand for dropping multiple views at once.
78
+ """
79
+ if sum([view_external_id is not None, group is not None]) != 1:
80
+ raise NeatSessionError("Only one of view_external_id or group can be specified.")
81
+ last_dms = self._state.rule_store.last_verified_dms_rules
82
+ if group is not None and last_dms.metadata.as_data_model_id() not in COGNITE_MODELS:
83
+ raise NeatSessionError("Group can only be specified for CogniteCore models.")
84
+ if view_external_id is not None:
85
+ existing_views = {view.view.external_id for view in last_dms.views}
86
+ requested_views = {view_external_id} if isinstance(view_external_id, str) else set(view_external_id)
87
+ missing_views = requested_views - existing_views
88
+ if missing_views:
89
+ suggestions: list[str] = []
90
+ for view in missing_views:
91
+ suggestion = difflib.get_close_matches(view, existing_views, n=1)
92
+ if suggestion:
93
+ suggestions.append(f"{view} -> {suggestion[0]}")
94
+ else:
95
+ suggestions.append(f"{view} -> NOT FOUND")
96
+ raise NeatSessionError(
97
+ f"{len(missing_views)} view(s) not found in the data model.\nDid you mean {', '.join(suggestions)}?"
98
+ )
99
+ before = len(last_dms.views)
100
+ issues = self._state.rule_transform(DropModelViews(view_external_id, group))
101
+ after = len(self._state.rule_store.last_verified_dms_rules.views)
102
+ print(f"Dropped {before - after} views.")
103
+ return issues
@@ -0,0 +1,28 @@
1
+ from cognite.neat._issues._base import IssueList
2
+ from cognite.neat._rules.transformers import (
3
+ ToCompliantEntities,
4
+ )
5
+
6
+ from ._state import SessionState
7
+ from .exceptions import session_class_wrapper
8
+
9
+
10
+ @session_class_wrapper
11
+ class FixAPI:
12
+ """Apply variety of fix methods to data model and isntances"""
13
+
14
+ def __init__(self, state: SessionState, verbose: bool) -> None:
15
+ self._state = state
16
+ self._verbose = verbose
17
+ self.data_model = DataModelFixAPI(state, verbose)
18
+
19
+
20
+ @session_class_wrapper
21
+ class DataModelFixAPI:
22
+ def __init__(self, state: SessionState, verbose: bool) -> None:
23
+ self._state = state
24
+ self._verbose = verbose
25
+
26
+ def cdf_compliant_external_ids(self) -> IssueList:
27
+ """Convert (information/logical) data model component external ids to CDF compliant entities."""
28
+ return self._state.rule_transform(ToCompliantEntities())
@@ -48,7 +48,6 @@ class InspectAPI:
48
48
  self.issues = InspectIssues(state)
49
49
  self.outcome = InspectOutcome(state)
50
50
 
51
- @property
52
51
  def properties(self) -> pd.DataFrame:
53
52
  """Returns the properties of the current data model.
54
53
 
@@ -59,7 +58,23 @@ class InspectAPI:
59
58
  neat.inspect.properties
60
59
  ```
61
60
  """
62
- df = self._state.rule_store.last_verified_rule.properties.to_pandas()
61
+ if self._state.rule_store.empty:
62
+ return pd.DataFrame()
63
+ last_entity = self._state.rule_store.provenance[-1].target_entity
64
+ if last_entity.dms:
65
+ df = last_entity.dms.properties.to_pandas()
66
+ else:
67
+ df = last_entity.information.properties.to_pandas()
68
+ df.drop(columns=["neatId"], errors="ignore", inplace=True)
69
+ return df
70
+
71
+ def views(self) -> pd.DataFrame:
72
+ if self._state.rule_store.empty:
73
+ return pd.DataFrame()
74
+ last_entity = self._state.rule_store.provenance[-1].target_entity
75
+ if last_entity.dms is None:
76
+ return pd.DataFrame()
77
+ df = last_entity.dms.views.to_pandas()
63
78
  df.drop(columns=["neatId"], errors="ignore", inplace=True)
64
79
  return df
65
80
 
@@ -91,10 +106,10 @@ class InspectIssues:
91
106
  return_dataframe: bool = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
92
107
  ) -> pd.DataFrame | None:
93
108
  """Returns the issues of the current data model."""
94
- if self._state.rule_store.provenance:
95
- issues = self._state.rule_store.last_issues
96
- elif self._state.instances.store.provenance:
97
- issues = self._state.instances.store.provenance[-1].target_entity.issues
109
+ issues = self._state.rule_store.last_issues
110
+ if issues is None and self._state.instances.store.provenance:
111
+ last_change = self._state.instances.store.provenance[-1]
112
+ issues = last_change.target_entity.issues
98
113
  else:
99
114
  self._print("No issues found.")
100
115
  return pd.DataFrame() if return_dataframe else None
@@ -231,7 +246,7 @@ class InspectUploadOutcome:
231
246
  if i < 50:
232
247
  lines.append(f" * {v}")
233
248
  elif i == 50 and total > 50:
234
- lines.append(f" * ... {total-50} more")
249
+ lines.append(f" * ... {total - 50} more")
235
250
  elif i == 50 and total == 50:
236
251
  lines.append(f" * {v}")
237
252
  else:
@@ -1,12 +1,11 @@
1
1
  from cognite.neat._issues import IssueList
2
- from cognite.neat._rules.models import DMSRules
3
2
  from cognite.neat._rules.models.mapping import load_classic_to_core_mapping
4
3
  from cognite.neat._rules.transformers import (
5
4
  AsParentPropertyId,
6
5
  ChangeViewPrefix,
7
6
  IncludeReferenced,
8
7
  RuleMapper,
9
- RulesTransformer,
8
+ VerifiedRulesTransformer,
10
9
  )
11
10
 
12
11
  from ._state import SessionState
@@ -43,15 +42,16 @@ class DataModelMappingAPI:
43
42
  neat.mapping.classic_to_core(company_prefix="WindFarmX", use_parent_property_name=True)
44
43
  ```
45
44
  """
46
- rules = self._state.rule_store.get_last_successful_entity().result
47
- if not isinstance(rules, DMSRules):
48
- # Todo better hint of what you should do.
49
- raise NeatSessionError(f"Expected DMSRules, got {type(rules)}")
50
-
45
+ if self._state.rule_store.empty:
46
+ raise NeatSessionError("No rules to map")
47
+ last_entity = self._state.rule_store.provenance[-1].target_entity
48
+ if last_entity.dms is None:
49
+ raise NeatSessionError("Data model not converted to DMS. Try running `neat.convert('dms')` first.")
50
+ rules = last_entity.dms
51
51
  if self._state.client is None:
52
52
  raise NeatSessionError("Client is required to map classic to core")
53
53
 
54
- transformers: list[RulesTransformer] = []
54
+ transformers: list[VerifiedRulesTransformer] = []
55
55
  if company_prefix:
56
56
  transformers.append(ChangeViewPrefix("Classic", company_prefix))
57
57
  transformers.extend(