cognite-neat 0.98.0__py3-none-any.whl → 0.99.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/_client/__init__.py +4 -0
- cognite/neat/_client/_api/data_modeling_loaders.py +512 -0
- cognite/neat/_client/_api/schema.py +50 -0
- cognite/neat/_client/_api_client.py +17 -0
- cognite/neat/_client/data_classes/__init__.py +0 -0
- cognite/neat/{_utils/cdf/data_classes.py → _client/data_classes/data_modeling.py} +8 -135
- cognite/neat/{_rules/models/dms/_schema.py → _client/data_classes/schema.py} +21 -281
- cognite/neat/_graph/_shared.py +14 -15
- cognite/neat/_graph/extractors/_classic_cdf/_assets.py +14 -154
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +154 -7
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +23 -12
- cognite/neat/_graph/extractors/_classic_cdf/_data_sets.py +17 -92
- cognite/neat/_graph/extractors/_classic_cdf/_events.py +13 -162
- cognite/neat/_graph/extractors/_classic_cdf/_files.py +15 -179
- cognite/neat/_graph/extractors/_classic_cdf/_labels.py +32 -100
- cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +27 -178
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +14 -139
- cognite/neat/_graph/extractors/_classic_cdf/_timeseries.py +15 -173
- cognite/neat/_graph/extractors/_rdf_file.py +6 -7
- cognite/neat/_graph/queries/_base.py +17 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +50 -134
- cognite/neat/_graph/transformers/_prune_graph.py +1 -1
- cognite/neat/_graph/transformers/_rdfpath.py +1 -1
- cognite/neat/_issues/warnings/__init__.py +6 -0
- cognite/neat/_issues/warnings/_external.py +8 -0
- cognite/neat/_issues/warnings/_properties.py +16 -0
- cognite/neat/_rules/_constants.py +7 -6
- cognite/neat/_rules/analysis/_base.py +8 -4
- cognite/neat/_rules/exporters/_base.py +3 -4
- cognite/neat/_rules/exporters/_rules2dms.py +29 -40
- cognite/neat/_rules/importers/_dms2rules.py +4 -5
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +25 -33
- cognite/neat/_rules/models/__init__.py +1 -1
- cognite/neat/_rules/models/_base_rules.py +22 -12
- cognite/neat/_rules/models/dms/__init__.py +2 -2
- cognite/neat/_rules/models/dms/_exporter.py +15 -20
- cognite/neat/_rules/models/dms/_rules.py +48 -3
- cognite/neat/_rules/models/dms/_rules_input.py +52 -8
- cognite/neat/_rules/models/dms/_validation.py +10 -5
- cognite/neat/_rules/models/entities/_single_value.py +32 -4
- cognite/neat/_rules/models/information/_rules.py +0 -8
- cognite/neat/_rules/models/mapping/__init__.py +2 -3
- cognite/neat/_rules/models/mapping/_classic2core.py +36 -146
- cognite/neat/_rules/models/mapping/_classic2core.yaml +339 -0
- cognite/neat/_rules/transformers/__init__.py +2 -2
- cognite/neat/_rules/transformers/_converters.py +110 -11
- cognite/neat/_rules/transformers/_mapping.py +105 -30
- cognite/neat/_rules/transformers/_verification.py +5 -2
- cognite/neat/_session/_base.py +49 -8
- cognite/neat/_session/_drop.py +35 -0
- cognite/neat/_session/_inspect.py +17 -5
- cognite/neat/_session/_mapping.py +39 -0
- cognite/neat/_session/_prepare.py +218 -23
- cognite/neat/_session/_read.py +49 -12
- cognite/neat/_session/_to.py +3 -3
- cognite/neat/_store/_base.py +27 -24
- cognite/neat/_utils/rdf_.py +28 -1
- cognite/neat/_version.py +1 -1
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +8 -3
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +4 -1
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +3 -2
- {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.0.dist-info}/METADATA +3 -3
- {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.0.dist-info}/RECORD +67 -64
- cognite/neat/_rules/models/mapping/_base.py +0 -131
- cognite/neat/_utils/cdf/loaders/__init__.py +0 -25
- cognite/neat/_utils/cdf/loaders/_base.py +0 -54
- cognite/neat/_utils/cdf/loaders/_data_modeling.py +0 -339
- cognite/neat/_utils/cdf/loaders/_ingestion.py +0 -167
- /cognite/neat/{_utils/cdf → _client/_api}/__init__.py +0 -0
- {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.98.0.dist-info → cognite_neat-0.99.0.dist-info}/entry_points.txt +0 -0
|
@@ -4,7 +4,6 @@ from datetime import datetime, timezone
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Literal, cast
|
|
6
6
|
|
|
7
|
-
from cognite.client import CogniteClient
|
|
8
7
|
from cognite.client import data_modeling as dm
|
|
9
8
|
from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier
|
|
10
9
|
from cognite.client.data_classes.data_modeling.containers import BTreeIndex, InvertedIndex
|
|
@@ -19,6 +18,7 @@ from cognite.client.data_classes.data_modeling.views import (
|
|
|
19
18
|
)
|
|
20
19
|
from cognite.client.utils import ms_to_datetime
|
|
21
20
|
|
|
21
|
+
from cognite.neat._client import NeatClient
|
|
22
22
|
from cognite.neat._issues import IssueList, NeatIssue
|
|
23
23
|
from cognite.neat._issues.errors import FileTypeUnexpectedError, ResourceMissingIdentifierError, ResourceRetrievalError
|
|
24
24
|
from cognite.neat._issues.warnings import (
|
|
@@ -95,7 +95,7 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
95
95
|
@classmethod
|
|
96
96
|
def from_data_model_id(
|
|
97
97
|
cls,
|
|
98
|
-
client:
|
|
98
|
+
client: NeatClient,
|
|
99
99
|
data_model_id: DataModelIdentifier,
|
|
100
100
|
reference_model_id: DataModelIdentifier | None = None,
|
|
101
101
|
) -> "DMSImporter":
|
|
@@ -147,12 +147,12 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
147
147
|
|
|
148
148
|
issue_list = IssueList()
|
|
149
149
|
with _handle_issues(issue_list) as result:
|
|
150
|
-
schema = DMSSchema.from_data_model(client, user_model, ref_model)
|
|
150
|
+
schema = DMSSchema.from_data_model(NeatClient(client), user_model, ref_model)
|
|
151
151
|
|
|
152
152
|
if result.result == "failure" or issue_list.has_errors:
|
|
153
153
|
return cls(DMSSchema(), issue_list)
|
|
154
154
|
|
|
155
|
-
metadata = cls._create_metadata_from_model(user_model
|
|
155
|
+
metadata = cls._create_metadata_from_model(user_model)
|
|
156
156
|
ref_metadata = cls._create_metadata_from_model(ref_model) if ref_model else None
|
|
157
157
|
|
|
158
158
|
return cls(schema, issue_list, metadata, ref_metadata)
|
|
@@ -174,7 +174,6 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
174
174
|
def _create_metadata_from_model(
|
|
175
175
|
cls,
|
|
176
176
|
model: dm.DataModel[dm.View] | dm.DataModelApply,
|
|
177
|
-
has_reference: bool = False,
|
|
178
177
|
) -> DMSInputMetadata:
|
|
179
178
|
description, creator = DMSInputMetadata._get_description_and_creator(model.description)
|
|
180
179
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from collections import Counter, defaultdict
|
|
2
2
|
from collections.abc import Mapping
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import ClassVar, cast
|
|
6
6
|
|
|
@@ -8,8 +8,7 @@ from cognite.client import data_modeling as dm
|
|
|
8
8
|
from rdflib import RDF, Namespace, URIRef
|
|
9
9
|
from rdflib import Literal as RdfLiteral
|
|
10
10
|
|
|
11
|
-
from cognite.neat.
|
|
12
|
-
from cognite.neat._issues.warnings import PropertyValueTypeUndefinedWarning
|
|
11
|
+
from cognite.neat._issues.warnings import PropertySkippedWarning, PropertyValueTypeUndefinedWarning
|
|
13
12
|
from cognite.neat._rules.models import data_types
|
|
14
13
|
from cognite.neat._rules.models.data_types import AnyURI
|
|
15
14
|
from cognite.neat._rules.models.entities._single_value import UnknownEntity
|
|
@@ -156,9 +155,21 @@ class InferenceImporter(BaseRDFImporter):
|
|
|
156
155
|
# this is to skip rdf:type property
|
|
157
156
|
if property_uri == RDF.type:
|
|
158
157
|
continue
|
|
158
|
+
property_id = remove_namespace_from_uri(property_uri)
|
|
159
|
+
if property_id in {"external_id", "externalId"}:
|
|
160
|
+
skip_issue = PropertySkippedWarning(
|
|
161
|
+
resource_type="Property",
|
|
162
|
+
identifier=f"{class_id}:{property_id}",
|
|
163
|
+
property_name=property_id,
|
|
164
|
+
reason="External ID is assumed to be the unique identifier of the instance "
|
|
165
|
+
"and is not part of the data model schema.",
|
|
166
|
+
)
|
|
167
|
+
if skip_issue not in self.issue_list:
|
|
168
|
+
self.issue_list.append(skip_issue)
|
|
169
|
+
continue
|
|
159
170
|
|
|
160
171
|
self._add_uri_namespace_to_prefixes(cast(URIRef, property_uri), prefixes)
|
|
161
|
-
|
|
172
|
+
|
|
162
173
|
if isinstance(data_type_uri, URIRef):
|
|
163
174
|
data_type_uri = self.overwrite_data_types.get(data_type_uri, data_type_uri)
|
|
164
175
|
|
|
@@ -198,11 +209,12 @@ class InferenceImporter(BaseRDFImporter):
|
|
|
198
209
|
|
|
199
210
|
# USE CASE 1: If property is not present in properties
|
|
200
211
|
if id_ not in properties:
|
|
212
|
+
definition["value_type"] = {definition["value_type"]}
|
|
201
213
|
properties[id_] = definition
|
|
202
214
|
|
|
203
215
|
# USE CASE 2: first time redefinition, value type change to multi
|
|
204
216
|
elif id_ in properties and definition["value_type"] not in properties[id_]["value_type"]:
|
|
205
|
-
properties[id_]["value_type"]
|
|
217
|
+
properties[id_]["value_type"].add(definition["value_type"])
|
|
206
218
|
|
|
207
219
|
# USE CASE 3: existing but max count is different
|
|
208
220
|
elif (
|
|
@@ -212,32 +224,12 @@ class InferenceImporter(BaseRDFImporter):
|
|
|
212
224
|
):
|
|
213
225
|
properties[id_]["max_count"] = max(properties[id_]["max_count"], definition["max_count"])
|
|
214
226
|
|
|
215
|
-
#
|
|
216
|
-
for
|
|
217
|
-
if
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
count_by_value_type = count_by_value_type_by_property[id_]
|
|
221
|
-
count_list = sorted(count_by_value_type.items(), key=lambda item: item[1], reverse=True)
|
|
222
|
-
# Make the comment more readable by adapting to the number of value types
|
|
223
|
-
base_string = "<{value_type}> which occurs <{count}> times"
|
|
224
|
-
if len(count_list) == 1:
|
|
225
|
-
type_, count = count_list[0]
|
|
226
|
-
counts_str = f"with value type {base_string.format(value_type=type_, count=count)} in the graph"
|
|
227
|
-
elif len(count_list) == 2:
|
|
228
|
-
first = base_string.format(value_type=count_list[0][0], count=count_list[0][1])
|
|
229
|
-
second = base_string.format(value_type=count_list[1][0], count=count_list[1][1])
|
|
230
|
-
counts_str = f"with value types {first} and {second} in the graph"
|
|
227
|
+
# Create multi-value properties otherwise single value
|
|
228
|
+
for property_ in properties.values():
|
|
229
|
+
if len(property_["value_type"]) > 1:
|
|
230
|
+
property_["value_type"] = " | ".join([str(t) for t in property_["value_type"]])
|
|
231
231
|
else:
|
|
232
|
-
|
|
233
|
-
base_string.format(value_type=type_, count=count) for type_, count in count_list[:-1]
|
|
234
|
-
)
|
|
235
|
-
last = base_string.format(value_type=count_list[-1][0], count=count_list[-1][1])
|
|
236
|
-
counts_str = f"with value types {first_part} and {last} in the graph"
|
|
237
|
-
|
|
238
|
-
class_id = property_["class_"]
|
|
239
|
-
property_id = property_["property_"]
|
|
240
|
-
property_["comment"] = f"Class <{class_id}> has property <{property_id}> {counts_str}"
|
|
232
|
+
property_["value_type"] = next(iter(property_["value_type"]))
|
|
241
233
|
|
|
242
234
|
return {
|
|
243
235
|
"metadata": self._default_metadata().model_dump(),
|
|
@@ -247,14 +239,14 @@ class InferenceImporter(BaseRDFImporter):
|
|
|
247
239
|
}
|
|
248
240
|
|
|
249
241
|
def _default_metadata(self):
|
|
242
|
+
now = datetime.now(timezone.utc)
|
|
250
243
|
return InformationMetadata(
|
|
251
244
|
space=self.data_model_id.space,
|
|
252
245
|
external_id=self.data_model_id.external_id,
|
|
253
246
|
version=self.data_model_id.version,
|
|
254
247
|
name="Inferred Model",
|
|
255
248
|
creator="NEAT",
|
|
256
|
-
created=
|
|
257
|
-
updated=
|
|
249
|
+
created=now,
|
|
250
|
+
updated=now,
|
|
258
251
|
description="Inferred model from knowledge graph",
|
|
259
|
-
namespace=DEFAULT_NAMESPACE,
|
|
260
252
|
)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
from cognite.neat._client.data_classes.schema import DMSSchema
|
|
1
2
|
from cognite.neat._rules.models.information._rules import InformationRules
|
|
2
3
|
from cognite.neat._rules.models.information._rules_input import InformationInputRules
|
|
3
4
|
|
|
4
5
|
from ._base_rules import DataModelType, ExtensionCategory, RoleTypes, SchemaCompleteness, SheetList, SheetRow
|
|
5
6
|
from .dms._rules import DMSRules
|
|
6
7
|
from .dms._rules_input import DMSInputRules
|
|
7
|
-
from .dms._schema import DMSSchema
|
|
8
8
|
|
|
9
9
|
INPUT_RULES_BY_ROLE: dict[RoleTypes, type[InformationInputRules] | type[DMSInputRules]] = {
|
|
10
10
|
RoleTypes.information: InformationInputRules,
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
its sub-models and validators.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
5
|
import math
|
|
8
6
|
import sys
|
|
9
7
|
import types
|
|
@@ -39,13 +37,16 @@ from rdflib import Namespace, URIRef
|
|
|
39
37
|
|
|
40
38
|
from cognite.neat._constants import DEFAULT_NAMESPACE
|
|
41
39
|
from cognite.neat._rules.models._types import (
|
|
42
|
-
|
|
40
|
+
ContainerEntityType,
|
|
43
41
|
DataModelExternalIdType,
|
|
44
|
-
|
|
42
|
+
DmsPropertyType,
|
|
45
43
|
SpaceType,
|
|
46
44
|
StrListType,
|
|
47
45
|
VersionType,
|
|
46
|
+
ViewEntityType,
|
|
48
47
|
)
|
|
48
|
+
from cognite.neat._rules.models.data_types import DataType
|
|
49
|
+
from cognite.neat._rules.models.entities import EdgeEntity, ReverseConnectionEntity, ViewEntity
|
|
49
50
|
|
|
50
51
|
if sys.version_info >= (3, 11):
|
|
51
52
|
from enum import StrEnum
|
|
@@ -249,7 +250,7 @@ class BaseRules(SchemaModel, ABC):
|
|
|
249
250
|
"""Returns a list of headers for the model, typically used by ExcelExporter"""
|
|
250
251
|
headers_by_sheet: dict[str, list[str]] = {}
|
|
251
252
|
for field_name, field in cls.model_fields.items():
|
|
252
|
-
if field_name
|
|
253
|
+
if field_name in ["validators_to_skip", "post_validate"]:
|
|
253
254
|
continue
|
|
254
255
|
sheet_name = (field.alias or field_name) if by_alias else field_name
|
|
255
256
|
annotation = field.annotation
|
|
@@ -379,9 +380,9 @@ class SheetList(list, MutableSequence[T_SheetRow]):
|
|
|
379
380
|
def __getitem__(self, index: SupportsIndex) -> T_SheetRow: ...
|
|
380
381
|
|
|
381
382
|
@overload
|
|
382
|
-
def __getitem__(self, index: slice) -> SheetList[T_SheetRow]: ...
|
|
383
|
+
def __getitem__(self, index: slice) -> "SheetList[T_SheetRow]": ...
|
|
383
384
|
|
|
384
|
-
def __getitem__(self, index: SupportsIndex | slice, /) -> T_SheetRow | SheetList[T_SheetRow]:
|
|
385
|
+
def __getitem__(self, index: SupportsIndex | slice, /) -> "T_SheetRow | SheetList[T_SheetRow]":
|
|
385
386
|
if isinstance(index, slice):
|
|
386
387
|
return SheetList[T_SheetRow](super().__getitem__(index))
|
|
387
388
|
return super().__getitem__(index)
|
|
@@ -399,10 +400,19 @@ ExtensionCategoryType = Annotated[
|
|
|
399
400
|
|
|
400
401
|
|
|
401
402
|
# Immutable such that this can be used as a key in a dictionary
|
|
402
|
-
class
|
|
403
|
-
|
|
404
|
-
property_:
|
|
403
|
+
class ContainerProperty(BaseModel, frozen=True):
|
|
404
|
+
container: ContainerEntityType
|
|
405
|
+
property_: DmsPropertyType
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class ContainerDestinationProperty(ContainerProperty, frozen=True):
|
|
409
|
+
value_type: DataType | ViewEntity
|
|
410
|
+
connection: Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None = None
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class ViewRef(BaseModel, frozen=True):
|
|
414
|
+
view: ViewEntityType
|
|
405
415
|
|
|
406
416
|
|
|
407
|
-
class
|
|
408
|
-
|
|
417
|
+
class ViewProperty(ViewRef, frozen=True):
|
|
418
|
+
property_: DmsPropertyType
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from cognite.neat._client.data_classes.schema import DMSSchema
|
|
2
|
+
|
|
1
3
|
from ._rules import DMSContainer, DMSEnum, DMSMetadata, DMSNode, DMSProperty, DMSRules, DMSView
|
|
2
4
|
from ._rules_input import (
|
|
3
5
|
DMSInputContainer,
|
|
@@ -8,7 +10,6 @@ from ._rules_input import (
|
|
|
8
10
|
DMSInputRules,
|
|
9
11
|
DMSInputView,
|
|
10
12
|
)
|
|
11
|
-
from ._schema import DMSSchema, PipelineSchema
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"DMSRules",
|
|
@@ -19,7 +20,6 @@ __all__ = [
|
|
|
19
20
|
"DMSContainer",
|
|
20
21
|
"DMSNode",
|
|
21
22
|
"DMSEnum",
|
|
22
|
-
"PipelineSchema",
|
|
23
23
|
"DMSInputRules",
|
|
24
24
|
"DMSInputMetadata",
|
|
25
25
|
"DMSInputView",
|
|
@@ -13,6 +13,13 @@ from cognite.client.data_classes.data_modeling.views import (
|
|
|
13
13
|
ViewPropertyApply,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
+
from cognite.neat._client.data_classes.data_modeling import (
|
|
17
|
+
ContainerApplyDict,
|
|
18
|
+
NodeApplyDict,
|
|
19
|
+
SpaceApplyDict,
|
|
20
|
+
ViewApplyDict,
|
|
21
|
+
)
|
|
22
|
+
from cognite.neat._client.data_classes.schema import DMSSchema
|
|
16
23
|
from cognite.neat._issues.errors import NeatTypeError, ResourceNotFoundError
|
|
17
24
|
from cognite.neat._issues.warnings import NotSupportedWarning, PropertyNotFoundWarning
|
|
18
25
|
from cognite.neat._issues.warnings.user_modeling import (
|
|
@@ -33,10 +40,8 @@ from cognite.neat._rules.models.entities import (
|
|
|
33
40
|
UnitEntity,
|
|
34
41
|
ViewEntity,
|
|
35
42
|
)
|
|
36
|
-
from cognite.neat._utils.cdf.data_classes import ContainerApplyDict, NodeApplyDict, SpaceApplyDict, ViewApplyDict
|
|
37
43
|
|
|
38
44
|
from ._rules import DMSEnum, DMSMetadata, DMSProperty, DMSRules, DMSView
|
|
39
|
-
from ._schema import DMSSchema, PipelineSchema
|
|
40
45
|
|
|
41
46
|
|
|
42
47
|
class _DMSExporter:
|
|
@@ -51,13 +56,7 @@ class _DMSExporter:
|
|
|
51
56
|
instance_space (str): The space to use for the instance. Defaults to None,`Rules.metadata.space` will be used
|
|
52
57
|
"""
|
|
53
58
|
|
|
54
|
-
def __init__(
|
|
55
|
-
self,
|
|
56
|
-
rules: DMSRules,
|
|
57
|
-
include_pipeline: bool = False,
|
|
58
|
-
instance_space: str | None = None,
|
|
59
|
-
):
|
|
60
|
-
self.include_pipeline = include_pipeline
|
|
59
|
+
def __init__(self, rules: DMSRules, instance_space: str | None = None):
|
|
61
60
|
self.instance_space = instance_space
|
|
62
61
|
self.rules = rules
|
|
63
62
|
self._ref_schema = None
|
|
@@ -79,9 +78,11 @@ class _DMSExporter:
|
|
|
79
78
|
view_properties_by_id, rules.views
|
|
80
79
|
)
|
|
81
80
|
|
|
82
|
-
views
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
views = self._create_views_with_node_types(view_properties_by_id, view_properties_with_ancestors_by_id)
|
|
82
|
+
view_node_type_filters: set[dm.NodeId] = set()
|
|
83
|
+
for dms_view in rules.views:
|
|
84
|
+
if isinstance(dms_view.filter_, NodeTypeFilter):
|
|
85
|
+
view_node_type_filters.update(node.as_id() for node in dms_view.filter_.inner or [])
|
|
85
86
|
if rules.nodes:
|
|
86
87
|
node_types = NodeApplyDict(
|
|
87
88
|
[node.as_node() for node in rules.nodes]
|
|
@@ -109,8 +110,6 @@ class _DMSExporter:
|
|
|
109
110
|
containers=containers,
|
|
110
111
|
node_types=node_types,
|
|
111
112
|
)
|
|
112
|
-
if self.include_pipeline:
|
|
113
|
-
return PipelineSchema.from_dms(output, self.instance_space)
|
|
114
113
|
|
|
115
114
|
if self._ref_schema:
|
|
116
115
|
output.reference = self._ref_schema
|
|
@@ -141,7 +140,7 @@ class _DMSExporter:
|
|
|
141
140
|
self,
|
|
142
141
|
view_properties_by_id: dict[dm.ViewId, list[DMSProperty]],
|
|
143
142
|
view_properties_with_ancestors_by_id: dict[dm.ViewId, list[DMSProperty]],
|
|
144
|
-
) ->
|
|
143
|
+
) -> ViewApplyDict:
|
|
145
144
|
input_views = list(self.rules.views)
|
|
146
145
|
|
|
147
146
|
views = ViewApplyDict([dms_view.as_view() for dms_view in input_views])
|
|
@@ -155,11 +154,7 @@ class _DMSExporter:
|
|
|
155
154
|
if view_property is not None:
|
|
156
155
|
view.properties[prop.view_property] = view_property
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
for view in views.values():
|
|
160
|
-
unique_node_types.add(dm.NodeId(space=view.space, external_id=view.external_id))
|
|
161
|
-
|
|
162
|
-
return views, unique_node_types
|
|
157
|
+
return views
|
|
163
158
|
|
|
164
159
|
@classmethod
|
|
165
160
|
def _create_edge_type_from_prop(cls, prop: DMSProperty) -> dm.DirectRelationReference:
|
|
@@ -8,18 +8,23 @@ from pydantic import Field, field_serializer, field_validator, model_validator
|
|
|
8
8
|
from pydantic_core.core_schema import SerializationInfo, ValidationInfo
|
|
9
9
|
from rdflib import URIRef
|
|
10
10
|
|
|
11
|
+
from cognite.neat._client.data_classes.schema import DMSSchema
|
|
11
12
|
from cognite.neat._constants import COGNITE_SPACES
|
|
12
13
|
from cognite.neat._issues import MultiValueError
|
|
14
|
+
from cognite.neat._issues.errors import NeatValueError
|
|
13
15
|
from cognite.neat._issues.warnings import (
|
|
14
16
|
PrincipleMatchingSpaceAndVersionWarning,
|
|
15
17
|
)
|
|
16
18
|
from cognite.neat._rules.models._base_rules import (
|
|
17
19
|
BaseMetadata,
|
|
18
20
|
BaseRules,
|
|
21
|
+
ContainerProperty,
|
|
19
22
|
DataModelAspect,
|
|
20
23
|
RoleTypes,
|
|
21
24
|
SheetList,
|
|
22
25
|
SheetRow,
|
|
26
|
+
ViewProperty,
|
|
27
|
+
ViewRef,
|
|
23
28
|
)
|
|
24
29
|
from cognite.neat._rules.models._types import (
|
|
25
30
|
ClassEntityType,
|
|
@@ -44,8 +49,6 @@ from cognite.neat._rules.models.entities import (
|
|
|
44
49
|
ViewEntityList,
|
|
45
50
|
)
|
|
46
51
|
|
|
47
|
-
from ._schema import DMSSchema
|
|
48
|
-
|
|
49
52
|
_DEFAULT_VERSION = "1"
|
|
50
53
|
|
|
51
54
|
|
|
@@ -180,6 +183,14 @@ class DMSProperty(SheetRow):
|
|
|
180
183
|
return value.dump(space=metadata.space, version=metadata.version, type=default_type)
|
|
181
184
|
return str(value)
|
|
182
185
|
|
|
186
|
+
def as_container_reference(self) -> ContainerProperty:
|
|
187
|
+
if self.container is None or self.container_property is None:
|
|
188
|
+
raise NeatValueError("Accessing container reference without container and container property set")
|
|
189
|
+
return ContainerProperty(container=self.container, property_=self.container_property)
|
|
190
|
+
|
|
191
|
+
def as_view_reference(self) -> ViewProperty:
|
|
192
|
+
return ViewProperty(view=self.view, property_=self.view_property)
|
|
193
|
+
|
|
183
194
|
|
|
184
195
|
class DMSContainer(SheetRow):
|
|
185
196
|
container: ContainerEntityType = Field(alias="Container")
|
|
@@ -272,10 +283,14 @@ class DMSView(SheetRow):
|
|
|
272
283
|
version=view_id.version or _DEFAULT_VERSION,
|
|
273
284
|
name=self.name or None,
|
|
274
285
|
description=self.description,
|
|
286
|
+
filter=None if self.filter_ is None else self.filter_.as_dms_filter(),
|
|
275
287
|
implements=implements,
|
|
276
288
|
properties={},
|
|
277
289
|
)
|
|
278
290
|
|
|
291
|
+
def as_view_reference(self) -> ViewRef:
|
|
292
|
+
return ViewRef(view=self.view)
|
|
293
|
+
|
|
279
294
|
|
|
280
295
|
class DMSNode(SheetRow):
|
|
281
296
|
node: DMSNodeEntity = Field(alias="Node")
|
|
@@ -324,6 +339,9 @@ class DMSRules(BaseRules):
|
|
|
324
339
|
containers: SheetList[DMSContainer] | None = Field(None, alias="Containers")
|
|
325
340
|
enum: SheetList[DMSEnum] | None = Field(None, alias="Enum")
|
|
326
341
|
nodes: SheetList[DMSNode] | None = Field(None, alias="Nodes")
|
|
342
|
+
# This is a hack to allow the post_validation to be turned off when needed
|
|
343
|
+
# Will likely be moved completely out of the rules in the future
|
|
344
|
+
post_validate: bool = Field(default=True, exclude=True, repr=False)
|
|
327
345
|
|
|
328
346
|
@field_validator("views")
|
|
329
347
|
def matching_version_and_space(cls, value: SheetList[DMSView], info: ValidationInfo) -> SheetList[DMSView]:
|
|
@@ -360,6 +378,8 @@ class DMSRules(BaseRules):
|
|
|
360
378
|
def post_validation(self) -> "DMSRules":
|
|
361
379
|
from ._validation import DMSPostValidation
|
|
362
380
|
|
|
381
|
+
if not self.post_validate:
|
|
382
|
+
return self
|
|
363
383
|
issue_list = DMSPostValidation(self).validate()
|
|
364
384
|
if issue_list.warnings:
|
|
365
385
|
issue_list.trigger_warnings()
|
|
@@ -370,7 +390,7 @@ class DMSRules(BaseRules):
|
|
|
370
390
|
def as_schema(self, include_pipeline: bool = False, instance_space: str | None = None) -> DMSSchema:
|
|
371
391
|
from ._exporter import _DMSExporter
|
|
372
392
|
|
|
373
|
-
return _DMSExporter(self,
|
|
393
|
+
return _DMSExporter(self, instance_space).to_schema()
|
|
374
394
|
|
|
375
395
|
def _repr_html_(self) -> str:
|
|
376
396
|
summary = {
|
|
@@ -386,3 +406,28 @@ class DMSRules(BaseRules):
|
|
|
386
406
|
}
|
|
387
407
|
|
|
388
408
|
return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
|
|
409
|
+
|
|
410
|
+
def imported_views_and_containers_ids(
|
|
411
|
+
self, include_model_views_with_no_properties: bool = True
|
|
412
|
+
) -> tuple[set[dm.ViewId], set[dm.ContainerId]]:
|
|
413
|
+
existing_views = {view.view for view in self.views}
|
|
414
|
+
imported_views: set[dm.ViewId] = set()
|
|
415
|
+
for view in self.views:
|
|
416
|
+
for parent in view.implements or []:
|
|
417
|
+
if parent not in existing_views:
|
|
418
|
+
imported_views.add(parent.as_id())
|
|
419
|
+
existing_containers = {container.container for container in self.containers or []}
|
|
420
|
+
imported_containers: set[dm.ContainerId] = set()
|
|
421
|
+
view_with_properties: set[ViewEntity] = set()
|
|
422
|
+
for prop in self.properties:
|
|
423
|
+
if prop.container and prop.container not in existing_containers:
|
|
424
|
+
imported_containers.add(prop.container.as_id())
|
|
425
|
+
if prop.view not in existing_views:
|
|
426
|
+
imported_views.add(prop.view.as_id())
|
|
427
|
+
view_with_properties.add(prop.view)
|
|
428
|
+
|
|
429
|
+
if include_model_views_with_no_properties:
|
|
430
|
+
extra_views = existing_views - view_with_properties
|
|
431
|
+
imported_views.update({view.as_id() for view in extra_views})
|
|
432
|
+
|
|
433
|
+
return imported_views, imported_containers
|
|
@@ -5,6 +5,7 @@ from typing import Any, Literal
|
|
|
5
5
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from cognite.client import data_modeling as dm
|
|
8
|
+
from cognite.client.data_classes.data_modeling import ContainerId, ViewId
|
|
8
9
|
from rdflib import Namespace, URIRef
|
|
9
10
|
|
|
10
11
|
from cognite.neat._constants import DEFAULT_NAMESPACE
|
|
@@ -125,6 +126,12 @@ class DMSInputProperty(InputComponent[DMSProperty]):
|
|
|
125
126
|
)
|
|
126
127
|
return output
|
|
127
128
|
|
|
129
|
+
def referenced_view(self, default_space: str, default_version: str) -> ViewEntity:
|
|
130
|
+
return ViewEntity.load(self.view, strict=True, space=default_space, version=default_version)
|
|
131
|
+
|
|
132
|
+
def referenced_container(self, default_space: str) -> ContainerEntity | None:
|
|
133
|
+
return ContainerEntity.load(self.container, strict=True, space=default_space) if self.container else None
|
|
134
|
+
|
|
128
135
|
|
|
129
136
|
@dataclass
|
|
130
137
|
class DMSInputContainer(InputComponent[DMSContainer]):
|
|
@@ -140,8 +147,7 @@ class DMSInputContainer(InputComponent[DMSContainer]):
|
|
|
140
147
|
|
|
141
148
|
def dump(self, default_space: str) -> dict[str, Any]: # type: ignore[override]
|
|
142
149
|
output = super().dump()
|
|
143
|
-
|
|
144
|
-
output["Container"] = container
|
|
150
|
+
output["Container"] = self.as_entity_id(default_space)
|
|
145
151
|
output["Constraint"] = (
|
|
146
152
|
[ContainerEntity.load(constraint.strip(), space=default_space) for constraint in self.constraint.split(",")]
|
|
147
153
|
if self.constraint
|
|
@@ -149,6 +155,9 @@ class DMSInputContainer(InputComponent[DMSContainer]):
|
|
|
149
155
|
)
|
|
150
156
|
return output
|
|
151
157
|
|
|
158
|
+
def as_entity_id(self, default_space: str) -> ContainerEntity:
|
|
159
|
+
return ContainerEntity.load(self.container, strict=True, space=default_space)
|
|
160
|
+
|
|
152
161
|
@classmethod
|
|
153
162
|
def from_container(cls, container: dm.ContainerApply) -> "DMSInputContainer":
|
|
154
163
|
constraints: list[str] = []
|
|
@@ -172,7 +181,7 @@ class DMSInputView(InputComponent[DMSView]):
|
|
|
172
181
|
name: str | None = None
|
|
173
182
|
description: str | None = None
|
|
174
183
|
implements: str | None = None
|
|
175
|
-
filter_: Literal["hasData", "nodeType", "rawFilter"] | None = None
|
|
184
|
+
filter_: Literal["hasData", "nodeType", "rawFilter"] | str | None = None
|
|
176
185
|
in_model: bool = True
|
|
177
186
|
logical: str | None = None
|
|
178
187
|
|
|
@@ -182,17 +191,25 @@ class DMSInputView(InputComponent[DMSView]):
|
|
|
182
191
|
|
|
183
192
|
def dump(self, default_space: str, default_version: str) -> dict[str, Any]: # type: ignore[override]
|
|
184
193
|
output = super().dump()
|
|
185
|
-
|
|
186
|
-
output["
|
|
187
|
-
output
|
|
194
|
+
output["View"] = self.as_entity_id(default_space, default_version)
|
|
195
|
+
output["Implements"] = self._load_implements(default_space, default_version)
|
|
196
|
+
return output
|
|
197
|
+
|
|
198
|
+
def as_entity_id(self, default_space: str, default_version: str) -> ViewEntity:
|
|
199
|
+
return ViewEntity.load(self.view, strict=True, space=default_space, version=default_version)
|
|
200
|
+
|
|
201
|
+
def _load_implements(self, default_space: str, default_version: str) -> list[ViewEntity] | None:
|
|
202
|
+
return (
|
|
188
203
|
[
|
|
189
|
-
ViewEntity.load(implement, space=default_space, version=default_version)
|
|
204
|
+
ViewEntity.load(implement, strict=True, space=default_space, version=default_version)
|
|
190
205
|
for implement in self.implements.split(",")
|
|
191
206
|
]
|
|
192
207
|
if self.implements
|
|
193
208
|
else None
|
|
194
209
|
)
|
|
195
|
-
|
|
210
|
+
|
|
211
|
+
def referenced_views(self, default_space: str, default_version: str) -> list[ViewEntity]:
|
|
212
|
+
return self._load_implements(default_space, default_version) or []
|
|
196
213
|
|
|
197
214
|
@classmethod
|
|
198
215
|
def from_view(cls, view: dm.ViewApply, in_model: bool) -> "DMSInputView":
|
|
@@ -287,3 +304,30 @@ class DMSInputRules(InputRules[DMSRules]):
|
|
|
287
304
|
return DEFAULT_NAMESPACE[
|
|
288
305
|
f"data-model/unverified/dms/{self.metadata.space}/{self.metadata.external_id}/{self.metadata.version}"
|
|
289
306
|
]
|
|
307
|
+
|
|
308
|
+
def referenced_views_and_containers(self) -> tuple[set[ViewEntity], set[ContainerEntity]]:
|
|
309
|
+
default_space = self.metadata.space
|
|
310
|
+
default_version = self.metadata.version
|
|
311
|
+
|
|
312
|
+
containers: set[ContainerEntity] = set()
|
|
313
|
+
views = {parent for view in self.views for parent in view.referenced_views(default_space, default_version)}
|
|
314
|
+
for prop in self.properties:
|
|
315
|
+
views.add(prop.referenced_view(default_space, default_version))
|
|
316
|
+
if ref_container := prop.referenced_container(default_space):
|
|
317
|
+
containers.add(ref_container)
|
|
318
|
+
|
|
319
|
+
return views, containers
|
|
320
|
+
|
|
321
|
+
def as_view_entities(self) -> list[ViewEntity]:
|
|
322
|
+
return [view.as_entity_id(self.metadata.space, self.metadata.version) for view in self.views]
|
|
323
|
+
|
|
324
|
+
def as_container_entities(self) -> list[ContainerEntity]:
|
|
325
|
+
return [container.as_entity_id(self.metadata.space) for container in self.containers or []]
|
|
326
|
+
|
|
327
|
+
def imported_views_and_containers(self) -> tuple[set[ViewEntity], set[ContainerEntity]]:
|
|
328
|
+
views, containers = self.referenced_views_and_containers()
|
|
329
|
+
return views - set(self.as_view_entities()), containers - set(self.as_container_entities())
|
|
330
|
+
|
|
331
|
+
def imported_views_and_containers_ids(self) -> tuple[set[ViewId], set[ContainerId]]:
|
|
332
|
+
views, containers = self.imported_views_and_containers()
|
|
333
|
+
return {view.as_id() for view in views}, {container.as_id() for container in containers}
|
|
@@ -3,6 +3,7 @@ from typing import Any, ClassVar, cast
|
|
|
3
3
|
|
|
4
4
|
from cognite.client import data_modeling as dm
|
|
5
5
|
|
|
6
|
+
from cognite.neat._client.data_classes.schema import DMSSchema
|
|
6
7
|
from cognite.neat._constants import COGNITE_MODELS, DMS_CONTAINER_PROPERTY_SIZE_LIMIT
|
|
7
8
|
from cognite.neat._issues import IssueList, NeatError, NeatIssue, NeatIssueList
|
|
8
9
|
from cognite.neat._issues.errors import (
|
|
@@ -19,6 +20,7 @@ from cognite.neat._issues.warnings.user_modeling import (
|
|
|
19
20
|
NotNeatSupportedFilterWarning,
|
|
20
21
|
ViewPropertyLimitWarning,
|
|
21
22
|
)
|
|
23
|
+
from cognite.neat._rules.analysis import DMSAnalysis
|
|
22
24
|
from cognite.neat._rules.models.data_types import DataType
|
|
23
25
|
from cognite.neat._rules.models.entities import ContainerEntity, RawFilter
|
|
24
26
|
from cognite.neat._rules.models.entities._single_value import (
|
|
@@ -27,7 +29,6 @@ from cognite.neat._rules.models.entities._single_value import (
|
|
|
27
29
|
)
|
|
28
30
|
|
|
29
31
|
from ._rules import DMSProperty, DMSRules
|
|
30
|
-
from ._schema import DMSSchema
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class DMSPostValidation:
|
|
@@ -45,6 +46,7 @@ class DMSPostValidation:
|
|
|
45
46
|
self.containers = rules.containers
|
|
46
47
|
self.views = rules.views
|
|
47
48
|
self.issue_list = IssueList()
|
|
49
|
+
self.probe = DMSAnalysis(rules)
|
|
48
50
|
|
|
49
51
|
def validate(self) -> NeatIssueList:
|
|
50
52
|
self._validate_raw_filter()
|
|
@@ -164,7 +166,7 @@ class DMSPostValidation:
|
|
|
164
166
|
view_id = prop.view.as_id()
|
|
165
167
|
if view_id not in defined_views:
|
|
166
168
|
errors.append(
|
|
167
|
-
ResourceNotDefinedError
|
|
169
|
+
ResourceNotDefinedError(
|
|
168
170
|
identifier=view_id,
|
|
169
171
|
resource_type="view",
|
|
170
172
|
location="Views Sheet",
|
|
@@ -229,7 +231,12 @@ class DMSPostValidation:
|
|
|
229
231
|
if self.metadata.as_data_model_id() in COGNITE_MODELS:
|
|
230
232
|
return None
|
|
231
233
|
|
|
232
|
-
properties_by_ids = {
|
|
234
|
+
properties_by_ids = {
|
|
235
|
+
f"{prop_.view!s}.{prop_.view_property}": prop_
|
|
236
|
+
for properties in self.probe.classes_with_properties(True, True).values()
|
|
237
|
+
for prop_ in properties
|
|
238
|
+
}
|
|
239
|
+
|
|
233
240
|
reversed_by_ids = {
|
|
234
241
|
id_: prop_
|
|
235
242
|
for id_, prop_ in properties_by_ids.items()
|
|
@@ -239,7 +246,6 @@ class DMSPostValidation:
|
|
|
239
246
|
for id_, prop_ in reversed_by_ids.items():
|
|
240
247
|
source_id = f"{prop_.value_type!s}." f"{cast(ReverseConnectionEntity, prop_.connection).property_}"
|
|
241
248
|
if source_id not in properties_by_ids:
|
|
242
|
-
print(f"source_id: {source_id}, first issue")
|
|
243
249
|
self.issue_list.append(
|
|
244
250
|
ReversedConnectionNotFeasibleError(
|
|
245
251
|
id_,
|
|
@@ -252,7 +258,6 @@ class DMSPostValidation:
|
|
|
252
258
|
)
|
|
253
259
|
|
|
254
260
|
elif source_id in properties_by_ids and properties_by_ids[source_id].value_type != prop_.view:
|
|
255
|
-
print(f"source_id: {source_id}, second issue")
|
|
256
261
|
self.issue_list.append(
|
|
257
262
|
ReversedConnectionNotFeasibleError(
|
|
258
263
|
id_,
|