cognite-neat 0.99.0__py3-none-any.whl → 0.99.1__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 (62) hide show
  1. cognite/neat/_client/_api/data_modeling_loaders.py +77 -4
  2. cognite/neat/_client/_api/schema.py +63 -2
  3. cognite/neat/_client/data_classes/schema.py +2 -348
  4. cognite/neat/_constants.py +27 -4
  5. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +5 -5
  6. cognite/neat/_graph/loaders/_rdf2dms.py +2 -2
  7. cognite/neat/_graph/transformers/_classic_cdf.py +24 -13
  8. cognite/neat/_issues/_base.py +26 -17
  9. cognite/neat/_issues/errors/__init__.py +4 -2
  10. cognite/neat/_issues/errors/_external.py +7 -0
  11. cognite/neat/_issues/errors/_properties.py +2 -7
  12. cognite/neat/_issues/errors/_resources.py +1 -1
  13. cognite/neat/_issues/warnings/__init__.py +4 -2
  14. cognite/neat/_issues/warnings/_external.py +9 -1
  15. cognite/neat/_issues/warnings/_resources.py +26 -2
  16. cognite/neat/_issues/warnings/user_modeling.py +4 -4
  17. cognite/neat/_rules/_constants.py +2 -6
  18. cognite/neat/_rules/exporters/_rules2dms.py +4 -6
  19. cognite/neat/_rules/importers/__init__.py +1 -3
  20. cognite/neat/_rules/importers/_base.py +1 -1
  21. cognite/neat/_rules/importers/_dms2rules.py +3 -25
  22. cognite/neat/_rules/importers/_rdf/__init__.py +5 -0
  23. cognite/neat/_rules/importers/_rdf/_base.py +34 -11
  24. cognite/neat/_rules/importers/_rdf/_imf2rules.py +91 -0
  25. cognite/neat/_rules/importers/_rdf/_inference2rules.py +18 -2
  26. cognite/neat/_rules/importers/_rdf/_owl2rules.py +80 -0
  27. cognite/neat/_rules/importers/_rdf/_shared.py +138 -441
  28. cognite/neat/_rules/models/dms/__init__.py +2 -0
  29. cognite/neat/_rules/models/dms/_exporter.py +32 -30
  30. cognite/neat/_rules/models/dms/_rules.py +3 -45
  31. cognite/neat/_rules/models/dms/_validation.py +389 -122
  32. cognite/neat/_rules/models/information/__init__.py +2 -0
  33. cognite/neat/_rules/models/information/_rules.py +0 -59
  34. cognite/neat/_rules/models/information/_validation.py +9 -9
  35. cognite/neat/_rules/models/mapping/_classic2core.py +1 -1
  36. cognite/neat/_rules/models/mapping/_classic2core.yaml +8 -4
  37. cognite/neat/_rules/transformers/_pipelines.py +1 -1
  38. cognite/neat/_rules/transformers/_verification.py +29 -4
  39. cognite/neat/_session/_base.py +16 -41
  40. cognite/neat/_session/_prepare.py +6 -5
  41. cognite/neat/_session/_to.py +5 -2
  42. cognite/neat/_session/exceptions.py +4 -0
  43. cognite/neat/_utils/rdf_.py +6 -4
  44. cognite/neat/_version.py +1 -1
  45. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -88
  46. cognite/neat/_workflows/steps/lib/current/rules_importer.py +2 -16
  47. cognite/neat/_workflows/steps/lib/current/rules_validator.py +3 -5
  48. {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/METADATA +1 -1
  49. {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/RECORD +52 -60
  50. cognite/neat/_rules/importers/_rdf/_imf2rules/__init__.py +0 -3
  51. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2classes.py +0 -86
  52. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2metadata.py +0 -29
  53. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2properties.py +0 -130
  54. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2rules.py +0 -154
  55. cognite/neat/_rules/importers/_rdf/_owl2rules/__init__.py +0 -3
  56. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2classes.py +0 -58
  57. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2metadata.py +0 -65
  58. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2properties.py +0 -59
  59. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2rules.py +0 -39
  60. {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/LICENSE +0 -0
  61. {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/WHEEL +0 -0
  62. {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/entry_points.txt +0 -0
@@ -1,13 +1,16 @@
1
+ from datetime import datetime
1
2
  from pathlib import Path
2
3
 
3
4
  from cognite.client import data_modeling as dm
4
- from rdflib import DC, DCTERMS, OWL, RDF, RDFS, SH, SKOS, XSD, Graph, Namespace, URIRef
5
+ from rdflib import Graph, Namespace, URIRef
5
6
 
7
+ from cognite.neat._constants import get_default_prefixes
6
8
  from cognite.neat._issues import IssueList
7
9
  from cognite.neat._issues.errors import FileReadError
8
10
  from cognite.neat._issues.errors._general import NeatValueError
9
11
  from cognite.neat._rules._shared import ReadRules
10
12
  from cognite.neat._rules.importers._base import BaseImporter
13
+ from cognite.neat._rules.models._base_rules import RoleTypes
11
14
  from cognite.neat._rules.models.data_types import AnyURI
12
15
  from cognite.neat._rules.models.entities import UnknownEntity
13
16
  from cognite.neat._rules.models.information import (
@@ -30,6 +33,13 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
30
33
  graph: Knowledge graph
31
34
  data_model_id: Data model id to be used for the imported rules
32
35
  space: CDF Space to be used for the imported rules
36
+ language: Language for description and human readable entity names
37
+
38
+
39
+
40
+ !!! note "Language"
41
+ Language is provided as ISO 639-1 code. If not provided, English will be used as default.
42
+
33
43
  """
34
44
 
35
45
  def __init__(
@@ -39,6 +49,7 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
39
49
  data_model_id: dm.DataModelId | tuple[str, str, str],
40
50
  max_number_of_instance: int,
41
51
  non_existing_node_type: UnknownEntity | AnyURI,
52
+ language: str,
42
53
  ) -> None:
43
54
  self.issue_list = issue_list
44
55
  self.graph = graph
@@ -48,6 +59,7 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
48
59
 
49
60
  self.max_number_of_instance = max_number_of_instance
50
61
  self.non_existing_node_type = non_existing_node_type
62
+ self.language = language
51
63
 
52
64
  @classmethod
53
65
  def from_graph_store(
@@ -56,6 +68,7 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
56
68
  data_model_id: (dm.DataModelId | tuple[str, str, str]) = DEFAULT_RDF_DATA_MODEL_ID,
57
69
  max_number_of_instance: int = -1,
58
70
  non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
71
+ language: str = "en",
59
72
  ):
60
73
  return cls(
61
74
  IssueList(title=f"{cls.__name__} issues"),
@@ -63,6 +76,7 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
63
76
  data_model_id=data_model_id,
64
77
  max_number_of_instance=max_number_of_instance,
65
78
  non_existing_node_type=non_existing_node_type,
79
+ language=language,
66
80
  )
67
81
 
68
82
  @classmethod
@@ -72,6 +86,7 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
72
86
  data_model_id: (dm.DataModelId | tuple[str, str, str]) = DEFAULT_RDF_DATA_MODEL_ID,
73
87
  max_number_of_instance: int = -1,
74
88
  non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
89
+ language: str = "en",
75
90
  ):
76
91
  issue_list = IssueList(title=f"{cls.__name__} issues")
77
92
 
@@ -82,15 +97,8 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
82
97
  issue_list.append(FileReadError(filepath, str(e)))
83
98
 
84
99
  # bind key namespaces
85
- graph.bind("owl", OWL)
86
- graph.bind("rdf", RDF)
87
- graph.bind("rdfs", RDFS)
88
- graph.bind("dcterms", DCTERMS)
89
- graph.bind("dc", DC)
90
- graph.bind("skos", SKOS)
91
- graph.bind("sh", SH)
92
- graph.bind("xsd", XSD)
93
- graph.bind("imf", "http://ns.imfid.org/imf#")
100
+ for prefix, namespace in get_default_prefixes().items():
101
+ graph.bind(prefix, namespace)
94
102
 
95
103
  return cls(
96
104
  issue_list,
@@ -98,6 +106,7 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
98
106
  data_model_id=data_model_id,
99
107
  max_number_of_instance=max_number_of_instance,
100
108
  non_existing_node_type=non_existing_node_type,
109
+ language=language,
101
110
  )
102
111
 
103
112
  def to_rules(
@@ -129,4 +138,18 @@ class BaseRDFImporter(BaseImporter[InformationInputRules]):
129
138
  prefixes: Dict of prefixes and namespaces
130
139
  """
131
140
  if Namespace(get_namespace(URI)) not in prefixes.values():
132
- prefixes[f"prefix-{len(prefixes)+1}"] = Namespace(get_namespace(URI))
141
+ prefixes[f"prefix_{len(prefixes)+1}"] = Namespace(get_namespace(URI))
142
+
143
+ @property
144
+ def _metadata(self) -> dict:
145
+ return {
146
+ "role": RoleTypes.information,
147
+ "space": self.data_model_id.space,
148
+ "external_id": self.data_model_id.external_id,
149
+ "version": self.data_model_id.version,
150
+ "created": datetime.now().replace(microsecond=0),
151
+ "updated": datetime.now().replace(microsecond=0),
152
+ "name": None,
153
+ "description": f"Data model imported using {type(self).__name__}",
154
+ "creator": "Neat",
155
+ }
@@ -0,0 +1,91 @@
1
+ """This module performs importing of various formats to one of serializations for which
2
+ there are loaders to TransformationRules pydantic class."""
3
+
4
+ from cognite.neat._rules.importers._rdf._base import BaseRDFImporter
5
+ from cognite.neat._rules.importers._rdf._shared import parse_classes, parse_properties
6
+
7
+ CLASSES_QUERY = """
8
+ SELECT ?class_ ?name ?description ?implements
9
+ WHERE {{
10
+ VALUES ?type {{ imf:BlockType imf:TerminalType imf:AttributeType }}
11
+ ?class_ a ?type .
12
+
13
+ OPTIONAL {{?class_ rdfs:subClassOf ?parent }}.
14
+ OPTIONAL {{?class_ rdfs:label|skos:prefLabel ?name }}.
15
+ OPTIONAL {{?class_ rdfs:comment|skos:definition ?description}}.
16
+
17
+
18
+ # Add imf:Attribute as parent class when no parent is found
19
+ BIND(IF(!bound(?parent) && ?type = imf:AttributeType, imf:Attribute, ?parent) AS ?implements)
20
+
21
+ # FILTERS
22
+ FILTER (!isBlank(?class_))
23
+ FILTER (!bound(?implements) || !isBlank(?implements))
24
+
25
+ FILTER (!bound(?name) || LANG(?name) = "" || LANGMATCHES(LANG(?name), "{language}"))
26
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "{language}"))
27
+ }}
28
+ """
29
+
30
+ PROPERTIES_QUERY = """
31
+ SELECT ?class_ ?property_ ?name ?description ?value_type ?min_count ?max_count ?default
32
+ WHERE
33
+ {{
34
+ # CASE 1: Handling Blocks and Terminals
35
+ {{
36
+ VALUES ?type {{ imf:BlockType imf:TerminalType }}
37
+ ?class_ a ?type ;
38
+ sh:property ?propertyShape .
39
+ ?propertyShape sh:path ?property_ .
40
+
41
+ OPTIONAL {{ ?property_ skos:prefLabel ?name . }}
42
+ OPTIONAL {{ ?property_ skos:definition ?description . }}
43
+ OPTIONAL {{ ?property_ rdfs:range ?range . }}
44
+
45
+ OPTIONAL {{ ?propertyShape sh:minCount ?min_count . }}
46
+ OPTIONAL {{ ?propertyShape sh:maxCount ?max_count . }}
47
+ OPTIONAL {{ ?propertyShape sh:hasValue ?default . }}
48
+ OPTIONAL {{ ?propertyShape sh:class | sh:qualifiedValueShape/sh:class ?valueShape . }}
49
+ }}
50
+
51
+ UNION
52
+
53
+ # CASE 2: Handling Attributes
54
+ {{
55
+ ?class_ a imf:AttributeType .
56
+ BIND(xsd:anyURI AS ?valueShape)
57
+ BIND(imf:predicate AS ?property_)
58
+ ?class_ ?property_ ?defaultURI .
59
+ BIND(STR(?defaultURI) AS ?default)
60
+
61
+ }}
62
+
63
+ # Set the value type for the property based on sh:class, sh:qualifiedValueType or rdfs:range
64
+ BIND(IF(BOUND(?valueShape), ?valueShape, IF(BOUND(?range) , ?range , ?valueShape)) AS ?value_type)
65
+
66
+ FILTER (!isBlank(?property_))
67
+ FILTER (!bound(?class_) || !isBlank(?class_))
68
+ FILTER (!bound(?name) || LANG(?name) = "" || LANGMATCHES(LANG(?name), "{language}"))
69
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "{language}"))
70
+ }}
71
+ """
72
+
73
+
74
+ class IMFImporter(BaseRDFImporter):
75
+ """Convert IMF Types provided as SHACL shapes to Input Rules."""
76
+
77
+ def _to_rules_components(
78
+ self,
79
+ ) -> dict:
80
+ classes, issue_list = parse_classes(self.graph, CLASSES_QUERY, self.language, self.issue_list)
81
+ self.issue_list = issue_list
82
+ properties, issue_list = parse_properties(self.graph, PROPERTIES_QUERY, self.language, self.issue_list)
83
+ self.issue_list = issue_list
84
+
85
+ components = {
86
+ "Metadata": self._metadata,
87
+ "Classes": list(classes.values()) if classes else [],
88
+ "Properties": list(properties.values()) if properties else [],
89
+ }
90
+
91
+ return components
@@ -69,8 +69,15 @@ class InferenceImporter(BaseRDFImporter):
69
69
  data_model_id: (dm.DataModelId | tuple[str, str, str]) = DEFAULT_INFERENCE_DATA_MODEL_ID,
70
70
  max_number_of_instance: int = -1,
71
71
  non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
72
+ language: str = "en",
72
73
  ) -> "InferenceImporter":
73
- return super().from_graph_store(store, data_model_id, max_number_of_instance, non_existing_node_type)
74
+ return super().from_graph_store(
75
+ store,
76
+ data_model_id,
77
+ max_number_of_instance,
78
+ non_existing_node_type,
79
+ language,
80
+ )
74
81
 
75
82
  @classmethod
76
83
  def from_file(
@@ -79,8 +86,15 @@ class InferenceImporter(BaseRDFImporter):
79
86
  data_model_id: (dm.DataModelId | tuple[str, str, str]) = DEFAULT_INFERENCE_DATA_MODEL_ID,
80
87
  max_number_of_instance: int = -1,
81
88
  non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
89
+ language: str = "en",
82
90
  ) -> "InferenceImporter":
83
- return super().from_file(filepath, data_model_id, max_number_of_instance, non_existing_node_type)
91
+ return super().from_file(
92
+ filepath,
93
+ data_model_id,
94
+ max_number_of_instance,
95
+ non_existing_node_type,
96
+ language,
97
+ )
84
98
 
85
99
  @classmethod
86
100
  def from_json_file(
@@ -88,6 +102,7 @@ class InferenceImporter(BaseRDFImporter):
88
102
  filepath: Path,
89
103
  data_model_id: (dm.DataModelId | tuple[str, str, str]) = DEFAULT_INFERENCE_DATA_MODEL_ID,
90
104
  max_number_of_instance: int = -1,
105
+ language: str = "en",
91
106
  ) -> "InferenceImporter":
92
107
  raise NotImplementedError("JSON file format is not supported yet.")
93
108
 
@@ -97,6 +112,7 @@ class InferenceImporter(BaseRDFImporter):
97
112
  filepath: Path,
98
113
  data_model_id: (dm.DataModelId | tuple[str, str, str]) = DEFAULT_INFERENCE_DATA_MODEL_ID,
99
114
  max_number_of_instance: int = -1,
115
+ language: str = "en",
100
116
  ) -> "InferenceImporter":
101
117
  raise NotImplementedError("YAML file format is not supported yet.")
102
118
 
@@ -0,0 +1,80 @@
1
+ """This module performs importing of various formats to one of serializations for which
2
+ there are loaders to TransformationRules pydantic class."""
3
+
4
+ from cognite.neat._rules.importers._rdf._base import BaseRDFImporter
5
+ from cognite.neat._rules.importers._rdf._shared import parse_classes, parse_properties
6
+
7
+ CLASSES_QUERY = """SELECT ?class_ ?name ?description ?implements
8
+ WHERE {{
9
+
10
+ ?class_ a owl:Class .
11
+ OPTIONAL {{?class_ rdfs:subClassOf ?implements }}.
12
+ OPTIONAL {{?class_ rdfs:label|skos:prefLabel ?name }}.
13
+ OPTIONAL {{?class_ rdfs:comment|skos:definition ?description}} .
14
+
15
+
16
+ FILTER (!isBlank(?class_))
17
+ FILTER (!bound(?implements) || !isBlank(?implements))
18
+
19
+ FILTER (!bound(?name) || LANG(?name) = "" || LANGMATCHES(LANG(?name), "{language}"))
20
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "{language}"))
21
+
22
+ }}
23
+ """
24
+
25
+ PROPERTIES_QUERY = """
26
+
27
+ SELECT ?class_ ?property_ ?name ?description ?value_type ?minCount ?maxCount ?default
28
+ WHERE {{
29
+ ?property_ a ?property_Type.
30
+ FILTER (?property_Type IN (owl:ObjectProperty, owl:DatatypeProperty ) )
31
+ OPTIONAL {{?property_ rdfs:domain ?class_ }}.
32
+ OPTIONAL {{?property_ rdfs:range ?value_type }}.
33
+ OPTIONAL {{?property_ rdfs:label|skos:prefLabel ?name }}.
34
+ OPTIONAL {{?property_ rdfs:comment|skos:definition ?description}}.
35
+ OPTIONAL {{?property_ owl:maxCardinality ?maxCount}}.
36
+ OPTIONAL {{?property_ owl:minCardinality ?minCount}}.
37
+
38
+ # FILTERS
39
+ FILTER (!isBlank(?property_))
40
+ FILTER (!bound(?name) || LANG(?name) = "" || LANGMATCHES(LANG(?name), "{language}"))
41
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "{language}"))
42
+ }}
43
+ """
44
+
45
+
46
+ class OWLImporter(BaseRDFImporter):
47
+ """Convert OWL ontology to tables/ transformation rules / Excel file.
48
+
49
+ Args:
50
+ filepath: Path to OWL ontology
51
+
52
+ !!! Note
53
+ OWL Ontologies are information models which completeness varies. As such, constructing functional
54
+ data model directly will often be impossible, therefore the produced Rules object will be ill formed.
55
+ To avoid this, neat will automatically attempt to make the imported rules compliant by adding default
56
+ values for missing information, attaching dangling properties to default containers based on the
57
+ property type, etc.
58
+
59
+ One has to be aware that NEAT will be opinionated about how to make the ontology
60
+ compliant, and that the resulting rules may not be what you expect.
61
+
62
+ """
63
+
64
+ def _to_rules_components(
65
+ self,
66
+ ) -> dict:
67
+ classes, issue_list = parse_classes(self.graph, CLASSES_QUERY, self.language, self.issue_list)
68
+ self.issue_list = issue_list
69
+
70
+ # NeatError
71
+ properties, issue_list = parse_properties(self.graph, PROPERTIES_QUERY, self.language, self.issue_list)
72
+ self.issue_list = issue_list
73
+
74
+ components = {
75
+ "Metadata": self._metadata,
76
+ "Classes": list(classes.values()) if classes else [],
77
+ "Properties": list(properties.values()) if properties else [],
78
+ }
79
+
80
+ return components