cognite-neat 0.88.2__py3-none-any.whl → 0.89.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 (129) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +3 -0
  3. cognite/neat/graph/__init__.py +0 -3
  4. cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
  5. cognite/neat/graph/loaders/_base.py +3 -3
  6. cognite/neat/graph/loaders/_rdf2asset.py +24 -25
  7. cognite/neat/graph/loaders/_rdf2dms.py +20 -15
  8. cognite/neat/issues/__init__.py +1 -3
  9. cognite/neat/issues/_base.py +261 -71
  10. cognite/neat/issues/errors/__init__.py +73 -0
  11. cognite/neat/issues/errors/_external.py +67 -0
  12. cognite/neat/issues/errors/_general.py +35 -0
  13. cognite/neat/issues/errors/_properties.py +62 -0
  14. cognite/neat/issues/errors/_resources.py +111 -0
  15. cognite/neat/issues/errors/_workflow.py +36 -0
  16. cognite/neat/issues/formatters.py +1 -1
  17. cognite/neat/issues/warnings/__init__.py +66 -0
  18. cognite/neat/issues/warnings/_external.py +40 -0
  19. cognite/neat/issues/warnings/_general.py +29 -0
  20. cognite/neat/issues/warnings/_models.py +92 -0
  21. cognite/neat/issues/warnings/_properties.py +44 -0
  22. cognite/neat/issues/warnings/_resources.py +55 -0
  23. cognite/neat/issues/warnings/user_modeling.py +113 -0
  24. cognite/neat/rules/_shared.py +53 -2
  25. cognite/neat/rules/analysis/_base.py +1 -1
  26. cognite/neat/rules/exporters/_base.py +7 -18
  27. cognite/neat/rules/exporters/_rules2dms.py +17 -20
  28. cognite/neat/rules/exporters/_rules2excel.py +9 -16
  29. cognite/neat/rules/exporters/_rules2ontology.py +77 -64
  30. cognite/neat/rules/exporters/_rules2yaml.py +6 -9
  31. cognite/neat/rules/exporters/_validation.py +11 -96
  32. cognite/neat/rules/importers/_base.py +9 -58
  33. cognite/neat/rules/importers/_dms2rules.py +188 -135
  34. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +48 -35
  35. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +36 -45
  36. cognite/neat/rules/importers/_dtdl2rules/spec.py +7 -0
  37. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +8 -4
  38. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  39. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  40. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +12 -19
  41. cognite/neat/rules/importers/_rdf/_inference2rules.py +14 -37
  42. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +1 -0
  43. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -0
  44. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  45. cognite/neat/rules/importers/_rdf/_shared.py +4 -4
  46. cognite/neat/rules/importers/_spreadsheet2rules.py +46 -97
  47. cognite/neat/rules/importers/_yaml2rules.py +32 -58
  48. cognite/neat/rules/models/__init__.py +21 -5
  49. cognite/neat/rules/models/_base_input.py +162 -0
  50. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  51. cognite/neat/rules/models/_rdfpath.py +4 -4
  52. cognite/neat/rules/models/{_types/_field.py → _types.py} +5 -10
  53. cognite/neat/rules/models/asset/__init__.py +5 -2
  54. cognite/neat/rules/models/asset/_rules.py +3 -23
  55. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  56. cognite/neat/rules/models/asset/_validation.py +14 -10
  57. cognite/neat/rules/models/data_types.py +150 -44
  58. cognite/neat/rules/models/dms/__init__.py +19 -7
  59. cognite/neat/rules/models/dms/_exporter.py +102 -34
  60. cognite/neat/rules/models/dms/_rules.py +65 -162
  61. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  62. cognite/neat/rules/models/dms/_schema.py +87 -78
  63. cognite/neat/rules/models/dms/_serializer.py +44 -3
  64. cognite/neat/rules/models/dms/_validation.py +106 -68
  65. cognite/neat/rules/models/domain.py +52 -1
  66. cognite/neat/rules/models/entities/__init__.py +63 -0
  67. cognite/neat/rules/models/entities/_constants.py +73 -0
  68. cognite/neat/rules/models/entities/_loaders.py +76 -0
  69. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  70. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  71. cognite/neat/rules/models/entities/_types.py +86 -0
  72. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  73. cognite/neat/rules/models/information/__init__.py +10 -2
  74. cognite/neat/rules/models/information/_rules.py +10 -22
  75. cognite/neat/rules/models/information/_rules_input.py +57 -204
  76. cognite/neat/rules/models/information/_validation.py +48 -25
  77. cognite/neat/rules/transformers/__init__.py +21 -0
  78. cognite/neat/rules/transformers/_base.py +81 -0
  79. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +217 -21
  80. cognite/neat/rules/transformers/_map_onto.py +97 -0
  81. cognite/neat/rules/transformers/_pipelines.py +61 -0
  82. cognite/neat/rules/transformers/_verification.py +136 -0
  83. cognite/neat/{graph/stores → store}/_provenance.py +10 -1
  84. cognite/neat/utils/auxiliary.py +2 -35
  85. cognite/neat/utils/cdf/data_classes.py +20 -0
  86. cognite/neat/utils/regex_patterns.py +6 -0
  87. cognite/neat/utils/text.py +17 -0
  88. cognite/neat/workflows/base.py +4 -4
  89. cognite/neat/workflows/cdf_store.py +3 -3
  90. cognite/neat/workflows/steps/data_contracts.py +1 -1
  91. cognite/neat/workflows/steps/lib/current/graph_extractor.py +3 -3
  92. cognite/neat/workflows/steps/lib/current/graph_loader.py +2 -2
  93. cognite/neat/workflows/steps/lib/current/graph_store.py +1 -1
  94. cognite/neat/workflows/steps/lib/current/rules_exporter.py +116 -47
  95. cognite/neat/workflows/steps/lib/current/rules_importer.py +30 -28
  96. cognite/neat/workflows/steps/lib/current/rules_validator.py +5 -6
  97. cognite/neat/workflows/steps/lib/io/io_steps.py +5 -5
  98. cognite/neat/workflows/steps_registry.py +4 -5
  99. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
  100. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +105 -106
  101. cognite/neat/exceptions.py +0 -145
  102. cognite/neat/graph/exceptions.py +0 -90
  103. cognite/neat/issues/errors/external.py +0 -21
  104. cognite/neat/issues/errors/properties.py +0 -75
  105. cognite/neat/issues/errors/resources.py +0 -123
  106. cognite/neat/issues/errors/schema.py +0 -0
  107. cognite/neat/issues/neat_warnings/__init__.py +0 -2
  108. cognite/neat/issues/neat_warnings/identifier.py +0 -27
  109. cognite/neat/issues/neat_warnings/models.py +0 -22
  110. cognite/neat/issues/neat_warnings/properties.py +0 -77
  111. cognite/neat/issues/neat_warnings/resources.py +0 -125
  112. cognite/neat/rules/issues/__init__.py +0 -22
  113. cognite/neat/rules/issues/base.py +0 -63
  114. cognite/neat/rules/issues/dms.py +0 -549
  115. cognite/neat/rules/issues/fileread.py +0 -197
  116. cognite/neat/rules/issues/ontology.py +0 -298
  117. cognite/neat/rules/issues/spreadsheet.py +0 -563
  118. cognite/neat/rules/issues/spreadsheet_file.py +0 -151
  119. cognite/neat/rules/issues/tables.py +0 -72
  120. cognite/neat/rules/models/_constants.py +0 -1
  121. cognite/neat/rules/models/_types/__init__.py +0 -19
  122. cognite/neat/rules/models/asset/_converter.py +0 -4
  123. cognite/neat/rules/models/dms/_converter.py +0 -145
  124. cognite/neat/workflows/_exceptions.py +0 -41
  125. /cognite/neat/{graph/stores → store}/__init__.py +0 -0
  126. /cognite/neat/{graph/stores → store}/_base.py +0 -0
  127. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
  128. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
  129. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/entry_points.txt +0 -0
@@ -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"
@@ -8,7 +8,7 @@ from typing import ClassVar, Literal
8
8
 
9
9
  from pydantic import BaseModel, field_validator, model_serializer
10
10
 
11
- from cognite.neat.rules.issues.tables import NotValidRAWLookUpError, NotValidRDFPathError, NotValidTableLookUpError
11
+ from cognite.neat.issues.errors import NeatValueError
12
12
 
13
13
  if sys.version_info >= (3, 11):
14
14
  from enum import StrEnum
@@ -313,7 +313,7 @@ def parse_traversal(raw: str) -> SelfReferenceProperty | SingleProperty | Hop:
313
313
  elif result := HOP_REGEX_COMPILED.match(raw):
314
314
  return Hop.from_string(class_=result.group("origin"), traversal=result.group(_traversal))
315
315
  else:
316
- raise NotValidRDFPathError(raw).as_pydantic_exception()
316
+ raise NeatValueError(f"Invalid RDF Path: {raw!r}")
317
317
 
318
318
 
319
319
  def parse_table_lookup(raw: str) -> TableLookup:
@@ -323,7 +323,7 @@ def parse_table_lookup(raw: str) -> TableLookup:
323
323
  key=result.group(Lookup.key),
324
324
  value=result.group(Lookup.value),
325
325
  )
326
- raise NotValidTableLookUpError(raw).as_pydantic_exception()
326
+ raise NeatValueError(f"Invalid table lookup: {raw!r}")
327
327
 
328
328
 
329
329
  def parse_rule(rule_raw: str, rule_type: TransformationRuleType | None) -> RDFPath:
@@ -334,7 +334,7 @@ def parse_rule(rule_raw: str, rule_type: TransformationRuleType | None) -> RDFPa
334
334
  case TransformationRuleType.rawlookup:
335
335
  rule_raw = rule_raw.replace(" ", "")
336
336
  if Counter(rule_raw).get("|") != 1:
337
- raise NotValidRAWLookUpError(rule_raw).as_pydantic_exception()
337
+ raise NeatValueError(f"Invalid rawlookup rule: {rule_raw!r}")
338
338
  traversal, table_lookup = rule_raw.split("|")
339
339
  return RawLookup(
340
340
  traversal=parse_traversal(traversal),
@@ -14,10 +14,9 @@ from pydantic import (
14
14
  WrapValidator,
15
15
  )
16
16
  from pydantic.functional_serializers import PlainSerializer
17
- from pydantic_core import PydanticCustomError
18
17
 
19
- from cognite.neat.issues.neat_warnings.identifier import RegexViolationWarning
20
- from cognite.neat.rules.issues.spreadsheet import RegexViolationError
18
+ from cognite.neat.issues.errors import RegexViolationError
19
+ from cognite.neat.issues.warnings import RegexViolationWarning
21
20
  from cognite.neat.utils.regex_patterns import (
22
21
  PATTERNS,
23
22
  PREFIX_COMPLIANCE_REGEX,
@@ -36,10 +35,6 @@ def _custom_error(exc_factory: Callable[[str | None, Exception], Any]) -> Any:
36
35
  return WrapValidator(_validator)
37
36
 
38
37
 
39
- def _raise(exception: PydanticCustomError):
40
- raise exception
41
-
42
-
43
38
  StrOrListType = Annotated[
44
39
  str | list[str],
45
40
  BeforeValidator(lambda value: value.replace(", ", ",").split(",") if isinstance(value, str) and value else value),
@@ -72,7 +67,7 @@ NamespaceType = Annotated[
72
67
  PrefixType = Annotated[
73
68
  str,
74
69
  StringConstraints(pattern=PREFIX_COMPLIANCE_REGEX),
75
- _custom_error(lambda _, value: RegexViolationError(value, PREFIX_COMPLIANCE_REGEX).as_pydantic_exception()),
70
+ _custom_error(lambda _, value: RegexViolationError(value, PREFIX_COMPLIANCE_REGEX)),
76
71
  ]
77
72
 
78
73
  ExternalIdType = Annotated[
@@ -83,13 +78,13 @@ ExternalIdType = Annotated[
83
78
  VersionType = Annotated[
84
79
  str,
85
80
  StringConstraints(pattern=VERSION_COMPLIANCE_REGEX),
86
- _custom_error(lambda _, value: RegexViolationError(value, VERSION_COMPLIANCE_REGEX).as_pydantic_exception()),
81
+ _custom_error(lambda _, value: RegexViolationError(value, VERSION_COMPLIANCE_REGEX)),
87
82
  ]
88
83
 
89
84
 
90
85
  def _property_validation(value: str) -> str:
91
86
  if not PATTERNS.property_id_compliance.match(value):
92
- _raise(RegexViolationError(value, PROPERTY_ID_COMPLIANCE_REGEX).as_pydantic_exception())
87
+ raise RegexViolationError(value, PROPERTY_ID_COMPLIANCE_REGEX)
93
88
  if PATTERNS.more_than_one_alphanumeric.search(value):
94
89
  warnings.warn(
95
90
  RegexViolationWarning(value, PROPERTY_ID_COMPLIANCE_REGEX, "property", "MoreThanOneNonAlphanumeric"),
@@ -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,15 +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.issues import MultiValueError
10
- from cognite.neat.rules import issues
11
- from cognite.neat.rules.models._base import BaseRules, RoleTypes, SheetList
12
- from cognite.neat.rules.models.domain import DomainRules
9
+ from cognite.neat.rules.models._base_rules import BaseRules, RoleTypes, SheetList
13
10
  from cognite.neat.rules.models.entities import (
14
11
  CdfResourceEntityList,
15
12
  ClassEntity,
@@ -23,10 +20,6 @@ from cognite.neat.rules.models.information import (
23
20
  InformationRules,
24
21
  )
25
22
 
26
- if TYPE_CHECKING:
27
- from cognite.neat.rules.models.dms._rules import DMSRules
28
-
29
-
30
23
  if sys.version_info >= (3, 11):
31
24
  from typing import Self
32
25
  else:
@@ -109,7 +102,7 @@ class AssetRules(BaseRules):
109
102
  if issue_list.warnings:
110
103
  issue_list.trigger_warnings()
111
104
  if issue_list.has_errors:
112
- raise MultiValueError([error for error in issue_list if isinstance(error, issues.NeatValidationError)])
105
+ raise issue_list.as_exception()
113
106
  return self
114
107
 
115
108
  def dump(
@@ -143,16 +136,3 @@ class AssetRules(BaseRules):
143
136
  prefix = self.reference.metadata.prefix
144
137
  cleaned[reference] = _AssetRulesSerializer(by_alias, prefix).clean(ref_dump, True)
145
138
  return cleaned
146
-
147
- def as_domain_rules(self) -> DomainRules:
148
- from ._converter import _AssetRulesConverter
149
-
150
- return _AssetRulesConverter(self.as_information_rules()).as_domain_rules()
151
-
152
- def as_dms_rules(self) -> "DMSRules":
153
- from ._converter import _AssetRulesConverter
154
-
155
- return _AssetRulesConverter(self.as_information_rules()).as_dms_rules()
156
-
157
- def as_information_rules(self) -> InformationRules:
158
- return InformationRules.model_validate(self.model_dump())
@@ -1,29 +1,33 @@
1
- from collections.abc import Sequence
2
1
  from dataclasses import dataclass
3
- from typing import Any, cast, overload
2
+ from typing import Any
4
3
 
5
- from cognite.neat.rules.models._base import _add_alias
4
+ from rdflib import Namespace
5
+
6
+ from cognite.neat.rules.models._base_input import InputComponent, InputRules
6
7
  from cognite.neat.rules.models.data_types import DataType
7
8
  from cognite.neat.rules.models.entities import (
8
9
  ClassEntity,
9
10
  MultiValueTypeInfo,
10
- Unknown,
11
11
  UnknownEntity,
12
+ load_value_type,
12
13
  )
13
- from cognite.neat.rules.models.information._rules_input import InformationClassInput, InformationMetadataInput
14
+ from cognite.neat.rules.models.information._rules_input import InformationInputClass, InformationInputMetadata
14
15
 
15
- from ._rules import AssetProperty, AssetRules
16
+ from ._rules import AssetClass, AssetMetadata, AssetProperty, AssetRules
16
17
 
17
18
 
18
19
  @dataclass
19
- class AssetMetadataInput(InformationMetadataInput): ...
20
+ class AssetInputMetadata(InformationInputMetadata):
21
+ @classmethod
22
+ def _get_verified_cls(cls) -> type[AssetMetadata]:
23
+ return AssetMetadata
20
24
 
21
25
 
22
26
  @dataclass
23
- class AssetPropertyInput:
24
- class_: str
27
+ class AssetInputProperty(InputComponent[AssetProperty]):
28
+ class_: ClassEntity | str
25
29
  property_: str
26
- value_type: str
30
+ value_type: DataType | ClassEntity | MultiValueTypeInfo | UnknownEntity | str
27
31
  name: str | None = None
28
32
  description: str | None = None
29
33
  comment: str | None = None
@@ -34,133 +38,54 @@ class AssetPropertyInput:
34
38
  match_type: str | None = None
35
39
  transformation: str | None = None
36
40
  implementation: str | None = None
41
+ # Only used internally
42
+ inherited: bool = False
37
43
 
38
44
  @classmethod
39
- @overload
40
- def load(cls, data: None) -> None: ...
45
+ def _get_verified_cls(cls) -> type[AssetProperty]:
46
+ return AssetProperty
41
47
 
42
- @classmethod
43
- @overload
44
- def load(cls, data: dict[str, Any]) -> "AssetPropertyInput": ...
48
+ def dump(self, default_prefix: str) -> dict[str, Any]: # type: ignore[override]
49
+ output = super().dump()
50
+ output["Class"] = ClassEntity.load(self.class_, prefix=default_prefix)
51
+ output["Value Type"] = load_value_type(self.value_type, default_prefix)
52
+ return output
45
53
 
46
- @classmethod
47
- @overload
48
- def load(cls, data: list[dict[str, Any]]) -> list["AssetPropertyInput"]: ...
49
54
 
55
+ @dataclass
56
+ class AssetInputClass(InformationInputClass):
50
57
  @classmethod
51
- def load(
52
- cls, data: dict[str, Any] | list[dict[str, Any]] | None
53
- ) -> "AssetPropertyInput | list[AssetPropertyInput] | None":
54
- if data is None:
55
- return None
56
- if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
57
- items = cast(list[dict[str, Any]], data.get("data") if isinstance(data, dict) else data)
58
- return [loaded for item in items if (loaded := cls.load(item)) is not None]
59
-
60
- _add_alias(data, AssetProperty)
61
- return cls(
62
- class_=data.get("class_"), # type: ignore[arg-type]
63
- property_=data.get("property_"), # type: ignore[arg-type]
64
- name=data.get("name", None),
65
- description=data.get("description", None),
66
- comment=data.get("comment", None),
67
- value_type=data.get("value_type"), # type: ignore[arg-type]
68
- min_count=data.get("min_count", None),
69
- max_count=data.get("max_count", None),
70
- default=data.get("default", None),
71
- reference=data.get("reference", None),
72
- match_type=data.get("match_type", None),
73
- transformation=data.get("transformation", None),
74
- implementation=data.get("implementation", None),
75
- )
76
-
77
- def dump(self, default_prefix: str) -> dict[str, Any]:
78
- value_type: MultiValueTypeInfo | DataType | ClassEntity | UnknownEntity
79
-
80
- # property holding xsd data type
81
- # check if it is multi value type
82
- if "|" in self.value_type:
83
- value_type = MultiValueTypeInfo.load(self.value_type)
84
- value_type.set_default_prefix(default_prefix)
85
-
86
- elif DataType.is_data_type(self.value_type):
87
- value_type = DataType.load(self.value_type)
88
-
89
- # unknown value type
90
- elif self.value_type == str(Unknown):
91
- value_type = UnknownEntity()
92
-
93
- # property holding link to class
94
- else:
95
- value_type = ClassEntity.load(self.value_type, prefix=default_prefix)
96
-
97
- return {
98
- "Class": ClassEntity.load(self.class_, prefix=default_prefix),
99
- "Property": self.property_,
100
- "Name": self.name,
101
- "Description": self.description,
102
- "Comment": self.comment,
103
- "Value Type": value_type,
104
- "Min Count": self.min_count,
105
- "Max Count": self.max_count,
106
- "Default": self.default,
107
- "Reference": self.reference,
108
- "Match Type": self.match_type,
109
- "Transformation": self.transformation,
110
- "Implementation": self.implementation,
111
- }
112
-
113
-
114
- class AssetClassInput(InformationClassInput): ...
58
+ def _get_verified_cls(cls) -> type[AssetClass]:
59
+ return AssetClass
115
60
 
116
61
 
117
62
  @dataclass
118
- class AssetRulesInput:
119
- metadata: AssetMetadataInput
120
- properties: Sequence[AssetPropertyInput]
121
- classes: Sequence[AssetClassInput]
122
- last: "AssetRulesInput | AssetRules | None" = None
123
- reference: "AssetRulesInput | AssetRules | None" = None
124
-
125
- @classmethod
126
- @overload
127
- def load(cls, data: dict[str, Any]) -> "AssetRulesInput": ...
128
-
129
- @classmethod
130
- @overload
131
- def load(cls, data: None) -> None: ...
63
+ class AssetInputRules(InputRules[AssetRules]):
64
+ metadata: AssetInputMetadata
65
+ properties: list[AssetInputProperty]
66
+ classes: list[AssetInputClass]
67
+ prefixes: dict[str, Namespace] | None = None
68
+ last: "AssetInputRules | None" = None
69
+ reference: "AssetInputRules | None" = None
132
70
 
133
71
  @classmethod
134
- def load(cls, data: dict | None) -> "AssetRulesInput | None":
135
- if data is None:
136
- return None
137
- _add_alias(data, AssetRules)
138
-
139
- return cls(
140
- metadata=AssetMetadataInput.load(data.get("metadata")), # type: ignore[arg-type]
141
- properties=AssetPropertyInput.load(data.get("properties")), # type: ignore[arg-type]
142
- classes=InformationClassInput.load(data.get("classes")), # type: ignore[arg-type]
143
- last=AssetRulesInput.load(data.get("last")),
144
- reference=AssetRulesInput.load(data.get("reference")),
145
- )
146
-
147
- def as_rules(self) -> AssetRules:
148
- return AssetRules.model_validate(self.dump())
72
+ def _get_verified_cls(cls) -> type[AssetRules]:
73
+ return AssetRules
149
74
 
150
75
  def dump(self) -> dict[str, Any]:
151
76
  default_prefix = self.metadata.prefix
152
77
  reference: dict[str, Any] | None = None
153
- if isinstance(self.reference, AssetRulesInput):
78
+ if isinstance(self.reference, AssetInputRules):
154
79
  reference = self.reference.dump()
155
80
  elif isinstance(self.reference, AssetRules):
156
81
  # We need to load through the AssetRulesInput to set the correct default space and version
157
- reference = AssetRulesInput.load(self.reference.model_dump()).dump()
82
+ reference = AssetInputRules.load(self.reference.model_dump()).dump()
158
83
  last: dict[str, Any] | None = None
159
- if isinstance(self.last, AssetRulesInput):
84
+ if isinstance(self.last, AssetInputRules):
160
85
  last = self.last.dump()
161
86
  elif isinstance(self.last, AssetRules):
162
87
  # We need to load through the AssetRulesInput to set the correct default space and version
163
- last = AssetRulesInput.load(self.last.model_dump()).dump()
88
+ last = AssetInputRules.load(self.last.model_dump()).dump()
164
89
 
165
90
  return dict(
166
91
  Metadata=self.metadata.dump(),
@@ -2,8 +2,8 @@ from graphlib import CycleError
2
2
  from typing import cast
3
3
 
4
4
  from cognite.neat.issues import IssueList
5
- from cognite.neat.rules import issues
6
- from cognite.neat.rules.models._base import SheetList
5
+ from cognite.neat.issues.errors import NeatValueError, PropertyDefinitionError
6
+ from cognite.neat.rules.models._base_rules import SheetList
7
7
  from cognite.neat.rules.models.asset._rules import AssetProperty, AssetRules
8
8
  from cognite.neat.rules.models.entities import AssetEntity, AssetFields, ClassEntity
9
9
  from cognite.neat.rules.models.information._validation import InformationPostValidation
@@ -17,7 +17,6 @@ class AssetPostValidation(InformationPostValidation):
17
17
  return self.issue_list
18
18
 
19
19
  def _parent_property_point_to_class(self) -> None:
20
- class_property_with_data_value_type = []
21
20
  for property_ in cast(SheetList[AssetProperty], self.properties):
22
21
  for implementation in property_.implementation:
23
22
  if (
@@ -25,12 +24,15 @@ class AssetPostValidation(InformationPostValidation):
25
24
  and implementation.property_ == AssetFields.parentExternalId
26
25
  and not isinstance(property_.value_type, ClassEntity)
27
26
  ):
28
- class_property_with_data_value_type.append((property_.class_.suffix, property_.property_))
29
-
30
- if class_property_with_data_value_type:
31
- self.issue_list.append(
32
- issues.spreadsheet.AssetParentPropertyPointsToDataValueTypeError(class_property_with_data_value_type)
33
- )
27
+ self.issue_list.append(
28
+ PropertyDefinitionError(
29
+ property_.class_,
30
+ "class",
31
+ property_.property_,
32
+ "parentExternalId is only allowed to "
33
+ f"point to a Class not {type(property_.value_type).__name__}",
34
+ )
35
+ )
34
36
 
35
37
  def _circular_dependency(self) -> None:
36
38
  from cognite.neat.rules.analysis import AssetAnalysis
@@ -38,4 +40,6 @@ class AssetPostValidation(InformationPostValidation):
38
40
  try:
39
41
  _ = AssetAnalysis(cast(AssetRules, self.rules)).class_topological_sort()
40
42
  except CycleError as error:
41
- self.issue_list.append(issues.spreadsheet.AssetRulesHaveCircularDependencyError(error.args[1]))
43
+ self.issue_list.append(
44
+ NeatValueError(f"Invalid Asset Hierarchy, circular dependency detected: {error.args[1]}")
45
+ )