cognite-neat 0.97.3__py3-none-any.whl → 0.98.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 (65) hide show
  1. cognite/neat/_graph/loaders/__init__.py +1 -2
  2. cognite/neat/_issues/warnings/_models.py +9 -0
  3. cognite/neat/_rules/_shared.py +3 -8
  4. cognite/neat/_rules/analysis/__init__.py +1 -2
  5. cognite/neat/_rules/analysis/_base.py +2 -23
  6. cognite/neat/_rules/analysis/_dms.py +4 -10
  7. cognite/neat/_rules/analysis/_information.py +2 -10
  8. cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
  9. cognite/neat/_rules/exporters/_rules2excel.py +15 -72
  10. cognite/neat/_rules/exporters/_rules2ontology.py +4 -4
  11. cognite/neat/_rules/importers/_base.py +3 -4
  12. cognite/neat/_rules/importers/_dms2rules.py +17 -40
  13. cognite/neat/_rules/importers/_dtdl2rules/dtdl_converter.py +1 -7
  14. cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +7 -10
  15. cognite/neat/_rules/importers/_rdf/_base.py +17 -29
  16. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2classes.py +2 -2
  17. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2metadata.py +5 -10
  18. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2properties.py +1 -2
  19. cognite/neat/_rules/importers/_rdf/_inference2rules.py +30 -18
  20. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2classes.py +2 -2
  21. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2metadata.py +5 -8
  22. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -2
  23. cognite/neat/_rules/importers/_rdf/_shared.py +25 -140
  24. cognite/neat/_rules/importers/_spreadsheet2rules.py +10 -41
  25. cognite/neat/_rules/models/__init__.py +2 -16
  26. cognite/neat/_rules/models/_base_rules.py +98 -52
  27. cognite/neat/_rules/models/dms/_exporter.py +7 -160
  28. cognite/neat/_rules/models/dms/_rules.py +18 -126
  29. cognite/neat/_rules/models/dms/_rules_input.py +20 -48
  30. cognite/neat/_rules/models/dms/_schema.py +11 -0
  31. cognite/neat/_rules/models/dms/_validation.py +9 -107
  32. cognite/neat/_rules/models/information/_rules.py +19 -114
  33. cognite/neat/_rules/models/information/_rules_input.py +32 -41
  34. cognite/neat/_rules/models/information/_validation.py +34 -102
  35. cognite/neat/_rules/transformers/__init__.py +1 -4
  36. cognite/neat/_rules/transformers/_converters.py +18 -195
  37. cognite/neat/_rules/transformers/_mapping.py +1 -5
  38. cognite/neat/_rules/transformers/_verification.py +0 -14
  39. cognite/neat/_session/_base.py +34 -13
  40. cognite/neat/_session/_collector.py +126 -0
  41. cognite/neat/_session/_inspect.py +5 -5
  42. cognite/neat/_session/_prepare.py +4 -4
  43. cognite/neat/_session/_read.py +62 -9
  44. cognite/neat/_session/_set.py +2 -2
  45. cognite/neat/_session/_show.py +11 -11
  46. cognite/neat/_session/_to.py +24 -11
  47. cognite/neat/_session/exceptions.py +20 -3
  48. cognite/neat/_store/_provenance.py +2 -2
  49. cognite/neat/_utils/auxiliary.py +19 -0
  50. cognite/neat/_version.py +1 -1
  51. cognite/neat/_workflows/steps/data_contracts.py +2 -10
  52. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +6 -46
  53. cognite/neat/_workflows/steps/lib/current/rules_validator.py +2 -7
  54. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/METADATA +2 -1
  55. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/RECORD +58 -64
  56. cognite/neat/_graph/loaders/_rdf2asset.py +0 -416
  57. cognite/neat/_rules/analysis/_asset.py +0 -173
  58. cognite/neat/_rules/models/asset/__init__.py +0 -13
  59. cognite/neat/_rules/models/asset/_rules.py +0 -109
  60. cognite/neat/_rules/models/asset/_rules_input.py +0 -101
  61. cognite/neat/_rules/models/asset/_validation.py +0 -45
  62. cognite/neat/_rules/models/domain.py +0 -136
  63. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/LICENSE +0 -0
  64. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/WHEEL +0 -0
  65. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,6 @@
1
1
  import math
2
2
  import sys
3
3
  from collections.abc import Hashable
4
- from datetime import datetime
5
4
  from typing import TYPE_CHECKING, Any, ClassVar
6
5
 
7
6
  import pandas as pd
@@ -9,20 +8,16 @@ from pydantic import Field, field_serializer, field_validator, model_validator
9
8
  from pydantic_core.core_schema import SerializationInfo
10
9
  from rdflib import Namespace, URIRef
11
10
 
12
- from cognite.neat._constants import DEFAULT_NAMESPACE, get_default_prefixes
11
+ from cognite.neat._constants import get_default_prefixes
13
12
  from cognite.neat._issues.errors import NeatValueError, PropertyDefinitionError
14
13
  from cognite.neat._rules._constants import EntityTypes
15
14
  from cognite.neat._rules.models._base_rules import (
16
15
  BaseMetadata,
17
16
  BaseRules,
18
17
  ClassRef,
19
- DataModelType,
20
- ExtensionCategory,
21
- ExtensionCategoryType,
22
- MatchType,
18
+ DataModelAspect,
23
19
  PropertyRef,
24
20
  RoleTypes,
25
- SchemaCompleteness,
26
21
  SheetList,
27
22
  SheetRow,
28
23
  )
@@ -35,10 +30,6 @@ from cognite.neat._rules.models._types import (
35
30
  ClassEntityType,
36
31
  InformationPropertyType,
37
32
  MultiValueTypeType,
38
- NamespaceType,
39
- PrefixType,
40
- StrListType,
41
- VersionType,
42
33
  )
43
34
  from cognite.neat._rules.models.data_types import DataType
44
35
  from cognite.neat._rules.models.entities import (
@@ -46,10 +37,8 @@ from cognite.neat._rules.models.entities import (
46
37
  ClassEntityList,
47
38
  Entity,
48
39
  MultiValueTypeInfo,
49
- ReferenceEntity,
50
40
  Undefined,
51
41
  UnknownEntity,
52
- URLEntity,
53
42
  )
54
43
 
55
44
  if TYPE_CHECKING:
@@ -64,62 +53,11 @@ else:
64
53
 
65
54
  class InformationMetadata(BaseMetadata):
66
55
  role: ClassVar[RoleTypes] = RoleTypes.information
67
- data_model_type: DataModelType = Field(DataModelType.enterprise, alias="dataModelType")
68
- schema_: SchemaCompleteness = Field(SchemaCompleteness.partial, alias="schema")
69
- extension: ExtensionCategoryType | None = ExtensionCategory.addition
70
-
71
- prefix: PrefixType
72
- namespace: NamespaceType
73
-
74
- name: str = Field(
75
- alias="title",
76
- description="Human readable name of the data model",
77
- min_length=1,
78
- max_length=255,
79
- )
80
- description: str | None = Field(None, min_length=1, max_length=1024)
81
- version: VersionType
82
-
83
- created: datetime = Field(
84
- description=("Date of the data model creation"),
85
- )
86
-
87
- updated: datetime = Field(
88
- description=("Date of the data model update"),
89
- )
90
- creator: StrListType = Field(
91
- description=(
92
- "List of contributors to the data model creation, "
93
- "typically information architects are considered as contributors."
94
- ),
95
- )
96
- license: str | None = None
97
- rights: str | None = None
98
-
99
- @model_validator(mode="after")
100
- def extension_none_but_schema_extend(self) -> Self:
101
- if self.extension is None:
102
- self.extension = ExtensionCategory.addition
103
- return self
104
- return self
105
-
106
- @field_validator("schema_", mode="plain")
107
- def as_enum_schema(cls, value: str) -> SchemaCompleteness:
108
- return SchemaCompleteness(value.strip())
56
+ aspect: ClassVar[DataModelAspect] = DataModelAspect.logical
109
57
 
110
- @field_validator("extension", mode="plain")
111
- def as_enum_extension(cls, value: str) -> ExtensionCategory:
112
- return ExtensionCategory(value.strip())
113
-
114
- @field_validator("data_model_type", mode="plain")
115
- def as_enum_model_type(cls, value: str) -> DataModelType:
116
- return DataModelType(value.strip())
117
-
118
- def as_identifier(self) -> str:
119
- return f"{self.prefix}:{self.name}"
120
-
121
- def get_prefix(self) -> str:
122
- return self.prefix
58
+ # Linking to Conceptual and Physical data model aspects
59
+ physical: URIRef | None = Field(None, description="Link to the logical data model aspect")
60
+ conceptual: URIRef | None = Field(None, description="Link to the logical data model aspect")
123
61
 
124
62
 
125
63
  def _get_metadata(context: Any) -> InformationMetadata | None:
@@ -135,42 +73,33 @@ class InformationClass(SheetRow):
135
73
  Args:
136
74
  class_: The class ID of the class.
137
75
  description: A description of the class.
138
- parent: The parent class of the class.
139
- reference: Reference of the source of the information for given resource
140
- match_type: The match type of the resource being described and the source entity.
76
+ implements: Which classes the current class implements.
141
77
  """
142
78
 
143
79
  class_: ClassEntityType = Field(alias="Class")
144
80
  name: str | None = Field(alias="Name", default=None)
145
81
  description: str | None = Field(alias="Description", default=None)
146
- parent: ClassEntityList | None = Field(alias="Parent Class", default=None)
147
- reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
148
- match_type: MatchType | None = Field(alias="Match Type", default=None)
149
- comment: str | None = Field(alias="Comment", default=None)
82
+ implements: ClassEntityList | None = Field(alias="Implements", default=None)
150
83
 
151
84
  def _identifier(self) -> tuple[Hashable, ...]:
152
85
  return (self.class_,)
153
86
 
154
- @field_serializer("reference", when_used="always")
155
- def set_reference(self, value: Any, info: SerializationInfo) -> str | None:
156
- if isinstance(info.context, dict) and info.context.get("as_reference") is True:
157
- return self.class_.dump()
158
- return str(value) if value is not None else None
159
-
160
87
  @field_serializer("class_", when_used="unless-none")
161
88
  def remove_default_prefix(self, value: Any, info: SerializationInfo) -> str:
162
89
  if (metadata := _get_metadata(info.context)) and isinstance(value, Entity):
163
90
  return value.dump(prefix=metadata.prefix, version=metadata.version)
164
91
  return str(value)
165
92
 
166
- @field_serializer("parent", when_used="unless-none")
93
+ @field_serializer("implements", when_used="unless-none")
167
94
  def remove_default_prefixes(self, value: Any, info: SerializationInfo) -> str:
168
95
  if isinstance(value, list) and (metadata := _get_metadata(info.context)):
169
96
  return ",".join(
170
- parent.dump(prefix=metadata.prefix, version=metadata.version)
171
- if isinstance(parent, Entity)
172
- else str(parent)
173
- for parent in value
97
+ (
98
+ class_.dump(prefix=metadata.prefix, version=metadata.version)
99
+ if isinstance(class_, Entity)
100
+ else str(class_)
101
+ )
102
+ for class_ in value
174
103
  )
175
104
  return ",".join(str(value) for value in value)
176
105
 
@@ -191,8 +120,6 @@ class InformationProperty(SheetRow):
191
120
  min_count: Minimum count of the property values. Defaults to 0
192
121
  max_count: Maximum count of the property values. Defaults to None
193
122
  default: Default value of the property
194
- reference: Reference to the source of the information, HTTP URI
195
- match_type: The match type of the resource being described and the source entity.
196
123
  transformation: Actual rule for the transformation from source to target representation of
197
124
  knowledge graph. Defaults to None (no transformation)
198
125
  """
@@ -207,10 +134,7 @@ class InformationProperty(SheetRow):
207
134
  min_count: int | None = Field(alias="Min Count", default=None)
208
135
  max_count: int | float | None = Field(alias="Max Count", default=None)
209
136
  default: Any | None = Field(alias="Default", default=None)
210
- reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
211
- match_type: MatchType | None = Field(alias="Match Type", default=None)
212
137
  transformation: RDFPath | None = Field(alias="Transformation", default=None)
213
- comment: str | None = Field(alias="Comment", default=None)
214
138
  inherited: bool = Field(
215
139
  default=False,
216
140
  exclude=True,
@@ -281,19 +205,6 @@ class InformationProperty(SheetRow):
281
205
  return None
282
206
  return value
283
207
 
284
- @field_serializer("reference", when_used="always")
285
- def set_reference(self, value: Any, info: SerializationInfo) -> str | None:
286
- # When rules as dumped as reference, we set the reference to the class
287
- if isinstance(info.context, dict) and info.context.get("as_reference") is True:
288
- return str(
289
- ReferenceEntity(
290
- prefix=str(self.class_.prefix),
291
- suffix=self.class_.suffix,
292
- property=self.property_,
293
- )
294
- )
295
- return str(value) if value is not None else None
296
-
297
208
  @field_serializer("class_", "value_type", when_used="unless-none")
298
209
  def remove_default_prefix(self, value: Any, info: SerializationInfo) -> str:
299
210
  if (metadata := _get_metadata(info.context)) and isinstance(value, Entity):
@@ -331,8 +242,6 @@ class InformationRules(BaseRules):
331
242
  properties: SheetList[InformationProperty] = Field(alias="Properties")
332
243
  classes: SheetList[InformationClass] = Field(alias="Classes")
333
244
  prefixes: dict[str, Namespace] = Field(default_factory=get_default_prefixes, alias="Prefixes")
334
- last: "InformationRules | None" = Field(None, alias="Last")
335
- reference: "InformationRules | None" = Field(None, alias="Reference")
336
245
 
337
246
  @field_validator("prefixes", mode="before")
338
247
  def parse_str(cls, values: Any) -> Any:
@@ -355,10 +264,10 @@ class InformationRules(BaseRules):
355
264
  if property_.class_.prefix is Undefined:
356
265
  property_.class_.prefix = self.metadata.prefix
357
266
 
358
- # update parent classes
267
+ # update implements
359
268
  for class_ in self.classes:
360
- if class_.parent:
361
- for parent in class_.parent:
269
+ if class_.implements:
270
+ for parent in class_.implements:
362
271
  if not isinstance(parent.prefix, str):
363
272
  parent.prefix = self.metadata.prefix
364
273
  if class_.class_.prefix is Undefined:
@@ -387,14 +296,10 @@ class InformationRules(BaseRules):
387
296
  "type": "Logical Data Model",
388
297
  "intended for": "Information Architect",
389
298
  "name": self.metadata.name,
390
- "external_id": self.metadata.prefix,
299
+ "external_id": self.metadata.external_id,
391
300
  "version": self.metadata.version,
392
301
  "classes": len(self.classes),
393
302
  "properties": len(self.properties),
394
303
  }
395
304
 
396
305
  return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
397
-
398
- @property
399
- def id_(self) -> URIRef:
400
- return DEFAULT_NAMESPACE[f"data-model/verified/info/{self.metadata.prefix}/{self.metadata.version}"]
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass, field
2
2
  from datetime import datetime
3
- from typing import Any, Literal
3
+ from typing import Any
4
4
 
5
5
  import pandas as pd
6
6
  from rdflib import Namespace, URIRef
@@ -25,19 +25,16 @@ from ._rules import (
25
25
 
26
26
  @dataclass
27
27
  class InformationInputMetadata(InputComponent[InformationMetadata]):
28
- schema_: Literal["complete", "partial", "extended"]
29
- prefix: str
30
- namespace: str
28
+ space: str
29
+ external_id: str
31
30
  version: str
32
31
  creator: str
33
- data_model_type: Literal["solution", "enterprise"] = "enterprise"
34
- extension: Literal["addition", "reshape", "rebuild"] = "addition"
35
32
  name: str | None = None
36
33
  description: str | None = None
37
34
  created: datetime | str | None = None
38
35
  updated: datetime | str | None = None
39
- license: str | None = None
40
- rights: str | None = None
36
+ physical: str | None = None
37
+ conceptual: str | None = None
41
38
 
42
39
  @classmethod
43
40
  def _get_verified_cls(cls) -> type[InformationMetadata]:
@@ -51,6 +48,25 @@ class InformationInputMetadata(InputComponent[InformationMetadata]):
51
48
  output["updated"] = datetime.now()
52
49
  return output
53
50
 
51
+ @property
52
+ def prefix(self) -> str:
53
+ return self.space
54
+
55
+ @property
56
+ def identifier(self) -> URIRef:
57
+ """Globally unique identifier for the data model.
58
+
59
+ !!! note
60
+ Unlike namespace, the identifier does not end with "/" or "#".
61
+
62
+ """
63
+ return DEFAULT_NAMESPACE[f"data-model/unverified/logical/{self.space}/{self.external_id}/{self.version}"]
64
+
65
+ @property
66
+ def namespace(self) -> Namespace:
67
+ """Namespace for the data model used for the entities in the data model."""
68
+ return Namespace(f"{self.identifier}/")
69
+
54
70
 
55
71
  @dataclass
56
72
  class InformationInputProperty(InputComponent[InformationProperty]):
@@ -59,12 +75,9 @@ class InformationInputProperty(InputComponent[InformationProperty]):
59
75
  value_type: DataType | ClassEntity | MultiValueTypeInfo | UnknownEntity | str
60
76
  name: str | None = None
61
77
  description: str | None = None
62
- comment: str | None = None
63
78
  min_count: int | None = None
64
79
  max_count: int | float | None = None
65
80
  default: Any | None = None
66
- reference: str | None = None
67
- match_type: str | None = None
68
81
  transformation: str | None = None
69
82
  # Only used internally
70
83
  inherited: bool = False
@@ -85,10 +98,7 @@ class InformationInputClass(InputComponent[InformationClass]):
85
98
  class_: ClassEntity | str
86
99
  name: str | None = None
87
100
  description: str | None = None
88
- comment: str | None = None
89
- parent: str | list[ClassEntity] | None = None
90
- reference: str | None = None
91
- match_type: str | None = None
101
+ implements: str | list[ClassEntity] | None = None
92
102
 
93
103
  @classmethod
94
104
  def _get_verified_cls(cls) -> type[InformationClass]:
@@ -101,12 +111,12 @@ class InformationInputClass(InputComponent[InformationClass]):
101
111
  def dump(self, default_prefix: str, **kwargs) -> dict[str, Any]: # type: ignore[override]
102
112
  output = super().dump()
103
113
  parent: list[ClassEntity] | None = None
104
- if isinstance(self.parent, str):
105
- parent = [ClassEntity.load(parent, prefix=default_prefix) for parent in self.parent.split(",")]
106
- elif isinstance(self.parent, list):
107
- parent = [ClassEntity.load(parent_, prefix=default_prefix) for parent_ in self.parent]
114
+ if isinstance(self.implements, str):
115
+ parent = [ClassEntity.load(parent, prefix=default_prefix) for parent in self.implements.split(",")]
116
+ elif isinstance(self.implements, list):
117
+ parent = [ClassEntity.load(parent_, prefix=default_prefix) for parent_ in self.implements]
108
118
  output["Class"] = ClassEntity.load(self.class_, prefix=default_prefix)
109
- output["Parent Class"] = parent
119
+ output["Implements"] = parent
110
120
  return output
111
121
 
112
122
 
@@ -116,8 +126,6 @@ class InformationInputRules(InputRules[InformationRules]):
116
126
  properties: list[InformationInputProperty] = field(default_factory=list)
117
127
  classes: list[InformationInputClass] = field(default_factory=list)
118
128
  prefixes: dict[str, Namespace] | None = None
119
- last: "InformationInputRules | None" = None
120
- reference: "InformationInputRules | None" = None
121
129
 
122
130
  @classmethod
123
131
  def _get_verified_cls(cls) -> type[InformationRules]:
@@ -125,26 +133,12 @@ class InformationInputRules(InputRules[InformationRules]):
125
133
 
126
134
  def dump(self) -> dict[str, Any]:
127
135
  default_prefix = self.metadata.prefix
128
- reference: dict[str, Any] | None = None
129
- if isinstance(self.reference, InformationInputRules):
130
- reference = self.reference.dump()
131
- elif isinstance(self.reference, InformationRules):
132
- # We need to load through the InformationRulesInput to set the correct default space and version
133
- reference = InformationInputRules.load(self.reference.model_dump()).dump()
134
- last: dict[str, Any] | None = None
135
- if isinstance(self.last, InformationInputRules):
136
- last = self.last.dump()
137
- elif isinstance(self.last, InformationRules):
138
- # We need to load through the InformationRulesInput to set the correct default space and version
139
- last = InformationInputRules.load(self.last.model_dump()).dump()
140
136
 
141
137
  return dict(
142
138
  Metadata=self.metadata.dump(),
143
139
  Properties=[prop.dump(default_prefix) for prop in self.properties],
144
140
  Classes=[class_.dump(default_prefix) for class_ in self.classes],
145
141
  Prefixes=self.prefixes,
146
- Last=last,
147
- Reference=reference,
148
142
  )
149
143
 
150
144
  def _repr_html_(self) -> str:
@@ -152,14 +146,11 @@ class InformationInputRules(InputRules[InformationRules]):
152
146
  "type": "Logical Data Model",
153
147
  "intended for": "Information Architect",
154
148
  "name": self.metadata.name,
155
- "external_id": self.metadata.prefix,
149
+ "external_id": self.metadata.external_id,
150
+ "space": self.metadata.space,
156
151
  "version": self.metadata.version,
157
152
  "classes": len(self.classes),
158
153
  "properties": len(self.properties),
159
154
  }
160
155
 
161
156
  return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
162
-
163
- @property
164
- def id_(self) -> URIRef:
165
- return DEFAULT_NAMESPACE[f"data-model/unverified/info/{self.metadata.prefix}/{self.metadata.version}"]
@@ -4,10 +4,9 @@ from typing import cast
4
4
 
5
5
  from cognite.neat._issues import IssueList
6
6
  from cognite.neat._issues.errors import NeatValueError, ResourceNotDefinedError
7
+ from cognite.neat._issues.warnings._models import UndefinedClassWarning
7
8
  from cognite.neat._rules._constants import EntityTypes
8
- from cognite.neat._rules.models._base_rules import DataModelType, SchemaCompleteness
9
9
  from cognite.neat._rules.models.entities import ClassEntity, UnknownEntity
10
- from cognite.neat._utils.rdf_ import get_inheritance_path
11
10
 
12
11
  from ._rules import InformationRules
13
12
 
@@ -24,72 +23,60 @@ class InformationPostValidation:
24
23
  self.issue_list = IssueList()
25
24
 
26
25
  def validate(self) -> IssueList:
27
- if self.metadata.schema_ == SchemaCompleteness.partial:
28
- return self.issue_list
29
-
30
- if self.metadata.data_model_type == DataModelType.solution and not self.rules.reference:
31
- raise ValueError("Reference data model is missing")
32
-
33
- if self.metadata.schema_ == SchemaCompleteness.extended and not self.rules.last:
34
- raise ValueError("Last version is missing")
35
-
36
- self._dangling_classes()
37
- self._referenced_parent_classes_exist()
26
+ self._namespaces_reassigned()
27
+ self._classes_without_properties()
28
+ self._parent_class_defined()
38
29
  self._referenced_classes_exist()
39
30
  self._referenced_value_types_exist()
40
- self._namespaces_reassigned()
41
31
 
42
32
  return self.issue_list
43
33
 
44
- def _dangling_classes(self) -> None:
34
+ def _classes_without_properties(self) -> None:
45
35
  # needs to be complete for this validation to pass
46
36
  defined_classes = {class_.class_ for class_ in self.classes}
47
37
  referred_classes = {property_.class_ for property_ in self.properties}
48
38
  class_parent_pairs = self._class_parent_pairs()
49
- dangling_classes = set()
50
39
 
51
40
  if classes_without_properties := defined_classes.difference(referred_classes):
52
41
  for class_ in classes_without_properties:
53
- # USE CASE: class has no direct properties and no parents
54
- if class_ not in class_parent_pairs:
55
- dangling_classes.add(class_)
56
42
  # USE CASE: class has no direct properties and no parents with properties
57
- elif class_ not in class_parent_pairs and not any(
58
- parent in referred_classes for parent in get_inheritance_path(class_, class_parent_pairs)
59
- ):
60
- dangling_classes.add(class_)
61
-
62
- for class_ in dangling_classes:
63
- self.issue_list.append(
64
- NeatValueError(f"Class {class_} has no properties and is not a parent of any class with properties")
65
- )
43
+ # and it is a class in the prefix of data model, as long as it is in the
44
+ # same prefix, meaning same space
45
+ if not class_parent_pairs[class_] and class_.prefix == self.metadata.prefix:
46
+ self.issue_list.append(
47
+ ResourceNotDefinedError[ClassEntity](
48
+ resource_type="class",
49
+ identifier=class_,
50
+ location="Classes sheet",
51
+ )
52
+ )
66
53
 
67
- def _referenced_parent_classes_exist(self) -> None:
68
- # needs to be complete for this validation to pass
54
+ def _parent_class_defined(self) -> None:
55
+ """This is a validation to check if the parent class of a class is defined in the classes sheet."""
69
56
  class_parent_pairs = self._class_parent_pairs()
70
57
  classes = set(class_parent_pairs.keys())
71
58
  parents = set(itertools.chain.from_iterable(class_parent_pairs.values()))
72
59
 
73
60
  if undefined_parents := parents.difference(classes):
74
61
  for parent in undefined_parents:
75
- # Todo: include row and column number
76
- self.issue_list.append(
77
- ResourceNotDefinedError[ClassEntity](
78
- resource_type="class",
79
- identifier=parent,
80
- location="Classes sheet",
62
+ if parent.prefix != self.metadata.prefix:
63
+ self.issue_list.append(UndefinedClassWarning(class_id=str(parent)))
64
+ else:
65
+ self.issue_list.append(
66
+ ResourceNotDefinedError[ClassEntity](
67
+ resource_type="class",
68
+ identifier=parent,
69
+ location="Classes sheet",
70
+ )
81
71
  )
82
- )
83
72
 
84
73
  def _referenced_classes_exist(self) -> None:
85
74
  # needs to be complete for this validation to pass
86
75
  defined_classes = {class_.class_ for class_ in self.classes}
87
- referred_classes = {property_.class_ for property_ in self.properties}
76
+ classes_with_explicit_properties = {property_.class_ for property_ in self.properties}
88
77
 
89
78
  # USE CASE: models are complete
90
- if self.metadata.schema_ == SchemaCompleteness.complete and (
91
- missing_classes := referred_classes.difference(defined_classes)
92
- ):
79
+ if missing_classes := classes_with_explicit_properties.difference(defined_classes):
93
80
  for class_ in missing_classes:
94
81
  self.issue_list.append(
95
82
  ResourceNotDefinedError[ClassEntity](
@@ -99,19 +86,6 @@ class InformationPostValidation:
99
86
  )
100
87
  )
101
88
 
102
- # USE CASE: models are extended (user + last = complete)
103
- if self.metadata.schema_ == SchemaCompleteness.extended:
104
- defined_classes |= {class_.class_ for class_ in cast(InformationRules, self.rules.last).classes}
105
- if missing_classes := referred_classes.difference(defined_classes):
106
- for class_ in missing_classes:
107
- self.issue_list.append(
108
- ResourceNotDefinedError[ClassEntity](
109
- resource_type="class",
110
- identifier=class_,
111
- location="Classes sheet",
112
- )
113
- )
114
-
115
89
  def _referenced_value_types_exist(self) -> None:
116
90
  # adding UnknownEntity to the set of defined classes to handle the case where a property references an unknown
117
91
  defined_classes = {class_.class_ for class_ in self.classes} | {UnknownEntity()}
@@ -121,10 +95,7 @@ class InformationPostValidation:
121
95
  if property_.type_ == EntityTypes.object_property
122
96
  }
123
97
 
124
- # USE CASE: models are complete
125
- if self.metadata.schema_ == SchemaCompleteness.complete and (
126
- missing_value_types := referred_object_types.difference(defined_classes)
127
- ):
98
+ if missing_value_types := referred_object_types.difference(defined_classes):
128
99
  # Todo: include row and column number
129
100
  for missing in missing_value_types:
130
101
  self.issue_list.append(
@@ -135,56 +106,17 @@ class InformationPostValidation:
135
106
  )
136
107
  )
137
108
 
138
- # USE CASE: models are extended (user + last = complete)
139
- if self.metadata.schema_ == SchemaCompleteness.extended:
140
- defined_classes |= {class_.class_ for class_ in cast(InformationRules, self.rules.last).classes}
141
- if missing_value_types := referred_object_types.difference(defined_classes):
142
- # Todo: include row and column number
143
- for missing in missing_value_types:
144
- self.issue_list.append(
145
- ResourceNotDefinedError(
146
- resource_type="class",
147
- identifier=cast(ClassEntity, missing),
148
- location="Classes sheet",
149
- )
150
- )
151
-
152
109
  def _class_parent_pairs(self) -> dict[ClassEntity, list[ClassEntity]]:
153
- class_subclass_pairs: dict[ClassEntity, list[ClassEntity]] = {}
154
-
110
+ class_parent_pairs: dict[ClassEntity, list[ClassEntity]] = {}
155
111
  classes = self.rules.model_copy(deep=True).classes
156
112
 
157
- # USE CASE: Solution model being extended (user + last + reference = complete)
158
- if (
159
- self.metadata.schema_ == SchemaCompleteness.extended
160
- and self.metadata.data_model_type == DataModelType.solution
161
- ):
162
- classes += (
163
- cast(InformationRules, self.rules.last).model_copy(deep=True).classes
164
- + cast(InformationRules, self.rules.reference).model_copy(deep=True).classes
165
- )
166
-
167
- # USE CASE: Solution model being created from scratch (user + reference = complete)
168
- elif (
169
- self.metadata.schema_ == SchemaCompleteness.complete
170
- and self.metadata.data_model_type == DataModelType.solution
171
- ):
172
- classes += cast(InformationRules, self.rules.reference).model_copy(deep=True).classes
173
-
174
- # USE CASE: Enterprise model being extended (user + last = complete)
175
- elif (
176
- self.metadata.schema_ == SchemaCompleteness.extended
177
- and self.metadata.data_model_type == DataModelType.enterprise
178
- ):
179
- classes += cast(InformationRules, self.rules.last).model_copy(deep=True).classes
180
-
181
113
  for class_ in classes:
182
- class_subclass_pairs[class_.class_] = []
183
- if class_.parent is None:
114
+ class_parent_pairs[class_.class_] = []
115
+ if class_.implements is None:
184
116
  continue
185
- class_subclass_pairs[class_.class_].extend(class_.parent)
117
+ class_parent_pairs[class_.class_].extend(class_.implements)
186
118
 
187
- return class_subclass_pairs
119
+ return class_parent_pairs
188
120
 
189
121
  def _namespaces_reassigned(self) -> None:
190
122
  prefixes = self.rules.prefixes.copy()
@@ -1,9 +1,7 @@
1
1
  from ._base import RulesPipeline, RulesTransformer
2
2
  from ._converters import (
3
- AssetToInformation,
4
3
  ConvertToRules,
5
4
  DMSToInformation,
6
- InformationToAsset,
7
5
  InformationToDMS,
8
6
  ReduceCogniteModel,
9
7
  SetIDDMSModel,
@@ -12,7 +10,7 @@ from ._converters import (
12
10
  )
13
11
  from ._mapping import MapOneToOne, RuleMapper
14
12
  from ._pipelines import ImporterPipeline
15
- from ._verification import VerifyAnyRules, VerifyAssetRules, VerifyDMSRules, VerifyInformationRules
13
+ from ._verification import VerifyAnyRules, VerifyDMSRules, VerifyInformationRules
16
14
 
17
15
  __all__ = [
18
16
  "ImporterPipeline",
@@ -23,7 +21,6 @@ __all__ = [
23
21
  "ConvertToRules",
24
22
  "AssetToInformation",
25
23
  "DMSToInformation",
26
- "VerifyAssetRules",
27
24
  "VerifyDMSRules",
28
25
  "VerifyInformationRules",
29
26
  "VerifyAnyRules",