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.
- cognite/neat/_version.py +1 -1
- cognite/neat/constants.py +3 -0
- cognite/neat/graph/__init__.py +0 -3
- cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
- cognite/neat/graph/loaders/_base.py +3 -3
- cognite/neat/graph/loaders/_rdf2asset.py +24 -25
- cognite/neat/graph/loaders/_rdf2dms.py +20 -15
- cognite/neat/issues/__init__.py +1 -3
- cognite/neat/issues/_base.py +261 -71
- cognite/neat/issues/errors/__init__.py +73 -0
- cognite/neat/issues/errors/_external.py +67 -0
- cognite/neat/issues/errors/_general.py +35 -0
- cognite/neat/issues/errors/_properties.py +62 -0
- cognite/neat/issues/errors/_resources.py +111 -0
- cognite/neat/issues/errors/_workflow.py +36 -0
- cognite/neat/issues/formatters.py +1 -1
- cognite/neat/issues/warnings/__init__.py +66 -0
- cognite/neat/issues/warnings/_external.py +40 -0
- cognite/neat/issues/warnings/_general.py +29 -0
- cognite/neat/issues/warnings/_models.py +92 -0
- cognite/neat/issues/warnings/_properties.py +44 -0
- cognite/neat/issues/warnings/_resources.py +55 -0
- cognite/neat/issues/warnings/user_modeling.py +113 -0
- cognite/neat/rules/_shared.py +53 -2
- cognite/neat/rules/analysis/_base.py +1 -1
- cognite/neat/rules/exporters/_base.py +7 -18
- cognite/neat/rules/exporters/_rules2dms.py +17 -20
- cognite/neat/rules/exporters/_rules2excel.py +9 -16
- cognite/neat/rules/exporters/_rules2ontology.py +77 -64
- cognite/neat/rules/exporters/_rules2yaml.py +6 -9
- cognite/neat/rules/exporters/_validation.py +11 -96
- cognite/neat/rules/importers/_base.py +9 -58
- cognite/neat/rules/importers/_dms2rules.py +188 -135
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +48 -35
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +36 -45
- cognite/neat/rules/importers/_dtdl2rules/spec.py +7 -0
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +8 -4
- 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 +12 -19
- cognite/neat/rules/importers/_rdf/_inference2rules.py +14 -37
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +1 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
- cognite/neat/rules/importers/_rdf/_shared.py +4 -4
- cognite/neat/rules/importers/_spreadsheet2rules.py +46 -97
- cognite/neat/rules/importers/_yaml2rules.py +32 -58
- 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/_rdfpath.py +4 -4
- cognite/neat/rules/models/{_types/_field.py → _types.py} +5 -10
- cognite/neat/rules/models/asset/__init__.py +5 -2
- cognite/neat/rules/models/asset/_rules.py +3 -23
- cognite/neat/rules/models/asset/_rules_input.py +40 -115
- cognite/neat/rules/models/asset/_validation.py +14 -10
- 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 +102 -34
- cognite/neat/rules/models/dms/_rules.py +65 -162
- cognite/neat/rules/models/dms/_rules_input.py +186 -254
- cognite/neat/rules/models/dms/_schema.py +87 -78
- cognite/neat/rules/models/dms/_serializer.py +44 -3
- cognite/neat/rules/models/dms/_validation.py +106 -68
- 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 +10 -22
- cognite/neat/rules/models/information/_rules_input.py +57 -204
- cognite/neat/rules/models/information/_validation.py +48 -25
- cognite/neat/rules/transformers/__init__.py +21 -0
- cognite/neat/rules/transformers/_base.py +81 -0
- cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +217 -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/{graph/stores → store}/_provenance.py +10 -1
- cognite/neat/utils/auxiliary.py +2 -35
- cognite/neat/utils/cdf/data_classes.py +20 -0
- cognite/neat/utils/regex_patterns.py +6 -0
- cognite/neat/utils/text.py +17 -0
- cognite/neat/workflows/base.py +4 -4
- cognite/neat/workflows/cdf_store.py +3 -3
- cognite/neat/workflows/steps/data_contracts.py +1 -1
- cognite/neat/workflows/steps/lib/current/graph_extractor.py +3 -3
- cognite/neat/workflows/steps/lib/current/graph_loader.py +2 -2
- cognite/neat/workflows/steps/lib/current/graph_store.py +1 -1
- cognite/neat/workflows/steps/lib/current/rules_exporter.py +116 -47
- cognite/neat/workflows/steps/lib/current/rules_importer.py +30 -28
- cognite/neat/workflows/steps/lib/current/rules_validator.py +5 -6
- cognite/neat/workflows/steps/lib/io/io_steps.py +5 -5
- cognite/neat/workflows/steps_registry.py +4 -5
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +105 -106
- cognite/neat/exceptions.py +0 -145
- cognite/neat/graph/exceptions.py +0 -90
- cognite/neat/issues/errors/external.py +0 -21
- cognite/neat/issues/errors/properties.py +0 -75
- cognite/neat/issues/errors/resources.py +0 -123
- cognite/neat/issues/errors/schema.py +0 -0
- cognite/neat/issues/neat_warnings/__init__.py +0 -2
- cognite/neat/issues/neat_warnings/identifier.py +0 -27
- cognite/neat/issues/neat_warnings/models.py +0 -22
- cognite/neat/issues/neat_warnings/properties.py +0 -77
- cognite/neat/issues/neat_warnings/resources.py +0 -125
- cognite/neat/rules/issues/__init__.py +0 -22
- cognite/neat/rules/issues/base.py +0 -63
- cognite/neat/rules/issues/dms.py +0 -549
- cognite/neat/rules/issues/fileread.py +0 -197
- cognite/neat/rules/issues/ontology.py +0 -298
- cognite/neat/rules/issues/spreadsheet.py +0 -563
- cognite/neat/rules/issues/spreadsheet_file.py +0 -151
- cognite/neat/rules/issues/tables.py +0 -72
- cognite/neat/rules/models/_constants.py +0 -1
- 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 -145
- cognite/neat/workflows/_exceptions.py +0 -41
- /cognite/neat/{graph/stores → store}/__init__.py +0 -0
- /cognite/neat/{graph/stores → store}/_base.py +0 -0
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
- {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,
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
20
|
-
from cognite.neat.
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
|
|
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,15 +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.
|
|
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
|
|
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
|
|
2
|
+
from typing import Any
|
|
4
3
|
|
|
5
|
-
from
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
45
|
+
def _get_verified_cls(cls) -> type[AssetProperty]:
|
|
46
|
+
return AssetProperty
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
52
|
-
|
|
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
|
|
119
|
-
metadata:
|
|
120
|
-
properties:
|
|
121
|
-
classes:
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
135
|
-
|
|
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,
|
|
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 =
|
|
82
|
+
reference = AssetInputRules.load(self.reference.model_dump()).dump()
|
|
158
83
|
last: dict[str, Any] | None = None
|
|
159
|
-
if isinstance(self.last,
|
|
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 =
|
|
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.
|
|
6
|
-
from cognite.neat.rules.models.
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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(
|
|
43
|
+
self.issue_list.append(
|
|
44
|
+
NeatValueError(f"Invalid Asset Hierarchy, circular dependency detected: {error.args[1]}")
|
|
45
|
+
)
|