cognite-neat 0.75.9__py3-none-any.whl → 0.76.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.

@@ -16,15 +16,16 @@ __all__ = [
16
16
  "MissingParentViewError",
17
17
  "MissingSourceViewError",
18
18
  "MissingEdgeViewError",
19
- "DuplicatedViewInDataModelError",
20
19
  "DirectRelationMissingSourceWarning",
20
+ "ViewModelVersionNotMatchingWarning",
21
+ "ViewModelSpaceNotMatchingWarning",
22
+ "DuplicatedViewInDataModelError",
21
23
  "ContainerPropertyUsedMultipleTimesError",
22
- "DirectRelationListWarning",
23
- "ReverseOfDirectRelationListWarning",
24
24
  "EmptyContainerWarning",
25
- "UnsupportedRelationWarning",
25
+ "UnsupportedConnectionWarning",
26
26
  "MultipleReferenceWarning",
27
27
  "HasDataFilterOnNoPropertiesViewWarning",
28
+ "ReverseRelationMissingOtherSideWarning",
28
29
  "NodeTypeFilterOnParentViewWarning",
29
30
  "ChangingContainerError",
30
31
  "ChangingViewError",
@@ -202,6 +203,53 @@ class DirectRelationMissingSourceWarning(DMSSchemaWarning):
202
203
  return output
203
204
 
204
205
 
206
+ @dataclass(frozen=True)
207
+ class ViewModelVersionNotMatchingWarning(DMSSchemaWarning):
208
+ description = "The view model version does not match the data model version"
209
+ fix = "Update the view model version to match the data model version"
210
+ error_name: ClassVar[str] = "ViewModelVersionNotMatching"
211
+ view_ids: list[dm.ViewId]
212
+ data_model_version: str
213
+
214
+ def message(self) -> str:
215
+ return (
216
+ f"The version in the views {self.view_ids} does not match the version in the data model "
217
+ f"{self.data_model_version}. This is not recommended as it easily leads to confusion and errors. "
218
+ f"Views are very cheap and we recommend you update the version of the views to match the data"
219
+ f" model version, irrespective of whether the views have changed or not."
220
+ " The approach of having same version of model components as the model itself "
221
+ " is a globally recognized data modeling practice."
222
+ )
223
+
224
+ def dump(self) -> dict[str, Any]:
225
+ output = super().dump()
226
+ output["view_id"] = [view_id.dump() for view_id in self.view_ids]
227
+ output["data_model_version"] = self.data_model_version
228
+ return output
229
+
230
+
231
+ @dataclass(frozen=True)
232
+ class ViewModelSpaceNotMatchingWarning(DMSSchemaWarning):
233
+ description = "The view model space does not match the data model space"
234
+ fix = "Update the view model space to match the data model space"
235
+ error_name: ClassVar[str] = "ViewModelSpaceNotMatching"
236
+ view_ids: list[dm.ViewId]
237
+ data_model_space: str
238
+
239
+ def message(self) -> str:
240
+ return (
241
+ f"The space in the views {self.view_ids} does not match the space in the data model "
242
+ f"{self.data_model_space}. This is not recommended as it easily leads to confusion and errors. "
243
+ f"Views are very cheap and we recommend you always have views in the same space as the data model."
244
+ )
245
+
246
+ def dump(self) -> dict[str, Any]:
247
+ output = super().dump()
248
+ output["view_id"] = [view_id.dump() for view_id in self.view_ids]
249
+ output["data_model_space"] = self.data_model_space
250
+ return output
251
+
252
+
205
253
  @dataclass(frozen=True)
206
254
  class ContainerPropertyUsedMultipleTimesError(DMSSchemaError):
207
255
  description = "The container property is used multiple times by the same view property"
@@ -298,92 +346,62 @@ class ChangingViewError(DMSSchemaError):
298
346
 
299
347
 
300
348
  @dataclass(frozen=True)
301
- class DirectRelationListWarning(DMSSchemaWarning):
302
- description = "The container property is set to a direct relation list, which is not supported by the CDF API"
303
- fix = "Make the property into a multiedge connection instead"
304
- error_name: ClassVar[str] = "DirectRelationListWarning"
305
- view_id: dm.ViewId
349
+ class EmptyContainerWarning(DMSSchemaWarning):
350
+ description = "The container is empty"
351
+ fix = "Add data to the container"
352
+ error_name: ClassVar[str] = "EmptyContainerWarning"
306
353
  container_id: dm.ContainerId
307
- property: str
308
354
 
309
355
  def message(self) -> str:
310
356
  return (
311
- f"The property in {self.container_id}.{self.property} is a list of direct relations. "
312
- f"This is not supported by the API, so it will be converted to an MultiEdgeConnection on"
313
- f"the view {self.view_id}.{self.property} instead"
357
+ f"The container {self.container_id} is empty. Is this intended? Skipping this container "
358
+ "in the data model."
314
359
  )
315
360
 
316
361
  def dump(self) -> dict[str, Any]:
317
362
  output = super().dump()
318
- output["view_id"] = self.view_id.dump()
319
363
  output["container_id"] = self.container_id.dump()
320
- output["property"] = self.property
321
364
  return output
322
365
 
323
366
 
324
367
  @dataclass(frozen=True)
325
- class ReverseOfDirectRelationListWarning(DMSSchemaWarning):
326
- description = (
327
- "The view property is set to a reverse of a direct relation list, which is not supported by the CDF API"
328
- )
329
- fix = "Make the property into a multiedge connection instead"
330
- error_name: ClassVar[str] = "ReverseOfDirectRelationListWarning"
368
+ class UnsupportedConnectionWarning(DMSSchemaWarning):
369
+ description = "The connection type is not supported by neat"
370
+ fix = "Change the connection to a supported type"
371
+ error_name: ClassVar[str] = "UnsupportedConnectionWarning"
331
372
  view_id: dm.ViewId
332
373
  property: str
374
+ connection: str
333
375
 
334
376
  def message(self) -> str:
335
377
  return (
336
- f"The property pointed to be {self.view_id}.{self.property} is a list of direct relations. "
337
- f"This is not supported by the API, so the {self.view_id}.{self.property} "
338
- "will be converted from a reverse direct relation to an MultiEdgeConnection instead"
378
+ f"The connection {self.connection} in {self.view_id}.{self.property} is not supported."
379
+ "This property will be ignored."
339
380
  )
340
381
 
341
382
  def dump(self) -> dict[str, Any]:
342
383
  output = super().dump()
343
384
  output["view_id"] = self.view_id.dump()
344
385
  output["property"] = self.property
386
+ output["connection"] = self.connection
345
387
  return output
346
388
 
347
389
 
348
390
  @dataclass(frozen=True)
349
- class EmptyContainerWarning(DMSSchemaWarning):
350
- description = "The container is empty"
351
- fix = "Add data to the container"
352
- error_name: ClassVar[str] = "EmptyContainerWarning"
353
- container_id: dm.ContainerId
354
-
355
- def message(self) -> str:
356
- return (
357
- f"The container {self.container_id} is empty. Is this intended? Skipping this container "
358
- "in the data model."
359
- )
360
-
361
- def dump(self) -> dict[str, Any]:
362
- output = super().dump()
363
- output["container_id"] = self.container_id.dump()
364
- return output
365
-
366
-
367
- @dataclass(frozen=True)
368
- class UnsupportedRelationWarning(DMSSchemaWarning):
369
- description = "The relatio type is not supported by neat"
370
- fix = "Change the relation to a supported type"
371
- error_name: ClassVar[str] = "UnsupportedRelationWarning"
391
+ class ReverseRelationMissingOtherSideWarning(DMSSchemaWarning):
392
+ description = "The relation is missing the other side"
393
+ fix = "Add the other side of the relation"
394
+ error_name: ClassVar[str] = "ReverseRelationMissingOtherSideWarning"
372
395
  view_id: dm.ViewId
373
396
  property: str
374
- relation: str
375
397
 
376
398
  def message(self) -> str:
377
- return (
378
- f"The relation {self.relation} in {self.view_id}.{self.property} is not supported."
379
- "This property will be ignored."
380
- )
399
+ return f"The reverse relation specified in {self.view_id}.{self.property} is missing the other side."
381
400
 
382
401
  def dump(self) -> dict[str, Any]:
383
402
  output = super().dump()
384
403
  output["view_id"] = self.view_id.dump()
385
404
  output["property"] = self.property
386
- output["relation"] = self.relation
387
405
  return output
388
406
 
389
407
 
@@ -427,7 +445,7 @@ class HasDataFilterOnNoPropertiesViewWarning(DMSSchemaWarning):
427
445
  @dataclass(frozen=True)
428
446
  class NodeTypeFilterOnParentViewWarning(DMSSchemaWarning):
429
447
  description = (
430
- "Setting a node type filter on a parent view. This is no "
448
+ "Setting a node type filter on a parent view. This is not "
431
449
  "recommended as parent views are typically used for multiple type of nodes."
432
450
  )
433
451
  fix = "Use a HasData filter instead"
@@ -444,3 +462,73 @@ class NodeTypeFilterOnParentViewWarning(DMSSchemaWarning):
444
462
  output = super().dump()
445
463
  output["view_id"] = self.view_id.dump()
446
464
  return output
465
+
466
+
467
+ @dataclass(frozen=True)
468
+ class HasDataFilterOnViewWithReferencesWarning(DMSSchemaWarning):
469
+ description = (
470
+ "Setting a hasData filter on a solution view which reference other containers is not recommended."
471
+ "This will lead to no nodes being returned when querying the solution view."
472
+ )
473
+ fix = "Use a node type filter instead"
474
+ error_name: ClassVar[str] = "HasDataFilterOnReferencedViewWarning"
475
+
476
+ view_id: dm.ViewId
477
+ references: list[dm.ViewId]
478
+
479
+ def message(self) -> str:
480
+ return (
481
+ f"Setting a hasData filter on view {self.view_id} which references other views {self.references}. "
482
+ "This is not recommended as it will lead to no nodes being returned when querying the solution view."
483
+ )
484
+
485
+ def dump(self) -> dict[str, Any]:
486
+ output = super().dump()
487
+ output["view_id"] = self.view_id.dump()
488
+ output["references"] = [view.dump() for view in sorted(self.references, key=lambda x: x.as_tuple())]
489
+ return output
490
+
491
+
492
+ @dataclass(frozen=True)
493
+ class OtherDataModelsInSpaceWarning(DMSSchemaWarning):
494
+ description = "The space contains other data models"
495
+ fix = "Move the data models to their respective spaces."
496
+ error_name: ClassVar[str] = "OtherDataModelsInSpaceWarning"
497
+ space: str
498
+ data_models: list[dm.DataModelId]
499
+
500
+ def message(self) -> str:
501
+ return (
502
+ f"The space {self.space} contains data models from other spaces: {self.data_models}. {self.fix}"
503
+ " It is recommended to only have one data model per space. This avoid potential conflicts and "
504
+ "makes it easier to manage the data models."
505
+ )
506
+
507
+ def dump(self) -> dict[str, Any]:
508
+ output = super().dump()
509
+ output["space"] = self.space
510
+ output["data_models"] = [data_model.dump() for data_model in self.data_models]
511
+ return output
512
+
513
+
514
+ @dataclass(frozen=True)
515
+ class SolutionOnTopOfSolutionModelWarning(DMSSchemaWarning):
516
+ description = "The data model is a solution on top of another solution"
517
+ fix = "Use the base solution as the data model"
518
+ error_name: ClassVar[str] = "SolutionOnTopOfSolutionModelWarning"
519
+ data_model: dm.DataModelId
520
+ base_data_model: dm.DataModelId
521
+
522
+ def message(self) -> str:
523
+ return (
524
+ f"The data model {self.data_model} is a solution model on top of another solution {self.base_data_model} "
525
+ "model. This is not recommended as it can lead to confusion and errors. It is very hard to "
526
+ "maintain a nested structure of solution models. Instead, only build solution models on "
527
+ "top of enterprise models."
528
+ )
529
+
530
+ def dump(self) -> dict[str, Any]:
531
+ output = super().dump()
532
+ output["data_model"] = self.data_model.dump()
533
+ output["base_data_model"] = self.base_data_model.dump()
534
+ return output
@@ -9,10 +9,13 @@ __all__ = [
9
9
  "InvalidFileFormatWarning",
10
10
  "UnsupportedSpecWarning",
11
11
  "UnknownItemWarning",
12
+ "FailedLoadWarning",
12
13
  "BugInImporterWarning",
13
14
  "FileReadError",
14
15
  "FileNotFoundError",
15
16
  "FileNotAFileError",
17
+ "InvalidFileFormatError",
18
+ "FailedStringLoadError",
16
19
  ]
17
20
 
18
21
 
@@ -96,6 +99,26 @@ class UnknownItemWarning(FileReadWarning):
96
99
  return output
97
100
 
98
101
 
102
+ @dataclass(frozen=True)
103
+ class FailedLoadWarning(FileReadWarning):
104
+ description = "The file content is invalid"
105
+ fix = "Check if the file content is valid"
106
+
107
+ expected_format: str
108
+ error_message: str
109
+
110
+ def message(self) -> str:
111
+ return (
112
+ f"Failed to load {self.filepath.name}. Expected format: {self.expected_format}. Error: {self.error_message}"
113
+ )
114
+
115
+ def dump(self) -> dict[str, str | None]:
116
+ output = super().dump()
117
+ output["expected_format"] = self.expected_format
118
+ output["error_message"] = self.error_message
119
+ return output
120
+
121
+
99
122
  @dataclass(frozen=True)
100
123
  class BugInImporterWarning(FileReadWarning):
101
124
  description = "A bug was raised during reading."
@@ -143,6 +166,24 @@ class InvalidFileFormatError(FileReadError):
143
166
  return f"{self.filepath} is not in the expected format. Expected format: {self.expected_format}."
144
167
 
145
168
 
169
+ @dataclass(frozen=True)
170
+ class FailedStringLoadError(NeatValidationError):
171
+ description = "The file content is invalid"
172
+ fix = "Check if the file content is valid"
173
+
174
+ expected_format: str
175
+ error_message: str
176
+
177
+ def message(self) -> str:
178
+ return f"Failed to load string. Expected format: {self.expected_format}. Error: {self.error_message}"
179
+
180
+ def dump(self) -> dict[str, str | None]:
181
+ output = super().dump()
182
+ output["expected_format"] = self.expected_format
183
+ output["error_message"] = self.error_message
184
+ return output
185
+
186
+
146
187
  @dataclass(frozen=True)
147
188
  class NoFilesFoundError(FileReadError):
148
189
  description = "No files were found in the directory"
@@ -10,11 +10,19 @@ __all__ = [
10
10
  "IgnoredComponentWarning",
11
11
  "UnknownPropertyWarning",
12
12
  "UnknownValueTypeWarning",
13
+ "MissingContainerWarning",
14
+ "MissingContainerPropertyWarning",
15
+ "MultipleDataModelsWarning",
16
+ "UnknownPropertyTypeWarning",
17
+ "FailedToInferValueTypeWarning",
18
+ "UnknownContainerConstraintWarning",
19
+ "NoDataModelError",
13
20
  "ModelImportError",
14
21
  "InvalidComponentError",
15
22
  "MissingParentDefinitionError",
16
23
  "MissingIdentifierError",
17
24
  "UnsupportedPropertyTypeError",
25
+ "APIError",
18
26
  ]
19
27
 
20
28
 
@@ -132,12 +140,159 @@ class UnknownValueTypeWarning(ModelImportWarning):
132
140
  )
133
141
 
134
142
 
143
+ @dataclass(frozen=True)
144
+ class MultipleDataModelsWarning(ModelImportWarning):
145
+ description = "Multiple data models detected. This is not supported."
146
+ fix = "Remove the extra data models."
147
+
148
+ data_models: list[str]
149
+
150
+ def message(self) -> str:
151
+ return f"Multiple data models detected: {self.data_models}. Will only import the first one."
152
+
153
+ def dump(self) -> dict[str, list[str]]:
154
+ return {"data_models": self.data_models}
155
+
156
+
157
+ @dataclass(frozen=True)
158
+ class MissingContainerWarning(ModelImportWarning):
159
+ description = "Missing container definition."
160
+ fix = "Add a container definition."
161
+
162
+ view_id: str
163
+ property_: str
164
+ container_id: str
165
+
166
+ def message(self) -> str:
167
+ return (
168
+ f"Container '{self.container_id}' is missing. "
169
+ f"Will skip property '{self.property_}' of view '{self.view_id}'."
170
+ )
171
+
172
+ def dump(self) -> dict[str, str]:
173
+ return {"view_id": self.view_id, "property": self.property_, "container_id": self.container_id}
174
+
175
+
176
+ @dataclass(frozen=True)
177
+ class MissingContainerPropertyWarning(ModelImportWarning):
178
+ description = "Missing container property definition."
179
+ fix = "Add a container property definition."
180
+
181
+ view_id: str
182
+ property_: str
183
+ container_id: str
184
+
185
+ def message(self) -> str:
186
+ return (
187
+ f"Container '{self.container_id}' is missing property '{self.property_}'. "
188
+ f"This property will be skipped for view '{self.view_id}'."
189
+ )
190
+
191
+ def dump(self) -> dict[str, str]:
192
+ return {"view_id": self.view_id, "property": self.property_, "container_id": self.container_id}
193
+
194
+
195
+ @dataclass(frozen=True)
196
+ class UnknownPropertyTypeWarning(ModelImportWarning):
197
+ description = "Unknown property type. This will be ignored in the imports."
198
+ fix = "Set to a supported property type."
199
+ view_id: str
200
+ property_id: str
201
+ property_type: str
202
+
203
+ def message(self) -> str:
204
+ return (
205
+ f"Unknown property type '{self.property_type}' for property '{self.property_id}' "
206
+ f"of view '{self.view_id}'. This will be ignored in the imports."
207
+ )
208
+
209
+ def dump(self) -> dict[str, str]:
210
+ return {"view_id": self.view_id, "property_id": self.property_id, "property_type": self.property_type}
211
+
212
+
213
+ @dataclass(frozen=True)
214
+ class UnknownContainerConstraintWarning(ModelImportWarning):
215
+ description = "Unknown container constraint. This will be ignored in the imports."
216
+ fix = "Set to a supported container constraint."
217
+ container_id: str
218
+ property_id: str
219
+ constraint: str
220
+
221
+ def message(self) -> str:
222
+ return (
223
+ f"Unknown container constraint '{self.constraint}' for property '{self.property_id}' of container "
224
+ f"'{self.container_id}'. This will be ignored in the imports."
225
+ )
226
+
227
+ def dump(self) -> dict[str, str]:
228
+ return {"container_id": self.container_id, "property_id": self.property_id, "constraint": self.constraint}
229
+
230
+
231
+ @dataclass(frozen=True)
232
+ class FailedToInferValueTypeWarning(ModelImportWarning):
233
+ description = "Failed to infer value type. This will be ignored in the imports."
234
+ fix = "Set to a supported value type."
235
+ view_id: str
236
+ property_id: str
237
+
238
+ def message(self) -> str:
239
+ return (
240
+ f"Failed to infer value type for property '{self.property_id}' of view '{self.view_id}'. "
241
+ f"This will be ignored in the imports."
242
+ )
243
+
244
+ def dump(self) -> dict[str, str]:
245
+ return {"view_id": self.view_id, "property_id": self.property_id}
246
+
247
+
248
+ @dataclass(frozen=True)
249
+ class APIWarning(ModelImportWarning):
250
+ description = "An error was raised."
251
+ fix = "No fix is available."
252
+
253
+ error_message: str
254
+
255
+ def message(self) -> str:
256
+ return self.error_message
257
+
258
+ def dump(self) -> dict[str, str]:
259
+ return {"error_message": self.error_message}
260
+
261
+
135
262
  @dataclass(frozen=True)
136
263
  class ModelImportError(NeatValidationError, ABC):
137
264
  description = "An error was raised during importing."
138
265
  fix = "No fix is available."
139
266
 
140
267
 
268
+ @dataclass(frozen=True)
269
+ class NoDataModelError(ModelImportError):
270
+ description = "No data model found.."
271
+ fix = "Check if the data model exists in the source."
272
+
273
+ error_message: str
274
+
275
+ def message(self) -> str:
276
+ return self.error_message
277
+
278
+ def dump(self) -> dict[str, str]:
279
+ return {"error_message": self.error_message}
280
+
281
+
282
+ @dataclass(frozen=True)
283
+ class APIError(ModelImportError):
284
+ description = "An error was raised during importing."
285
+ fix = "No fix is available."
286
+
287
+ error_message: str
288
+
289
+ def message(self) -> str:
290
+ return self.error_message
291
+
292
+ def dump(self) -> dict[str, str]:
293
+ return {"error_message": self.error_message}
294
+
295
+
141
296
  @dataclass(frozen=True)
142
297
  class InvalidComponentError(ModelImportError, ABC):
143
298
  description = "This is a base class for all errors related invalid component definitions"
@@ -176,15 +176,18 @@ class InvalidRowUnknownSheetError(InvalidRowError):
176
176
  ) -> Self:
177
177
  sheet_name, _, row, column, *__ = error["loc"]
178
178
  reader = (read_info_by_sheet or {}).get(str(sheet_name), SpreadsheetRead())
179
- return cls(
180
- column=str(column),
181
- row=reader.adjusted_row_number(int(row)),
182
- actual_sheet_name=str(sheet_name),
183
- type=error["type"],
184
- msg=error["msg"],
185
- input=error.get("input"),
186
- url=str(url) if (url := error.get("url")) else None,
187
- )
179
+ try:
180
+ return cls(
181
+ column=str(column),
182
+ row=reader.adjusted_row_number(int(row)),
183
+ actual_sheet_name=str(sheet_name),
184
+ type=error["type"],
185
+ msg=error["msg"],
186
+ input=error.get("input"),
187
+ url=str(url) if (url := error.get("url")) else None,
188
+ )
189
+ except ValueError:
190
+ return DefaultPydanticError.from_pydantic_error(error) # type: ignore[return-value]
188
191
 
189
192
  def dump(self) -> dict[str, Any]:
190
193
  output = super().dump()
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
4
4
  from functools import total_ordering
5
5
  from typing import Annotated, Any, ClassVar, Generic, TypeVar, cast
6
6
 
7
- from cognite.client.data_classes.data_modeling.ids import ContainerId, DataModelId, PropertyId, ViewId
7
+ from cognite.client.data_classes.data_modeling.ids import ContainerId, DataModelId, NodeId, PropertyId, ViewId
8
8
  from pydantic import AnyHttpUrl, BaseModel, BeforeValidator, Field, PlainSerializer, model_serializer, model_validator
9
9
 
10
10
  if sys.version_info >= (3, 11):
@@ -30,6 +30,7 @@ class EntityTypes(StrEnum):
30
30
  data_value_type = "data_value_type" # these are strings, floats, ...
31
31
  xsd_value_type = "xsd_value_type"
32
32
  dms_value_type = "dms_value_type"
33
+ dms_node = "dms_node"
33
34
  view = "view"
34
35
  reference_entity = "reference_entity"
35
36
  container = "container"
@@ -248,7 +249,7 @@ class UnknownEntity(ClassEntity):
248
249
  return str(Unknown)
249
250
 
250
251
 
251
- T_ID = TypeVar("T_ID", bound=ContainerId | ViewId | DataModelId | PropertyId | None)
252
+ T_ID = TypeVar("T_ID", bound=ContainerId | ViewId | DataModelId | PropertyId | NodeId | None)
252
253
 
253
254
 
254
255
  class DMSEntity(Entity, Generic[T_ID], ABC):
@@ -302,7 +303,9 @@ class ContainerEntity(DMSEntity[ContainerId]):
302
303
  class DMSVersionedEntity(DMSEntity[T_ID], ABC):
303
304
  version: str
304
305
 
305
- def as_class(self) -> ClassEntity:
306
+ def as_class(self, skip_version: bool = False) -> ClassEntity:
307
+ if skip_version:
308
+ return ClassEntity(prefix=self.space, suffix=self.external_id)
306
309
  return ClassEntity(prefix=self.space, suffix=self.external_id, version=self.version)
307
310
 
308
311
 
@@ -315,10 +318,13 @@ class ViewEntity(DMSVersionedEntity[ViewId]):
315
318
  return ViewId(space=self.space, external_id=self.external_id, version=self.version)
316
319
 
317
320
  @classmethod
318
- def from_id(cls, id: ViewId) -> "ViewEntity":
319
- if id.version is None:
321
+ def from_id(cls, id: ViewId, default_version: str | None = None) -> "ViewEntity":
322
+ if id.version is not None:
323
+ return cls(space=id.space, externalId=id.external_id, version=id.version)
324
+ elif default_version is not None:
325
+ return cls(space=id.space, externalId=id.external_id, version=default_version)
326
+ else:
320
327
  raise ValueError("Version must be specified")
321
- return cls(space=id.space, externalId=id.external_id, version=id.version)
322
328
 
323
329
 
324
330
  class DMSUnknownEntity(DMSEntity[None]):
@@ -376,6 +382,17 @@ class DataModelEntity(DMSVersionedEntity[DataModelId]):
376
382
  return cls(space=id.space, externalId=id.external_id, version=id.version)
377
383
 
378
384
 
385
+ class DMSNodeEntity(DMSEntity[NodeId]):
386
+ type_: ClassVar[EntityTypes] = EntityTypes.dms_node
387
+
388
+ def as_id(self) -> NodeId:
389
+ return NodeId(space=self.space, external_id=self.external_id)
390
+
391
+ @classmethod
392
+ def from_id(cls, id: NodeId) -> "DMSNodeEntity":
393
+ return cls(space=id.space, externalId=id.external_id)
394
+
395
+
379
396
  class ReferenceEntity(ClassEntity):
380
397
  type_: ClassVar[EntityTypes] = EntityTypes.reference_entity
381
398
  prefix: str
@@ -391,6 +408,12 @@ class ReferenceEntity(ClassEntity):
391
408
  raise ValueError("Property is not defined or prefix is not defined or suffix is unknown")
392
409
  return PropertyId(source=self.as_view_id(), property=self.property_)
393
410
 
411
+ def as_node_id(self) -> NodeId:
412
+ return NodeId(space=self.prefix, external_id=self.suffix)
413
+
414
+ def as_node_entity(self) -> DMSNodeEntity:
415
+ return DMSNodeEntity(space=self.prefix, externalId=self.suffix)
416
+
394
417
  def as_class_entity(self) -> ClassEntity:
395
418
  return ClassEntity(prefix=self.prefix, suffix=self.suffix, version=self.version)
396
419
 
@@ -27,8 +27,6 @@ from pydantic import (
27
27
  )
28
28
  from pydantic.fields import FieldInfo
29
29
 
30
- from cognite.neat.rules.models.entities import ClassEntity
31
-
32
30
  if sys.version_info >= (3, 11):
33
31
  from enum import StrEnum
34
32
  from typing import Self
@@ -140,6 +138,11 @@ class ExtensionCategory(StrEnum):
140
138
  rebuild = "rebuild"
141
139
 
142
140
 
141
+ class DataModelType(StrEnum):
142
+ solution = "solution"
143
+ enterprise = "enterprise"
144
+
145
+
143
146
  class RoleTypes(StrEnum):
144
147
  domain_expert = "domain expert"
145
148
  information_architect = "information architect"
@@ -274,10 +277,6 @@ class BaseRules(RuleModel):
274
277
 
275
278
  # An sheet entity is either a class or a property.
276
279
  class SheetEntity(RuleModel):
277
- class_: ClassEntity = Field(alias="Class")
278
- name: str | None = Field(alias="Name", default=None)
279
- description: str | None = Field(alias="Description", default=None)
280
-
281
280
  @field_validator("*", mode="before")
282
281
  def strip_string(cls, value: Any) -> Any:
283
282
  if isinstance(value, str):