cognite-neat 0.88.3__py3-none-any.whl → 0.90.0__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/constants.py +6 -3
- cognite/neat/graph/extractors/__init__.py +2 -0
- cognite/neat/graph/extractors/_classic_cdf/_base.py +2 -2
- cognite/neat/graph/extractors/_dms.py +158 -0
- cognite/neat/graph/extractors/_mock_graph_generator.py +50 -9
- cognite/neat/graph/loaders/_rdf2dms.py +16 -13
- cognite/neat/graph/models.py +1 -0
- cognite/neat/graph/queries/_base.py +4 -2
- cognite/neat/issues/_base.py +3 -1
- cognite/neat/issues/errors/__init__.py +2 -1
- cognite/neat/issues/errors/_general.py +7 -0
- cognite/neat/issues/warnings/__init__.py +2 -0
- cognite/neat/issues/warnings/_models.py +1 -1
- cognite/neat/issues/warnings/_properties.py +12 -0
- cognite/neat/issues/warnings/user_modeling.py +1 -1
- cognite/neat/rules/_shared.py +49 -6
- cognite/neat/rules/analysis/_base.py +1 -1
- cognite/neat/rules/exporters/_base.py +7 -18
- cognite/neat/rules/exporters/_rules2dms.py +8 -18
- cognite/neat/rules/exporters/_rules2excel.py +5 -12
- cognite/neat/rules/exporters/_rules2ontology.py +9 -19
- cognite/neat/rules/exporters/_rules2yaml.py +3 -6
- cognite/neat/rules/importers/_base.py +7 -52
- cognite/neat/rules/importers/_dms2rules.py +172 -116
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +9 -18
- cognite/neat/rules/importers/_rdf/_inference2rules.py +37 -65
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
- cognite/neat/rules/importers/_rdf/_shared.py +1 -1
- cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
- cognite/neat/rules/importers/_yaml2rules.py +14 -41
- cognite/neat/rules/models/__init__.py +21 -5
- cognite/neat/rules/models/_base_input.py +162 -0
- cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
- cognite/neat/rules/models/asset/__init__.py +5 -2
- cognite/neat/rules/models/asset/_rules.py +2 -20
- cognite/neat/rules/models/asset/_rules_input.py +40 -115
- cognite/neat/rules/models/asset/_validation.py +1 -1
- cognite/neat/rules/models/data_types.py +150 -44
- cognite/neat/rules/models/dms/__init__.py +19 -7
- cognite/neat/rules/models/dms/_exporter.py +82 -39
- cognite/neat/rules/models/dms/_rules.py +42 -155
- cognite/neat/rules/models/dms/_rules_input.py +186 -254
- cognite/neat/rules/models/dms/_serializer.py +44 -3
- cognite/neat/rules/models/dms/_validation.py +3 -4
- cognite/neat/rules/models/domain.py +52 -1
- cognite/neat/rules/models/entities/__init__.py +63 -0
- cognite/neat/rules/models/entities/_constants.py +73 -0
- cognite/neat/rules/models/entities/_loaders.py +76 -0
- cognite/neat/rules/models/entities/_multi_value.py +67 -0
- cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
- cognite/neat/rules/models/entities/_types.py +86 -0
- cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
- cognite/neat/rules/models/information/__init__.py +10 -2
- cognite/neat/rules/models/information/_rules.py +3 -14
- cognite/neat/rules/models/information/_rules_input.py +57 -204
- cognite/neat/rules/models/information/_validation.py +1 -1
- cognite/neat/rules/transformers/__init__.py +21 -0
- cognite/neat/rules/transformers/_base.py +69 -3
- cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +226 -21
- cognite/neat/rules/transformers/_map_onto.py +97 -0
- cognite/neat/rules/transformers/_pipelines.py +61 -0
- cognite/neat/rules/transformers/_verification.py +136 -0
- cognite/neat/store/_base.py +2 -2
- cognite/neat/store/_provenance.py +10 -1
- cognite/neat/utils/cdf/data_classes.py +20 -0
- cognite/neat/utils/regex_patterns.py +6 -0
- cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
- cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/RECORD +80 -74
- cognite/neat/rules/models/_constants.py +0 -2
- cognite/neat/rules/models/_types/__init__.py +0 -19
- cognite/neat/rules/models/asset/_converter.py +0 -4
- cognite/neat/rules/models/dms/_converter.py +0 -143
- /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/entry_points.txt +0 -0
|
@@ -6,35 +6,30 @@ generating a list of rules based on which nodes that form the graph are made.
|
|
|
6
6
|
from collections import UserDict, defaultdict
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Literal, cast
|
|
9
|
+
from typing import Literal, cast
|
|
10
10
|
|
|
11
11
|
import pandas as pd
|
|
12
|
+
from cognite.client.utils._importing import local_import
|
|
12
13
|
from pandas import ExcelFile
|
|
13
14
|
|
|
14
|
-
from cognite.neat.issues import IssueList
|
|
15
|
+
from cognite.neat.issues import IssueList
|
|
15
16
|
from cognite.neat.issues.errors import (
|
|
16
17
|
FileMissingRequiredFieldError,
|
|
17
18
|
FileNotFoundNeatError,
|
|
18
19
|
FileReadError,
|
|
19
20
|
PropertyDefinitionDuplicatedError,
|
|
20
21
|
)
|
|
22
|
+
from cognite.neat.rules._shared import ReadRules, T_InputRules
|
|
21
23
|
from cognite.neat.rules.models import (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
DMSRules,
|
|
25
|
-
DomainRules,
|
|
26
|
-
InformationRules,
|
|
24
|
+
INPUT_RULES_BY_ROLE,
|
|
25
|
+
VERIFIED_RULES_BY_ROLE,
|
|
27
26
|
RoleTypes,
|
|
28
27
|
SchemaCompleteness,
|
|
29
28
|
)
|
|
30
|
-
from cognite.neat.rules.models.asset import AssetRulesInput
|
|
31
|
-
from cognite.neat.rules.models.dms import DMSRulesInput
|
|
32
|
-
from cognite.neat.rules.models.information import InformationRulesInput
|
|
33
|
-
from cognite.neat.utils.auxiliary import local_import
|
|
34
29
|
from cognite.neat.utils.spreadsheet import SpreadsheetRead, read_individual_sheet
|
|
35
30
|
from cognite.neat.utils.text import humanize_collection
|
|
36
31
|
|
|
37
|
-
from ._base import BaseImporter
|
|
32
|
+
from ._base import BaseImporter
|
|
38
33
|
|
|
39
34
|
SOURCE_SHEET__TARGET_FIELD__HEADERS = [
|
|
40
35
|
(
|
|
@@ -53,7 +48,7 @@ SOURCE_SHEET__TARGET_FIELD__HEADERS = [
|
|
|
53
48
|
]
|
|
54
49
|
|
|
55
50
|
MANDATORY_SHEETS_BY_ROLE: dict[RoleTypes, set[str]] = {
|
|
56
|
-
role_type: {str(sheet_name) for sheet_name in
|
|
51
|
+
role_type: {str(sheet_name) for sheet_name in VERIFIED_RULES_BY_ROLE[role_type].mandatory_fields(use_alias=True)}
|
|
57
52
|
for role_type in RoleTypes.__members__.values()
|
|
58
53
|
}
|
|
59
54
|
|
|
@@ -207,7 +202,7 @@ class SpreadsheetReader:
|
|
|
207
202
|
return sheets, read_info_by_sheet
|
|
208
203
|
|
|
209
204
|
|
|
210
|
-
class ExcelImporter(BaseImporter):
|
|
205
|
+
class ExcelImporter(BaseImporter[T_InputRules]):
|
|
211
206
|
"""Import rules from an Excel file.
|
|
212
207
|
|
|
213
208
|
Args:
|
|
@@ -217,30 +212,18 @@ class ExcelImporter(BaseImporter):
|
|
|
217
212
|
def __init__(self, filepath: Path):
|
|
218
213
|
self.filepath = filepath
|
|
219
214
|
|
|
220
|
-
|
|
221
|
-
def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
|
|
222
|
-
|
|
223
|
-
@overload
|
|
224
|
-
def to_rules(
|
|
225
|
-
self,
|
|
226
|
-
errors: Literal["continue"] = "continue",
|
|
227
|
-
role: RoleTypes | None = None,
|
|
228
|
-
) -> tuple[VerifiedRules | None, IssueList]: ...
|
|
229
|
-
|
|
230
|
-
def to_rules(
|
|
231
|
-
self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
|
|
232
|
-
) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
|
|
215
|
+
def to_rules(self) -> ReadRules[T_InputRules]:
|
|
233
216
|
issue_list = IssueList(title=f"'{self.filepath.name}'")
|
|
234
217
|
if not self.filepath.exists():
|
|
235
218
|
issue_list.append(FileNotFoundNeatError(self.filepath))
|
|
236
|
-
return
|
|
219
|
+
return ReadRules(None, issue_list, {})
|
|
237
220
|
|
|
238
221
|
with pd.ExcelFile(self.filepath) as excel_file:
|
|
239
222
|
user_reader = SpreadsheetReader(issue_list)
|
|
240
223
|
|
|
241
224
|
user_read = user_reader.read(excel_file, self.filepath)
|
|
242
225
|
if user_read is None or issue_list.has_errors:
|
|
243
|
-
return
|
|
226
|
+
return ReadRules(None, issue_list, {})
|
|
244
227
|
|
|
245
228
|
last_read: ReadResult | None = None
|
|
246
229
|
if any(sheet_name.startswith("Last") for sheet_name in user_reader.seen_sheets):
|
|
@@ -252,7 +235,7 @@ class ExcelImporter(BaseImporter):
|
|
|
252
235
|
reference_read = SpreadsheetReader(issue_list, sheet_prefix="Ref").read(excel_file, self.filepath)
|
|
253
236
|
|
|
254
237
|
if issue_list.has_errors:
|
|
255
|
-
return
|
|
238
|
+
return ReadRules(None, issue_list, {})
|
|
256
239
|
|
|
257
240
|
if reference_read and user_read.role != reference_read.role:
|
|
258
241
|
issue_list.append(
|
|
@@ -265,7 +248,7 @@ class ExcelImporter(BaseImporter):
|
|
|
265
248
|
"sheet",
|
|
266
249
|
)
|
|
267
250
|
)
|
|
268
|
-
return
|
|
251
|
+
return ReadRules(None, issue_list, {})
|
|
269
252
|
|
|
270
253
|
sheets = user_read.sheets
|
|
271
254
|
original_role = user_read.role
|
|
@@ -280,34 +263,12 @@ class ExcelImporter(BaseImporter):
|
|
|
280
263
|
sheets["reference"] = reference_read.sheets
|
|
281
264
|
read_info_by_sheet.update(reference_read.read_info_by_sheet)
|
|
282
265
|
|
|
283
|
-
rules_cls =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
error_cls=NeatError,
|
|
287
|
-
error_args={"read_info_by_sheet": read_info_by_sheet},
|
|
288
|
-
) as future:
|
|
289
|
-
rules: VerifiedRules
|
|
290
|
-
if rules_cls is DMSRules:
|
|
291
|
-
rules = DMSRulesInput.load(sheets).as_rules()
|
|
292
|
-
elif rules_cls is InformationRules:
|
|
293
|
-
rules = InformationRulesInput.load(sheets).as_rules()
|
|
294
|
-
elif rules_cls is AssetRules:
|
|
295
|
-
rules = AssetRulesInput.load(sheets).as_rules()
|
|
296
|
-
else:
|
|
297
|
-
rules = rules_cls.model_validate(sheets) # type: ignore[attr-defined]
|
|
298
|
-
|
|
299
|
-
if future.result == "failure" or issue_list.has_errors:
|
|
300
|
-
return self._return_or_raise(issue_list, errors)
|
|
266
|
+
rules_cls = INPUT_RULES_BY_ROLE[original_role]
|
|
267
|
+
rules = cast(T_InputRules, rules_cls.load(sheets))
|
|
268
|
+
return ReadRules(rules, issue_list, {"read_info_by_sheet": read_info_by_sheet})
|
|
301
269
|
|
|
302
|
-
return self._to_output(
|
|
303
|
-
rules,
|
|
304
|
-
issue_list,
|
|
305
|
-
errors=errors,
|
|
306
|
-
role=role,
|
|
307
|
-
)
|
|
308
270
|
|
|
309
|
-
|
|
310
|
-
class GoogleSheetImporter(BaseImporter):
|
|
271
|
+
class GoogleSheetImporter(BaseImporter[T_InputRules]):
|
|
311
272
|
"""Import rules from a Google Sheet.
|
|
312
273
|
|
|
313
274
|
.. warning::
|
|
@@ -323,38 +284,13 @@ class GoogleSheetImporter(BaseImporter):
|
|
|
323
284
|
self.sheet_id = sheet_id
|
|
324
285
|
self.skiprows = skiprows
|
|
325
286
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
@overload
|
|
330
|
-
def to_rules(
|
|
331
|
-
self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
|
|
332
|
-
) -> tuple[VerifiedRules | None, IssueList]: ...
|
|
287
|
+
def to_rules(self) -> ReadRules[T_InputRules]:
|
|
288
|
+
raise NotImplementedError("Google Sheet Importer is not yet implemented.")
|
|
333
289
|
|
|
334
|
-
def
|
|
335
|
-
self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
|
|
336
|
-
) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
|
|
290
|
+
def _get_sheets(self) -> dict[str, pd.DataFrame]:
|
|
337
291
|
local_import("gspread", "google")
|
|
338
292
|
import gspread # type: ignore[import]
|
|
339
293
|
|
|
340
|
-
role = role or RoleTypes.domain_expert
|
|
341
|
-
rules_model = cast(DomainRules | InformationRules | AssetRules | DMSRules, RULES_PER_ROLE[role])
|
|
342
|
-
|
|
343
294
|
client_google = gspread.service_account()
|
|
344
295
|
google_sheet = client_google.open_by_key(self.sheet_id)
|
|
345
|
-
|
|
346
|
-
sheet_names = {str(name).lower() for name in sheets.keys()}
|
|
347
|
-
|
|
348
|
-
if missing_sheets := rules_model.mandatory_fields().difference(sheet_names):
|
|
349
|
-
raise ValueError(f"Missing mandatory sheets: {missing_sheets}")
|
|
350
|
-
|
|
351
|
-
if role == RoleTypes.domain_expert:
|
|
352
|
-
output = rules_model.model_validate(sheets)
|
|
353
|
-
elif role == RoleTypes.information:
|
|
354
|
-
output = rules_model.model_validate(sheets)
|
|
355
|
-
elif role == RoleTypes.dms:
|
|
356
|
-
output = rules_model.model_validate(sheets)
|
|
357
|
-
else:
|
|
358
|
-
raise ValueError(f"Role {role} is not valid.")
|
|
359
|
-
|
|
360
|
-
return self._to_output(output, IssueList(), errors=errors, role=role)
|
|
296
|
+
return {worksheet.title: pd.DataFrame(worksheet.get_all_records()) for worksheet in google_sheet.worksheets()}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Any,
|
|
2
|
+
from typing import Any, cast
|
|
3
3
|
|
|
4
4
|
import yaml
|
|
5
5
|
|
|
@@ -11,13 +11,13 @@ from cognite.neat.issues.errors import (
|
|
|
11
11
|
FileTypeUnexpectedError,
|
|
12
12
|
)
|
|
13
13
|
from cognite.neat.issues.warnings import NeatValueWarning
|
|
14
|
-
from cognite.neat.rules.
|
|
15
|
-
from cognite.neat.rules.models
|
|
14
|
+
from cognite.neat.rules._shared import ReadRules, T_InputRules
|
|
15
|
+
from cognite.neat.rules.models import INPUT_RULES_BY_ROLE, RoleTypes
|
|
16
16
|
|
|
17
|
-
from ._base import BaseImporter
|
|
17
|
+
from ._base import BaseImporter
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class YAMLImporter(BaseImporter):
|
|
20
|
+
class YAMLImporter(BaseImporter[T_InputRules]):
|
|
21
21
|
"""Imports the rules from a YAML file.
|
|
22
22
|
|
|
23
23
|
Args:
|
|
@@ -51,21 +51,9 @@ class YAMLImporter(BaseImporter):
|
|
|
51
51
|
return cls({}, [FileTypeUnexpectedError(filepath, frozenset([".yaml", ".yml"]))])
|
|
52
52
|
return cls(yaml.safe_load(filepath.read_text()), filepaths=[filepath])
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
|
|
56
|
-
|
|
57
|
-
@overload
|
|
58
|
-
def to_rules(
|
|
59
|
-
self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
|
|
60
|
-
) -> tuple[VerifiedRules | None, IssueList]: ...
|
|
61
|
-
|
|
62
|
-
def to_rules(
|
|
63
|
-
self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
|
|
64
|
-
) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
|
|
54
|
+
def to_rules(self) -> ReadRules[T_InputRules]:
|
|
65
55
|
if self._read_issues.has_errors or not self.raw_data:
|
|
66
|
-
|
|
67
|
-
raise self._read_issues.as_errors()
|
|
68
|
-
return None, self._read_issues
|
|
56
|
+
return ReadRules(None, self._read_issues, {})
|
|
69
57
|
issue_list = IssueList(title="YAML Importer", issues=self._read_issues)
|
|
70
58
|
|
|
71
59
|
if not self._filepaths:
|
|
@@ -81,33 +69,18 @@ class YAMLImporter(BaseImporter):
|
|
|
81
69
|
|
|
82
70
|
if "metadata" not in self.raw_data:
|
|
83
71
|
self._read_issues.append(FileMissingRequiredFieldError(metadata_file, "section", "metadata"))
|
|
84
|
-
|
|
85
|
-
raise self._read_issues.as_errors()
|
|
86
|
-
return None, self._read_issues
|
|
72
|
+
return ReadRules(None, self._read_issues, {})
|
|
87
73
|
|
|
88
74
|
metadata = self.raw_data["metadata"]
|
|
89
75
|
|
|
90
76
|
if "role" not in metadata:
|
|
91
77
|
self._read_issues.append(FileMissingRequiredFieldError(metadata, "metadata", "role"))
|
|
92
|
-
|
|
93
|
-
raise self._read_issues.as_errors()
|
|
94
|
-
return None, self._read_issues
|
|
78
|
+
return ReadRules(None, self._read_issues, {})
|
|
95
79
|
|
|
96
80
|
role_input = RoleTypes(metadata["role"])
|
|
97
81
|
role_enum = RoleTypes(role_input)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
rules = DMSRulesInput.load(self.raw_data).as_rules()
|
|
104
|
-
else:
|
|
105
|
-
rules = rules_model.model_validate(self.raw_data)
|
|
106
|
-
|
|
107
|
-
if future.result == "failure":
|
|
108
|
-
if errors == "continue":
|
|
109
|
-
return None, issue_list
|
|
110
|
-
else:
|
|
111
|
-
raise issue_list.as_errors()
|
|
112
|
-
|
|
113
|
-
return self._to_output(rules, issue_list, errors, role)
|
|
82
|
+
rules_cls = INPUT_RULES_BY_ROLE[role_enum]
|
|
83
|
+
|
|
84
|
+
rules = cast(T_InputRules, rules_cls.load(self.raw_data))
|
|
85
|
+
|
|
86
|
+
return ReadRules(rules, issue_list, {})
|
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
from cognite.neat.rules.models.asset import AssetRules
|
|
2
|
-
from cognite.neat.rules.models.
|
|
1
|
+
from cognite.neat.rules.models.asset._rules import AssetRules
|
|
2
|
+
from cognite.neat.rules.models.asset._rules_input import AssetInputRules
|
|
3
|
+
from cognite.neat.rules.models.domain import DomainInputRules, DomainRules
|
|
3
4
|
from cognite.neat.rules.models.information._rules import InformationRules
|
|
5
|
+
from cognite.neat.rules.models.information._rules_input import InformationInputRules
|
|
4
6
|
|
|
5
|
-
from .
|
|
7
|
+
from ._base_rules import DataModelType, ExtensionCategory, RoleTypes, SchemaCompleteness, SheetEntity, SheetList
|
|
6
8
|
from .dms._rules import DMSRules
|
|
9
|
+
from .dms._rules_input import DMSInputRules
|
|
7
10
|
from .dms._schema import DMSSchema
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
INPUT_RULES_BY_ROLE: dict[
|
|
13
|
+
RoleTypes, type[InformationInputRules] | type[AssetInputRules] | type[DMSInputRules] | type[DomainInputRules]
|
|
14
|
+
] = {
|
|
15
|
+
RoleTypes.domain_expert: DomainInputRules,
|
|
16
|
+
RoleTypes.information: InformationInputRules,
|
|
17
|
+
RoleTypes.asset: AssetInputRules,
|
|
18
|
+
RoleTypes.dms: DMSInputRules,
|
|
19
|
+
}
|
|
20
|
+
VERIFIED_RULES_BY_ROLE: dict[
|
|
21
|
+
RoleTypes, type[InformationRules] | type[AssetRules] | type[DMSRules] | type[DomainRules]
|
|
22
|
+
] = {
|
|
10
23
|
RoleTypes.domain_expert: DomainRules,
|
|
11
24
|
RoleTypes.information: InformationRules,
|
|
12
25
|
RoleTypes.asset: AssetRules,
|
|
@@ -16,10 +29,13 @@ RULES_PER_ROLE: dict[RoleTypes, type[DomainRules] | type[InformationRules] | typ
|
|
|
16
29
|
|
|
17
30
|
__all__ = [
|
|
18
31
|
"DomainRules",
|
|
32
|
+
"DMSInputRules",
|
|
33
|
+
"InformationInputRules",
|
|
34
|
+
"AssetInputRules",
|
|
19
35
|
"InformationRules",
|
|
20
36
|
"AssetRules",
|
|
21
37
|
"DMSRules",
|
|
22
|
-
"
|
|
38
|
+
"INPUT_RULES_BY_ROLE",
|
|
23
39
|
"DMSSchema",
|
|
24
40
|
"RoleTypes",
|
|
25
41
|
"SchemaCompleteness",
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Module for base classes for the input models.
|
|
2
|
+
|
|
3
|
+
The philosophy of the input models is:
|
|
4
|
+
|
|
5
|
+
* Provide an easy way to input rules. The type hints are made to be human-friendly, for example, Literal instead of
|
|
6
|
+
Enum.
|
|
7
|
+
* The .dump() method should fill out defaults and have shortcuts. For example, if the prefix is not provided for
|
|
8
|
+
a class, then the prefix from the metadata is used. For views, if the class is not provided, it is assumed to
|
|
9
|
+
be the same as the view.
|
|
10
|
+
|
|
11
|
+
The base classes are to make it easy to create the input models with default behavior. They are also used for
|
|
12
|
+
testing to ensure that input models correctly map to the verified rules models.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from dataclasses import Field, dataclass, fields, is_dataclass
|
|
18
|
+
from types import GenericAlias, UnionType
|
|
19
|
+
from typing import Any, Generic, TypeVar, Union, cast, get_args, get_origin, overload
|
|
20
|
+
|
|
21
|
+
from ._base_rules import BaseRules, RuleModel
|
|
22
|
+
|
|
23
|
+
if sys.version_info >= (3, 11):
|
|
24
|
+
from typing import Self
|
|
25
|
+
else:
|
|
26
|
+
from typing_extensions import Self
|
|
27
|
+
|
|
28
|
+
T_BaseRules = TypeVar("T_BaseRules", bound=BaseRules)
|
|
29
|
+
T_RuleModel = TypeVar("T_RuleModel", bound=RuleModel)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class InputRules(Generic[T_BaseRules], ABC):
|
|
34
|
+
"""Input rules are raw data that is not yet validated."""
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def _get_verified_cls(cls) -> type[T_BaseRules]:
|
|
39
|
+
raise NotImplementedError("This method should be implemented in the subclass.")
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
@overload
|
|
43
|
+
def load(cls, data: dict[str, Any]) -> Self: ...
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
@overload
|
|
47
|
+
def load(cls, data: None) -> None: ...
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def load(cls, data: dict | None) -> Self | None:
|
|
51
|
+
if data is None:
|
|
52
|
+
return None
|
|
53
|
+
return cls._load(data)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def _type_by_field_name(cls) -> dict[str, type]:
|
|
57
|
+
output: dict[str, type] = {}
|
|
58
|
+
for field_ in fields(cls):
|
|
59
|
+
type_ = field_.type
|
|
60
|
+
if isinstance(type_, UnionType) or get_origin(type_) is Union:
|
|
61
|
+
type_ = get_args(type_)[0]
|
|
62
|
+
if isinstance(type_, str) and type_.startswith(cls.__name__):
|
|
63
|
+
type_ = cls
|
|
64
|
+
|
|
65
|
+
if is_dataclass(type_):
|
|
66
|
+
candidate = type_
|
|
67
|
+
elif isinstance(type_, GenericAlias) and type_.__origin__ is list and is_dataclass(type_.__args__[0]):
|
|
68
|
+
candidate = type_.__args__[0]
|
|
69
|
+
else:
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
if hasattr(candidate, "_load"):
|
|
73
|
+
output[field_.name] = candidate
|
|
74
|
+
return output
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def _load(cls, data: dict[str, Any]) -> Self:
|
|
78
|
+
args: dict[str, Any] = {}
|
|
79
|
+
field_type_by_name = cls._type_by_field_name()
|
|
80
|
+
for field_name, field_ in cls._get_verified_cls().model_fields.items():
|
|
81
|
+
field_type = field_type_by_name.get(field_name)
|
|
82
|
+
if field_type is None:
|
|
83
|
+
continue
|
|
84
|
+
if field_name in data:
|
|
85
|
+
value = data[field_name]
|
|
86
|
+
elif field_.alias in data:
|
|
87
|
+
value = data[field_.alias]
|
|
88
|
+
else:
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
if isinstance(value, dict):
|
|
92
|
+
args[field_name] = field_type._load(value) # type: ignore[attr-defined]
|
|
93
|
+
elif isinstance(value, list) and value and isinstance(value[0], dict):
|
|
94
|
+
args[field_name] = [field_type._load(item) for item in value] # type: ignore[attr-defined]
|
|
95
|
+
return cls(**args)
|
|
96
|
+
|
|
97
|
+
def _dataclass_fields(self) -> list[Field]:
|
|
98
|
+
return list(fields(self))
|
|
99
|
+
|
|
100
|
+
def as_rules(self) -> T_BaseRules:
|
|
101
|
+
cls_ = self._get_verified_cls()
|
|
102
|
+
return cls_.model_validate(self.dump())
|
|
103
|
+
|
|
104
|
+
def dump(self) -> dict[str, Any]:
|
|
105
|
+
output: dict[str, Any] = {}
|
|
106
|
+
for field_ in self._dataclass_fields():
|
|
107
|
+
value = getattr(self, field_.name)
|
|
108
|
+
if value is None:
|
|
109
|
+
continue
|
|
110
|
+
if hasattr(value, "dump"):
|
|
111
|
+
output[field_.name] = value.dump()
|
|
112
|
+
elif isinstance(value, list) and value and hasattr(value[0], "dump"):
|
|
113
|
+
output[field_.name] = [item.dump() for item in value]
|
|
114
|
+
return output
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class InputComponent(ABC, Generic[T_RuleModel]):
|
|
119
|
+
@classmethod
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def _get_verified_cls(cls) -> type[T_RuleModel]:
|
|
122
|
+
raise NotImplementedError("This method should be implemented in the subclass.")
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
@overload
|
|
126
|
+
def load(cls, data: None) -> None: ...
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
@overload
|
|
130
|
+
def load(cls, data: dict[str, Any]) -> Self: ...
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
@overload
|
|
134
|
+
def load(cls, data: list[dict[str, Any]]) -> list[Self]: ...
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def load(cls, data: dict[str, Any] | list[dict[str, Any]] | None) -> Self | list[Self] | None:
|
|
138
|
+
if data is None:
|
|
139
|
+
return None
|
|
140
|
+
if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
|
|
141
|
+
items = cast(list[dict[str, Any]], data.get("data") if isinstance(data, dict) else data)
|
|
142
|
+
return [loaded for item in items if (loaded := cls.load(item)) is not None]
|
|
143
|
+
return cls._load(data)
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def _load(cls, data: dict[str, Any]) -> Self:
|
|
147
|
+
args: dict[str, Any] = {}
|
|
148
|
+
for field_name, field_ in cls._get_verified_cls().model_fields.items(): # type: ignore[attr-defined]
|
|
149
|
+
if field_.exclude:
|
|
150
|
+
continue
|
|
151
|
+
if field_name in data:
|
|
152
|
+
args[field_name] = data[field_name]
|
|
153
|
+
elif field_.alias in data:
|
|
154
|
+
args[field_name] = data[field_.alias]
|
|
155
|
+
return cls(**args)
|
|
156
|
+
|
|
157
|
+
def dump(self, **kwargs) -> dict[str, Any]:
|
|
158
|
+
return {
|
|
159
|
+
field_.alias or name: getattr(self, name)
|
|
160
|
+
for name, field_ in self._get_verified_cls().model_fields.items()
|
|
161
|
+
if not field_.exclude
|
|
162
|
+
}
|
|
@@ -10,7 +10,7 @@ import types
|
|
|
10
10
|
from abc import ABC, abstractmethod
|
|
11
11
|
from collections.abc import Callable, Iterator
|
|
12
12
|
from functools import wraps
|
|
13
|
-
from typing import Annotated, Any, ClassVar, Generic, Literal,
|
|
13
|
+
from typing import Annotated, Any, ClassVar, Generic, Literal, TypeVar
|
|
14
14
|
|
|
15
15
|
import pandas as pd
|
|
16
16
|
from pydantic import (
|
|
@@ -19,7 +19,6 @@ from pydantic import (
|
|
|
19
19
|
ConfigDict,
|
|
20
20
|
Field,
|
|
21
21
|
PlainSerializer,
|
|
22
|
-
constr,
|
|
23
22
|
field_validator,
|
|
24
23
|
model_serializer,
|
|
25
24
|
model_validator,
|
|
@@ -36,12 +35,6 @@ else:
|
|
|
36
35
|
METADATA_VALUE_MAX_LENGTH = 5120
|
|
37
36
|
|
|
38
37
|
|
|
39
|
-
def _add_alias(data: dict[str, Any], base_model: type[BaseModel]) -> None:
|
|
40
|
-
for field_name, field_ in base_model.model_fields.items():
|
|
41
|
-
if field_name not in data and field_.alias in data:
|
|
42
|
-
data[field_name] = data[field_.alias]
|
|
43
|
-
|
|
44
|
-
|
|
45
38
|
def replace_nan_floats_with_default(values: dict, model_fields: dict[str, FieldInfo]) -> dict:
|
|
46
39
|
output = {}
|
|
47
40
|
for field_name, value in values.items():
|
|
@@ -126,10 +119,6 @@ def _get_required_fields(model: type[BaseModel], use_alias: bool = False) -> set
|
|
|
126
119
|
return required_fields
|
|
127
120
|
|
|
128
121
|
|
|
129
|
-
Space: TypeAlias = str
|
|
130
|
-
Description: TypeAlias = constr(min_length=1, max_length=1024) # type: ignore[valid-type]
|
|
131
|
-
|
|
132
|
-
|
|
133
122
|
class SchemaCompleteness(StrEnum):
|
|
134
123
|
complete = "complete"
|
|
135
124
|
partial = "partial"
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from ._rules import AssetClass, AssetMetadata, AssetProperty, AssetRules
|
|
2
|
-
from ._rules_input import
|
|
2
|
+
from ._rules_input import AssetInputClass, AssetInputMetadata, AssetInputProperty, AssetInputRules
|
|
3
3
|
|
|
4
4
|
__all__ = [
|
|
5
5
|
"AssetRules",
|
|
6
6
|
"AssetMetadata",
|
|
7
7
|
"AssetClass",
|
|
8
8
|
"AssetProperty",
|
|
9
|
-
"
|
|
9
|
+
"AssetInputRules",
|
|
10
|
+
"AssetInputMetadata",
|
|
11
|
+
"AssetInputClass",
|
|
12
|
+
"AssetInputProperty",
|
|
10
13
|
]
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import sys
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any, ClassVar, Literal, cast
|
|
3
3
|
|
|
4
4
|
from pydantic import Field, field_validator, model_validator
|
|
5
5
|
from pydantic.main import IncEx
|
|
6
6
|
from rdflib import Namespace
|
|
7
7
|
|
|
8
8
|
from cognite.neat.constants import get_default_prefixes
|
|
9
|
-
from cognite.neat.rules.models.
|
|
10
|
-
from cognite.neat.rules.models.domain import DomainRules
|
|
9
|
+
from cognite.neat.rules.models._base_rules import BaseRules, RoleTypes, SheetList
|
|
11
10
|
from cognite.neat.rules.models.entities import (
|
|
12
11
|
CdfResourceEntityList,
|
|
13
12
|
ClassEntity,
|
|
@@ -21,10 +20,6 @@ from cognite.neat.rules.models.information import (
|
|
|
21
20
|
InformationRules,
|
|
22
21
|
)
|
|
23
22
|
|
|
24
|
-
if TYPE_CHECKING:
|
|
25
|
-
from cognite.neat.rules.models.dms._rules import DMSRules
|
|
26
|
-
|
|
27
|
-
|
|
28
23
|
if sys.version_info >= (3, 11):
|
|
29
24
|
from typing import Self
|
|
30
25
|
else:
|
|
@@ -141,16 +136,3 @@ class AssetRules(BaseRules):
|
|
|
141
136
|
prefix = self.reference.metadata.prefix
|
|
142
137
|
cleaned[reference] = _AssetRulesSerializer(by_alias, prefix).clean(ref_dump, True)
|
|
143
138
|
return cleaned
|
|
144
|
-
|
|
145
|
-
def as_domain_rules(self) -> DomainRules:
|
|
146
|
-
from ._converter import _AssetRulesConverter
|
|
147
|
-
|
|
148
|
-
return _AssetRulesConverter(self.as_information_rules()).as_domain_rules()
|
|
149
|
-
|
|
150
|
-
def as_dms_rules(self) -> "DMSRules":
|
|
151
|
-
from ._converter import _AssetRulesConverter
|
|
152
|
-
|
|
153
|
-
return _AssetRulesConverter(self.as_information_rules()).as_dms_rules()
|
|
154
|
-
|
|
155
|
-
def as_information_rules(self) -> InformationRules:
|
|
156
|
-
return InformationRules.model_validate(self.model_dump())
|