cognite-neat 0.90.2__py3-none-any.whl → 0.92.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.
- cognite/neat/_version.py +1 -1
- cognite/neat/graph/extractors/__init__.py +5 -0
- cognite/neat/graph/extractors/_classic_cdf/_assets.py +8 -8
- cognite/neat/graph/extractors/_classic_cdf/_base.py +26 -1
- cognite/neat/graph/extractors/_classic_cdf/_classic.py +208 -0
- cognite/neat/graph/extractors/_classic_cdf/_data_sets.py +110 -0
- cognite/neat/graph/extractors/_classic_cdf/_events.py +30 -5
- cognite/neat/graph/extractors/_classic_cdf/_files.py +33 -8
- cognite/neat/graph/extractors/_classic_cdf/_labels.py +14 -6
- cognite/neat/graph/extractors/_classic_cdf/_relationships.py +38 -7
- cognite/neat/graph/extractors/_classic_cdf/_sequences.py +30 -5
- cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +30 -5
- cognite/neat/graph/extractors/_dexpi.py +4 -4
- cognite/neat/graph/extractors/_iodd.py +160 -0
- cognite/neat/issues/_base.py +6 -2
- cognite/neat/rules/exporters/_rules2excel.py +3 -3
- cognite/neat/rules/exporters/_rules2yaml.py +5 -1
- cognite/neat/rules/models/__init__.py +2 -2
- cognite/neat/rules/models/_base_input.py +2 -2
- cognite/neat/rules/models/_base_rules.py +142 -142
- cognite/neat/rules/models/asset/_rules.py +1 -34
- cognite/neat/rules/models/dms/_rules.py +127 -46
- cognite/neat/rules/models/dms/_validation.py +2 -2
- cognite/neat/rules/models/domain.py +16 -19
- cognite/neat/rules/models/entities/_single_value.py +25 -11
- cognite/neat/rules/models/entities/_types.py +0 -10
- cognite/neat/rules/models/information/_rules.py +68 -43
- cognite/neat/rules/models/information/_validation.py +5 -5
- cognite/neat/rules/transformers/_converters.py +6 -8
- cognite/neat/rules/transformers/_pipelines.py +8 -4
- cognite/neat/store/_base.py +1 -1
- cognite/neat/utils/collection_.py +4 -3
- cognite/neat/utils/xml_.py +27 -12
- {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/RECORD +38 -38
- cognite/neat/rules/models/asset/_serializer.py +0 -73
- cognite/neat/rules/models/dms/_serializer.py +0 -157
- cognite/neat/rules/models/information/_serializer.py +0 -73
- {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import sys
|
|
3
3
|
import warnings
|
|
4
|
+
from collections.abc import Hashable
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
|
6
7
|
|
|
7
8
|
from cognite.client import data_modeling as dm
|
|
8
9
|
from pydantic import Field, field_serializer, field_validator, model_validator
|
|
9
|
-
from
|
|
10
|
-
from pydantic_core.core_schema import ValidationInfo
|
|
10
|
+
from pydantic_core.core_schema import SerializationInfo, ValidationInfo
|
|
11
11
|
|
|
12
12
|
from cognite.neat.issues import MultiValueError
|
|
13
13
|
from cognite.neat.issues.warnings import (
|
|
@@ -21,8 +21,8 @@ from cognite.neat.rules.models._base_rules import (
|
|
|
21
21
|
ExtensionCategory,
|
|
22
22
|
RoleTypes,
|
|
23
23
|
SchemaCompleteness,
|
|
24
|
-
SheetEntity,
|
|
25
24
|
SheetList,
|
|
25
|
+
SheetRow,
|
|
26
26
|
)
|
|
27
27
|
from cognite.neat.rules.models._types import (
|
|
28
28
|
ExternalIdType,
|
|
@@ -35,9 +35,11 @@ from cognite.neat.rules.models.entities import (
|
|
|
35
35
|
ClassEntity,
|
|
36
36
|
ContainerEntity,
|
|
37
37
|
ContainerEntityList,
|
|
38
|
+
DMSEntity,
|
|
38
39
|
DMSNodeEntity,
|
|
39
40
|
DMSUnknownEntity,
|
|
40
41
|
EdgeEntity,
|
|
42
|
+
Entity,
|
|
41
43
|
HasDataFilter,
|
|
42
44
|
NodeTypeFilter,
|
|
43
45
|
RawFilter,
|
|
@@ -91,8 +93,7 @@ class DMSMetadata(BaseMetadata):
|
|
|
91
93
|
return value
|
|
92
94
|
|
|
93
95
|
@field_serializer("schema_", "extension", "data_model_type", when_used="always")
|
|
94
|
-
|
|
95
|
-
def as_string(value: SchemaCompleteness | ExtensionCategory | DataModelType) -> str:
|
|
96
|
+
def as_string(self, value: SchemaCompleteness | ExtensionCategory | DataModelType) -> str:
|
|
96
97
|
return str(value)
|
|
97
98
|
|
|
98
99
|
@field_validator("schema_", mode="plain")
|
|
@@ -144,7 +145,13 @@ class DMSMetadata(BaseMetadata):
|
|
|
144
145
|
return self.space
|
|
145
146
|
|
|
146
147
|
|
|
147
|
-
|
|
148
|
+
def _metadata(context: Any) -> DMSMetadata | None:
|
|
149
|
+
if isinstance(context, dict) and isinstance(context.get("metadata"), DMSMetadata):
|
|
150
|
+
return context["metadata"]
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class DMSProperty(SheetRow):
|
|
148
155
|
view: ViewEntity = Field(alias="View")
|
|
149
156
|
view_property: str = Field(alias="View Property")
|
|
150
157
|
name: str | None = Field(alias="Name", default=None)
|
|
@@ -163,6 +170,9 @@ class DMSProperty(SheetEntity):
|
|
|
163
170
|
class_: ClassEntity = Field(alias="Class (linage)")
|
|
164
171
|
property_: PropertyType = Field(alias="Property (linage)")
|
|
165
172
|
|
|
173
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
174
|
+
return self.view, self.view_property
|
|
175
|
+
|
|
166
176
|
@field_validator("nullable")
|
|
167
177
|
def direct_relation_must_be_nullable(cls, value: Any, info: ValidationInfo) -> None:
|
|
168
178
|
if info.data.get("connection") == "direct" and value is False:
|
|
@@ -183,16 +193,46 @@ class DMSProperty(SheetEntity):
|
|
|
183
193
|
raise ValueError(f"Reverse connection must have a value type that points to a view, got {value}")
|
|
184
194
|
return value
|
|
185
195
|
|
|
196
|
+
@field_serializer("reference", when_used="always")
|
|
197
|
+
def set_reference(self, value: Any, info: SerializationInfo) -> str | None:
|
|
198
|
+
if isinstance(info.context, dict) and info.context.get("as_reference") is True:
|
|
199
|
+
return str(
|
|
200
|
+
ReferenceEntity(
|
|
201
|
+
prefix=self.view.prefix,
|
|
202
|
+
suffix=self.view.suffix,
|
|
203
|
+
version=self.view.version,
|
|
204
|
+
property=self.view_property,
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
return str(value) if value is not None else None
|
|
208
|
+
|
|
186
209
|
@field_serializer("value_type", when_used="always")
|
|
187
|
-
|
|
188
|
-
def as_dms_type(value_type: DataType | EdgeEntity | ViewEntity) -> str:
|
|
210
|
+
def as_dms_type(self, value_type: DataType | EdgeEntity | ViewEntity, info: SerializationInfo) -> str:
|
|
189
211
|
if isinstance(value_type, DataType):
|
|
190
212
|
return value_type._suffix_extra_args(value_type.dms._type)
|
|
191
|
-
|
|
192
|
-
return
|
|
213
|
+
elif isinstance(value_type, EdgeEntity | ViewEntity) and (metadata := _metadata(info.context)):
|
|
214
|
+
return value_type.dump(space=metadata.space, version=metadata.version)
|
|
215
|
+
return str(value_type)
|
|
216
|
+
|
|
217
|
+
@field_serializer("view", "container", "class_", when_used="unless-none")
|
|
218
|
+
def remove_default_space(self, value: str, info: SerializationInfo) -> str:
|
|
219
|
+
if (metadata := _metadata(info.context)) and isinstance(value, Entity):
|
|
220
|
+
if info.field_name == "container" and info.context.get("as_reference") is True:
|
|
221
|
+
# When dumping as reference, the container should keep the default space for easy copying
|
|
222
|
+
# over to user sheets.
|
|
223
|
+
return value.dump()
|
|
224
|
+
return value.dump(prefix=metadata.space, version=metadata.version)
|
|
225
|
+
return str(value)
|
|
226
|
+
|
|
227
|
+
@field_serializer("connection", when_used="unless-none")
|
|
228
|
+
def remove_defaults(self, value: Any, info: SerializationInfo) -> str:
|
|
229
|
+
if isinstance(value, Entity) and (metadata := _metadata(info.context)):
|
|
230
|
+
default_type = f"{metadata.space}{self.view.external_id}.{self.view_property}"
|
|
231
|
+
return value.dump(space=metadata.space, version=metadata.version, type=default_type)
|
|
232
|
+
return str(value)
|
|
193
233
|
|
|
194
234
|
|
|
195
|
-
class DMSContainer(
|
|
235
|
+
class DMSContainer(SheetRow):
|
|
196
236
|
container: ContainerEntity = Field(alias="Container")
|
|
197
237
|
name: str | None = Field(alias="Name", default=None)
|
|
198
238
|
description: str | None = Field(alias="Description", default=None)
|
|
@@ -201,6 +241,9 @@ class DMSContainer(SheetEntity):
|
|
|
201
241
|
used_for: Literal["node", "edge", "all"] | None = Field("all", alias="Used For")
|
|
202
242
|
class_: ClassEntity = Field(alias="Class (linage)")
|
|
203
243
|
|
|
244
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
245
|
+
return (self.container,)
|
|
246
|
+
|
|
204
247
|
def as_container(self) -> dm.ContainerApply:
|
|
205
248
|
container_id = self.container.as_id()
|
|
206
249
|
constraints: dict[str, dm.Constraint] = {}
|
|
@@ -218,8 +261,34 @@ class DMSContainer(SheetEntity):
|
|
|
218
261
|
used_for=self.used_for,
|
|
219
262
|
)
|
|
220
263
|
|
|
264
|
+
@field_serializer("reference", when_used="always")
|
|
265
|
+
def set_reference(self, value: Any, info: SerializationInfo) -> str | None:
|
|
266
|
+
if isinstance(info.context, dict) and info.context.get("as_reference") is True:
|
|
267
|
+
return self.container.dump()
|
|
268
|
+
return str(value) if value is not None else None
|
|
269
|
+
|
|
270
|
+
@field_serializer("container", "class_", when_used="unless-none")
|
|
271
|
+
def remove_default_space(self, value: Any, info: SerializationInfo) -> str:
|
|
272
|
+
if metadata := _metadata(info.context):
|
|
273
|
+
if isinstance(value, DMSEntity):
|
|
274
|
+
return value.dump(space=metadata.space, version=metadata.version)
|
|
275
|
+
elif isinstance(value, Entity):
|
|
276
|
+
return value.dump(prefix=metadata.space, version=metadata.version)
|
|
277
|
+
return str(value)
|
|
278
|
+
|
|
279
|
+
@field_serializer("constraint", when_used="unless-none")
|
|
280
|
+
def remove_default_spaces(self, value: Any, info: SerializationInfo) -> str:
|
|
281
|
+
if isinstance(value, list) and (metadata := _metadata(info.context)):
|
|
282
|
+
return ",".join(
|
|
283
|
+
constraint.dump(space=metadata.space, version=metadata.version)
|
|
284
|
+
if isinstance(constraint, DMSEntity)
|
|
285
|
+
else str(constraint)
|
|
286
|
+
for constraint in value
|
|
287
|
+
)
|
|
288
|
+
return ",".join(str(value) for value in value)
|
|
289
|
+
|
|
221
290
|
|
|
222
|
-
class DMSView(
|
|
291
|
+
class DMSView(SheetRow):
|
|
223
292
|
view: ViewEntity = Field(alias="View")
|
|
224
293
|
name: str | None = Field(alias="Name", default=None)
|
|
225
294
|
description: str | None = Field(alias="Description", default=None)
|
|
@@ -229,6 +298,32 @@ class DMSView(SheetEntity):
|
|
|
229
298
|
in_model: bool = Field(True, alias="In Model")
|
|
230
299
|
class_: ClassEntity = Field(alias="Class (linage)")
|
|
231
300
|
|
|
301
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
302
|
+
return (self.view,)
|
|
303
|
+
|
|
304
|
+
@field_serializer("reference", when_used="always")
|
|
305
|
+
def set_reference(self, value: Any, info: SerializationInfo) -> str | None:
|
|
306
|
+
if isinstance(info.context, dict) and info.context.get("as_reference") is True:
|
|
307
|
+
return self.view.dump()
|
|
308
|
+
return str(value) if value is not None else None
|
|
309
|
+
|
|
310
|
+
@field_serializer("view", "class_", when_used="unless-none")
|
|
311
|
+
def remove_default_space(self, value: Any, info: SerializationInfo) -> str:
|
|
312
|
+
if (metadata := _metadata(info.context)) and isinstance(value, Entity):
|
|
313
|
+
return value.dump(prefix=metadata.space, version=metadata.version)
|
|
314
|
+
return str(value)
|
|
315
|
+
|
|
316
|
+
@field_serializer("implements", when_used="unless-none")
|
|
317
|
+
def remove_default_spaces(self, value: Any, info: SerializationInfo) -> str:
|
|
318
|
+
if isinstance(value, list) and (metadata := _metadata(info.context)):
|
|
319
|
+
return ",".join(
|
|
320
|
+
parent.dump(space=metadata.space, version=metadata.version)
|
|
321
|
+
if isinstance(parent, DMSEntity)
|
|
322
|
+
else str(parent)
|
|
323
|
+
for parent in value
|
|
324
|
+
)
|
|
325
|
+
return ",".join(str(value) for value in value) if isinstance(value, list) else value
|
|
326
|
+
|
|
232
327
|
def as_view(self) -> dm.ViewApply:
|
|
233
328
|
view_id = self.view.as_id()
|
|
234
329
|
implements = [parent.as_id() for parent in self.implements or []] or None
|
|
@@ -249,12 +344,15 @@ class DMSView(SheetEntity):
|
|
|
249
344
|
)
|
|
250
345
|
|
|
251
346
|
|
|
252
|
-
class DMSNode(
|
|
347
|
+
class DMSNode(SheetRow):
|
|
253
348
|
node: DMSNodeEntity = Field(alias="Node")
|
|
254
349
|
usage: Literal["type", "collection"] = Field(alias="Usage")
|
|
255
350
|
name: str | None = Field(alias="Name", default=None)
|
|
256
351
|
description: str | None = Field(alias="Description", default=None)
|
|
257
352
|
|
|
353
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
354
|
+
return (self.node,)
|
|
355
|
+
|
|
258
356
|
def as_node(self) -> dm.NodeApply:
|
|
259
357
|
if self.usage == "type":
|
|
260
358
|
return dm.NodeApply(space=self.node.space, external_id=self.node.external_id)
|
|
@@ -263,13 +361,28 @@ class DMSNode(SheetEntity):
|
|
|
263
361
|
else:
|
|
264
362
|
raise ValueError(f"Unknown usage {self.usage}")
|
|
265
363
|
|
|
364
|
+
@field_serializer("node", when_used="unless-none")
|
|
365
|
+
def remove_default_space(self, value: Any, info: SerializationInfo) -> str:
|
|
366
|
+
if isinstance(value, DMSEntity) and (metadata := _metadata(info.context)):
|
|
367
|
+
return value.dump(space=metadata.space, version=metadata.version)
|
|
368
|
+
return str(value)
|
|
369
|
+
|
|
266
370
|
|
|
267
|
-
class DMSEnum(
|
|
371
|
+
class DMSEnum(SheetRow):
|
|
268
372
|
collection: ClassEntity = Field(alias="Collection")
|
|
269
373
|
value: str = Field(alias="Value")
|
|
270
374
|
name: str | None = Field(alias="Name", default=None)
|
|
271
375
|
description: str | None = Field(alias="Description", default=None)
|
|
272
376
|
|
|
377
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
378
|
+
return self.collection, self.value
|
|
379
|
+
|
|
380
|
+
@field_serializer("collection", when_used="unless-none")
|
|
381
|
+
def remove_default_space(self, value: Any, info: SerializationInfo) -> str:
|
|
382
|
+
if isinstance(value, DMSEntity) and (metadata := _metadata(info.context)):
|
|
383
|
+
return value.dump(space=metadata.space, version=metadata.version)
|
|
384
|
+
return str(value)
|
|
385
|
+
|
|
273
386
|
|
|
274
387
|
class DMSRules(BaseRules):
|
|
275
388
|
metadata: DMSMetadata = Field(alias="Metadata")
|
|
@@ -331,38 +444,6 @@ class DMSRules(BaseRules):
|
|
|
331
444
|
raise MultiValueError(issue_list.errors)
|
|
332
445
|
return self
|
|
333
446
|
|
|
334
|
-
def dump(
|
|
335
|
-
self,
|
|
336
|
-
mode: Literal["python", "json"] = "python",
|
|
337
|
-
by_alias: bool = False,
|
|
338
|
-
exclude: IncEx = None,
|
|
339
|
-
exclude_none: bool = False,
|
|
340
|
-
exclude_unset: bool = False,
|
|
341
|
-
exclude_defaults: bool = False,
|
|
342
|
-
as_reference: bool = False,
|
|
343
|
-
) -> dict[str, Any]:
|
|
344
|
-
from ._serializer import _DMSRulesSerializer
|
|
345
|
-
|
|
346
|
-
dumped = self.model_dump(
|
|
347
|
-
mode=mode,
|
|
348
|
-
by_alias=by_alias,
|
|
349
|
-
exclude=exclude,
|
|
350
|
-
exclude_none=exclude_none,
|
|
351
|
-
exclude_unset=exclude_unset,
|
|
352
|
-
exclude_defaults=exclude_defaults,
|
|
353
|
-
)
|
|
354
|
-
space, version = self.metadata.space, self.metadata.version
|
|
355
|
-
serializer = _DMSRulesSerializer(by_alias, space, version)
|
|
356
|
-
clean = serializer.clean(dumped, as_reference)
|
|
357
|
-
last = "Last" if by_alias else "last"
|
|
358
|
-
if last_dump := clean.get(last):
|
|
359
|
-
clean[last] = serializer.clean(last_dump, False)
|
|
360
|
-
reference = "Reference" if by_alias else "reference"
|
|
361
|
-
if self.reference and (ref_dump := clean.get(reference)):
|
|
362
|
-
space, version = self.reference.metadata.space, self.reference.metadata.version
|
|
363
|
-
clean[reference] = _DMSRulesSerializer(by_alias, space, version).clean(ref_dump, True)
|
|
364
|
-
return clean
|
|
365
|
-
|
|
366
447
|
def as_schema(self, include_pipeline: bool = False, instance_space: str | None = None) -> DMSSchema:
|
|
367
448
|
from ._exporter import _DMSExporter
|
|
368
449
|
|
|
@@ -201,7 +201,7 @@ class DMSPostValidation:
|
|
|
201
201
|
for prop_no, prop in enumerate(self.properties):
|
|
202
202
|
if prop.container and (container_id := prop.container.as_id()) not in defined_containers:
|
|
203
203
|
errors.append(
|
|
204
|
-
ResourceNotDefinedError
|
|
204
|
+
ResourceNotDefinedError(
|
|
205
205
|
identifier=container_id,
|
|
206
206
|
resource_type="container",
|
|
207
207
|
location="Containers Sheet",
|
|
@@ -214,7 +214,7 @@ class DMSPostValidation:
|
|
|
214
214
|
for constraint_no, constraint in enumerate(container.constraint or []):
|
|
215
215
|
if constraint.as_id() not in defined_containers:
|
|
216
216
|
errors.append(
|
|
217
|
-
ResourceNotDefinedError
|
|
217
|
+
ResourceNotDefinedError(
|
|
218
218
|
identifier=constraint.as_id(),
|
|
219
219
|
resource_type="container",
|
|
220
220
|
location="Containers Sheet",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import math
|
|
2
|
+
from collections.abc import Hashable
|
|
2
3
|
from dataclasses import dataclass, field
|
|
3
|
-
from typing import
|
|
4
|
+
from typing import ClassVar
|
|
4
5
|
|
|
5
|
-
from pydantic import Field, field_serializer, field_validator
|
|
6
|
-
from pydantic_core.core_schema import SerializationInfo
|
|
6
|
+
from pydantic import Field, field_serializer, field_validator
|
|
7
7
|
|
|
8
8
|
from cognite.neat.rules.models.data_types import DataType
|
|
9
9
|
from cognite.neat.rules.models.entities import ClassEntity, ClassEntityList
|
|
@@ -13,8 +13,8 @@ from ._base_rules import (
|
|
|
13
13
|
BaseMetadata,
|
|
14
14
|
BaseRules,
|
|
15
15
|
RoleTypes,
|
|
16
|
-
SheetEntity,
|
|
17
16
|
SheetList,
|
|
17
|
+
SheetRow,
|
|
18
18
|
)
|
|
19
19
|
from ._types import PropertyType, StrOrListType
|
|
20
20
|
|
|
@@ -30,7 +30,7 @@ class DomainMetadata(BaseMetadata):
|
|
|
30
30
|
return "domain"
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class DomainProperty(
|
|
33
|
+
class DomainProperty(SheetRow):
|
|
34
34
|
class_: ClassEntity = Field(alias="Class")
|
|
35
35
|
property_: PropertyType = Field(alias="Property")
|
|
36
36
|
name: str | None = Field(alias="Name", default=None)
|
|
@@ -39,6 +39,9 @@ class DomainProperty(SheetEntity):
|
|
|
39
39
|
min_count: int | None = Field(alias="Min Count", default=None)
|
|
40
40
|
max_count: int | float | None = Field(alias="Max Count", default=None)
|
|
41
41
|
|
|
42
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
43
|
+
return self.class_, self.property_
|
|
44
|
+
|
|
42
45
|
@field_serializer("max_count", when_used="json-unless-none")
|
|
43
46
|
def serialize_max_count(self, value: int | float | None) -> int | float | None | str:
|
|
44
47
|
if isinstance(value, float) and math.isinf(value):
|
|
@@ -52,12 +55,19 @@ class DomainProperty(SheetEntity):
|
|
|
52
55
|
return value
|
|
53
56
|
|
|
54
57
|
|
|
55
|
-
class DomainClass(
|
|
58
|
+
class DomainClass(SheetRow):
|
|
56
59
|
class_: ClassEntity = Field(alias="Class")
|
|
57
60
|
name: str | None = Field(alias="Name", default=None)
|
|
58
61
|
description: str | None = Field(None, alias="Description")
|
|
59
62
|
parent: ClassEntityList | None = Field(alias="Parent Class")
|
|
60
63
|
|
|
64
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
65
|
+
return (self.class_,)
|
|
66
|
+
|
|
67
|
+
@field_serializer("parent", when_used="unless-none")
|
|
68
|
+
def serialize_parent(self, value: list[ClassEntity]) -> str:
|
|
69
|
+
return ",".join([str(entry) for entry in value])
|
|
70
|
+
|
|
61
71
|
|
|
62
72
|
class DomainRules(BaseRules):
|
|
63
73
|
metadata: DomainMetadata = Field(alias="Metadata")
|
|
@@ -66,19 +76,6 @@ class DomainRules(BaseRules):
|
|
|
66
76
|
last: "DomainRules | None" = Field(None, alias="Last")
|
|
67
77
|
reference: "DomainRules | None" = Field(None, alias="Reference")
|
|
68
78
|
|
|
69
|
-
@model_serializer(mode="plain", when_used="always")
|
|
70
|
-
def domain_rules_serializer(self, info: SerializationInfo) -> dict[str, Any]:
|
|
71
|
-
kwargs = vars(info)
|
|
72
|
-
output: dict[str, Any] = {
|
|
73
|
-
"Metadata" if info.by_alias else "metadata": self.metadata.model_dump(**kwargs),
|
|
74
|
-
"Properties" if info.by_alias else "properties": [prop.model_dump(**kwargs) for prop in self.properties],
|
|
75
|
-
}
|
|
76
|
-
if self.classes or not info.exclude_none:
|
|
77
|
-
output["Classes" if info.by_alias else "classes"] = [
|
|
78
|
-
cls.model_dump(**kwargs) for cls in self.classes or []
|
|
79
|
-
] or None
|
|
80
|
-
return output
|
|
81
|
-
|
|
82
79
|
|
|
83
80
|
@dataclass
|
|
84
81
|
class DomainInputMetadata(InputComponent[DomainMetadata]):
|
|
@@ -58,7 +58,7 @@ class Entity(BaseModel, extra="ignore"):
|
|
|
58
58
|
elif isinstance(data, str) and data == str(Unknown):
|
|
59
59
|
return UnknownEntity(prefix=Undefined, suffix=Unknown)
|
|
60
60
|
if defaults and isinstance(defaults, dict):
|
|
61
|
-
# This is
|
|
61
|
+
# This is a trick to pass in default values
|
|
62
62
|
return cls.model_validate({_PARSE: data, "defaults": defaults})
|
|
63
63
|
else:
|
|
64
64
|
return cls.model_validate(data)
|
|
@@ -123,8 +123,8 @@ class Entity(BaseModel, extra="ignore"):
|
|
|
123
123
|
extra_args[key] = annotation.load(extra_args[key], **defaults) # type: ignore[union-attr, assignment]
|
|
124
124
|
return dict(prefix=prefix, suffix=suffix, **extra_args)
|
|
125
125
|
|
|
126
|
-
def dump(self) -> str:
|
|
127
|
-
return
|
|
126
|
+
def dump(self, **defaults: Any) -> str:
|
|
127
|
+
return self._as_str(**defaults)
|
|
128
128
|
|
|
129
129
|
def as_tuple(self) -> tuple[str, ...]:
|
|
130
130
|
# We haver overwritten the serialization to str, so we need to do it manually
|
|
@@ -164,17 +164,22 @@ class Entity(BaseModel, extra="ignore"):
|
|
|
164
164
|
|
|
165
165
|
@property
|
|
166
166
|
def id(self) -> str:
|
|
167
|
+
return self._as_str()
|
|
168
|
+
|
|
169
|
+
def _as_str(self, **defaults: Any) -> str:
|
|
167
170
|
# We have overwritten the serialization to str, so we need to do it manually
|
|
168
|
-
model_dump =
|
|
169
|
-
|
|
171
|
+
model_dump = {
|
|
172
|
+
field.alias or field_name: v.dump(**defaults) if isinstance(v, Entity) else v
|
|
170
173
|
for field_name, field in self.model_fields.items()
|
|
171
174
|
if (v := getattr(self, field_name)) is not None and field_name not in {"prefix", "suffix"}
|
|
172
|
-
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
}
|
|
176
|
+
if isinstance(defaults, dict):
|
|
177
|
+
for key, value in defaults.items():
|
|
178
|
+
if key in model_dump and value == defaults.get(key):
|
|
179
|
+
del model_dump[key]
|
|
180
|
+
|
|
181
|
+
args = ",".join(f"{k}={v}" for k, v in model_dump.items())
|
|
182
|
+
if self.prefix == Undefined or (isinstance(defaults, dict) and self.prefix == defaults.get("prefix")):
|
|
178
183
|
base_id = str(self.suffix)
|
|
179
184
|
else:
|
|
180
185
|
base_id = f"{self.prefix}:{self.suffix!s}"
|
|
@@ -269,6 +274,11 @@ class DMSEntity(Entity, Generic[T_ID], ABC):
|
|
|
269
274
|
prefix: str = Field(alias="space")
|
|
270
275
|
suffix: str = Field(alias="externalId")
|
|
271
276
|
|
|
277
|
+
def dump(self, **defaults: Any) -> str:
|
|
278
|
+
if isinstance(defaults, dict) and "space" in defaults:
|
|
279
|
+
defaults["prefix"] = defaults.pop("space")
|
|
280
|
+
return super().dump(**defaults)
|
|
281
|
+
|
|
272
282
|
@classmethod
|
|
273
283
|
def load(cls: "type[T_DMSEntity]", data: Any, **defaults) -> "T_DMSEntity | DMSUnknownEntity": # type: ignore[override]
|
|
274
284
|
if isinstance(data, str) and data == str(Unknown):
|
|
@@ -407,6 +417,10 @@ class EdgeEntity(DMSEntity[None]):
|
|
|
407
417
|
properties: ViewEntity | None = None
|
|
408
418
|
direction: Literal["outwards", "inwards"] = "outwards"
|
|
409
419
|
|
|
420
|
+
def dump(self, **defaults: Any) -> str:
|
|
421
|
+
# Add default direction
|
|
422
|
+
return super().dump(**defaults, direction="outwards")
|
|
423
|
+
|
|
410
424
|
def as_id(self) -> None:
|
|
411
425
|
return None
|
|
412
426
|
|
|
@@ -41,11 +41,6 @@ def _generate_cdf_resource_list(v: Any) -> list[AssetEntity | RelationshipEntity
|
|
|
41
41
|
ClassEntityList = Annotated[
|
|
42
42
|
list[ClassEntity],
|
|
43
43
|
BeforeValidator(_split_str),
|
|
44
|
-
PlainSerializer(
|
|
45
|
-
_join_str,
|
|
46
|
-
return_type=str,
|
|
47
|
-
when_used="unless-none",
|
|
48
|
-
),
|
|
49
44
|
]
|
|
50
45
|
|
|
51
46
|
|
|
@@ -63,11 +58,6 @@ CdfResourceEntityList = Annotated[
|
|
|
63
58
|
ContainerEntityList = Annotated[
|
|
64
59
|
list[ContainerEntity],
|
|
65
60
|
BeforeValidator(_split_str),
|
|
66
|
-
PlainSerializer(
|
|
67
|
-
_join_str,
|
|
68
|
-
return_type=str,
|
|
69
|
-
when_used="unless-none",
|
|
70
|
-
),
|
|
71
61
|
]
|
|
72
62
|
|
|
73
63
|
ViewEntityList = Annotated[
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import sys
|
|
3
|
+
from collections.abc import Hashable
|
|
3
4
|
from datetime import datetime
|
|
4
|
-
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
6
|
|
|
6
7
|
from pydantic import Field, field_serializer, field_validator, model_validator
|
|
7
|
-
from
|
|
8
|
+
from pydantic_core.core_schema import SerializationInfo
|
|
8
9
|
from rdflib import Namespace
|
|
9
10
|
|
|
10
11
|
from cognite.neat.constants import get_default_prefixes
|
|
@@ -18,8 +19,8 @@ from cognite.neat.rules.models._base_rules import (
|
|
|
18
19
|
MatchType,
|
|
19
20
|
RoleTypes,
|
|
20
21
|
SchemaCompleteness,
|
|
21
|
-
SheetEntity,
|
|
22
22
|
SheetList,
|
|
23
|
+
SheetRow,
|
|
23
24
|
)
|
|
24
25
|
from cognite.neat.rules.models._rdfpath import (
|
|
25
26
|
RDFPath,
|
|
@@ -37,6 +38,7 @@ from cognite.neat.rules.models.data_types import DataType
|
|
|
37
38
|
from cognite.neat.rules.models.entities import (
|
|
38
39
|
ClassEntity,
|
|
39
40
|
ClassEntityList,
|
|
41
|
+
Entity,
|
|
40
42
|
EntityTypes,
|
|
41
43
|
MultiValueTypeInfo,
|
|
42
44
|
ReferenceEntity,
|
|
@@ -115,7 +117,13 @@ class InformationMetadata(BaseMetadata):
|
|
|
115
117
|
return self.prefix
|
|
116
118
|
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
def _get_metadata(context: Any) -> InformationMetadata | None:
|
|
121
|
+
if isinstance(context, dict) and isinstance(context.get("metadata"), InformationMetadata):
|
|
122
|
+
return context["metadata"]
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class InformationClass(SheetRow):
|
|
119
127
|
"""
|
|
120
128
|
Class is a category of things that share a common set of attributes and relationships.
|
|
121
129
|
|
|
@@ -135,8 +143,34 @@ class InformationClass(SheetEntity):
|
|
|
135
143
|
match_type: MatchType | None = Field(alias="Match Type", default=None)
|
|
136
144
|
comment: str | None = Field(alias="Comment", default=None)
|
|
137
145
|
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
147
|
+
return (self.class_,)
|
|
148
|
+
|
|
149
|
+
@field_serializer("reference", when_used="always")
|
|
150
|
+
def set_reference(self, value: Any, info: SerializationInfo) -> str | None:
|
|
151
|
+
if isinstance(info.context, dict) and info.context.get("as_reference") is True:
|
|
152
|
+
return self.class_.dump()
|
|
153
|
+
return str(value) if value is not None else None
|
|
154
|
+
|
|
155
|
+
@field_serializer("class_", when_used="unless-none")
|
|
156
|
+
def remove_default_prefix(self, value: Any, info: SerializationInfo) -> str:
|
|
157
|
+
if (metadata := _get_metadata(info.context)) and isinstance(value, Entity):
|
|
158
|
+
return value.dump(prefix=metadata.prefix, version=metadata.version)
|
|
159
|
+
return str(value)
|
|
160
|
+
|
|
161
|
+
@field_serializer("parent", when_used="unless-none")
|
|
162
|
+
def remove_default_prefixes(self, value: Any, info: SerializationInfo) -> str:
|
|
163
|
+
if isinstance(value, list) and (metadata := _get_metadata(info.context)):
|
|
164
|
+
return ",".join(
|
|
165
|
+
parent.dump(prefix=metadata.prefix, version=metadata.version)
|
|
166
|
+
if isinstance(parent, Entity)
|
|
167
|
+
else str(parent)
|
|
168
|
+
for parent in value
|
|
169
|
+
)
|
|
170
|
+
return ",".join(str(value) for value in value)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class InformationProperty(SheetRow):
|
|
140
174
|
"""
|
|
141
175
|
A property is a characteristic of a class. It is a named attribute of a class that describes a range of values
|
|
142
176
|
or a relationship to another class.
|
|
@@ -171,15 +205,13 @@ class InformationProperty(SheetEntity):
|
|
|
171
205
|
comment: str | None = Field(alias="Comment", default=None)
|
|
172
206
|
inherited: bool = Field(
|
|
173
207
|
default=False,
|
|
208
|
+
exclude=True,
|
|
174
209
|
alias="Inherited",
|
|
175
210
|
description="Flag to indicate if the property is inherited, only use for internal purposes",
|
|
176
211
|
)
|
|
177
212
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if isinstance(value, float) and math.isinf(value):
|
|
181
|
-
return None
|
|
182
|
-
return value
|
|
213
|
+
def _identifier(self) -> tuple[Hashable, ...]:
|
|
214
|
+
return self.class_, self.property_
|
|
183
215
|
|
|
184
216
|
@field_validator("max_count", mode="before")
|
|
185
217
|
def parse_max_count(cls, value: int | float | None) -> int | float | None:
|
|
@@ -233,6 +265,31 @@ class InformationProperty(SheetEntity):
|
|
|
233
265
|
) from None
|
|
234
266
|
return self
|
|
235
267
|
|
|
268
|
+
@field_serializer("max_count", when_used="json-unless-none")
|
|
269
|
+
def serialize_max_count(self, value: int | float | None) -> int | float | None | str:
|
|
270
|
+
if isinstance(value, float) and math.isinf(value):
|
|
271
|
+
return None
|
|
272
|
+
return value
|
|
273
|
+
|
|
274
|
+
@field_serializer("reference", when_used="always")
|
|
275
|
+
def set_reference(self, value: Any, info: SerializationInfo) -> str | None:
|
|
276
|
+
# When rules as dumped as reference, we set the reference to the class
|
|
277
|
+
if isinstance(info.context, dict) and info.context.get("as_reference") is True:
|
|
278
|
+
return str(
|
|
279
|
+
ReferenceEntity(
|
|
280
|
+
prefix=str(self.class_.prefix),
|
|
281
|
+
suffix=self.class_.suffix,
|
|
282
|
+
property=self.property_,
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
return str(value) if value is not None else None
|
|
286
|
+
|
|
287
|
+
@field_serializer("class_", "value_type", when_used="unless-none")
|
|
288
|
+
def remove_default_prefix(self, value: Any, info: SerializationInfo) -> str:
|
|
289
|
+
if (metadata := _get_metadata(info.context)) and isinstance(value, Entity):
|
|
290
|
+
return value.dump(prefix=metadata.prefix, version=metadata.version)
|
|
291
|
+
return str(value)
|
|
292
|
+
|
|
236
293
|
@property
|
|
237
294
|
def type_(self) -> EntityTypes:
|
|
238
295
|
"""Type of property based on value type. Either data (attribute) or object (edge) property."""
|
|
@@ -307,38 +364,6 @@ class InformationRules(BaseRules):
|
|
|
307
364
|
raise issue_list.as_exception()
|
|
308
365
|
return self
|
|
309
366
|
|
|
310
|
-
def dump(
|
|
311
|
-
self,
|
|
312
|
-
mode: Literal["python", "json"] = "python",
|
|
313
|
-
by_alias: bool = False,
|
|
314
|
-
exclude: IncEx = None,
|
|
315
|
-
exclude_none: bool = False,
|
|
316
|
-
exclude_unset: bool = False,
|
|
317
|
-
exclude_defaults: bool = False,
|
|
318
|
-
as_reference: bool = False,
|
|
319
|
-
) -> dict[str, Any]:
|
|
320
|
-
from ._serializer import _InformationRulesSerializer
|
|
321
|
-
|
|
322
|
-
dumped = self.model_dump(
|
|
323
|
-
mode=mode,
|
|
324
|
-
by_alias=by_alias,
|
|
325
|
-
exclude=exclude,
|
|
326
|
-
exclude_none=exclude_none,
|
|
327
|
-
exclude_unset=exclude_unset,
|
|
328
|
-
exclude_defaults=exclude_defaults,
|
|
329
|
-
)
|
|
330
|
-
prefix = self.metadata.prefix
|
|
331
|
-
serializer = _InformationRulesSerializer(by_alias, prefix)
|
|
332
|
-
cleaned = serializer.clean(dumped, as_reference)
|
|
333
|
-
last = "Last" if by_alias else "last"
|
|
334
|
-
if last_dump := cleaned.get(last):
|
|
335
|
-
cleaned[last] = serializer.clean(last_dump, False)
|
|
336
|
-
reference = "Reference" if by_alias else "reference"
|
|
337
|
-
if self.reference and (ref_dump := cleaned.get(reference)):
|
|
338
|
-
prefix = self.reference.metadata.prefix
|
|
339
|
-
cleaned[reference] = _InformationRulesSerializer(by_alias, prefix).clean(ref_dump, True)
|
|
340
|
-
return cleaned
|
|
341
|
-
|
|
342
367
|
def as_dms_rules(self) -> "DMSRules":
|
|
343
368
|
from cognite.neat.rules.transformers._converters import _InformationRulesConverter
|
|
344
369
|
|