cognite-neat 0.78.2__py3-none-any.whl → 0.78.4__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.78.2"
1
+ __version__ = "0.78.4"
@@ -0,0 +1,4 @@
1
+ from ._classic_cdf._asset_hierarchy import AssetHierarchyExtractor
2
+ from ._mock_graph_generator import MockGraphGenerator
3
+
4
+ __all__ = ["AssetHierarchyExtractor", "MockGraphGenerator"]
File without changes
@@ -0,0 +1,100 @@
1
+ from collections.abc import Iterable
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ from typing import cast
5
+
6
+ import pytz
7
+ from cognite.client import CogniteClient
8
+ from cognite.client.data_classes import Asset, AssetList
9
+ from rdflib import RDF, Literal, Namespace
10
+
11
+ from cognite.neat.constants import DEFAULT_NAMESPACE
12
+ from cognite.neat.graph.extractors._base import BaseExtractor
13
+ from cognite.neat.graph.models import Triple
14
+ from cognite.neat.utils.utils import string_to_ideal_type
15
+
16
+
17
+ class AssetHierarchyExtractor(BaseExtractor):
18
+ def __init__(
19
+ self,
20
+ assets: Iterable[Asset],
21
+ namespace: Namespace | None = None,
22
+ ):
23
+ self.namespace = namespace or DEFAULT_NAMESPACE
24
+ self.assets = assets
25
+
26
+ @classmethod
27
+ def from_dataset(
28
+ cls,
29
+ client: CogniteClient,
30
+ data_set_external_id: str,
31
+ namespace: Namespace | None = None,
32
+ ):
33
+ return cls(cast(Iterable[Asset], client.assets(data_set_external_ids=data_set_external_id)), namespace)
34
+
35
+ @classmethod
36
+ def from_hierarchy(cls, client: CogniteClient, root_asset_external_id: str, namespace: Namespace | None = None):
37
+ return cls(cast(Iterable[Asset], client.assets(asset_subtree_external_ids=root_asset_external_id)), namespace)
38
+
39
+ @classmethod
40
+ def from_file(cls, file_path: str, namespace: Namespace | None = None):
41
+ return cls(AssetList.load(Path(file_path).read_text()), namespace)
42
+
43
+ def extract(self) -> Iterable[Triple]:
44
+ """Extracts an asset with the given asset_id."""
45
+ for asset in self.assets:
46
+ yield from self._asset2triples(asset, self.namespace)
47
+
48
+ @classmethod
49
+ def _asset2triples(cls, asset: Asset, namespace: Namespace) -> list[Triple]:
50
+ """Converts an asset to triples."""
51
+ triples: list[Triple] = [(namespace[str(asset.id)], RDF.type, namespace["Asset"])]
52
+
53
+ if asset.name:
54
+ triples.append((namespace[str(asset.id)], namespace["name"], Literal(asset.name)))
55
+
56
+ if asset.description:
57
+ triples.append((namespace[str(asset.id)], namespace["description"], Literal(asset.description)))
58
+
59
+ if asset.external_id:
60
+ triples.append((namespace[str(asset.id)], namespace["externalId"], Literal(asset.external_id)))
61
+
62
+ if asset.source:
63
+ triples.append((namespace[str(asset.id)], namespace["source"], Literal(asset.source)))
64
+
65
+ # properties ref creation and update
66
+ triples.append(
67
+ (
68
+ namespace[str(asset.id)],
69
+ namespace["createdTime"],
70
+ Literal(datetime.fromtimestamp(asset.created_time / 1000, pytz.utc)),
71
+ )
72
+ )
73
+ triples.append(
74
+ (
75
+ namespace[str(asset.id)],
76
+ namespace["lastUpdatedTime"],
77
+ Literal(datetime.fromtimestamp(asset.last_updated_time / 1000, pytz.utc)),
78
+ )
79
+ )
80
+
81
+ if asset.parent_id:
82
+ triples.append((namespace[str(asset.id)], namespace["parent"], namespace[str(asset.parent_id)]))
83
+
84
+ if asset.root_id:
85
+ triples.append((namespace[str(asset.id)], namespace["root"], namespace[str(asset.root_id)]))
86
+
87
+ if asset.data_set_id:
88
+ triples.append((namespace[str(asset.id)], namespace["dataset"], namespace[str(asset.data_set_id)]))
89
+
90
+ if asset.labels:
91
+ for label in asset.labels:
92
+ # external_id can create ill-formed URIs, so we opt for Literal instead
93
+ triples.append((namespace[str(asset.id)], namespace["label"], Literal(label.dump()["externalId"])))
94
+
95
+ if asset.metadata:
96
+ for key, value in asset.metadata.items():
97
+ if value:
98
+ triples.append((namespace[str(asset.id)], namespace[key], Literal(string_to_ideal_type(value))))
99
+
100
+ return triples
@@ -331,7 +331,7 @@ class OWLProperty(OntologyModel):
331
331
  elif isinstance(definition.value_type, ClassEntity):
332
332
  owl_property.range_.add(namespace[str(definition.value_type.suffix)])
333
333
  else:
334
- raise ValueError(f"Value type {definition.value_type} is not supported")
334
+ raise ValueError(f"Value type {definition.value_type.type_} is not supported")
335
335
  owl_property.domain.add(namespace[str(definition.class_.suffix)])
336
336
  owl_property.label.add(definition.name or definition.property_)
337
337
  if definition.description:
@@ -549,15 +549,19 @@ class SHACLPropertyShape(OntologyModel):
549
549
 
550
550
  @classmethod
551
551
  def from_property(cls, definition: InformationProperty, namespace: Namespace) -> "SHACLPropertyShape":
552
+ # TODO requires PR to fix MultiValueType and UnknownValueType
553
+ if isinstance(definition.value_type, ClassEntity):
554
+ expected_value_type = namespace[f"{definition.value_type.suffix}Shape"]
555
+ elif isinstance(definition.value_type, DataType):
556
+ expected_value_type = XSD[definition.value_type.xsd]
557
+ else:
558
+ raise ValueError(f"Value type {definition.value_type.type_} is not supported")
559
+
552
560
  return cls(
553
561
  id_=BNode(),
554
562
  path=namespace[definition.property_],
555
563
  node_kind=SHACL.IRI if definition.type_ == EntityTypes.object_property else SHACL.Literal,
556
- expected_value_type=(
557
- namespace[f"{definition.value_type.suffix}Shape"]
558
- if isinstance(definition.value_type, ClassEntity)
559
- else XSD[definition.value_type.xsd]
560
- ),
564
+ expected_value_type=expected_value_type,
561
565
  min_count=definition.min_count,
562
566
  max_count=(
563
567
  int(definition.max_count) if definition.max_count and definition.max_count != float("inf") else None
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from datetime import datetime
2
3
  from pathlib import Path
3
4
  from typing import Literal, cast, overload
@@ -12,12 +13,11 @@ from cognite.neat.rules.importers._base import BaseImporter, Rules, _handle_issu
12
13
  from cognite.neat.rules.issues import IssueList
13
14
  from cognite.neat.rules.models import InformationRules, RoleTypes
14
15
  from cognite.neat.rules.models._base import MatchType
15
- from cognite.neat.rules.models.entities import ClassEntity
16
16
  from cognite.neat.rules.models.information import (
17
17
  InformationMetadata,
18
18
  InformationRulesInput,
19
19
  )
20
- from cognite.neat.utils.utils import get_namespace, remove_namespace, replace_non_alphanumeric_with_underscore
20
+ from cognite.neat.utils.utils import get_namespace, remove_namespace
21
21
 
22
22
  ORDERED_CLASSES_QUERY = """SELECT ?class (count(?s) as ?instances )
23
23
  WHERE { ?s a ?class . }
@@ -39,29 +39,21 @@ class InferenceImporter(BaseImporter):
39
39
  issue_list: Issue list to store issues
40
40
  graph: Knowledge graph
41
41
  max_number_of_instance: Maximum number of instances to be used in inference
42
- make_compliant: If True, NEAT will attempt to make the imported rules compliant with CDF
43
42
  """
44
43
 
45
- def __init__(
46
- self, issue_list: IssueList, graph: Graph, make_compliant: bool = False, max_number_of_instance: int = -1
47
- ):
44
+ def __init__(self, issue_list: IssueList, graph: Graph, max_number_of_instance: int = -1):
48
45
  self.issue_list = issue_list
49
46
  self.graph = graph
50
47
  self.max_number_of_instance = max_number_of_instance
51
- self.make_compliant = make_compliant
52
48
 
53
49
  @classmethod
54
- def from_graph_store(
55
- cls, store: NeatGraphStoreBase, make_compliant: bool = False, max_number_of_instance: int = -1
56
- ):
50
+ def from_graph_store(cls, store: NeatGraphStoreBase, max_number_of_instance: int = -1):
57
51
  issue_list = IssueList(title="Inferred from graph store")
58
52
 
59
- return cls(
60
- issue_list, store.graph, make_compliant=make_compliant, max_number_of_instance=max_number_of_instance
61
- )
53
+ return cls(issue_list, store.graph, max_number_of_instance=max_number_of_instance)
62
54
 
63
55
  @classmethod
64
- def from_rdf_file(cls, filepath: Path, make_compliant: bool = False, max_number_of_instance: int = -1):
56
+ def from_rdf_file(cls, filepath: Path, max_number_of_instance: int = -1):
65
57
  issue_list = IssueList(title=f"'{filepath.name}'")
66
58
 
67
59
  graph = Graph()
@@ -70,18 +62,18 @@ class InferenceImporter(BaseImporter):
70
62
  except Exception:
71
63
  issue_list.append(issues.fileread.FileReadError(filepath))
72
64
 
73
- return cls(issue_list, graph, make_compliant=make_compliant, max_number_of_instance=max_number_of_instance)
65
+ return cls(issue_list, graph, max_number_of_instance=max_number_of_instance)
74
66
 
75
67
  @classmethod
76
- def from_json_file(cls, filepath: Path, make_compliant: bool = False, max_number_of_instance: int = -1):
68
+ def from_json_file(cls, filepath: Path, max_number_of_instance: int = -1):
77
69
  raise NotImplementedError("JSON file format is not supported yet.")
78
70
 
79
71
  @classmethod
80
- def from_yaml_file(cls, filepath: Path, make_compliant: bool = False, max_number_of_instance: int = -1):
72
+ def from_yaml_file(cls, filepath: Path, max_number_of_instance: int = -1):
81
73
  raise NotImplementedError("YAML file format is not supported yet.")
82
74
 
83
75
  @classmethod
84
- def from_xml_file(cls, filepath: Path, make_compliant: bool = False, max_number_of_instance: int = -1):
76
+ def from_xml_file(cls, filepath: Path, max_number_of_instance: int = -1):
85
77
  raise NotImplementedError("JSON file format is not supported yet.")
86
78
 
87
79
  @overload
@@ -117,9 +109,6 @@ class InferenceImporter(BaseImporter):
117
109
  if future.result == "failure" or self.issue_list.has_errors:
118
110
  return self._return_or_raise(self.issue_list, errors)
119
111
 
120
- if self.make_compliant and rules:
121
- self._make_dms_compliant_rules(rules)
122
-
123
112
  return self._to_output(
124
113
  rules,
125
114
  self.issue_list,
@@ -155,7 +144,7 @@ class InferenceImporter(BaseImporter):
155
144
  "class_": class_id,
156
145
  "reference": class_uri,
157
146
  "match_type": MatchType.exact,
158
- "comment": f"Inferred from knowledge graph, where this class has {no_instances} instances",
147
+ "comment": f"Inferred from knowledge graph, where this class has <{no_instances}> instances",
159
148
  }
160
149
 
161
150
  # Infers all the properties of the class
@@ -179,7 +168,7 @@ class InferenceImporter(BaseImporter):
179
168
 
180
169
  self._add_uri_namespace_to_prefixes(cast(URIRef, value_type_uri), prefixes)
181
170
  value_type_id = remove_namespace(value_type_uri)
182
- id_ = f"{class_id}:{property_id}:{value_type_id}"
171
+ id_ = f"{class_id}:{property_id}"
183
172
 
184
173
  definition = {
185
174
  "class_": class_id,
@@ -187,15 +176,41 @@ class InferenceImporter(BaseImporter):
187
176
  "max_count": cast(RdfLiteral, occurrence).value,
188
177
  "value_type": value_type_id,
189
178
  "reference": property_uri,
179
+ "comment": (
180
+ f"Class <{class_id}> has property <{property_id}> with "
181
+ f"value type <{value_type_id}> which occurs <1> times in the graph"
182
+ ),
190
183
  }
191
184
 
192
185
  # USE CASE 1: If property is not present in properties
193
186
  if id_ not in properties:
194
187
  properties[id_] = definition
195
- # USE CASE 2: If property is present in properties but with different max count
196
- elif id_ in properties and not (properties[id_]["max_count"] == definition["max_count"]):
188
+
189
+ # USE CASE 2: first time redefinition, value type change to multi
190
+ elif id_ in properties and definition["value_type"] not in properties[id_]["value_type"]:
191
+ properties[id_]["value_type"] = properties[id_]["value_type"] + " | " + definition["value_type"]
192
+ properties[id_]["comment"] = (
193
+ properties[id_]["comment"] + ", with" + definition["comment"].split("with")[1]
194
+ )
195
+
196
+ # USE CASE 3: existing but max count is different
197
+ elif (
198
+ id_ in properties
199
+ and definition["value_type"] in properties[id_]["value_type"]
200
+ and not (properties[id_]["max_count"] == definition["max_count"])
201
+ ):
197
202
  properties[id_]["max_count"] = max(properties[id_]["max_count"], definition["max_count"])
198
203
 
204
+ properties[id_]["comment"] = self._update_value_type_occurrence_in_comment(
205
+ definition["value_type"], properties[id_]["comment"]
206
+ )
207
+
208
+ # USE CASE 4: Just update the comment with occurrence
209
+ else:
210
+ properties[id_]["comment"] = self._update_value_type_occurrence_in_comment(
211
+ definition["value_type"], properties[id_]["comment"]
212
+ )
213
+
199
214
  return {
200
215
  "metadata": self._default_metadata().model_dump(),
201
216
  "classes": list(classes.values()),
@@ -228,36 +243,17 @@ class InferenceImporter(BaseImporter):
228
243
  )
229
244
 
230
245
  @classmethod
231
- def _make_dms_compliant_rules(cls, rules: InformationRules) -> None:
232
- cls._fix_property_redefinition(rules)
233
- cls._fix_naming_of_entities(rules)
234
-
235
- @classmethod
236
- def _fix_property_redefinition(cls, rules: InformationRules) -> None:
237
- seen = set()
238
- for i, property_ in enumerate(rules.properties.data):
239
- prop_id = f"{property_.class_}.{property_.property_}"
240
- if prop_id in seen:
241
- property_.property_ = f"{property_.property_}_{i+1}"
242
- seen.add(f"{property_.class_}.{property_.property_}")
243
- else:
244
- seen.add(prop_id)
246
+ def _update_value_type_occurrence_in_comment(cls, value_type: str, comment: str) -> str:
247
+ occurrence = cls._read_value_type_occurrence_from_comment(value_type, comment)
248
+ return comment.replace(
249
+ f"with value type <{value_type}> which occurs <{occurrence}> times in the graph",
250
+ f"with value type <{value_type}> which occurs <{occurrence+1}> times in the graph",
251
+ )
245
252
 
246
253
  @classmethod
247
- def _fix_naming_of_entities(cls, rules: InformationRules) -> None:
248
- # Fixing class ids
249
- for class_ in rules.classes:
250
- class_.class_ = class_.class_.as_dms_compliant_entity()
251
- class_.parent = [parent.as_dms_compliant_entity() for parent in class_.parent] if class_.parent else None
252
-
253
- # Fixing property definitions
254
- for property_ in rules.properties:
255
- # fix class id
256
- property_.class_ = property_.class_.as_dms_compliant_entity()
257
-
258
- # fix property id
259
- property_.property_ = replace_non_alphanumeric_with_underscore(property_.property_)
260
-
261
- # fix value type
262
- if isinstance(property_.value_type, ClassEntity):
263
- property_.value_type = property_.value_type.as_dms_compliant_entity()
254
+ def _read_value_type_occurrence_from_comment(cls, value_type: str, comment: str) -> int:
255
+ return int(
256
+ cast(
257
+ re.Match, re.search(rf"with value type <{value_type}> which occurs <(\d+)> times in the graph", comment)
258
+ ).group(1)
259
+ )
@@ -5,8 +5,17 @@ from functools import total_ordering
5
5
  from typing import Annotated, Any, ClassVar, Generic, TypeVar, cast
6
6
 
7
7
  from cognite.client.data_classes.data_modeling.ids import ContainerId, DataModelId, NodeId, PropertyId, ViewId
8
- from pydantic import AnyHttpUrl, BaseModel, BeforeValidator, Field, PlainSerializer, model_serializer, model_validator
8
+ from pydantic import (
9
+ AnyHttpUrl,
10
+ BaseModel,
11
+ BeforeValidator,
12
+ Field,
13
+ PlainSerializer,
14
+ model_serializer,
15
+ model_validator,
16
+ )
9
17
 
18
+ from cognite.neat.rules.models.data_types import DataType
10
19
  from cognite.neat.utils.utils import replace_non_alphanumeric_with_underscore
11
20
 
12
21
  if sys.version_info >= (3, 11):
@@ -38,6 +47,7 @@ class EntityTypes(StrEnum):
38
47
  container = "container"
39
48
  datamodel = "datamodel"
40
49
  undefined = "undefined"
50
+ multi_value_type = "multi_value_type"
41
51
 
42
52
 
43
53
  # ALLOWED
@@ -58,6 +68,7 @@ _CLASS_ID_REGEX_COMPILED = re.compile(rf"^{_CLASS_ID_REGEX}$")
58
68
  _PROPERTY_ID_REGEX = rf"\((?P<{EntityTypes.property_}>{_ENTITY_ID_REGEX})\)"
59
69
 
60
70
  _ENTITY_PATTERN = re.compile(r"^(?P<prefix>.*?):?(?P<suffix>[^(:]*)(\((?P<content>[^)]+)\))?$")
71
+ _MULTI_VALUE_TYPE_PATTERN = re.compile(r"^(?P<types>.*?)(\((?P<content>[^)]+)\))?$")
61
72
 
62
73
 
63
74
  class _UndefinedType(BaseModel): ...
@@ -238,11 +249,6 @@ class ClassEntity(Entity):
238
249
  space = default_space if isinstance(self.prefix, _UndefinedType) else self.prefix
239
250
  return ContainerEntity(space=space, externalId=str(self.suffix))
240
251
 
241
- def as_dms_compliant_entity(self) -> "Self":
242
- new_entity = self.model_copy(deep=True)
243
- new_entity.suffix = replace_non_alphanumeric_with_underscore(new_entity.suffix)
244
- return new_entity
245
-
246
252
 
247
253
  class ParentClassEntity(ClassEntity):
248
254
  type_: ClassVar[EntityTypes] = EntityTypes.parent_class
@@ -264,6 +270,60 @@ class UnknownEntity(ClassEntity):
264
270
  T_ID = TypeVar("T_ID", bound=ContainerId | ViewId | DataModelId | PropertyId | NodeId | None)
265
271
 
266
272
 
273
+ class MultiValueTypeInfo(BaseModel):
274
+ type_: ClassVar[EntityTypes] = EntityTypes.multi_value_type
275
+ types: list[DataType | ClassEntity]
276
+
277
+ def __str__(self) -> str:
278
+ return " | ".join([str(t) for t in self.types])
279
+
280
+ @model_serializer(when_used="unless-none", return_type=str)
281
+ def as_str(self) -> str:
282
+ return str(self)
283
+
284
+ @classmethod
285
+ def load(cls, data: Any) -> "MultiValueTypeInfo":
286
+ # already instance of MultiValueTypeInfo
287
+ if isinstance(data, cls):
288
+ return data
289
+
290
+ # it is a raw string that needs to be parsed
291
+ elif isinstance(data, str):
292
+ return cls.model_validate({_PARSE: data})
293
+
294
+ # it is dict that needs to be parsed
295
+ else:
296
+ return cls.model_validate(data)
297
+
298
+ @model_validator(mode="before")
299
+ def _load(cls, data: Any) -> "dict | MultiValueTypeInfo":
300
+ if isinstance(data, dict) and _PARSE in data:
301
+ data = data[_PARSE]
302
+ elif isinstance(data, dict):
303
+ return data
304
+ else:
305
+ raise ValueError(f"Cannot load {cls.__name__} from {data}")
306
+
307
+ result = cls._parse(data)
308
+ return result
309
+
310
+ @classmethod
311
+ def _parse(cls, raw: str) -> dict:
312
+ if not (types := [type_.strip() for type_ in raw.split("|")]):
313
+ return {"types": [UnknownEntity()]}
314
+ else:
315
+ return {
316
+ "types": [
317
+ DataType.load(type_) if DataType.is_data_type(type_) else ClassEntity.load(type_) for type_ in types
318
+ ]
319
+ }
320
+
321
+ def set_default_prefix(self, prefix: str):
322
+ for type_ in self.types:
323
+ if isinstance(type_, ClassEntity) and type_.prefix is Undefined:
324
+ type_.prefix = prefix
325
+
326
+
267
327
  class DMSEntity(Entity, Generic[T_ID], ABC):
268
328
  type_: ClassVar[EntityTypes] = EntityTypes.undefined
269
329
  prefix: str = Field(alias="space")
@@ -297,6 +357,11 @@ class DMSEntity(Entity, Generic[T_ID], ABC):
297
357
  def as_class(self) -> ClassEntity:
298
358
  return ClassEntity(prefix=self.space, suffix=self.external_id)
299
359
 
360
+ def as_dms_compliant_entity(self) -> "Self":
361
+ new_entity = self.model_copy(deep=True)
362
+ new_entity.suffix = replace_non_alphanumeric_with_underscore(new_entity.suffix)
363
+ return new_entity
364
+
300
365
 
301
366
  T_DMSEntity = TypeVar("T_DMSEntity", bound=DMSEntity)
302
367
 
@@ -44,6 +44,7 @@ from cognite.neat.rules.models.domain import DomainRules
44
44
  from cognite.neat.rules.models.entities import (
45
45
  ClassEntity,
46
46
  EntityTypes,
47
+ MultiValueTypeInfo,
47
48
  ParentClassEntity,
48
49
  ParentEntityList,
49
50
  ReferenceEntity,
@@ -162,7 +163,9 @@ class InformationProperty(SheetEntity):
162
163
  property_: PropertyType = Field(alias="Property")
163
164
  name: str | None = Field(alias="Name", default=None)
164
165
  description: str | None = Field(alias="Description", default=None)
165
- value_type: DataType | ClassEntity | UnknownEntity = Field(alias="Value Type", union_mode="left_to_right")
166
+ value_type: DataType | ClassEntity | MultiValueTypeInfo | UnknownEntity = Field(
167
+ alias="Value Type", union_mode="left_to_right"
168
+ )
166
169
  min_count: int | None = Field(alias="Min Count", default=None)
167
170
  max_count: int | float | None = Field(alias="Max Count", default=None)
168
171
  default: Any | None = Field(alias="Default", default=None)
@@ -277,6 +280,10 @@ class InformationRules(BaseRules):
277
280
  for property_ in self.properties:
278
281
  if isinstance(property_.value_type, ClassEntity) and property_.value_type.prefix is Undefined:
279
282
  property_.value_type.prefix = self.metadata.prefix
283
+
284
+ if isinstance(property_.value_type, MultiValueTypeInfo):
285
+ property_.value_type.set_default_prefix(self.metadata.prefix)
286
+
280
287
  if property_.class_.prefix is Undefined:
281
288
  property_.class_.prefix = self.metadata.prefix
282
289
 
@@ -7,7 +7,13 @@ from rdflib import Namespace
7
7
 
8
8
  from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness, _add_alias
9
9
  from cognite.neat.rules.models.data_types import DataType
10
- from cognite.neat.rules.models.entities import ClassEntity, ParentClassEntity, Unknown, UnknownEntity
10
+ from cognite.neat.rules.models.entities import (
11
+ ClassEntity,
12
+ MultiValueTypeInfo,
13
+ ParentClassEntity,
14
+ Unknown,
15
+ UnknownEntity,
16
+ )
11
17
 
12
18
  from ._rules import InformationClass, InformationMetadata, InformationProperty, InformationRules
13
19
 
@@ -121,10 +127,15 @@ class InformationPropertyInput:
121
127
  )
122
128
 
123
129
  def dump(self, default_prefix: str) -> dict[str, Any]:
124
- value_type: DataType | ClassEntity | UnknownEntity
130
+ value_type: MultiValueTypeInfo | DataType | ClassEntity | UnknownEntity
125
131
 
126
132
  # property holding xsd data type
127
- if DataType.is_data_type(self.value_type):
133
+ # check if it is multi value type
134
+ if "|" in self.value_type:
135
+ value_type = MultiValueTypeInfo.load(self.value_type)
136
+ value_type.set_default_prefix(default_prefix)
137
+
138
+ elif DataType.is_data_type(self.value_type):
128
139
  value_type = DataType.load(self.value_type)
129
140
 
130
141
  # unknown value type
@@ -257,6 +268,7 @@ class InformationRulesInput:
257
268
  elif isinstance(self.last, InformationRules):
258
269
  # We need to load through the InformationRulesInput to set the correct default space and version
259
270
  last = InformationRulesInput.load(self.last.model_dump()).dump()
271
+
260
272
  return dict(
261
273
  Metadata=self.metadata.dump(),
262
274
  Properties=[prop.dump(default_prefix) for prop in self.properties],
@@ -355,3 +355,27 @@ def get_inheritance_path(child: Any, child_parent: dict[Any, list[Any]]) -> list
355
355
 
356
356
  def replace_non_alphanumeric_with_underscore(text):
357
357
  return re.sub(r"\W+", "_", text)
358
+
359
+
360
+ def string_to_ideal_type(input_string: str) -> int | bool | float | datetime | str:
361
+ try:
362
+ # Try converting to int
363
+ return int(input_string)
364
+ except ValueError:
365
+ try:
366
+ # Try converting to float
367
+ return float(input_string) # type: ignore
368
+ except ValueError:
369
+ if input_string.lower() == "true":
370
+ # Return True if input is 'true'
371
+ return True
372
+ elif input_string.lower() == "false":
373
+ # Return False if input is 'false'
374
+ return False
375
+ else:
376
+ try:
377
+ # Try converting to datetime
378
+ return datetime.fromisoformat(input_string) # type: ignore
379
+ except ValueError:
380
+ # Return the input string if no conversion is possible
381
+ return input_string
@@ -276,16 +276,6 @@ class RulesInferenceFromRdfFile(Step):
276
276
  label="For what role Rules are intended?",
277
277
  options=["infer", *RoleTypes.__members__.keys()],
278
278
  ),
279
- Configurable(
280
- name="Make compliant",
281
- value="True",
282
- label=(
283
- "Attempt to make the imported Rules compliant, by fixing "
284
- "redefinition of properties and by making ids of entities compliant with"
285
- " CDF-allowed set of characters ."
286
- ),
287
- options=["True", "False"],
288
- ),
289
279
  Configurable(
290
280
  name="Maximum number of instances to process",
291
281
  value="-1",
@@ -302,7 +292,6 @@ class RulesInferenceFromRdfFile(Step):
302
292
 
303
293
  file_path = self.configs.get("File path", None)
304
294
  full_path = flow_message.payload.get("full_path", None) if flow_message.payload else None
305
- make_compliant = self.configs.get("Make compliant", "True") == "True"
306
295
 
307
296
  try:
308
297
  max_number_of_instance = int(self.configs.get("Maximum number of instances to process", -1))
@@ -325,7 +314,7 @@ class RulesInferenceFromRdfFile(Step):
325
314
  role_enum = RoleTypes[role]
326
315
 
327
316
  inference_importer = importers.InferenceImporter.from_rdf_file(
328
- rdf_file_path, make_compliant=make_compliant, max_number_of_instance=max_number_of_instance
317
+ rdf_file_path, max_number_of_instance=max_number_of_instance
329
318
  )
330
319
  rules, issues = inference_importer.to_rules(errors="continue", role=role_enum)
331
320
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cognite-neat
3
- Version: 0.78.2
3
+ Version: 0.78.4
4
4
  Summary: Knowledge graph transformation
5
5
  Home-page: https://cognite-neat.readthedocs-hosted.com/
6
6
  License: Apache-2.0
@@ -1,5 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=v-rRiDOgZ3sQSMQKq0vgUQZvpeOkoHFXissAx6Ktg84,61
2
- cognite/neat/_version.py,sha256=kWBpkJnZGsOhuJ5bhE96-VWCpvSaENAUiBvGF98dgWI,23
2
+ cognite/neat/_version.py,sha256=4qUludpNixgmhGKESx2FYWVdHE0dHtestyzPDEVZ-iA,23
3
3
  cognite/neat/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cognite/neat/app/api/asgi/metrics.py,sha256=nxFy7L5cChTI0a-zkCiJ59Aq8yLuIJp5c9Dg0wRXtV0,152
5
5
  cognite/neat/app/api/configuration.py,sha256=2U5M6M252swvQPQyooA1EBzFUZNtcTmuSaywfJDgckM,4232
@@ -49,8 +49,10 @@ cognite/neat/graph/examples/Knowledge-Graph-Nordic44.xml,sha256=U2Ns-M4LRjT1fBkh
49
49
  cognite/neat/graph/examples/__init__.py,sha256=yAjHVY3b5jOjmbW-iLbhvu7BG014TpGi3K4igkDqW5I,368
50
50
  cognite/neat/graph/examples/skos-capturing-sheet-wind-topics.xlsx,sha256=CV_yK5ZSbYS_ktfIZUPD8Sevs47zpswLXQUDFkGE4Gw,45798
51
51
  cognite/neat/graph/exceptions.py,sha256=R6pyOH774n9w2x_X_nrUr8OMAdjJMf_XPIqAvxIQaWo,3401
52
- cognite/neat/graph/extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
+ cognite/neat/graph/extractors/__init__.py,sha256=YpoCBdmRyn3Pjav2cAYBi5beEnyJBHu-jB7rh9XpgVE,182
53
53
  cognite/neat/graph/extractors/_base.py,sha256=TOXDnlqske8DgnJwA0THDVRgmR79Acjm56yF0E-2w7I,356
54
+ cognite/neat/graph/extractors/_classic_cdf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
+ cognite/neat/graph/extractors/_classic_cdf/_asset_hierarchy.py,sha256=p9jrHSxMsqPbkHBnstvktIF4lRgHvxpKvcgxY22YS2k,3851
54
56
  cognite/neat/graph/extractors/_mock_graph_generator.py,sha256=gziG2FFsLk-HmA9uxAeT9RCjVpFxjkCTLiC4tq2zgvw,14961
55
57
  cognite/neat/graph/models.py,sha256=AtLgZh2qyRP6NRetjQCy9qLMuTQB0CH52Zsev-qa2sk,149
56
58
  cognite/neat/graph/stores/__init__.py,sha256=ivvk7STSo-4wuP_CpizKUCPKmt_ufpNWRJUN9Bv5gdY,543
@@ -167,7 +169,7 @@ cognite/neat/rules/exporters/_base.py,sha256=m63iw8xjlZbZAxGL8mn7pjGf1pW3rVv8C20
167
169
  cognite/neat/rules/exporters/_models.py,sha256=vRd0P_YsrZ1eaAGGHfdTeFunaqHdaa0ZtnWiVZBR1nc,1976
168
170
  cognite/neat/rules/exporters/_rules2dms.py,sha256=US2IO4YTJSF_CDzau1dTpXyeHntJWVSPkMCQoUoKeC0,13688
169
171
  cognite/neat/rules/exporters/_rules2excel.py,sha256=HvUdXYHxfLMijYWdTnfqCsw3Izf8S-XDSve-2ZbqF8Y,14248
170
- cognite/neat/rules/exporters/_rules2ontology.py,sha256=NWS3cn2927LQqW_PdQ-92OLIlmIKGNk7xh5yOMAyj94,20120
172
+ cognite/neat/rules/exporters/_rules2ontology.py,sha256=Od53uLdcC2Q7UiF5PA2P0gw3O14eTD3MeJ1-trd64ZM,20388
171
173
  cognite/neat/rules/exporters/_rules2yaml.py,sha256=GA8eUYRxUfIU6IMvlyGO5JidkOD5eUKSbH3qAiFiaCg,3026
172
174
  cognite/neat/rules/exporters/_validation.py,sha256=OlKIyf4nhSDehJwFHDQ8Zdf6HpNfW7dSe2s67eywHu4,4078
173
175
  cognite/neat/rules/importers/__init__.py,sha256=gR6_TAEa3iO5NCLKRztHg-FMiLdBnx47Z3iSzbwLfcE,481
@@ -178,7 +180,7 @@ cognite/neat/rules/importers/_dtdl2rules/_unit_lookup.py,sha256=wW4saKva61Q_i17g
178
180
  cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py,sha256=ysmWUxZ0npwrTB0uiH5jA0v37sfCwowGaYk17IyxPUU,12663
179
181
  cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py,sha256=QDyGt5YBaxzF4v_oCFSgKRSpwVdVruDU3-VW0DEiHbY,6718
180
182
  cognite/neat/rules/importers/_dtdl2rules/spec.py,sha256=tim_MfN1J0F3Oeqk3BMgIA82d_MZvhRuRMsLK3B4PYc,11897
181
- cognite/neat/rules/importers/_inference2rules.py,sha256=CFSwf8ZTg_luMooBaP3_0762fjDlJxCoxjLRm4J2_m4,11187
183
+ cognite/neat/rules/importers/_inference2rules.py,sha256=JsV3Ii2wmgRELtpV0GC4Y1KtjhyyGR0dtEFpBonHoA8,11213
182
184
  cognite/neat/rules/importers/_owl2rules/__init__.py,sha256=tdGcrgtozdQyST-pTlxIa4cLBNTLvtk1nNYR4vOdFSw,63
183
185
  cognite/neat/rules/importers/_owl2rules/_owl2classes.py,sha256=QpTxvrTGczIa48X8lgXGnMN1AWPhHK0DR6uNq175xak,7357
184
186
  cognite/neat/rules/importers/_owl2rules/_owl2metadata.py,sha256=nwnUaBNAAYMoBre2UmsnkJXUuaqGEpR3U3txDrH2w6g,7527
@@ -210,11 +212,11 @@ cognite/neat/rules/models/dms/_schema.py,sha256=A4z8CINmLQgWzHoScxejRPMRo40ngKly
210
212
  cognite/neat/rules/models/dms/_serializer.py,sha256=iqp2zyyf8jEcU-R3PERuN8nu248xIqyxiWj4owAn92g,6406
211
213
  cognite/neat/rules/models/dms/_validation.py,sha256=nPSyfM1vGZ7d9Uv_2vF2HvMetygtehXW7eNtPD6eW8E,13937
212
214
  cognite/neat/rules/models/domain.py,sha256=tkKcHvDXnZ5IkOr1wHiuNBtE1h8OCFmf6GZSqzHzxjI,2814
213
- cognite/neat/rules/models/entities.py,sha256=qQIH93t2Vmyz-aqh-HWarQbenpmauPeECS9rkS6ihFM,16898
215
+ cognite/neat/rules/models/entities.py,sha256=lkLsKg8U3Xto30PCB85ScDpv2SPRVq1ukVEQHzH53_g,18868
214
216
  cognite/neat/rules/models/information/__init__.py,sha256=HR6g8xgyU53U7Ck8pPdbT70817Q4NC1r1pCRq5SA8iw,291
215
217
  cognite/neat/rules/models/information/_converter.py,sha256=JN63_G5bygdL5WCz-q0_ygiU0NHkzUxm5mZ3WD8yUes,11029
216
- cognite/neat/rules/models/information/_rules.py,sha256=tdCjvAtqnFzEo2zcx-BZHmvjSs28gVun2wz8UaT-AOA,13268
217
- cognite/neat/rules/models/information/_rules_input.py,sha256=IZp_Wnrac0nVaHKth1YttWQOs-kULGKLMtngNQFY40A,10147
218
+ cognite/neat/rules/models/information/_rules.py,sha256=bUzDzwFyWK9EH0FKCQPH9DCfKG3AUtBHaOshTW7VMRY,13476
219
+ cognite/neat/rules/models/information/_rules_input.py,sha256=xmcQQl2vBYSG_IbxOwb6x4CdN3nIg_TY2-3RAeGDYic,10418
218
220
  cognite/neat/rules/models/information/_serializer.py,sha256=yti9I_xJruxrib66YIBInhze___Io-oPTQH6uWDumPE,3503
219
221
  cognite/neat/rules/models/information/_validation.py,sha256=Is2GzL2lZU3A5zPu3NjvlXfmIU2_Y10C5Nxi5Denz4g,7528
220
222
  cognite/neat/rules/models/wrapped_entities.py,sha256=ThhjnNNrpgz0HeORIQ8Q894trxP73P7T_TuZj6qH2CU,7157
@@ -230,7 +232,7 @@ cognite/neat/utils/cdf_loaders/data_classes.py,sha256=0apspfwVlFltYOZfmk_PNknS3Z
230
232
  cognite/neat/utils/exceptions.py,sha256=-w4cAcvcoWLf-_ZwAl7QV_NysfqtQzIOd1Ti-mpxJgM,981
231
233
  cognite/neat/utils/spreadsheet.py,sha256=LI0c7dlW0zXHkHw0NvB-gg6Df6cDcE3FbiaHBYLXdzQ,2714
232
234
  cognite/neat/utils/text.py,sha256=4bg1_Q0lg7KsoxaDOvXrVyeY78BJN8i-27BlyDzUCls,3082
233
- cognite/neat/utils/utils.py,sha256=gcbBbuUSm7xJhgU7ZRzeNc4eJtDVCS2CdNhbGP9gLq4,12919
235
+ cognite/neat/utils/utils.py,sha256=OOuL0l-pv_8gDJCpXGBx-U9CEYDKQffP9dt8Dbg5kdU,13807
234
236
  cognite/neat/utils/xml.py,sha256=ppLT3lQKVp8wOP-m8-tFY8uB2P4R76l7R_-kUtsABng,992
235
237
  cognite/neat/workflows/__init__.py,sha256=oiKub_U9f5cA0I1nKl5dFkR4BD8_6Be9eMzQ_50PwP0,396
236
238
  cognite/neat/workflows/_exceptions.py,sha256=ugI_X1XNpikAiL8zIggBjcx6q7WvOpRIgvxHrj2Rhr4,1348
@@ -259,7 +261,7 @@ cognite/neat/workflows/steps/lib/current/graph_extractor.py,sha256=vW9UpJScx5dFV
259
261
  cognite/neat/workflows/steps/lib/current/graph_loader.py,sha256=HfGg1HRZhbV58TFu89FTjKeUxGsbCYLeFJIQFDN_pQM,2341
260
262
  cognite/neat/workflows/steps/lib/current/graph_store.py,sha256=r7VTxdaz8jJQU7FJbnRDMxvEYbSAZFNMABhPyfNwiFk,6295
261
263
  cognite/neat/workflows/steps/lib/current/rules_exporter.py,sha256=iFwzDWgUDBSPajaNAcXvu9pVw-jX66upvwyMXyTq7SE,23822
262
- cognite/neat/workflows/steps/lib/current/rules_importer.py,sha256=sZJv367gxLrF_ZrQrE8nibIEYh3N6JYiOjZ2MeBO1Cs,15190
264
+ cognite/neat/workflows/steps/lib/current/rules_importer.py,sha256=QaGEVJ0JFoDbUt4YuJMQQrmL8jVubIzC077g4Wq9iCs,14697
263
265
  cognite/neat/workflows/steps/lib/current/rules_validator.py,sha256=LwF9lXlnuPOxDSsOMMTHBi2BHc5rk08IF5zahS9yQuw,4844
264
266
  cognite/neat/workflows/steps/lib/io/__init__.py,sha256=k7IPbIq3ey19oRc5sA_15F99-O6dxzqbm1LihGRRo5A,32
265
267
  cognite/neat/workflows/steps/lib/io/io_steps.py,sha256=QAGypoi1vP32BRiIgBZ0B4qsbFMcwhzpRiVUUnWysLA,16874
@@ -276,8 +278,8 @@ cognite/neat/workflows/steps_registry.py,sha256=fkTX14ZA7_gkUYfWIlx7A1XbCidvqR23
276
278
  cognite/neat/workflows/tasks.py,sha256=dqlJwKAb0jlkl7abbY8RRz3m7MT4SK8-7cntMWkOYjw,788
277
279
  cognite/neat/workflows/triggers.py,sha256=_BLNplzoz0iic367u1mhHMHiUrCwP-SLK6_CZzfODX0,7071
278
280
  cognite/neat/workflows/utils.py,sha256=gKdy3RLG7ctRhbCRwaDIWpL9Mi98zm56-d4jfHDqP1E,453
279
- cognite_neat-0.78.2.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
280
- cognite_neat-0.78.2.dist-info/METADATA,sha256=iUWwZcRnmk4ccIsio_g3ktMvOuv2Bi6vdvoke6ERqys,9306
281
- cognite_neat-0.78.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
282
- cognite_neat-0.78.2.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
283
- cognite_neat-0.78.2.dist-info/RECORD,,
281
+ cognite_neat-0.78.4.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
282
+ cognite_neat-0.78.4.dist-info/METADATA,sha256=rCJxBd0usi6z7qKES5e_B3x6vTLxlXGQNraOAuXaOIs,9306
283
+ cognite_neat-0.78.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
284
+ cognite_neat-0.78.4.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
285
+ cognite_neat-0.78.4.dist-info/RECORD,,