cognite-neat 0.88.0__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.
- cognite/neat/_version.py +1 -1
- cognite/neat/app/api/routers/configuration.py +1 -1
- cognite/neat/app/ui/neat-app/build/asset-manifest.json +7 -7
- cognite/neat/app/ui/neat-app/build/index.html +1 -1
- cognite/neat/app/ui/neat-app/build/static/css/{main.38a62222.css → main.72e3d92e.css} +2 -2
- cognite/neat/app/ui/neat-app/build/static/css/main.72e3d92e.css.map +1 -0
- cognite/neat/app/ui/neat-app/build/static/js/main.5a52cf09.js +3 -0
- cognite/neat/app/ui/neat-app/build/static/js/{main.ec7f72e2.js.LICENSE.txt → main.5a52cf09.js.LICENSE.txt} +0 -9
- cognite/neat/app/ui/neat-app/build/static/js/main.5a52cf09.js.map +1 -0
- cognite/neat/config.py +44 -27
- cognite/neat/exceptions.py +8 -2
- cognite/neat/graph/extractors/_classic_cdf/_assets.py +21 -73
- cognite/neat/graph/extractors/_classic_cdf/_base.py +102 -0
- cognite/neat/graph/extractors/_classic_cdf/_events.py +46 -42
- cognite/neat/graph/extractors/_classic_cdf/_files.py +41 -45
- cognite/neat/graph/extractors/_classic_cdf/_labels.py +75 -52
- cognite/neat/graph/extractors/_classic_cdf/_relationships.py +49 -27
- cognite/neat/graph/extractors/_classic_cdf/_sequences.py +47 -50
- cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +47 -49
- cognite/neat/graph/loaders/_base.py +4 -4
- cognite/neat/graph/loaders/_rdf2asset.py +12 -14
- cognite/neat/graph/loaders/_rdf2dms.py +14 -10
- cognite/neat/graph/queries/_base.py +22 -29
- cognite/neat/graph/queries/_shared.py +1 -1
- cognite/neat/graph/stores/_base.py +19 -11
- cognite/neat/graph/transformers/_rdfpath.py +3 -2
- cognite/neat/issues/__init__.py +16 -0
- cognite/neat/{issues.py → issues/_base.py} +78 -2
- cognite/neat/issues/errors/external.py +21 -0
- cognite/neat/issues/errors/properties.py +75 -0
- cognite/neat/issues/errors/resources.py +123 -0
- cognite/neat/issues/errors/schema.py +0 -0
- cognite/neat/{rules/issues → issues}/formatters.py +9 -9
- cognite/neat/issues/neat_warnings/__init__.py +2 -0
- cognite/neat/issues/neat_warnings/identifier.py +27 -0
- cognite/neat/issues/neat_warnings/models.py +22 -0
- cognite/neat/issues/neat_warnings/properties.py +77 -0
- cognite/neat/issues/neat_warnings/resources.py +125 -0
- cognite/neat/rules/exporters/_rules2dms.py +3 -2
- cognite/neat/rules/exporters/_rules2ontology.py +28 -20
- cognite/neat/rules/exporters/_validation.py +15 -21
- cognite/neat/rules/importers/__init__.py +7 -3
- cognite/neat/rules/importers/_base.py +3 -3
- cognite/neat/rules/importers/_dms2rules.py +39 -18
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +44 -53
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +6 -5
- cognite/neat/rules/importers/_rdf/__init__.py +0 -0
- cognite/neat/rules/importers/_rdf/_imf2rules/__init__.py +3 -0
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +82 -0
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +34 -0
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +123 -0
- cognite/neat/rules/importers/{_owl2rules/_owl2rules.py → _rdf/_imf2rules/_imf2rules.py} +15 -11
- cognite/neat/rules/importers/{_inference2rules.py → _rdf/_inference2rules.py} +1 -1
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +57 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2metadata.py +68 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +59 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +76 -0
- cognite/neat/rules/importers/_rdf/_shared.py +586 -0
- cognite/neat/rules/importers/_spreadsheet2rules.py +31 -28
- cognite/neat/rules/importers/_yaml2rules.py +2 -1
- cognite/neat/rules/issues/__init__.py +1 -5
- cognite/neat/rules/issues/base.py +2 -21
- cognite/neat/rules/issues/dms.py +20 -134
- cognite/neat/rules/issues/ontology.py +298 -0
- cognite/neat/rules/issues/spreadsheet.py +51 -3
- cognite/neat/rules/issues/tables.py +72 -0
- cognite/neat/rules/models/_rdfpath.py +4 -4
- cognite/neat/rules/models/_types/_field.py +14 -21
- cognite/neat/rules/models/asset/_validation.py +1 -1
- cognite/neat/rules/models/dms/_schema.py +53 -30
- cognite/neat/rules/models/dms/_validation.py +2 -2
- cognite/neat/rules/models/entities.py +3 -0
- cognite/neat/rules/models/information/_rules.py +5 -4
- cognite/neat/rules/models/information/_validation.py +1 -1
- cognite/neat/utils/rdf_.py +17 -9
- cognite/neat/utils/regex_patterns.py +52 -0
- cognite/neat/workflows/steps/lib/current/rules_importer.py +73 -1
- cognite/neat/workflows/steps/lib/current/rules_validator.py +19 -7
- {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/METADATA +2 -6
- {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/RECORD +85 -72
- cognite/neat/app/ui/neat-app/build/static/css/main.38a62222.css.map +0 -1
- cognite/neat/app/ui/neat-app/build/static/js/main.ec7f72e2.js +0 -3
- cognite/neat/app/ui/neat-app/build/static/js/main.ec7f72e2.js.map +0 -1
- cognite/neat/graph/issues/loader.py +0 -104
- cognite/neat/graph/stores/_oxrdflib.py +0 -247
- cognite/neat/rules/exceptions.py +0 -2972
- cognite/neat/rules/importers/_owl2rules/_owl2classes.py +0 -215
- cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +0 -213
- cognite/neat/rules/importers/_owl2rules/_owl2properties.py +0 -203
- cognite/neat/rules/issues/importing.py +0 -408
- cognite/neat/rules/models/_types/_base.py +0 -16
- cognite/neat/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +0 -152
- cognite/neat/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +0 -139
- cognite/neat/workflows/examples/Ontology_to_Data_Model/workflow.yaml +0 -116
- /cognite/neat/{graph/issues → issues/errors}/__init__.py +0 -0
- /cognite/neat/rules/importers/{_owl2rules → _rdf/_owl2rules}/__init__.py +0 -0
- {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/LICENSE +0 -0
- {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/WHEEL +0 -0
- {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/entry_points.txt +0 -0
|
@@ -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 .
|
|
15
|
-
from .
|
|
16
|
-
from .
|
|
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
|
|
20
|
-
"""Convert
|
|
19
|
+
class IMFImporter(BaseImporter):
|
|
20
|
+
"""Convert SHACL shapes to tables/ transformation rules / Excel file.
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
|
-
filepath: Path to
|
|
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":
|
|
67
|
-
"Classes":
|
|
68
|
-
"Properties":
|
|
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)
|