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.

Files changed (84) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +6 -3
  3. cognite/neat/graph/extractors/__init__.py +2 -0
  4. cognite/neat/graph/extractors/_classic_cdf/_base.py +2 -2
  5. cognite/neat/graph/extractors/_dms.py +158 -0
  6. cognite/neat/graph/extractors/_mock_graph_generator.py +50 -9
  7. cognite/neat/graph/loaders/_rdf2dms.py +16 -13
  8. cognite/neat/graph/models.py +1 -0
  9. cognite/neat/graph/queries/_base.py +4 -2
  10. cognite/neat/issues/_base.py +3 -1
  11. cognite/neat/issues/errors/__init__.py +2 -1
  12. cognite/neat/issues/errors/_general.py +7 -0
  13. cognite/neat/issues/warnings/__init__.py +2 -0
  14. cognite/neat/issues/warnings/_models.py +1 -1
  15. cognite/neat/issues/warnings/_properties.py +12 -0
  16. cognite/neat/issues/warnings/user_modeling.py +1 -1
  17. cognite/neat/rules/_shared.py +49 -6
  18. cognite/neat/rules/analysis/_base.py +1 -1
  19. cognite/neat/rules/exporters/_base.py +7 -18
  20. cognite/neat/rules/exporters/_rules2dms.py +8 -18
  21. cognite/neat/rules/exporters/_rules2excel.py +5 -12
  22. cognite/neat/rules/exporters/_rules2ontology.py +9 -19
  23. cognite/neat/rules/exporters/_rules2yaml.py +3 -6
  24. cognite/neat/rules/importers/_base.py +7 -52
  25. cognite/neat/rules/importers/_dms2rules.py +172 -116
  26. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
  27. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
  28. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
  29. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  30. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  31. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +9 -18
  32. cognite/neat/rules/importers/_rdf/_inference2rules.py +37 -65
  33. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  34. cognite/neat/rules/importers/_rdf/_shared.py +1 -1
  35. cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
  36. cognite/neat/rules/importers/_yaml2rules.py +14 -41
  37. cognite/neat/rules/models/__init__.py +21 -5
  38. cognite/neat/rules/models/_base_input.py +162 -0
  39. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  40. cognite/neat/rules/models/asset/__init__.py +5 -2
  41. cognite/neat/rules/models/asset/_rules.py +2 -20
  42. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  43. cognite/neat/rules/models/asset/_validation.py +1 -1
  44. cognite/neat/rules/models/data_types.py +150 -44
  45. cognite/neat/rules/models/dms/__init__.py +19 -7
  46. cognite/neat/rules/models/dms/_exporter.py +82 -39
  47. cognite/neat/rules/models/dms/_rules.py +42 -155
  48. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  49. cognite/neat/rules/models/dms/_serializer.py +44 -3
  50. cognite/neat/rules/models/dms/_validation.py +3 -4
  51. cognite/neat/rules/models/domain.py +52 -1
  52. cognite/neat/rules/models/entities/__init__.py +63 -0
  53. cognite/neat/rules/models/entities/_constants.py +73 -0
  54. cognite/neat/rules/models/entities/_loaders.py +76 -0
  55. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  56. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  57. cognite/neat/rules/models/entities/_types.py +86 -0
  58. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  59. cognite/neat/rules/models/information/__init__.py +10 -2
  60. cognite/neat/rules/models/information/_rules.py +3 -14
  61. cognite/neat/rules/models/information/_rules_input.py +57 -204
  62. cognite/neat/rules/models/information/_validation.py +1 -1
  63. cognite/neat/rules/transformers/__init__.py +21 -0
  64. cognite/neat/rules/transformers/_base.py +69 -3
  65. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +226 -21
  66. cognite/neat/rules/transformers/_map_onto.py +97 -0
  67. cognite/neat/rules/transformers/_pipelines.py +61 -0
  68. cognite/neat/rules/transformers/_verification.py +136 -0
  69. cognite/neat/store/_base.py +2 -2
  70. cognite/neat/store/_provenance.py +10 -1
  71. cognite/neat/utils/cdf/data_classes.py +20 -0
  72. cognite/neat/utils/regex_patterns.py +6 -0
  73. cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
  74. cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
  75. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/METADATA +1 -1
  76. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/RECORD +80 -74
  77. cognite/neat/rules/models/_constants.py +0 -2
  78. cognite/neat/rules/models/_types/__init__.py +0 -19
  79. cognite/neat/rules/models/asset/_converter.py +0 -4
  80. cognite/neat/rules/models/dms/_converter.py +0 -143
  81. /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
  82. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/LICENSE +0 -0
  83. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/WHEEL +0 -0
  84. {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, overload
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, NeatError
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
- RULES_PER_ROLE,
23
- AssetRules,
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, VerifiedRules, _handle_issues
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 RULES_PER_ROLE[role_type].mandatory_fields(use_alias=True)}
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
- @overload
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 self._return_or_raise(issue_list, errors)
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 self._return_or_raise(issue_list, errors)
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 self._return_or_raise(issue_list, errors)
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 self._return_or_raise(issue_list, errors)
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 = RULES_PER_ROLE[original_role]
284
- with _handle_issues(
285
- issue_list,
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
- @overload
327
- def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
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 to_rules(
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
- sheets = {worksheet.title: pd.DataFrame(worksheet.get_all_records()) for worksheet in google_sheet.worksheets()}
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, Literal, overload
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.models import RULES_PER_ROLE, DMSRules, RoleTypes
15
- from cognite.neat.rules.models.dms import DMSRulesInput
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, VerifiedRules, _handle_issues
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
- @overload
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
- if errors == "raise":
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
- if errors == "raise":
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
- if errors == "raise":
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
- rules_model = RULES_PER_ROLE[role_enum]
99
-
100
- with _handle_issues(issue_list) as future:
101
- rules: VerifiedRules
102
- if rules_model is DMSRules:
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.domain import DomainRules
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 ._base import DataModelType, ExtensionCategory, RoleTypes, SchemaCompleteness, SheetEntity, SheetList
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
- RULES_PER_ROLE: dict[RoleTypes, type[DomainRules] | type[InformationRules] | type[AssetRules] | type[DMSRules]] = {
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
- "RULES_PER_ROLE",
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, TypeAlias, TypeVar
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 AssetRulesInput
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
- "AssetRulesInput",
9
+ "AssetInputRules",
10
+ "AssetInputMetadata",
11
+ "AssetInputClass",
12
+ "AssetInputProperty",
10
13
  ]
@@ -1,13 +1,12 @@
1
1
  import sys
2
- from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
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._base import BaseRules, RoleTypes, SheetList
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())