cognite-neat 0.106.0__py3-none-any.whl → 0.107.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 (47) hide show
  1. cognite/neat/_graph/extractors/__init__.py +5 -1
  2. cognite/neat/_graph/extractors/_base.py +32 -0
  3. cognite/neat/_graph/extractors/_classic_cdf/_base.py +16 -3
  4. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +74 -7
  5. cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +2 -0
  6. cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +8 -1
  7. cognite/neat/_graph/extractors/_dms.py +48 -14
  8. cognite/neat/_graph/extractors/_dms_graph.py +149 -0
  9. cognite/neat/_graph/extractors/_rdf_file.py +32 -5
  10. cognite/neat/_graph/loaders/_rdf2dms.py +112 -18
  11. cognite/neat/_graph/queries/_construct.py +1 -1
  12. cognite/neat/_graph/transformers/__init__.py +5 -0
  13. cognite/neat/_graph/transformers/_base.py +9 -1
  14. cognite/neat/_graph/transformers/_classic_cdf.py +90 -3
  15. cognite/neat/_graph/transformers/_rdfpath.py +3 -3
  16. cognite/neat/_graph/transformers/_value_type.py +54 -44
  17. cognite/neat/_rules/analysis/_base.py +1 -1
  18. cognite/neat/_rules/analysis/_information.py +14 -13
  19. cognite/neat/_rules/catalog/__init__.py +1 -0
  20. cognite/neat/_rules/catalog/classic_model.xlsx +0 -0
  21. cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
  22. cognite/neat/_rules/importers/_dms2rules.py +7 -5
  23. cognite/neat/_rules/importers/_rdf/_inference2rules.py +1 -1
  24. cognite/neat/_rules/models/_base_rules.py +0 -12
  25. cognite/neat/_rules/models/_types.py +5 -0
  26. cognite/neat/_rules/models/dms/_rules.py +50 -2
  27. cognite/neat/_rules/models/information/_rules.py +48 -5
  28. cognite/neat/_rules/models/information/_rules_input.py +1 -1
  29. cognite/neat/_rules/models/mapping/_classic2core.py +4 -5
  30. cognite/neat/_rules/transformers/__init__.py +4 -0
  31. cognite/neat/_rules/transformers/_converters.py +209 -62
  32. cognite/neat/_session/_base.py +2 -6
  33. cognite/neat/_session/_mapping.py +17 -6
  34. cognite/neat/_session/_prepare.py +0 -47
  35. cognite/neat/_session/_read.py +63 -5
  36. cognite/neat/_session/_state.py +7 -0
  37. cognite/neat/_session/_to.py +40 -2
  38. cognite/neat/_session/exceptions.py +7 -3
  39. cognite/neat/_store/_graph_store.py +52 -11
  40. cognite/neat/_store/_rules_store.py +22 -0
  41. cognite/neat/_utils/auth.py +2 -0
  42. cognite/neat/_version.py +1 -1
  43. {cognite_neat-0.106.0.dist-info → cognite_neat-0.107.0.dist-info}/METADATA +2 -2
  44. {cognite_neat-0.106.0.dist-info → cognite_neat-0.107.0.dist-info}/RECORD +47 -45
  45. {cognite_neat-0.106.0.dist-info → cognite_neat-0.107.0.dist-info}/WHEEL +1 -1
  46. {cognite_neat-0.106.0.dist-info → cognite_neat-0.107.0.dist-info}/LICENSE +0 -0
  47. {cognite_neat-0.106.0.dist-info → cognite_neat-0.107.0.dist-info}/entry_points.txt +0 -0
@@ -55,12 +55,13 @@ class InformationAnalysis(BaseAnalysis[InformationRules, InformationClass, Infor
55
55
 
56
56
  def has_hop_transformations(self):
57
57
  return any(
58
- prop_.transformation and isinstance(prop_.transformation.traversal, Hop) for prop_ in self.rules.properties
58
+ prop_.instance_source and isinstance(prop_.instance_source.traversal, Hop)
59
+ for prop_ in self.rules.properties
59
60
  )
60
61
 
61
62
  def has_self_reference_property_transformations(self):
62
63
  return any(
63
- prop_.transformation and isinstance(prop_.transformation.traversal, SelfReferenceProperty)
64
+ prop_.instance_source and isinstance(prop_.instance_source.traversal, SelfReferenceProperty)
64
65
  for prop_ in self.rules.properties
65
66
  )
66
67
 
@@ -68,7 +69,7 @@ class InformationAnalysis(BaseAnalysis[InformationRules, InformationClass, Infor
68
69
  return [
69
70
  prop_
70
71
  for prop_ in self.rules.properties
71
- if prop_.transformation and isinstance(prop_.transformation.traversal, SelfReferenceProperty)
72
+ if prop_.instance_source and isinstance(prop_.instance_source.traversal, SelfReferenceProperty)
72
73
  ]
73
74
 
74
75
  def define_property_renaming_config(self, class_: ClassEntity) -> dict[str | URIRef, str]:
@@ -76,7 +77,7 @@ class InformationAnalysis(BaseAnalysis[InformationRules, InformationClass, Infor
76
77
 
77
78
  if definitions := self.class_property_pairs(only_rdfpath=True, consider_inheritance=True).get(class_, None):
78
79
  for property_id, definition in definitions.items():
79
- transformation = cast(RDFPath, definition.transformation)
80
+ transformation = cast(RDFPath, definition.instance_source)
80
81
 
81
82
  # use case we have a single property rdf path, and defined prefix
82
83
  # in either metadata or prefixes of rules
@@ -101,26 +102,26 @@ class InformationAnalysis(BaseAnalysis[InformationRules, InformationClass, Infor
101
102
 
102
103
  return property_renaming_configuration
103
104
 
104
- def neat_id_to_transformation_property_uri(self, property_neat_id: URIRef) -> URIRef | None:
105
+ def neat_id_to_instance_source_property_uri(self, property_neat_id: URIRef) -> URIRef | None:
105
106
  if (
106
107
  (property_ := self.properties_by_neat_id.get(property_neat_id))
107
- and property_.transformation
108
+ and property_.instance_source
108
109
  and isinstance(
109
- property_.transformation.traversal,
110
+ property_.instance_source.traversal,
110
111
  SingleProperty,
111
112
  )
112
113
  and (
113
- property_.transformation.traversal.property.prefix in self.rules.prefixes
114
- or property_.transformation.traversal.property.prefix == self.rules.metadata.prefix
114
+ property_.instance_source.traversal.property.prefix in self.rules.prefixes
115
+ or property_.instance_source.traversal.property.prefix == self.rules.metadata.prefix
115
116
  )
116
117
  ):
117
118
  namespace = (
118
119
  self.rules.metadata.namespace
119
- if property_.transformation.traversal.property.prefix == self.rules.metadata.prefix
120
- else self.rules.prefixes[property_.transformation.traversal.property.prefix]
120
+ if property_.instance_source.traversal.property.prefix == self.rules.metadata.prefix
121
+ else self.rules.prefixes[property_.instance_source.traversal.property.prefix]
121
122
  )
122
123
 
123
- return namespace[property_.transformation.traversal.property.suffix]
124
+ return namespace[property_.instance_source.traversal.property.suffix]
124
125
  return None
125
126
 
126
127
  def property_types(self, class_: ClassEntity) -> dict[str, EntityTypes]:
@@ -137,7 +138,7 @@ class InformationAnalysis(BaseAnalysis[InformationRules, InformationClass, Infor
137
138
  class_, None
138
139
  ):
139
140
  for property_ in class_property_pairs.values():
140
- classes.append(cast(RDFPath, property_.transformation).traversal.class_)
141
+ classes.append(cast(RDFPath, property_.instance_source).traversal.class_)
141
142
 
142
143
  return cast(ClassEntity, most_occurring_element(classes))
143
144
  else:
@@ -5,3 +5,4 @@ from pathlib import Path
5
5
  _CATALOG = Path(__file__).parent
6
6
  imf_attributes = _CATALOG / "info-rules-imf.xlsx"
7
7
  hello_world_pump = _CATALOG / "hello_world_pump.xlsx"
8
+ classic_model = _CATALOG / "classic_model.xlsx"
@@ -115,8 +115,8 @@ class DMSImporter(BaseImporter[DMSInputRules]):
115
115
  data_model_ids = [data_model_id]
116
116
  data_models = client.data_modeling.data_models.retrieve(data_model_ids, inline_views=True)
117
117
 
118
- user_models = cls._find_model_in_list(data_models, data_model_id)
119
- if len(user_models) == 0:
118
+ retrieved_models = cls._find_model_in_list(data_models, data_model_id)
119
+ if len(retrieved_models) == 0:
120
120
  return cls(
121
121
  DMSSchema(),
122
122
  [
@@ -127,16 +127,18 @@ class DMSImporter(BaseImporter[DMSInputRules]):
127
127
  )
128
128
  ],
129
129
  )
130
- user_model = user_models.latest_version()
130
+ return cls.from_data_model(client, retrieved_models.latest_version())
131
131
 
132
+ @classmethod
133
+ def from_data_model(cls, client: NeatClient, model: dm.DataModel[dm.View]) -> "DMSImporter":
132
134
  issue_list = IssueList()
133
135
  with _handle_issues(issue_list) as result:
134
- schema = NeatClient(client).schema.retrieve_data_model(user_model)
136
+ schema = client.schema.retrieve_data_model(model)
135
137
 
136
138
  if result.result == "failure" or issue_list.has_errors:
137
139
  return cls(DMSSchema(), issue_list)
138
140
 
139
- metadata = cls._create_metadata_from_model(user_model)
141
+ metadata = cls._create_metadata_from_model(model)
140
142
 
141
143
  return cls(
142
144
  schema,
@@ -233,7 +233,7 @@ class InferenceImporter(BaseRDFImporter):
233
233
  "property_": property_id,
234
234
  "max_count": cast(RdfLiteral, occurrence).value,
235
235
  "value_type": value_type_id,
236
- "transformation": (
236
+ "instance_source": (
237
237
  f"{uri_to_short_form(class_definition['uri'], prefixes)}"
238
238
  f"({uri_to_short_form(cast(URIRef, property_uri), prefixes)})"
239
239
  ),
@@ -5,7 +5,6 @@ its sub-models and validators.
5
5
  import math
6
6
  import sys
7
7
  import types
8
- import uuid
9
8
  from abc import ABC, abstractmethod
10
9
  from collections.abc import Callable, Hashable, Iterator, MutableSequence, Sequence
11
10
  from datetime import datetime
@@ -31,7 +30,6 @@ from pydantic import (
31
30
  PlainSerializer,
32
31
  field_validator,
33
32
  model_serializer,
34
- model_validator,
35
33
  )
36
34
  from pydantic.main import IncEx
37
35
  from pydantic_core import core_schema
@@ -343,10 +341,6 @@ class BaseRules(SchemaModel, ABC):
343
341
  return output
344
342
 
345
343
 
346
- def make_neat_id() -> URIRef:
347
- return DEFAULT_NAMESPACE[f"neatId_{str(uuid.uuid4()).replace('-', '_')}"]
348
-
349
-
350
344
  class SheetRow(SchemaModel):
351
345
  neatId: URIRefType | None = Field(
352
346
  alias="Neat ID",
@@ -354,12 +348,6 @@ class SheetRow(SchemaModel):
354
348
  default=None,
355
349
  )
356
350
 
357
- @model_validator(mode="after")
358
- def set_neat_id(self) -> "SheetRow":
359
- if self.neatId is None:
360
- self.neatId = DEFAULT_NAMESPACE[f"neatId_{str(uuid.uuid4()).replace('-', '_')}"]
361
- return self
362
-
363
351
  @abstractmethod
364
352
  def _identifier(self) -> tuple[Hashable, ...]:
365
353
  raise NotImplementedError()
@@ -76,6 +76,11 @@ NamespaceType = Annotated[
76
76
  URIRefType = Annotated[
77
77
  rdflib.URIRef,
78
78
  BeforeValidator(lambda value: rdflib.URIRef(value)),
79
+ PlainSerializer(
80
+ lambda value: str(value),
81
+ return_type=str,
82
+ when_used="unless-none",
83
+ ),
79
84
  ]
80
85
 
81
86
  PrefixType = Annotated[
@@ -1,10 +1,10 @@
1
1
  import warnings
2
2
  from collections.abc import Hashable
3
- from typing import Any, ClassVar, Literal
3
+ from typing import TYPE_CHECKING, Any, ClassVar, Literal
4
4
 
5
5
  import pandas as pd
6
6
  from cognite.client import data_modeling as dm
7
- from pydantic import Field, field_serializer, field_validator
7
+ from pydantic import Field, field_serializer, field_validator, model_validator
8
8
  from pydantic_core.core_schema import SerializationInfo, ValidationInfo
9
9
 
10
10
  from cognite.neat._client.data_classes.schema import DMSSchema
@@ -48,6 +48,9 @@ from cognite.neat._rules.models.entities import (
48
48
  ViewEntityList,
49
49
  )
50
50
 
51
+ if TYPE_CHECKING:
52
+ from cognite.neat._rules.models import InformationRules
53
+
51
54
  _DEFAULT_VERSION = "1"
52
55
 
53
56
 
@@ -442,6 +445,51 @@ class DMSRules(BaseRules):
442
445
  )
443
446
  return value
444
447
 
448
+ @model_validator(mode="after")
449
+ def set_neat_id(self) -> "DMSRules":
450
+ namespace = self.metadata.namespace
451
+
452
+ for view in self.views:
453
+ if not view.neatId:
454
+ view.neatId = namespace[view.view.suffix]
455
+
456
+ for property_ in self.properties:
457
+ if not property_.neatId:
458
+ property_.neatId = namespace[f"{property_.view.suffix}/{property_.view_property}"]
459
+
460
+ return self
461
+
462
+ def update_neat_id(self) -> None:
463
+ """Update neat ids"""
464
+
465
+ namespace = self.metadata.namespace
466
+
467
+ for view in self.views:
468
+ view.neatId = namespace[view.view.suffix]
469
+
470
+ for property_ in self.properties:
471
+ property_.neatId = namespace[f"{property_.view.suffix}/{property_.view_property}"]
472
+
473
+ def sync_with_info_rules(self, info_rules: "InformationRules") -> None:
474
+ # Sync at the metadata level
475
+ if info_rules.metadata.physical == self.metadata.identifier:
476
+ self.metadata.logical = info_rules.metadata.identifier
477
+ else:
478
+ # if models are not linked to start with, we skip
479
+ return None
480
+
481
+ info_properties_by_neat_id = {prop.neatId: prop for prop in info_rules.properties}
482
+ dms_properties_by_neat_id = {prop.neatId: prop for prop in self.properties}
483
+ for neat_id, prop in info_properties_by_neat_id.items():
484
+ if prop.physical in dms_properties_by_neat_id:
485
+ dms_properties_by_neat_id[prop.physical].logical = neat_id
486
+
487
+ info_classes_by_neat_id = {cls.neatId: cls for cls in info_rules.classes}
488
+ dms_views_by_neat_id = {view.neatId: view for view in self.views}
489
+ for neat_id, class_ in info_classes_by_neat_id.items():
490
+ if class_.physical in dms_views_by_neat_id:
491
+ dms_views_by_neat_id[class_.physical].logical = neat_id
492
+
445
493
  def as_schema(self, instance_space: str | None = None, remove_cdf_spaces: bool = False) -> DMSSchema:
446
494
  from ._exporter import _DMSExporter
447
495
 
@@ -121,7 +121,7 @@ class InformationProperty(SheetRow):
121
121
  min_count: Minimum count of the property values. Defaults to 0
122
122
  max_count: Maximum count of the property values. Defaults to None
123
123
  default: Default value of the property
124
- transformation: Actual rule for the transformation from source to target representation of
124
+ instance_source: Actual rule for the transformation from source to target representation of
125
125
  knowledge graph. Defaults to None (no transformation)
126
126
  """
127
127
 
@@ -153,10 +153,10 @@ class InformationProperty(SheetRow):
153
153
  "which means that the property can hold any number of values (listable).",
154
154
  )
155
155
  default: Any | None = Field(alias="Default", default=None, description="Default value of the property.")
156
- transformation: RDFPath | None = Field(
157
- alias="Transformation",
156
+ instance_source: RDFPath | None = Field(
157
+ alias="Instance Source",
158
158
  default=None,
159
- description="The rule that is used to populate the data model. "
159
+ description="The link to to the instance property for the model. "
160
160
  "The rule is provided in a RDFPath query syntax which is converted to downstream solution query (e.g. SPARQL).",
161
161
  )
162
162
  inherited: bool = Field(
@@ -181,7 +181,7 @@ class InformationProperty(SheetRow):
181
181
  return float("inf")
182
182
  return value
183
183
 
184
- @field_validator("transformation", mode="before")
184
+ @field_validator("instance_source", mode="before")
185
185
  def generate_rdfpath(cls, value: str | RDFPath | None) -> RDFPath | None:
186
186
  if value is None or isinstance(value, RDFPath):
187
187
  return value
@@ -267,6 +267,49 @@ class InformationRules(BaseRules):
267
267
  values = get_default_prefixes_and_namespaces()
268
268
  return values
269
269
 
270
+ @model_validator(mode="after")
271
+ def set_neat_id(self) -> "InformationRules":
272
+ namespace = self.metadata.namespace
273
+
274
+ for class_ in self.classes:
275
+ if not class_.neatId:
276
+ class_.neatId = namespace[class_.class_.suffix]
277
+ for property_ in self.properties:
278
+ if not property_.neatId:
279
+ property_.neatId = namespace[f"{property_.class_.suffix}/{property_.property_}"]
280
+
281
+ return self
282
+
283
+ def update_neat_id(self) -> None:
284
+ """Update neat ids"""
285
+
286
+ namespace = self.metadata.namespace
287
+
288
+ for class_ in self.classes:
289
+ class_.neatId = namespace[class_.class_.suffix]
290
+ for property_ in self.properties:
291
+ property_.neatId = namespace[f"{property_.class_.suffix}/{property_.property_}"]
292
+
293
+ def sync_with_dms_rules(self, dms_rules: "DMSRules") -> None:
294
+ # Sync at the metadata level
295
+ if dms_rules.metadata.logical == self.metadata.identifier:
296
+ self.metadata.physical = dms_rules.metadata.identifier
297
+ else:
298
+ # if models are not linked to start with, we skip
299
+ return None
300
+
301
+ info_properties_by_neat_id = {prop.neatId: prop for prop in self.properties}
302
+ dms_properties_by_neat_id = {prop.neatId: prop for prop in dms_rules.properties}
303
+ for neat_id, prop in dms_properties_by_neat_id.items():
304
+ if prop.logical in info_properties_by_neat_id:
305
+ info_properties_by_neat_id[prop.logical].physical = neat_id
306
+
307
+ info_classes_by_neat_id = {cls.neatId: cls for cls in self.classes}
308
+ dms_views_by_neat_id = {view.neatId: view for view in dms_rules.views}
309
+ for neat_id, view in dms_views_by_neat_id.items():
310
+ if view.logical in info_classes_by_neat_id:
311
+ info_classes_by_neat_id[view.logical].physical = neat_id
312
+
270
313
  def as_dms_rules(self) -> "DMSRules":
271
314
  from cognite.neat._rules.transformers._converters import _InformationRulesConverter
272
315
 
@@ -79,7 +79,7 @@ class InformationInputProperty(InputComponent[InformationProperty]):
79
79
  min_count: int | None = None
80
80
  max_count: int | float | None = None
81
81
  default: Any | None = None
82
- transformation: str | None = None
82
+ instance_source: str | None = None
83
83
  # Only used internally
84
84
  inherited: bool = False
85
85
  neatId: str | URIRef | None = None
@@ -15,14 +15,13 @@ def _read_source_file() -> str:
15
15
  return _CLASSIC_TO_CORE_MAPPING.read_text()
16
16
 
17
17
 
18
- def load_classic_to_core_mapping(org_name: str, source_space: str, source_version: str) -> DMSRules:
19
- if not org_name:
20
- raise NeatValueError("Organization name must be provided.")
21
-
18
+ def load_classic_to_core_mapping(org_name: str | None, source_space: str, source_version: str) -> DMSRules:
22
19
  from cognite.neat._rules.importers import YAMLImporter
23
20
  from cognite.neat._rules.transformers import VerifyDMSRules
24
21
 
25
- raw_str = _read_source_file().replace("Classic", org_name)
22
+ raw_str = _read_source_file()
23
+ if org_name is not None:
24
+ raw_str = raw_str.replace("Classic", org_name)
26
25
 
27
26
  loaded = yaml.safe_load(raw_str)
28
27
  loaded["metadata"]["space"] = source_space
@@ -1,6 +1,8 @@
1
1
  from ._base import RulesTransformer
2
2
  from ._converters import (
3
3
  AddClassImplements,
4
+ ChangeViewPrefix,
5
+ ClassicPrepareCore,
4
6
  ConvertToRules,
5
7
  DMSToInformation,
6
8
  IncludeReferenced,
@@ -20,6 +22,8 @@ from ._verification import VerifyAnyRules, VerifyDMSRules, VerifyInformationRule
20
22
  __all__ = [
21
23
  "AddClassImplements",
22
24
  "AsParentPropertyId",
25
+ "ChangeViewPrefix",
26
+ "ClassicPrepareCore",
23
27
  "ConvertToRules",
24
28
  "DMSToInformation",
25
29
  "IncludeReferenced",