cognite-neat 0.77.8__py3-none-any.whl → 0.77.9__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.77.8"
1
+ __version__ = "0.77.9"
@@ -1,6 +1,7 @@
1
1
  from ._base import BaseImporter
2
2
  from ._dms2rules import DMSImporter
3
3
  from ._dtdl2rules import DTDLImporter
4
+ from ._inference2rules import InferenceImporter
4
5
  from ._owl2rules import OWLImporter
5
6
  from ._spreadsheet2rules import ExcelImporter, GoogleSheetImporter
6
7
  from ._yaml2rules import YAMLImporter
@@ -13,4 +14,5 @@ __all__ = [
13
14
  "GoogleSheetImporter",
14
15
  "DTDLImporter",
15
16
  "YAMLImporter",
17
+ "InferenceImporter",
16
18
  ]
@@ -0,0 +1,221 @@
1
+ from datetime import datetime
2
+ from pathlib import Path
3
+ from typing import Literal, cast, overload
4
+
5
+ from rdflib import Graph, Namespace, URIRef
6
+ from rdflib import Literal as RdfLiteral
7
+
8
+ import cognite.neat.rules.issues as issues
9
+ from cognite.neat.constants import PREFIXES
10
+ from cognite.neat.rules.importers._base import BaseImporter, Rules, _handle_issues
11
+ from cognite.neat.rules.issues import IssueList
12
+ from cognite.neat.rules.models import InformationRules, RoleTypes
13
+ from cognite.neat.rules.models._base import MatchType
14
+ from cognite.neat.rules.models.information import (
15
+ InformationMetadata,
16
+ InformationRulesInput,
17
+ )
18
+ from cognite.neat.utils.utils import get_namespace, remove_namespace
19
+
20
+ ORDERED_CLASSES_QUERY = """SELECT ?class (count(?s) as ?instances )
21
+ WHERE { ?s a ?class . }
22
+ group by ?class order by DESC(?instances)"""
23
+
24
+ INSTANCES_OF_CLASS_QUERY = """SELECT ?s WHERE { ?s a <class> . }"""
25
+
26
+ INSTANCE_PROPERTIES_DEFINITION = """SELECT ?property (count(?property) as ?occurrence) ?dataType ?objectType
27
+ WHERE {<instance_id> ?property ?value .
28
+ BIND(datatype(?value) AS ?dataType)
29
+ OPTIONAL {?value rdf:type ?objectType .}}
30
+ GROUP BY ?property ?dataType ?objectType"""
31
+
32
+
33
+ class InferenceImporter(BaseImporter):
34
+ """Rules inference through analysis of knowledge graph provided in various formats.
35
+
36
+ Args:
37
+ issue_list: Issue list to store issues
38
+ graph: Knowledge graph
39
+ max_number_of_instance: Maximum number of instances to be used in inference
40
+ make_compliant: If True, NEAT will attempt to make the imported rules compliant with CDF
41
+ """
42
+
43
+ def __init__(
44
+ self, issue_list: IssueList, graph: Graph, max_number_of_instance: int = -1, make_compliant: bool = False
45
+ ):
46
+ self.issue_list = issue_list
47
+ self.graph = graph
48
+ self.max_number_of_instance = max_number_of_instance
49
+ self.make_compliant = make_compliant
50
+
51
+ @classmethod
52
+ def from_rdf_file(cls, filepath: Path, make_compliant: bool = False, max_number_of_instance: int = -1):
53
+ issue_list = IssueList(title=f"'{filepath.name}'")
54
+
55
+ graph = Graph()
56
+ try:
57
+ graph.parse(filepath)
58
+ except Exception:
59
+ issue_list.append(issues.fileread.FileReadError(filepath))
60
+
61
+ return cls(issue_list, graph, make_compliant=make_compliant, max_number_of_instance=max_number_of_instance)
62
+
63
+ @classmethod
64
+ def from_json_file(cls, filepath: Path, make_compliant: bool = False, max_number_of_instance: int = -1):
65
+ raise NotImplementedError("JSON file format is not supported yet.")
66
+
67
+ @classmethod
68
+ def from_yaml_file(cls, filepath: Path, make_compliant: bool = False, max_number_of_instance: int = -1):
69
+ raise NotImplementedError("YAML file format is not supported yet.")
70
+
71
+ @classmethod
72
+ def from_xml_file(cls, filepath: Path, make_compliant: bool = False, max_number_of_instance: int = -1):
73
+ raise NotImplementedError("JSON file format is not supported yet.")
74
+
75
+ @overload
76
+ def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> Rules: ...
77
+
78
+ @overload
79
+ def to_rules(
80
+ self,
81
+ errors: Literal["continue"] = "continue",
82
+ role: RoleTypes | None = None,
83
+ ) -> tuple[Rules | None, IssueList]: ...
84
+
85
+ def to_rules(
86
+ self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
87
+ ) -> tuple[Rules | None, IssueList] | Rules:
88
+ """
89
+ Creates `Rules` object from the data for target role.
90
+ """
91
+
92
+ if self.issue_list.has_errors:
93
+ # In case there were errors during the import, the to_rules method will return None
94
+ return self._return_or_raise(self.issue_list, errors)
95
+
96
+ rules_dict = self._to_rules_components()
97
+
98
+ # adding additional prefix
99
+ rules_dict["prefixes"][rules_dict["metadata"]["prefix"]] = rules_dict["metadata"]["namespace"]
100
+
101
+ with _handle_issues(self.issue_list) as future:
102
+ rules: InformationRules
103
+ rules = InformationRulesInput.load(rules_dict).as_rules()
104
+
105
+ if future.result == "failure" or self.issue_list.has_errors:
106
+ return self._return_or_raise(self.issue_list, errors)
107
+
108
+ if self.make_compliant and rules:
109
+ rules = self._make_dms_compliant_rules(rules)
110
+
111
+ return self._to_output(
112
+ rules,
113
+ self.issue_list,
114
+ errors=errors,
115
+ role=role,
116
+ )
117
+
118
+ def _to_rules_components(
119
+ self,
120
+ ) -> dict:
121
+ """Convert RDF graph to dictionary defining data model and prefixes of the graph
122
+
123
+ Args:
124
+ graph: RDF graph to be converted to TransformationRules object
125
+ max_number_of_instance: Max number of instances to be considered for each class
126
+
127
+ Returns:
128
+ Tuple of data model and prefixes of the graph
129
+ """
130
+ classes: dict[str, dict] = {}
131
+ properties: dict[str, dict] = {}
132
+ prefixes: dict[str, Namespace] = PREFIXES
133
+
134
+ # Infers all the classes in the graph
135
+ for class_uri, no_instances in self.graph.query(ORDERED_CLASSES_QUERY): # type: ignore[misc]
136
+ self._add_uri_namespace_to_prefixes(cast(URIRef, class_uri), prefixes)
137
+
138
+ if (class_id := remove_namespace(class_uri)) in classes:
139
+ # handles cases when class id is already present in classes
140
+ class_id = f"{class_id}_{len(classes)+1}"
141
+
142
+ classes[class_id] = {
143
+ "class_": class_id,
144
+ "reference": class_uri,
145
+ "match_type": MatchType.exact,
146
+ "comment": f"Inferred from knowledge graph, where this class has {no_instances} instances",
147
+ }
148
+
149
+ # Infers all the properties of the class
150
+ for class_id, class_definition in classes.items():
151
+ for (instance,) in self.graph.query( # type: ignore[misc]
152
+ INSTANCES_OF_CLASS_QUERY.replace("class", class_definition["reference"])
153
+ if self.max_number_of_instance < 0
154
+ else INSTANCES_OF_CLASS_QUERY.replace("class", class_definition["reference"])
155
+ + f" LIMIT {self.max_number_of_instance}"
156
+ ):
157
+ for property_uri, occurrence, data_type_uri, object_type_uri in self.graph.query( # type: ignore[misc]
158
+ INSTANCE_PROPERTIES_DEFINITION.replace("instance_id", instance)
159
+ ): # type: ignore[misc]
160
+ property_id = remove_namespace(property_uri)
161
+ self._add_uri_namespace_to_prefixes(cast(URIRef, property_uri), prefixes)
162
+ value_type_uri = data_type_uri if data_type_uri else object_type_uri
163
+
164
+ # this is to skip rdf:type property
165
+ if not value_type_uri:
166
+ continue
167
+
168
+ self._add_uri_namespace_to_prefixes(cast(URIRef, value_type_uri), prefixes)
169
+ value_type_id = remove_namespace(value_type_uri)
170
+ id_ = f"{class_id}:{property_id}:{value_type_id}"
171
+
172
+ definition = {
173
+ "class_": class_id,
174
+ "property_": property_id,
175
+ "max_occurrence": cast(RdfLiteral, occurrence).value,
176
+ "value_type": value_type_id,
177
+ "reference": property_uri,
178
+ }
179
+
180
+ # USE CASE 1: If property is not present in properties
181
+ if id_ not in properties:
182
+ properties[id_] = definition
183
+
184
+ # USE CASE 2: If property is present in properties but with different max count
185
+ elif id_ in properties and not (properties[id_]["max_count"] == definition["max_count"]):
186
+ properties[id_]["max_count"] = max(properties[id_]["max_count"], definition["max_count"])
187
+
188
+ return {
189
+ "metadata": self._default_metadata().model_dump(),
190
+ "classes": list(classes.values()),
191
+ "properties": list(properties.values()),
192
+ "prefixes": prefixes,
193
+ }
194
+
195
+ @classmethod
196
+ def _add_uri_namespace_to_prefixes(cls, URI: URIRef, prefixes: dict[str, Namespace]):
197
+ """Add URI to prefixes dict if not already present
198
+
199
+ Args:
200
+ URI: URI from which namespace is being extracted
201
+ prefixes: Dict of prefixes and namespaces
202
+ """
203
+ if Namespace(get_namespace(URI)) not in prefixes.values():
204
+ prefixes[f"prefix-{len(prefixes)+1}"] = Namespace(get_namespace(URI))
205
+
206
+ @classmethod
207
+ def _default_metadata(cls):
208
+ return InformationMetadata(
209
+ name="Inferred Model",
210
+ creator="NEAT",
211
+ version="inferred",
212
+ created=datetime.now(),
213
+ updated=datetime.now(),
214
+ description="Inferred model from knowledge graph",
215
+ prefix="inferred",
216
+ namespace="http://purl.org/cognite/neat/inferred/",
217
+ )
218
+
219
+ @classmethod
220
+ def _make_dms_compliant_rules(cls, rules: InformationRules) -> InformationRules:
221
+ raise NotImplementedError("Compliance with DMS is not supported yet.")
@@ -1,8 +1,6 @@
1
1
  """This module performs importing of various formats to one of serializations for which
2
2
  there are loaders to TransformationRules pydantic class."""
3
3
 
4
- # TODO: if this module grows too big, split it into several files and place under ./converter directory
5
-
6
4
  from pathlib import Path
7
5
  from typing import Literal, overload
8
6
 
@@ -22,7 +20,8 @@ class OWLImporter(BaseImporter):
22
20
  """Convert OWL ontology to tables/ transformation rules / Excel file.
23
21
 
24
22
  Args:
25
- owl_filepath: Path to OWL ontology
23
+ filepath: Path to OWL ontology
24
+ make_compliant: If True, NEAT will attempt to make the imported rules compliant with CDF
26
25
 
27
26
  !!! Note
28
27
  OWL Ontologies typically lacks some information that is required for making a complete
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cognite-neat
3
- Version: 0.77.8
3
+ Version: 0.77.9
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=mXpwNIS8gqWNHN489kDHmqkjGuUNEJ_NwDA07_xdNQk,23
2
+ cognite/neat/_version.py,sha256=7VDP9r1I_BSyywO4zzqjsH4vyV6SUFzaVXKYLN3DGd0,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
@@ -170,7 +170,7 @@ cognite/neat/rules/exporters/_rules2excel.py,sha256=HvUdXYHxfLMijYWdTnfqCsw3Izf8
170
170
  cognite/neat/rules/exporters/_rules2ontology.py,sha256=NWS3cn2927LQqW_PdQ-92OLIlmIKGNk7xh5yOMAyj94,20120
171
171
  cognite/neat/rules/exporters/_rules2yaml.py,sha256=GA8eUYRxUfIU6IMvlyGO5JidkOD5eUKSbH3qAiFiaCg,3026
172
172
  cognite/neat/rules/exporters/_validation.py,sha256=OlKIyf4nhSDehJwFHDQ8Zdf6HpNfW7dSe2s67eywHu4,4078
173
- cognite/neat/rules/importers/__init__.py,sha256=zqNbGpvdVhYkLjWx1i9dJ3FXzYGtuQyTydUYsj-BndQ,408
173
+ cognite/neat/rules/importers/__init__.py,sha256=gR6_TAEa3iO5NCLKRztHg-FMiLdBnx47Z3iSzbwLfcE,481
174
174
  cognite/neat/rules/importers/_base.py,sha256=GUiJrYwJ25thI71iS9hCeP_iSZ0Vv8ou3z6MfD07FAk,4274
175
175
  cognite/neat/rules/importers/_dms2rules.py,sha256=5yJGYkM7lAMu-QfO0_r59WE4RGtMu2smMqLm16ohgLQ,18994
176
176
  cognite/neat/rules/importers/_dtdl2rules/__init__.py,sha256=CNR-sUihs2mnR1bPMKs3j3L4ds3vFTsrl6YycExZTfU,68
@@ -178,11 +178,12 @@ cognite/neat/rules/importers/_dtdl2rules/_unit_lookup.py,sha256=wW4saKva61Q_i17g
178
178
  cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py,sha256=ysmWUxZ0npwrTB0uiH5jA0v37sfCwowGaYk17IyxPUU,12663
179
179
  cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py,sha256=QDyGt5YBaxzF4v_oCFSgKRSpwVdVruDU3-VW0DEiHbY,6718
180
180
  cognite/neat/rules/importers/_dtdl2rules/spec.py,sha256=tim_MfN1J0F3Oeqk3BMgIA82d_MZvhRuRMsLK3B4PYc,11897
181
+ cognite/neat/rules/importers/_inference2rules.py,sha256=VYs4SCYQk3OMJnCkpJ-fTsK73OfK8cOyYXBAdYI3_WU,9389
181
182
  cognite/neat/rules/importers/_owl2rules/__init__.py,sha256=tdGcrgtozdQyST-pTlxIa4cLBNTLvtk1nNYR4vOdFSw,63
182
183
  cognite/neat/rules/importers/_owl2rules/_owl2classes.py,sha256=LInFeBq-NbBIuMEAwgWch2a4DbBUt4_OMqPkwehW-sw,7591
183
184
  cognite/neat/rules/importers/_owl2rules/_owl2metadata.py,sha256=NdPN0dBB0NYkAcfC0yrYdIrGfdPbl5gfeGnSV3EtUPM,7786
184
185
  cognite/neat/rules/importers/_owl2rules/_owl2properties.py,sha256=BLptGmH-Aa5gZu0hDIxSZTrn9GmB2FicWgRYoETLSnQ,7437
185
- cognite/neat/rules/importers/_owl2rules/_owl2rules.py,sha256=H2Vv56hXGFnq_b0obWGWr5ErDFcoWpT8G2uy89100cU,6925
186
+ cognite/neat/rules/importers/_owl2rules/_owl2rules.py,sha256=vyXtAhHCg-zCv_sfq28bx5BK96GXoCB4KA5LeO_ZuvU,6917
186
187
  cognite/neat/rules/importers/_spreadsheet2rules.py,sha256=nKSJyZGoTho0bqQ_5_1XB9Z1C-MwovRgkVrC-AhOuzs,12438
187
188
  cognite/neat/rules/importers/_yaml2rules.py,sha256=F0uksSz1A3po5OlRM2152_w5j8D9oYTLB9NFTkSMlWI,4275
188
189
  cognite/neat/rules/issues/__init__.py,sha256=Ms6jgCxCezc5IgTOwCFtXQPtoVFfOvdcXj84_rs917I,563
@@ -275,8 +276,8 @@ cognite/neat/workflows/steps_registry.py,sha256=fkTX14ZA7_gkUYfWIlx7A1XbCidvqR23
275
276
  cognite/neat/workflows/tasks.py,sha256=dqlJwKAb0jlkl7abbY8RRz3m7MT4SK8-7cntMWkOYjw,788
276
277
  cognite/neat/workflows/triggers.py,sha256=_BLNplzoz0iic367u1mhHMHiUrCwP-SLK6_CZzfODX0,7071
277
278
  cognite/neat/workflows/utils.py,sha256=gKdy3RLG7ctRhbCRwaDIWpL9Mi98zm56-d4jfHDqP1E,453
278
- cognite_neat-0.77.8.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
279
- cognite_neat-0.77.8.dist-info/METADATA,sha256=fr3l3w0YmPmZwtA9G4rKyL5fVBvN8ThIeBQNtJtzLwk,9316
280
- cognite_neat-0.77.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
281
- cognite_neat-0.77.8.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
282
- cognite_neat-0.77.8.dist-info/RECORD,,
279
+ cognite_neat-0.77.9.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
280
+ cognite_neat-0.77.9.dist-info/METADATA,sha256=jh0m1wUZZS-M6ugn8Ju41qFQwRVziN8Nw7nSV845-Mw,9316
281
+ cognite_neat-0.77.9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
282
+ cognite_neat-0.77.9.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
283
+ cognite_neat-0.77.9.dist-info/RECORD,,