cognite-neat 0.88.1__py3-none-any.whl → 0.88.2__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/_version.py +1 -1
  2. cognite/neat/exceptions.py +2 -2
  3. cognite/neat/graph/loaders/_base.py +4 -4
  4. cognite/neat/graph/loaders/_rdf2asset.py +12 -14
  5. cognite/neat/graph/loaders/_rdf2dms.py +14 -10
  6. cognite/neat/issues/__init__.py +16 -0
  7. cognite/neat/{issues.py → issues/_base.py} +73 -5
  8. cognite/neat/issues/errors/external.py +21 -0
  9. cognite/neat/issues/errors/properties.py +75 -0
  10. cognite/neat/issues/errors/resources.py +123 -0
  11. cognite/neat/issues/errors/schema.py +0 -0
  12. cognite/neat/{rules/issues → issues}/formatters.py +9 -9
  13. cognite/neat/issues/neat_warnings/__init__.py +2 -0
  14. cognite/neat/issues/neat_warnings/identifier.py +27 -0
  15. cognite/neat/issues/neat_warnings/models.py +22 -0
  16. cognite/neat/issues/neat_warnings/properties.py +77 -0
  17. cognite/neat/issues/neat_warnings/resources.py +125 -0
  18. cognite/neat/rules/exporters/_rules2dms.py +3 -2
  19. cognite/neat/rules/exporters/_validation.py +2 -2
  20. cognite/neat/rules/importers/__init__.py +7 -3
  21. cognite/neat/rules/importers/_base.py +3 -3
  22. cognite/neat/rules/importers/_dms2rules.py +39 -18
  23. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +44 -53
  24. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +6 -5
  25. cognite/neat/rules/importers/_rdf/__init__.py +0 -0
  26. cognite/neat/rules/importers/_rdf/_imf2rules/__init__.py +3 -0
  27. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +82 -0
  28. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +34 -0
  29. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +123 -0
  30. cognite/neat/rules/importers/{_owl2rules/_owl2rules.py → _rdf/_imf2rules/_imf2rules.py} +15 -11
  31. cognite/neat/rules/importers/{_inference2rules.py → _rdf/_inference2rules.py} +1 -1
  32. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +57 -0
  33. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2metadata.py +68 -0
  34. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +59 -0
  35. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +76 -0
  36. cognite/neat/rules/importers/_rdf/_shared.py +586 -0
  37. cognite/neat/rules/importers/_spreadsheet2rules.py +1 -1
  38. cognite/neat/rules/importers/_yaml2rules.py +2 -1
  39. cognite/neat/rules/issues/__init__.py +1 -5
  40. cognite/neat/rules/issues/base.py +2 -21
  41. cognite/neat/rules/issues/dms.py +0 -134
  42. cognite/neat/rules/issues/spreadsheet.py +3 -3
  43. cognite/neat/rules/models/_types/_field.py +5 -2
  44. cognite/neat/rules/models/asset/_validation.py +1 -1
  45. cognite/neat/rules/models/dms/_schema.py +53 -30
  46. cognite/neat/rules/models/dms/_validation.py +2 -2
  47. cognite/neat/rules/models/entities.py +3 -0
  48. cognite/neat/rules/models/information/_validation.py +1 -1
  49. cognite/neat/workflows/steps/lib/current/rules_importer.py +73 -1
  50. cognite/neat/workflows/steps/lib/current/rules_validator.py +19 -7
  51. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.2.dist-info}/METADATA +1 -1
  52. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.2.dist-info}/RECORD +57 -42
  53. cognite/neat/graph/issues/loader.py +0 -104
  54. cognite/neat/rules/importers/_owl2rules/_owl2classes.py +0 -215
  55. cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +0 -209
  56. cognite/neat/rules/importers/_owl2rules/_owl2properties.py +0 -203
  57. cognite/neat/rules/issues/importing.py +0 -423
  58. /cognite/neat/{graph/issues → issues/errors}/__init__.py +0 -0
  59. /cognite/neat/rules/importers/{_owl2rules → _rdf/_owl2rules}/__init__.py +0 -0
  60. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.2.dist-info}/LICENSE +0 -0
  61. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.2.dist-info}/WHEEL +0 -0
  62. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,82 @@
1
+ from typing import cast
2
+
3
+ from rdflib import Graph
4
+
5
+ from cognite.neat.rules.importers._rdf._shared import (
6
+ clean_up_classes,
7
+ make_classes_compliant,
8
+ parse_raw_classes_dataframe,
9
+ )
10
+
11
+
12
+ def parse_imf_to_classes(graph: Graph, language: str = "en") -> list[dict]:
13
+ """Parse IMF elements from RDF-graph and extract classes to pandas dataframe.
14
+
15
+ Args:
16
+ graph: Graph containing imf elements
17
+ language: Language to use for parsing, by default "en"
18
+
19
+ Returns:
20
+ Dataframe containing imf elements
21
+
22
+ !!! note "IMF Compliance"
23
+ The IMF elements are expressed in RDF, primarily using SHACL and OWL. To ensure
24
+ that the resulting classes are compliant with CDF, similar validation checks as
25
+ in the OWL ontology importer are applied.
26
+
27
+ For the IMF-types more of the compliance logic is placed directly in the SPARQL
28
+ query. Among these are the creation of class name not starting with a number,
29
+ and ensuring that all classes have a parent.
30
+
31
+ IMF-attributes are considered both classes and properties. This kind of punning
32
+ is necessary to capture additional information carried by attributes. They carry,
33
+ among other things, a set of relationsships to reference terms, units of measure,
34
+ and qualifiers that together make up the meaning of the attribute.
35
+ """
36
+
37
+ query = """
38
+ SELECT ?class ?name ?description ?parentClass ?reference ?match ?comment
39
+ WHERE {
40
+ #Finding IMF - elements
41
+ VALUES ?type { imf:BlockType imf:TerminalType imf:AttributeType }
42
+ ?imfClass a ?type .
43
+ OPTIONAL {?imfClass rdfs:subClassOf ?parent }.
44
+ OPTIONAL {?imfClass rdfs:label | skos:prefLabel ?name }.
45
+ OPTIONAL {?imfClass rdfs:comment | skos:description ?description} .
46
+
47
+ # Finding the last segment of the class IRI
48
+ BIND(STR(?imfClass) AS ?classString)
49
+ BIND(REPLACE(?classString, "^.*[/#]([^/#]*)$", "$1") AS ?classSegment)
50
+ BIND(IF(CONTAINS(?classString, "imf/"), CONCAT("IMF_", ?classSegment) , ?classSegment) AS ?class)
51
+
52
+ # Add imf:Attribute as parent class
53
+ BIND(IF(!bound(?parent) && ?type = imf:AttributeType, imf:Attribute, ?parent) AS ?parentClass)
54
+
55
+ # Rebind the IRI of the IMF-type to the ?reference variable to align with dataframe column headers
56
+ # This is solely for readability, the ?imfClass could have been returnered directly instead of ?reference
57
+ BIND(?imfClass AS ?reference)
58
+
59
+ FILTER (!isBlank(?class))
60
+ FILTER (!bound(?parentClass) || !isBlank(?parentClass))
61
+ FILTER (!bound(?name) || LANG(?name) = "" || LANGMATCHES(LANG(?name), "en"))
62
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "en"))
63
+ }
64
+ """
65
+
66
+ # create raw dataframe
67
+ raw_df = parse_raw_classes_dataframe(cast(list[tuple], list(graph.query(query.replace("en", language)))))
68
+ if raw_df.empty:
69
+ return []
70
+
71
+ # group values and clean up
72
+ processed_df = clean_up_classes(raw_df)
73
+
74
+ # make compliant
75
+ processed_df = make_classes_compliant(processed_df, importer="IMF")
76
+
77
+ # Make Parent Class list elements into string joined with comma
78
+ processed_df["Parent Class"] = processed_df["Parent Class"].apply(
79
+ lambda x: ", ".join(x) if isinstance(x, list) and x else None
80
+ )
81
+
82
+ return processed_df.dropna(axis=0, how="all").replace(float("nan"), None).to_dict(orient="records")
@@ -0,0 +1,34 @@
1
+ from rdflib import Namespace
2
+
3
+ from cognite.neat.rules.importers._rdf._shared import make_metadata_compliant
4
+ from cognite.neat.rules.models import RoleTypes, SchemaCompleteness
5
+
6
+
7
+ def parse_imf_metadata() -> dict:
8
+ """Provide hardcoded IMF metadata to dict.
9
+
10
+ Returns:
11
+ Dictionary containing IMF metadata
12
+
13
+ !!! note "Compliant IMF metadata"
14
+ The current RDF provide IMF types as SHACL, but there are not any metadata describing
15
+ the actual content.
16
+
17
+ """
18
+
19
+ raw_metadata = {
20
+ "role": RoleTypes.information,
21
+ "schema": SchemaCompleteness.partial,
22
+ "prefix": "pca-imf",
23
+ "namespace": Namespace("http://posccaesar.org/imf/"),
24
+ "version": None,
25
+ "created": None,
26
+ "updated": None,
27
+ "title": "IMF - types",
28
+ "description": "IMF - types",
29
+ "creator": None,
30
+ "rights": None,
31
+ "license": None,
32
+ }
33
+
34
+ return make_metadata_compliant(raw_metadata)
@@ -0,0 +1,123 @@
1
+ from typing import cast
2
+
3
+ from rdflib import Graph
4
+
5
+ from cognite.neat.rules.importers._rdf._shared import (
6
+ clean_up_properties,
7
+ make_properties_compliant,
8
+ parse_raw_properties_dataframe,
9
+ )
10
+
11
+
12
+ def parse_imf_to_properties(graph: Graph, language: str = "en") -> list[dict]:
13
+ """Parse IMF elements from RDF-graph and extract properties to pandas dataframe.
14
+
15
+ Args:
16
+ graph: Graph containing imf elements
17
+ language: Language to use for parsing, by default "en"
18
+
19
+ Returns:
20
+ List of dictionaries containing properties extracted from IMF elements
21
+
22
+ !!! note "IMF Compliance"
23
+ The IMF elements are expressed in RDF, primarily using SHACL and OWL. To ensure
24
+ that the resulting properties are compliant with CDF, similar validation checks
25
+ as in the OWL ontology importer are applied.
26
+
27
+ For the IMF-types more of the compliance logic is placed directly in the SPARQL
28
+ query. Among these are the creation of class and property names not starting
29
+ with a number, ensuring property types as well as default cardinality boundraries.
30
+
31
+ IMF-attributes are considered both classes and properties. This kind of punning
32
+ is necessary to capture additional information carried by attributes. They carry,
33
+ among other things, a set of relationsships to reference terms, units of measure,
34
+ and qualifiers that together make up the meaning of the attribute. These references
35
+ are listed as additional properties with default values.
36
+ """
37
+
38
+ query = """
39
+ SELECT ?class ?property ?name ?description ?valueType ?minCount ?maxCount ?default ?reference
40
+ ?match ?comment ?propertyType
41
+ WHERE
42
+ {
43
+ # Finding IMF-blocks and terminals
44
+ {
45
+ VALUES ?classType { imf:BlockType imf:TerminalType }
46
+ ?imfClass a ?classType ;
47
+ sh:property ?propertyShape .
48
+ ?propertyShape sh:path ?imfProperty .
49
+
50
+ OPTIONAL { ?imfProperty skos:prefLabel ?name . }
51
+ OPTIONAL { ?imfProperty skos:description ?description . }
52
+ OPTIONAL { ?imfProperty rdfs:range ?range . }
53
+ OPTIONAL { ?imfProperty a ?type . }
54
+ OPTIONAL { ?propertyShape sh:minCount ?minCardinality} .
55
+ OPTIONAL { ?propertyShape sh:maxCount ?maxCardinality} .
56
+ OPTIONAL { ?propertyShape sh:hasValue ?defualt . }
57
+ OPTIONAL { ?propertyShape sh:class | sh:qualifiedValueShape/sh:class ?valueShape .}
58
+ }
59
+ UNION
60
+ # Finding the IMF-attribute types
61
+ {
62
+ ?imfClass a imf:AttributeType ;
63
+ ?imfProperty ?default .
64
+
65
+ # imf:predicate is required
66
+ BIND(IF(?imfProperty = <http://ns.imfid.org/imf#predicate>, 1, 0) AS ?minCardinality)
67
+
68
+ # The following information is used to describe the attribute when it is connected to a block or a terminal
69
+ # and not duplicated here.
70
+ FILTER(?imfProperty != rdf:type && ?imfProperty != skos:prefLabel && ?imfProperty != skos:description)
71
+ }
72
+
73
+ # Finding the last segment of the class IRI
74
+ BIND(STR(?imfClass) AS ?classString)
75
+ BIND(REPLACE(?classString, "^.*[/#]([^/#]*)$", "$1") AS ?classSegment)
76
+ BIND(IF(CONTAINS(?classString, "imf/"), CONCAT("IMF_", ?classSegment) , ?classSegment) AS ?class)
77
+
78
+ # Finding the last segment of the property IRI
79
+ BIND(STR(?imfProperty) AS ?propertyString)
80
+ BIND(REPLACE(?propertyString, "^.*[/#]([^/#]*)$", "$1") AS ?propertySegment)
81
+ BIND(IF(CONTAINS(?propertyString, "imf/"), CONCAT("IMF_", ?propertySegment) , ?propertySegment) AS ?property)
82
+
83
+ # Set the value type for the property based on sh:class, sh:qualifiedValueType or rdfs:range
84
+ BIND(IF(BOUND(?valueShape), ?valueShape, IF(BOUND(?range) , ?range , ?valueShape)) AS ?valueIriType)
85
+
86
+ # Finding the last segment of value types
87
+ BIND(STR(?valueIriType) AS ?valueTypeString)
88
+ BIND(REPLACE(?valueTypeString, "^.*[/#]([^/#]*)$", "$1") AS ?valueTypeSegment)
89
+ BIND(IF(CONTAINS(?valueTypeString, "imf/"), CONCAT("IMF_", ?valueTypeSegment) , ?valueTypeSegment)
90
+ AS ?valueType)
91
+
92
+ # Helper variable to set property type if value type is not already set.
93
+ BIND(IF(BOUND(?type) && ?type = owl:DatatypeProperty, ?type , owl:ObjectProperty) AS ?propertyType)
94
+
95
+ # Assert cardinality values if they do not exist
96
+ BIND(IF(BOUND(?minCardinality), ?minCardinality, 0) AS ?minCount)
97
+ BIND(IF(BOUND(?maxCardinality), ?maxCardinality, 1) AS ?maxCount)
98
+
99
+ # Rebind the IRI of the IMF-attribute to the ?reference variable to align with dataframe column headers
100
+ # This is solely for readability, the ?imfClass could have been returnered directly instead of ?reference
101
+ BIND(?imfProperty AS ?reference)
102
+
103
+ FILTER (!isBlank(?property))
104
+ FILTER (!bound(?class) || !isBlank(?class))
105
+ FILTER (!bound(?name) || LANG(?name) = "" || LANGMATCHES(LANG(?name), "en"))
106
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "en"))
107
+ }
108
+ """
109
+
110
+ raw_df = parse_raw_properties_dataframe(cast(list[tuple], list(graph.query(query.replace("en", language)))))
111
+ if raw_df.empty:
112
+ return []
113
+
114
+ # group values and clean up
115
+ processed_df = clean_up_properties(raw_df)
116
+
117
+ # make compliant
118
+ processed_df = make_properties_compliant(processed_df, importer="IMF")
119
+
120
+ # drop column _property_type, which was a helper column:
121
+ processed_df.drop(columns=["_property_type"], inplace=True)
122
+
123
+ return processed_df.to_dict(orient="records")
@@ -4,25 +4,26 @@ there are loaders to TransformationRules pydantic class."""
4
4
  from pathlib import Path
5
5
  from typing import Literal, overload
6
6
 
7
- from rdflib import DC, DCTERMS, OWL, RDF, RDFS, SKOS, Graph
7
+ from rdflib import DC, DCTERMS, OWL, RDF, RDFS, SH, SKOS, Graph
8
8
 
9
+ from cognite.neat.issues import IssueList
9
10
  from cognite.neat.rules.importers._base import BaseImporter, Rules
10
- from cognite.neat.rules.issues import IssueList
11
11
  from cognite.neat.rules.models import InformationRules, RoleTypes
12
12
  from cognite.neat.rules.models.data_types import _XSD_TYPES
13
13
 
14
- from ._owl2classes import parse_owl_classes
15
- from ._owl2metadata import parse_owl_metadata
16
- from ._owl2properties import parse_owl_properties
14
+ from ._imf2classes import parse_imf_to_classes
15
+ from ._imf2metadata import parse_imf_metadata
16
+ from ._imf2properties import parse_imf_to_properties
17
17
 
18
18
 
19
- class OWLImporter(BaseImporter):
20
- """Convert OWL ontology to tables/ transformation rules / Excel file.
19
+ class IMFImporter(BaseImporter):
20
+ """Convert SHACL shapes to tables/ transformation rules / Excel file.
21
21
 
22
22
  Args:
23
- filepath: Path to OWL ontology
23
+ filepath: Path to RDF file containing the SHACL Shapes
24
24
 
25
25
  !!! Note
26
+ Rewrite to fit the SHACL rules we apply
26
27
  OWL Ontologies are information models which completeness varies. As such, constructing functional
27
28
  data model directly will often be impossible, therefore the produced Rules object will be ill formed.
28
29
  To avoid this, neat will automatically attempt to make the imported rules compliant by adding default
@@ -61,11 +62,14 @@ class OWLImporter(BaseImporter):
61
62
  graph.bind("dcterms", DCTERMS)
62
63
  graph.bind("dc", DC)
63
64
  graph.bind("skos", SKOS)
65
+ graph.bind("sh", SH)
66
+ graph.bind("xsd", _XSD_TYPES)
67
+ graph.bind("imf", "http://ns.imfid.org/imf#")
64
68
 
65
69
  components = {
66
- "Metadata": parse_owl_metadata(graph),
67
- "Classes": parse_owl_classes(graph),
68
- "Properties": parse_owl_properties(graph),
70
+ "Metadata": parse_imf_metadata(),
71
+ "Classes": parse_imf_to_classes(graph),
72
+ "Properties": parse_imf_to_properties(graph),
69
73
  }
70
74
 
71
75
  components = make_components_compliant(components)
@@ -9,8 +9,8 @@ from rdflib import Literal as RdfLiteral
9
9
  import cognite.neat.rules.issues as issues
10
10
  from cognite.neat.constants import DEFAULT_NAMESPACE, get_default_prefixes
11
11
  from cognite.neat.graph.stores import NeatGraphStore
12
+ from cognite.neat.issues import IssueList
12
13
  from cognite.neat.rules.importers._base import BaseImporter, Rules, _handle_issues
13
- from cognite.neat.rules.issues import IssueList
14
14
  from cognite.neat.rules.models import InformationRules, RoleTypes
15
15
  from cognite.neat.rules.models._base import MatchType
16
16
  from cognite.neat.rules.models.information import (
@@ -0,0 +1,57 @@
1
+ from typing import cast
2
+
3
+ from rdflib import Graph
4
+
5
+ from cognite.neat.rules.importers._rdf._shared import (
6
+ clean_up_classes,
7
+ make_classes_compliant,
8
+ parse_raw_classes_dataframe,
9
+ )
10
+
11
+
12
+ def parse_owl_classes(graph: Graph, language: str = "en") -> list[dict]:
13
+ """Parse owl classes from graph to pandas dataframe.
14
+
15
+ Args:
16
+ graph: Graph containing owl classes
17
+ language: Language to use for parsing, by default "en"
18
+
19
+ Returns:
20
+ Dataframe containing owl classes
21
+
22
+ !!! note "Compliant OWL classes"
23
+ This makes the method very opinionated, but results in a compliant classes.
24
+ """
25
+
26
+ query = """
27
+ SELECT ?class ?name ?description ?parentClass ?reference ?match ?comment
28
+ WHERE {
29
+ ?class a owl:Class .
30
+ OPTIONAL {?class rdfs:subClassOf ?parentClass }.
31
+ OPTIONAL {?class rdfs:label ?name }.
32
+ OPTIONAL {?class rdfs:comment ?description} .
33
+ FILTER (!isBlank(?class))
34
+ FILTER (!bound(?parentClass) || !isBlank(?parentClass))
35
+ FILTER (!bound(?name) || LANG(?name) = "" || LANGMATCHES(LANG(?name), "en"))
36
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "en"))
37
+ }
38
+ """
39
+
40
+ # create raw dataframe
41
+
42
+ raw_df = parse_raw_classes_dataframe(cast(list[tuple], list(graph.query(query.replace("en", language)))))
43
+ if raw_df.empty:
44
+ return []
45
+
46
+ # group values and clean up
47
+ processed_df = clean_up_classes(raw_df)
48
+
49
+ # make compliant
50
+ processed_df = make_classes_compliant(processed_df, importer="OWL")
51
+
52
+ # Make Parent Class list elements into string joined with comma
53
+ processed_df["Parent Class"] = processed_df["Parent Class"].apply(
54
+ lambda x: ", ".join(x) if isinstance(x, list) and x else None
55
+ )
56
+
57
+ return processed_df.dropna(axis=0, how="all").replace(float("nan"), None).to_dict(orient="records")
@@ -0,0 +1,68 @@
1
+ from rdflib import Graph, Namespace
2
+
3
+ from cognite.neat.constants import DEFAULT_NAMESPACE
4
+ from cognite.neat.rules.importers._rdf._shared import make_metadata_compliant
5
+ from cognite.neat.rules.models import RoleTypes, SchemaCompleteness
6
+ from cognite.neat.utils.collection_ import remove_none_elements_from_set
7
+ from cognite.neat.utils.rdf_ import convert_rdflib_content
8
+
9
+
10
+ def parse_owl_metadata(graph: Graph) -> dict:
11
+ """Parse owl metadata from graph to dict.
12
+
13
+ Args:
14
+ graph: Graph containing owl metadata
15
+
16
+ Returns:
17
+ Dictionary containing owl metadata
18
+
19
+ !!! note "Compliant OWL metadata"
20
+ This makes the method very opinionated, but results in a compliant metadata.
21
+
22
+
23
+ """
24
+ # TODO: Move dataframe to dict representation
25
+
26
+ query = f"""SELECT ?namespace ?prefix ?version ?created ?updated ?title ?description ?creator ?rights ?license
27
+ WHERE {{
28
+ ?namespace a owl:Ontology .
29
+ OPTIONAL {{?namespace owl:versionInfo ?version }}.
30
+ OPTIONAL {{?namespace dcterms:creator ?creator }}.
31
+ OPTIONAL {{?namespace <{DEFAULT_NAMESPACE.prefix}> ?prefix }}.
32
+ OPTIONAL {{?namespace dcterms:title|rdfs:label|skos:prefLabel ?title }}.
33
+ OPTIONAL {{?namespace dcterms:modified ?updated }}.
34
+ OPTIONAL {{?namespace dcterms:created ?created }}.
35
+ OPTIONAL {{?namespace dcterms:description ?description }}.
36
+ OPTIONAL {{?namespace dcterms:rights|dc:rights ?rights }}.
37
+
38
+ OPTIONAL {{?namespace dcterms:license|dc:license ?license }}.
39
+ FILTER (!isBlank(?namespace))
40
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "en"))
41
+ FILTER (!bound(?title) || LANG(?title) = "" || LANGMATCHES(LANG(?title), "en"))
42
+ }}
43
+ """
44
+
45
+ results = [{item for item in sublist} for sublist in list(zip(*graph.query(query), strict=True))]
46
+
47
+ raw_metadata = convert_rdflib_content(
48
+ {
49
+ "role": RoleTypes.information,
50
+ "schema": SchemaCompleteness.partial,
51
+ "prefix": results[1].pop(),
52
+ "namespace": Namespace(results[0].pop()),
53
+ "version": results[2].pop(),
54
+ "created": results[3].pop(),
55
+ "updated": results[4].pop(),
56
+ "title": results[5].pop(),
57
+ "description": results[6].pop(),
58
+ "creator": (
59
+ ", ".join(remove_none_elements_from_set(results[7]))
60
+ if remove_none_elements_from_set(results[7])
61
+ else None
62
+ ),
63
+ "rights": results[8].pop(),
64
+ "license": results[9].pop(),
65
+ }
66
+ )
67
+
68
+ return make_metadata_compliant(raw_metadata)
@@ -0,0 +1,59 @@
1
+ from typing import cast
2
+
3
+ from rdflib import Graph
4
+
5
+ from cognite.neat.rules.importers._rdf._shared import (
6
+ clean_up_properties,
7
+ make_properties_compliant,
8
+ parse_raw_properties_dataframe,
9
+ )
10
+
11
+
12
+ def parse_owl_properties(graph: Graph, language: str = "en") -> list[dict]:
13
+ """Parse owl properties from graph to pandas dataframe.
14
+
15
+ Args:
16
+ graph: Graph containing owl properties
17
+ language: Language to use for parsing, by default "en"
18
+
19
+ Returns:
20
+ List of dictionaries containing owl properties
21
+ """
22
+
23
+ query = """
24
+
25
+ SELECT ?class ?property ?name ?description ?type ?minCount ?maxCount ?default ?reference
26
+ ?match ?comment ?propertyType
27
+ WHERE {
28
+ ?property a ?propertyType.
29
+ FILTER (?propertyType IN (owl:ObjectProperty, owl:DatatypeProperty ) )
30
+ OPTIONAL {?property rdfs:domain ?class }.
31
+ OPTIONAL {?property rdfs:range ?type }.
32
+ OPTIONAL {?property rdfs:label ?name }.
33
+ OPTIONAL {?property rdfs:comment ?description} .
34
+ OPTIONAL {?property owl:maxCardinality ?maxCount} .
35
+ OPTIONAL {?property owl:minCardinality ?minCount} .
36
+ FILTER (!isBlank(?property))
37
+ FILTER (!bound(?type) || !isBlank(?type))
38
+ FILTER (!bound(?class) || !isBlank(?class))
39
+ FILTER (!bound(?name) || LANG(?name) = "" || LANGMATCHES(LANG(?name), "en"))
40
+ FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "en"))
41
+ BIND(IF(bound(?minCount), ?minCount, 0) AS ?minCount)
42
+ BIND(IF(bound(?maxCount), ?maxCount, 1) AS ?maxCount)
43
+ }
44
+ """
45
+
46
+ raw_df = parse_raw_properties_dataframe(cast(list[tuple], list(graph.query(query.replace("en", language)))))
47
+ if raw_df.empty:
48
+ return []
49
+
50
+ # group values and clean up
51
+ processed_df = clean_up_properties(raw_df)
52
+
53
+ # make compliant
54
+ processed_df = make_properties_compliant(processed_df, importer="OWL")
55
+
56
+ # drop column _property_type, which was a helper column:
57
+ processed_df.drop(columns=["_property_type"], inplace=True)
58
+
59
+ return processed_df.to_dict(orient="records")
@@ -0,0 +1,76 @@
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 pathlib import Path
5
+ from typing import Literal, overload
6
+
7
+ from rdflib import DC, DCTERMS, OWL, RDF, RDFS, SKOS, Graph
8
+
9
+ from cognite.neat.issues import IssueList
10
+ from cognite.neat.rules.importers._base import BaseImporter, Rules
11
+ from cognite.neat.rules.importers._rdf._shared import make_components_compliant
12
+ from cognite.neat.rules.models import InformationRules, RoleTypes
13
+
14
+ from ._owl2classes import parse_owl_classes
15
+ from ._owl2metadata import parse_owl_metadata
16
+ from ._owl2properties import parse_owl_properties
17
+
18
+
19
+ class OWLImporter(BaseImporter):
20
+ """Convert OWL ontology to tables/ transformation rules / Excel file.
21
+
22
+ Args:
23
+ filepath: Path to OWL ontology
24
+
25
+ !!! Note
26
+ OWL Ontologies are information models which completeness varies. As such, constructing functional
27
+ data model directly will often be impossible, therefore the produced Rules object will be ill formed.
28
+ To avoid this, neat will automatically attempt to make the imported rules compliant by adding default
29
+ values for missing information, attaching dangling properties to default containers based on the
30
+ property type, etc.
31
+
32
+ One has to be aware that NEAT will be opinionated about how to make the ontology
33
+ compliant, and that the resulting rules may not be what you expect.
34
+
35
+ """
36
+
37
+ def __init__(self, filepath: Path):
38
+ self.owl_filepath = filepath
39
+
40
+ @overload
41
+ def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> Rules: ...
42
+
43
+ @overload
44
+ def to_rules(
45
+ self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
46
+ ) -> tuple[Rules | None, IssueList]: ...
47
+
48
+ def to_rules(
49
+ self,
50
+ errors: Literal["raise", "continue"] = "continue",
51
+ role: RoleTypes | None = None,
52
+ ) -> tuple[Rules | None, IssueList] | Rules:
53
+ graph = Graph()
54
+ try:
55
+ graph.parse(self.owl_filepath)
56
+ except Exception as e:
57
+ raise Exception(f"Could not parse owl file: {e}") from e
58
+
59
+ # bind key namespaces
60
+ graph.bind("owl", OWL)
61
+ graph.bind("rdf", RDF)
62
+ graph.bind("rdfs", RDFS)
63
+ graph.bind("dcterms", DCTERMS)
64
+ graph.bind("dc", DC)
65
+ graph.bind("skos", SKOS)
66
+
67
+ components = {
68
+ "Metadata": parse_owl_metadata(graph),
69
+ "Classes": parse_owl_classes(graph),
70
+ "Properties": parse_owl_properties(graph),
71
+ }
72
+
73
+ components = make_components_compliant(components)
74
+
75
+ rules = InformationRules.model_validate(components)
76
+ return self._to_output(rules, IssueList(), errors, role)