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.
- cognite/neat/_client/_api/data_modeling_loaders.py +77 -4
- cognite/neat/_client/_api/schema.py +63 -2
- cognite/neat/_client/data_classes/schema.py +2 -348
- cognite/neat/_constants.py +27 -4
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +5 -5
- cognite/neat/_graph/loaders/_rdf2dms.py +2 -2
- cognite/neat/_graph/transformers/_classic_cdf.py +24 -13
- cognite/neat/_issues/_base.py +26 -17
- cognite/neat/_issues/errors/__init__.py +4 -2
- cognite/neat/_issues/errors/_external.py +7 -0
- cognite/neat/_issues/errors/_properties.py +2 -7
- cognite/neat/_issues/errors/_resources.py +1 -1
- cognite/neat/_issues/warnings/__init__.py +4 -2
- cognite/neat/_issues/warnings/_external.py +9 -1
- cognite/neat/_issues/warnings/_resources.py +26 -2
- cognite/neat/_issues/warnings/user_modeling.py +4 -4
- cognite/neat/_rules/_constants.py +2 -6
- cognite/neat/_rules/exporters/_rules2dms.py +4 -6
- cognite/neat/_rules/importers/__init__.py +1 -3
- cognite/neat/_rules/importers/_base.py +1 -1
- cognite/neat/_rules/importers/_dms2rules.py +3 -25
- cognite/neat/_rules/importers/_rdf/__init__.py +5 -0
- cognite/neat/_rules/importers/_rdf/_base.py +34 -11
- cognite/neat/_rules/importers/_rdf/_imf2rules.py +91 -0
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +18 -2
- cognite/neat/_rules/importers/_rdf/_owl2rules.py +80 -0
- cognite/neat/_rules/importers/_rdf/_shared.py +138 -441
- cognite/neat/_rules/models/dms/__init__.py +2 -0
- cognite/neat/_rules/models/dms/_exporter.py +32 -30
- cognite/neat/_rules/models/dms/_rules.py +3 -45
- cognite/neat/_rules/models/dms/_validation.py +389 -122
- cognite/neat/_rules/models/information/__init__.py +2 -0
- cognite/neat/_rules/models/information/_rules.py +0 -59
- cognite/neat/_rules/models/information/_validation.py +9 -9
- cognite/neat/_rules/models/mapping/_classic2core.py +1 -1
- cognite/neat/_rules/models/mapping/_classic2core.yaml +8 -4
- cognite/neat/_rules/transformers/_pipelines.py +1 -1
- cognite/neat/_rules/transformers/_verification.py +29 -4
- cognite/neat/_session/_base.py +16 -41
- cognite/neat/_session/_prepare.py +6 -5
- cognite/neat/_session/_to.py +5 -2
- cognite/neat/_session/exceptions.py +4 -0
- cognite/neat/_utils/rdf_.py +6 -4
- cognite/neat/_version.py +1 -1
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -88
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +2 -16
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +3 -5
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/RECORD +52 -60
- cognite/neat/_rules/importers/_rdf/_imf2rules/__init__.py +0 -3
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2classes.py +0 -86
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2metadata.py +0 -29
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2properties.py +0 -130
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2rules.py +0 -154
- cognite/neat/_rules/importers/_rdf/_owl2rules/__init__.py +0 -3
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2classes.py +0 -58
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2metadata.py +0 -65
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2properties.py +0 -59
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2rules.py +0 -39
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/LICENSE +0 -0
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/WHEEL +0 -0
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,471 +1,168 @@
|
|
|
1
|
-
import
|
|
1
|
+
from typing import cast
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
from rdflib import
|
|
3
|
+
from rdflib import BNode, Graph
|
|
4
|
+
from rdflib.plugins.sparql import prepareQuery
|
|
5
|
+
from rdflib.query import ResultRow
|
|
6
6
|
|
|
7
|
-
from cognite.neat.
|
|
8
|
-
from cognite.neat.
|
|
9
|
-
from cognite.neat.
|
|
7
|
+
from cognite.neat._issues._base import IssueList
|
|
8
|
+
from cognite.neat._issues.errors._general import NeatValueError
|
|
9
|
+
from cognite.neat._issues.warnings._resources import (
|
|
10
|
+
ResourceRedefinedWarning,
|
|
11
|
+
ResourceRetrievalWarning,
|
|
12
|
+
)
|
|
13
|
+
from cognite.neat._utils.rdf_ import convert_rdflib_content
|
|
10
14
|
|
|
11
15
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
query_results,
|
|
15
|
-
columns=[
|
|
16
|
-
"Class",
|
|
17
|
-
"Name",
|
|
18
|
-
"Description",
|
|
19
|
-
"Implements",
|
|
20
|
-
],
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
if df.empty:
|
|
24
|
-
return df
|
|
25
|
-
|
|
26
|
-
# # remove NaNs
|
|
27
|
-
df.replace(np.nan, "", regex=True, inplace=True)
|
|
28
|
-
|
|
29
|
-
df.Class = df.Class.apply(lambda x: remove_namespace_from_uri(x))
|
|
30
|
-
df["Comment"] = len(df) * [None]
|
|
31
|
-
df["Implements"] = df["Implements"].apply(lambda x: remove_namespace_from_uri(x))
|
|
32
|
-
|
|
33
|
-
return df
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def clean_up_classes(df: pd.DataFrame) -> pd.DataFrame:
|
|
37
|
-
clean_list = [
|
|
38
|
-
{
|
|
39
|
-
"Class": class_,
|
|
40
|
-
"Name": group_df["Name"].unique()[0],
|
|
41
|
-
"Description": "\n".join(list(group_df.Description.unique())),
|
|
42
|
-
"Implements": ", ".join(list(group_df["Implements"].unique())),
|
|
43
|
-
}
|
|
44
|
-
for class_, group_df in df.groupby("Class")
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
df = pd.DataFrame(clean_list)
|
|
48
|
-
|
|
49
|
-
# bring NaNs back
|
|
50
|
-
df.replace("", None, inplace=True)
|
|
51
|
-
|
|
52
|
-
# split Implements column back into list
|
|
53
|
-
df["Implements"] = df["Implements"].apply(lambda x: x.split(", ") if isinstance(x, str) else None)
|
|
54
|
-
|
|
55
|
-
return df
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def make_classes_compliant(classes: pd.DataFrame, importer: str = "RDF-based") -> pd.DataFrame:
|
|
59
|
-
"""Make classes compliant.
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
Dataframe containing compliant classes
|
|
63
|
-
|
|
64
|
-
!!! note "About the compliant classes"
|
|
65
|
-
The compliant classes are based on the OWL base ontology, but adapted to NEAT and use in CDF.
|
|
66
|
-
One thing to note is that this method would not be able to fix issues with class ids which
|
|
67
|
-
are not compliant with the CDF naming convention. For example, if a class id contains a space,
|
|
68
|
-
starts with a number, etc. This will cause issues when trying to create the class in CDF.
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
# Add _object_property_class, _data_type_property_class, _thing_class to the dataframe
|
|
72
|
-
classes = pd.concat(
|
|
73
|
-
[
|
|
74
|
-
classes,
|
|
75
|
-
pd.DataFrame([object_property_class(), data_type_property_class(), thing_class()]),
|
|
76
|
-
],
|
|
77
|
-
ignore_index=True,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# Reduce length of elements in the "Description" column to 1024 characters
|
|
81
|
-
classes["Description"] = classes["Description"].apply(lambda x: x[:1024] if isinstance(x, str) else None)
|
|
82
|
-
|
|
83
|
-
# Add missing parent classes to the dataframe
|
|
84
|
-
classes = pd.concat(
|
|
85
|
-
[classes, pd.DataFrame(add_parent_class(classes))],
|
|
86
|
-
ignore_index=True,
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
return classes
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def object_property_class() -> dict:
|
|
93
|
-
return {
|
|
94
|
-
"Class": "ObjectProperty",
|
|
95
|
-
"Name": None,
|
|
96
|
-
"Description": "The class of object properties.",
|
|
97
|
-
"Implements": None,
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def data_type_property_class() -> dict:
|
|
102
|
-
return {
|
|
103
|
-
"Class": "DatatypeProperty",
|
|
104
|
-
"Name": None,
|
|
105
|
-
"Description": "The class of data properties.",
|
|
106
|
-
"Implements": None,
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def thing_class() -> dict:
|
|
111
|
-
return {
|
|
112
|
-
"Class": "Thing",
|
|
113
|
-
"Name": None,
|
|
114
|
-
"Description": "The class of holding class individuals.",
|
|
115
|
-
"Implements": None,
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def add_parent_class(df: pd.DataFrame) -> list[dict]:
|
|
120
|
-
parent_set = {
|
|
121
|
-
item for sublist in df["Implements"].tolist() if sublist for item in sublist if item != "" and item is not None
|
|
122
|
-
}
|
|
123
|
-
class_set = set(df["Class"].tolist())
|
|
124
|
-
|
|
125
|
-
rows = []
|
|
126
|
-
for missing_parent_class in parent_set.difference(class_set):
|
|
127
|
-
rows += [
|
|
128
|
-
{
|
|
129
|
-
"Class": missing_parent_class,
|
|
130
|
-
"Name": None,
|
|
131
|
-
"Description": None,
|
|
132
|
-
"Implements": None,
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
return rows
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def parse_raw_properties_dataframe(query_results: list[tuple]) -> pd.DataFrame:
|
|
140
|
-
df = pd.DataFrame(
|
|
141
|
-
query_results,
|
|
142
|
-
columns=[
|
|
143
|
-
"Class",
|
|
144
|
-
"Property",
|
|
145
|
-
"Name",
|
|
146
|
-
"Description",
|
|
147
|
-
"Value Type",
|
|
148
|
-
"Min Count",
|
|
149
|
-
"Max Count",
|
|
150
|
-
"Default",
|
|
151
|
-
"_property_type",
|
|
152
|
-
],
|
|
153
|
-
)
|
|
154
|
-
if df.empty:
|
|
155
|
-
return df
|
|
156
|
-
|
|
157
|
-
df.replace(np.nan, "", regex=True, inplace=True)
|
|
158
|
-
|
|
159
|
-
df.Class = df.Class.apply(lambda x: remove_namespace_from_uri(x))
|
|
160
|
-
df.Property = df.Property.apply(lambda x: remove_namespace_from_uri(x))
|
|
161
|
-
df["Value Type"] = df["Value Type"].apply(lambda x: remove_namespace_from_uri(x))
|
|
162
|
-
df["_property_type"] = df["_property_type"].apply(lambda x: remove_namespace_from_uri(x))
|
|
163
|
-
|
|
164
|
-
return df
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def clean_up_properties(df: pd.DataFrame) -> pd.DataFrame:
|
|
168
|
-
class_grouped_dfs = df.groupby("Class")
|
|
169
|
-
|
|
170
|
-
clean_list = []
|
|
171
|
-
|
|
172
|
-
for class_, class_grouped_df in class_grouped_dfs:
|
|
173
|
-
property_grouped_dfs = class_grouped_df.groupby("Property")
|
|
174
|
-
for property_, property_grouped_df in property_grouped_dfs:
|
|
175
|
-
clean_list += [
|
|
176
|
-
{
|
|
177
|
-
"Class": class_,
|
|
178
|
-
"Property": property_,
|
|
179
|
-
"Name": property_grouped_df["Name"].unique()[0],
|
|
180
|
-
"Description": "\n".join(list(property_grouped_df["Description"].unique()))[:1024],
|
|
181
|
-
"Value Type": property_grouped_df["Value Type"].unique()[0],
|
|
182
|
-
"Min Count": property_grouped_df["Min Count"].unique()[0],
|
|
183
|
-
"Max Count": property_grouped_df["Max Count"].unique()[0],
|
|
184
|
-
"Default": property_grouped_df["Default"].unique()[0],
|
|
185
|
-
"_property_type": property_grouped_df["_property_type"].unique()[0],
|
|
186
|
-
}
|
|
187
|
-
]
|
|
188
|
-
|
|
189
|
-
df = pd.DataFrame(clean_list)
|
|
190
|
-
df.replace("", None, inplace=True)
|
|
191
|
-
|
|
192
|
-
return df
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def make_properties_compliant(properties: pd.DataFrame, importer: str = "RDF-based") -> pd.DataFrame:
|
|
196
|
-
# default to 0 if "Min Count" is not specified
|
|
197
|
-
properties["Min Count"] = properties["Min Count"].apply(lambda x: 0 if not isinstance(x, Literal) or x == "" else x)
|
|
198
|
-
|
|
199
|
-
# default to 1 if "Max Count" is not specified
|
|
200
|
-
properties["Max Count"] = properties["Max Count"].apply(lambda x: 1 if not isinstance(x, Literal) or x == "" else x)
|
|
201
|
-
|
|
202
|
-
# Reduce length of elements in the "Description" column to 1024 characters
|
|
203
|
-
properties["Description"] = properties["Description"].apply(lambda x: x[:1024] if isinstance(x, str) else None)
|
|
204
|
-
|
|
205
|
-
# fixes and additions
|
|
206
|
-
properties = fix_dangling_properties(properties)
|
|
207
|
-
properties = fix_missing_property_value_type(properties)
|
|
208
|
-
|
|
209
|
-
return properties
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def fix_dangling_properties(properties: pd.DataFrame) -> pd.DataFrame:
|
|
213
|
-
"""This method fixes properties which are missing a domain definition in the ontology.
|
|
214
|
-
|
|
215
|
-
Args:
|
|
216
|
-
properties: Dataframe containing properties
|
|
217
|
-
|
|
218
|
-
Returns:
|
|
219
|
-
Dataframe containing properties with fixed domain
|
|
220
|
-
"""
|
|
221
|
-
domain = {
|
|
222
|
-
"ObjectProperty": object_property_class()["Class"],
|
|
223
|
-
"DatatypeProperty": data_type_property_class()["Class"],
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
# apply missing range
|
|
227
|
-
properties["Class"] = properties.apply(
|
|
228
|
-
lambda row: (
|
|
229
|
-
domain[row._property_type]
|
|
230
|
-
if row._property_type == "ObjectProperty" and pd.isna(row["Class"])
|
|
231
|
-
else domain["DatatypeProperty"]
|
|
232
|
-
if pd.isna(row["Class"])
|
|
233
|
-
else row["Class"]
|
|
234
|
-
),
|
|
235
|
-
axis=1,
|
|
236
|
-
)
|
|
237
|
-
return properties
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def fix_missing_property_value_type(properties: pd.DataFrame) -> pd.DataFrame:
|
|
241
|
-
"""This method fixes properties which are missing a range definition in the ontology.
|
|
16
|
+
def parse_classes(graph: Graph, query: str, language: str, issue_list: IssueList) -> tuple[dict, IssueList]:
|
|
17
|
+
"""Parse classes from graph
|
|
242
18
|
|
|
243
19
|
Args:
|
|
244
|
-
|
|
20
|
+
graph: Graph containing classes definitions
|
|
21
|
+
language: Language to use for parsing, by default "en"
|
|
245
22
|
|
|
246
23
|
Returns:
|
|
247
|
-
Dataframe containing
|
|
24
|
+
Dataframe containing owl classes
|
|
248
25
|
"""
|
|
249
|
-
# apply missing range
|
|
250
|
-
properties["Value Type"] = properties.apply(
|
|
251
|
-
lambda row: (
|
|
252
|
-
thing_class()["Class"]
|
|
253
|
-
if row._property_type == "ObjectProperty" and pd.isna(row["Value Type"])
|
|
254
|
-
else "string"
|
|
255
|
-
if pd.isna(row["Value Type"])
|
|
256
|
-
else row["Value Type"]
|
|
257
|
-
),
|
|
258
|
-
axis=1,
|
|
259
|
-
)
|
|
260
26
|
|
|
261
|
-
|
|
27
|
+
classes: dict[str, dict] = {}
|
|
262
28
|
|
|
29
|
+
query = prepareQuery(query.format(language=language), initNs={k: v for k, v in graph.namespaces()})
|
|
30
|
+
expected_keys = [str(v) for v in query.algebra._vars]
|
|
263
31
|
|
|
264
|
-
|
|
265
|
-
|
|
32
|
+
for raw in graph.query(query):
|
|
33
|
+
res: dict = convert_rdflib_content(cast(ResultRow, raw).asdict(), True)
|
|
34
|
+
res = {key: res.get(key, None) for key in expected_keys}
|
|
266
35
|
|
|
267
|
-
|
|
268
|
-
metadata: Dictionary containing metadata
|
|
36
|
+
class_id = res["class_"]
|
|
269
37
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
38
|
+
# Safeguarding against incomplete semantic definitions
|
|
39
|
+
if res["implements"] and isinstance(res["implements"], BNode):
|
|
40
|
+
issue_list.append(
|
|
41
|
+
ResourceRetrievalWarning(
|
|
42
|
+
class_id,
|
|
43
|
+
"implements",
|
|
44
|
+
error=("Unable to determine class that is being implemented"),
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
continue
|
|
273
48
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
metadata = fix_date(
|
|
277
|
-
metadata,
|
|
278
|
-
date_type="created",
|
|
279
|
-
default=datetime.datetime.now().replace(microsecond=0),
|
|
280
|
-
)
|
|
281
|
-
metadata = fix_date(
|
|
282
|
-
metadata,
|
|
283
|
-
date_type="updated",
|
|
284
|
-
default=datetime.datetime.now().replace(microsecond=0),
|
|
285
|
-
)
|
|
286
|
-
metadata = fix_name(metadata)
|
|
287
|
-
metadata = fix_description(metadata)
|
|
288
|
-
metadata = fix_author(metadata, "creator")
|
|
289
|
-
|
|
290
|
-
return metadata
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def fix_author(metadata: dict, author_type: str = "creator", default: str = "NEAT") -> dict:
|
|
294
|
-
if author := metadata.get(author_type, None):
|
|
295
|
-
if not isinstance(author, str) or isinstance(author, list):
|
|
296
|
-
metadata[author_type] = default
|
|
297
|
-
elif isinstance(author, str) and len(author) == 0:
|
|
298
|
-
metadata[author_type] = default
|
|
299
|
-
else:
|
|
300
|
-
metadata[author_type] = default
|
|
301
|
-
return metadata
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
def fix_description(metadata: dict, default: str = "This model has been inferred from OWL ontology") -> dict:
|
|
305
|
-
if description := metadata.get("description", None):
|
|
306
|
-
if not isinstance(description, str) or len(description) == 0:
|
|
307
|
-
metadata["description"] = default
|
|
308
|
-
elif isinstance(description, str) and len(description) > 1024:
|
|
309
|
-
metadata["description"] = metadata["description"][:1024]
|
|
310
|
-
else:
|
|
311
|
-
metadata["description"] = default
|
|
312
|
-
return metadata
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def fix_space(metadata: dict, default: str = "neat") -> dict:
|
|
316
|
-
if space := metadata.get("space", None):
|
|
317
|
-
if not isinstance(space, str) or not PATTERNS.space_compliance.match(space):
|
|
318
|
-
metadata["space"] = default
|
|
319
|
-
else:
|
|
320
|
-
metadata["space"] = default
|
|
321
|
-
return metadata
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def fix_date(
|
|
325
|
-
metadata: dict,
|
|
326
|
-
date_type: str,
|
|
327
|
-
default: datetime.datetime,
|
|
328
|
-
) -> dict:
|
|
329
|
-
if date := metadata.get(date_type, None):
|
|
330
|
-
try:
|
|
331
|
-
if isinstance(date, datetime.datetime):
|
|
332
|
-
return metadata
|
|
333
|
-
elif isinstance(date, datetime.date):
|
|
334
|
-
metadata[date_type] = datetime.datetime.combine(metadata[date_type], datetime.datetime.min.time())
|
|
335
|
-
elif isinstance(date, str):
|
|
336
|
-
metadata[date_type] = datetime.datetime.strptime(metadata[date_type], "%Y-%m-%dT%H:%M:%SZ")
|
|
337
|
-
else:
|
|
338
|
-
metadata[date_type] = default
|
|
339
|
-
except Exception:
|
|
340
|
-
metadata[date_type] = default
|
|
341
|
-
else:
|
|
342
|
-
metadata[date_type] = default
|
|
343
|
-
|
|
344
|
-
return metadata
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
def fix_version(metadata: dict, default: str = "1.0.0") -> dict:
|
|
348
|
-
if version := metadata.get("version", None):
|
|
349
|
-
if not PATTERNS.version_compliance.match(version):
|
|
350
|
-
metadata["version"] = default
|
|
351
|
-
else:
|
|
352
|
-
metadata["version"] = default
|
|
353
|
-
|
|
354
|
-
return metadata
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
def fix_name(metadata: dict, default: str = "OWL Inferred Data Model") -> dict:
|
|
358
|
-
if name := metadata.get("name", None):
|
|
359
|
-
if not isinstance(name, str):
|
|
360
|
-
metadata["title"] = default
|
|
361
|
-
elif isinstance(name, str) and len(name) == 0:
|
|
362
|
-
metadata["title"] = default
|
|
363
|
-
elif isinstance(name, str) and len(name) > 255:
|
|
364
|
-
metadata["title"] = metadata["title"][:255]
|
|
49
|
+
if class_id not in classes:
|
|
50
|
+
classes[class_id] = res
|
|
365
51
|
else:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
52
|
+
# Handling implements
|
|
53
|
+
if classes[class_id]["implements"] and isinstance(classes[class_id]["implements"], list):
|
|
54
|
+
if res["implements"] not in classes[class_id]["implements"]:
|
|
55
|
+
classes[class_id]["implements"].append(res["implements"])
|
|
369
56
|
|
|
370
|
-
|
|
57
|
+
elif classes[class_id]["implements"] and isinstance(classes[class_id]["implements"], str):
|
|
58
|
+
classes[class_id]["implements"] = [classes[class_id]["implements"]]
|
|
371
59
|
|
|
60
|
+
if res["implements"] not in classes[class_id]["implements"]:
|
|
61
|
+
classes[class_id]["implements"].append(res["implements"])
|
|
62
|
+
elif res["implements"]:
|
|
63
|
+
classes[class_id]["implements"] = [res["implements"]]
|
|
372
64
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
components = add_missing_value_types(components)
|
|
376
|
-
components = add_default_property_to_dangling_classes(components)
|
|
65
|
+
handle_meta("class_", classes, class_id, res, "name", issue_list)
|
|
66
|
+
handle_meta("class_", classes, class_id, res, "description", issue_list)
|
|
377
67
|
|
|
378
|
-
|
|
68
|
+
if not classes:
|
|
69
|
+
issue_list.append(NeatValueError("Unable to parse classes"))
|
|
379
70
|
|
|
71
|
+
return classes, issue_list
|
|
380
72
|
|
|
381
|
-
def add_missing_classes(components: dict[str, list[dict]]) -> dict:
|
|
382
|
-
"""Add missing classes to Classes.
|
|
383
73
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
Returns:
|
|
388
|
-
Updated tables with missing classes added to containers
|
|
389
|
-
"""
|
|
390
|
-
|
|
391
|
-
missing_classes = {definition["Class"] for definition in components["Properties"]} - {
|
|
392
|
-
definition["Class"] for definition in components["Classes"]
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
comment = (
|
|
396
|
-
"Added by NEAT. "
|
|
397
|
-
"This is a class that a domain of a property but was not defined in the ontology. "
|
|
398
|
-
"It is added by NEAT to make the ontology compliant with CDF."
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
for class_ in missing_classes:
|
|
402
|
-
components["Classes"].append({"Class": class_, "Comment": comment})
|
|
403
|
-
|
|
404
|
-
return components
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def add_missing_value_types(components: dict) -> dict:
|
|
408
|
-
"""Add properties to classes that do not have any properties defined to them
|
|
74
|
+
def parse_properties(graph: Graph, query: str, language: str, issue_list: IssueList) -> tuple[dict, IssueList]:
|
|
75
|
+
"""Parse properties from graph
|
|
409
76
|
|
|
410
77
|
Args:
|
|
411
|
-
|
|
78
|
+
graph: Graph containing owl classes
|
|
79
|
+
language: Language to use for parsing, by default "en"
|
|
412
80
|
|
|
413
81
|
Returns:
|
|
414
|
-
|
|
82
|
+
Dataframe containing owl classes
|
|
415
83
|
"""
|
|
416
84
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
85
|
+
properties: dict[str, dict] = {}
|
|
86
|
+
|
|
87
|
+
query = prepareQuery(query.format(language=language), initNs={k: v for k, v in graph.namespaces()})
|
|
88
|
+
expected_keys = [str(v) for v in query.algebra._vars]
|
|
89
|
+
|
|
90
|
+
for raw in graph.query(query):
|
|
91
|
+
res: dict = convert_rdflib_content(cast(ResultRow, raw).asdict(), True)
|
|
92
|
+
res = {key: res.get(key, None) for key in expected_keys}
|
|
93
|
+
|
|
94
|
+
property_id = res["property_"]
|
|
95
|
+
|
|
96
|
+
# Safeguarding against incomplete semantic definitions
|
|
97
|
+
if not res["class_"] or isinstance(res["class_"], BNode):
|
|
98
|
+
issue_list.append(
|
|
99
|
+
ResourceRetrievalWarning(
|
|
100
|
+
property_id,
|
|
101
|
+
"property",
|
|
102
|
+
error=("Unable to determine to what class property is being defined"),
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
# Safeguarding against incomplete semantic definitions
|
|
108
|
+
if not res["value_type"] or isinstance(res["value_type"], BNode):
|
|
109
|
+
issue_list.append(
|
|
110
|
+
ResourceRetrievalWarning(
|
|
111
|
+
property_id,
|
|
112
|
+
"property",
|
|
113
|
+
error=("Unable to determine value type of property"),
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
id_ = f"{res['class_']}.{res['property_']}"
|
|
119
|
+
|
|
120
|
+
if id_ not in properties:
|
|
121
|
+
properties[id_] = res
|
|
122
|
+
properties[id_]["value_type"] = [properties[id_]["value_type"]]
|
|
123
|
+
else:
|
|
124
|
+
handle_meta("property", properties, id_, res, "name", issue_list)
|
|
125
|
+
handle_meta(
|
|
126
|
+
"property",
|
|
127
|
+
properties,
|
|
128
|
+
id_,
|
|
129
|
+
res,
|
|
130
|
+
"description",
|
|
131
|
+
issue_list,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Handling multi-value types
|
|
135
|
+
if res["value_type"] not in properties[id_]["value_type"]:
|
|
136
|
+
properties[id_]["value_type"].append(res["value_type"])
|
|
137
|
+
|
|
138
|
+
for prop in properties.values():
|
|
139
|
+
prop["value_type"] = "|".join(prop["value_type"])
|
|
140
|
+
|
|
141
|
+
if not properties:
|
|
142
|
+
issue_list.append(NeatValueError("Unable to parse properties"))
|
|
143
|
+
|
|
144
|
+
return properties, issue_list
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def handle_meta(
|
|
148
|
+
resource_type: str,
|
|
149
|
+
resources: dict[str, dict],
|
|
150
|
+
resource_id: str,
|
|
151
|
+
res: dict,
|
|
152
|
+
feature: str,
|
|
153
|
+
issue_list: IssueList,
|
|
154
|
+
):
|
|
155
|
+
if not resources[resource_id][feature] and res[feature]:
|
|
156
|
+
resources[resource_id][feature] = res[feature]
|
|
157
|
+
|
|
158
|
+
# RAISE warning only if the feature is being redefined
|
|
159
|
+
elif resources[resource_id][feature] and res[feature]:
|
|
160
|
+
issue_list.append(
|
|
161
|
+
ResourceRedefinedWarning(
|
|
162
|
+
identifier=resource_id,
|
|
163
|
+
resource_type=resource_type,
|
|
164
|
+
feature=feature,
|
|
165
|
+
current_value=resources[resource_id][feature],
|
|
166
|
+
new_value=res[feature],
|
|
167
|
+
)
|
|
469
168
|
)
|
|
470
|
-
|
|
471
|
-
return components
|
|
@@ -10,6 +10,7 @@ from ._rules_input import (
|
|
|
10
10
|
DMSInputRules,
|
|
11
11
|
DMSInputView,
|
|
12
12
|
)
|
|
13
|
+
from ._validation import DMSValidation
|
|
13
14
|
|
|
14
15
|
__all__ = [
|
|
15
16
|
"DMSRules",
|
|
@@ -27,4 +28,5 @@ __all__ = [
|
|
|
27
28
|
"DMSInputContainer",
|
|
28
29
|
"DMSInputNode",
|
|
29
30
|
"DMSInputEnum",
|
|
31
|
+
"DMSValidation",
|
|
30
32
|
]
|