cognite-neat 0.88.3__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/extractors/_mock_graph_generator.py +2 -1
- cognite/neat/issues/_base.py +2 -1
- cognite/neat/issues/errors/__init__.py +2 -1
- cognite/neat/issues/errors/_general.py +7 -0
- cognite/neat/issues/warnings/_models.py +1 -1
- cognite/neat/issues/warnings/user_modeling.py +1 -1
- cognite/neat/rules/_shared.py +49 -6
- cognite/neat/rules/analysis/_base.py +1 -1
- cognite/neat/rules/exporters/_base.py +7 -18
- cognite/neat/rules/exporters/_rules2dms.py +8 -18
- cognite/neat/rules/exporters/_rules2excel.py +5 -12
- cognite/neat/rules/exporters/_rules2ontology.py +9 -19
- cognite/neat/rules/exporters/_rules2yaml.py +3 -6
- cognite/neat/rules/importers/_base.py +7 -52
- cognite/neat/rules/importers/_dms2rules.py +171 -115
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +9 -18
- cognite/neat/rules/importers/_rdf/_inference2rules.py +10 -33
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
- cognite/neat/rules/importers/_rdf/_shared.py +1 -1
- cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
- cognite/neat/rules/importers/_yaml2rules.py +14 -41
- cognite/neat/rules/models/__init__.py +21 -5
- cognite/neat/rules/models/_base_input.py +162 -0
- cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
- cognite/neat/rules/models/asset/__init__.py +5 -2
- cognite/neat/rules/models/asset/_rules.py +2 -20
- cognite/neat/rules/models/asset/_rules_input.py +40 -115
- cognite/neat/rules/models/asset/_validation.py +1 -1
- cognite/neat/rules/models/data_types.py +150 -44
- cognite/neat/rules/models/dms/__init__.py +19 -7
- cognite/neat/rules/models/dms/_exporter.py +72 -26
- cognite/neat/rules/models/dms/_rules.py +42 -155
- cognite/neat/rules/models/dms/_rules_input.py +186 -254
- cognite/neat/rules/models/dms/_serializer.py +44 -3
- cognite/neat/rules/models/dms/_validation.py +3 -4
- cognite/neat/rules/models/domain.py +52 -1
- cognite/neat/rules/models/entities/__init__.py +63 -0
- cognite/neat/rules/models/entities/_constants.py +73 -0
- cognite/neat/rules/models/entities/_loaders.py +76 -0
- cognite/neat/rules/models/entities/_multi_value.py +67 -0
- cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
- cognite/neat/rules/models/entities/_types.py +86 -0
- cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
- cognite/neat/rules/models/information/__init__.py +10 -2
- cognite/neat/rules/models/information/_rules.py +3 -14
- cognite/neat/rules/models/information/_rules_input.py +57 -204
- cognite/neat/rules/models/information/_validation.py +1 -1
- cognite/neat/rules/transformers/__init__.py +21 -0
- cognite/neat/rules/transformers/_base.py +69 -3
- cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +216 -20
- cognite/neat/rules/transformers/_map_onto.py +97 -0
- cognite/neat/rules/transformers/_pipelines.py +61 -0
- cognite/neat/rules/transformers/_verification.py +136 -0
- cognite/neat/store/_provenance.py +10 -1
- cognite/neat/utils/cdf/data_classes.py +20 -0
- cognite/neat/utils/regex_patterns.py +6 -0
- cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
- cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +71 -66
- cognite/neat/rules/models/_constants.py +0 -2
- cognite/neat/rules/models/_types/__init__.py +0 -19
- cognite/neat/rules/models/asset/_converter.py +0 -4
- cognite/neat/rules/models/dms/_converter.py +0 -143
- /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,30 +1,62 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import sys
|
|
3
|
+
import typing
|
|
2
4
|
from datetime import date, datetime
|
|
3
5
|
from typing import Any, ClassVar
|
|
4
6
|
|
|
5
7
|
from cognite.client.data_classes import data_modeling as dms
|
|
6
|
-
from
|
|
8
|
+
from cognite.client.data_classes.data_modeling.data_types import Enum as DMSEnum
|
|
9
|
+
from pydantic import BaseModel, Field, model_serializer, model_validator
|
|
7
10
|
from pydantic.functional_validators import ModelWrapValidatorHandler
|
|
8
11
|
|
|
12
|
+
from cognite.neat.rules.models.entities._single_value import ClassEntity, UnitEntity
|
|
13
|
+
from cognite.neat.utils.regex_patterns import SPLIT_ON_COMMA_PATTERN, SPLIT_ON_EQUAL_PATTERN
|
|
14
|
+
|
|
9
15
|
if sys.version_info >= (3, 11):
|
|
10
16
|
from typing import Self
|
|
11
17
|
else:
|
|
12
18
|
from typing_extensions import Self
|
|
13
19
|
|
|
20
|
+
# This patterns matches a string that is a data type, with optional content in parentheses.
|
|
21
|
+
# For example, it matches "float(unit=power:megaw)" as name="float" and content="unit=power:megaw"
|
|
22
|
+
_DATATYPE_PATTERN = re.compile(r"^(?P<name>[^(:]*)(\((?P<content>.+)\))?$")
|
|
14
23
|
|
|
15
|
-
class DataType(BaseModel):
|
|
16
|
-
# These are necessary for Pydantic to work
|
|
17
|
-
# pydantic gets confused as we have no fields.
|
|
18
|
-
__pydantic_extra__ = None
|
|
19
|
-
__pydantic_fields_set__ = set()
|
|
20
|
-
__pydantic_private__ = {}
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
class DataType(BaseModel):
|
|
23
26
|
python: ClassVar[type]
|
|
24
27
|
dms: ClassVar[type[dms.PropertyType]]
|
|
25
28
|
graphql: ClassVar[str]
|
|
26
29
|
xsd: ClassVar[str]
|
|
27
30
|
sql: ClassVar[str]
|
|
31
|
+
_dms_loaded: bool = False
|
|
32
|
+
# Repeat all here, just to make mypy happy
|
|
33
|
+
name: typing.Literal[
|
|
34
|
+
"boolean",
|
|
35
|
+
"token",
|
|
36
|
+
"float",
|
|
37
|
+
"double",
|
|
38
|
+
"integer",
|
|
39
|
+
"nonPositiveInteger",
|
|
40
|
+
"nonNegativeInteger",
|
|
41
|
+
"long",
|
|
42
|
+
"negativeInteger",
|
|
43
|
+
"string",
|
|
44
|
+
"langString",
|
|
45
|
+
"anyURI",
|
|
46
|
+
"normalizedString",
|
|
47
|
+
"dateTime",
|
|
48
|
+
"timestamp",
|
|
49
|
+
"dateTimeStamp",
|
|
50
|
+
"date",
|
|
51
|
+
"plainLiteral",
|
|
52
|
+
"Literal",
|
|
53
|
+
"timeseries",
|
|
54
|
+
"file",
|
|
55
|
+
"sequence",
|
|
56
|
+
"json",
|
|
57
|
+
"enum",
|
|
58
|
+
"",
|
|
59
|
+
] = ""
|
|
28
60
|
|
|
29
61
|
@classmethod
|
|
30
62
|
def load(cls, data: Any) -> Self:
|
|
@@ -35,15 +67,31 @@ class DataType(BaseModel):
|
|
|
35
67
|
|
|
36
68
|
@model_validator(mode="wrap")
|
|
37
69
|
def _load(cls, value: Any, handler: ModelWrapValidatorHandler["DataType"]) -> Any:
|
|
38
|
-
if isinstance(value,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
70
|
+
if cls is not DataType or isinstance(value, DataType):
|
|
71
|
+
# This is a subclass, let the subclass handle it
|
|
72
|
+
return handler(value)
|
|
73
|
+
elif isinstance(value, str) and (match := _DATATYPE_PATTERN.match(value)):
|
|
74
|
+
name = match.group("name").casefold()
|
|
75
|
+
cls_: type[DataType]
|
|
76
|
+
if name in _DATA_TYPE_BY_DMS_TYPE:
|
|
77
|
+
cls_ = _DATA_TYPE_BY_DMS_TYPE[name]
|
|
78
|
+
dms_loaded = True
|
|
79
|
+
elif name in _DATA_TYPE_BY_NAME:
|
|
80
|
+
cls_ = _DATA_TYPE_BY_NAME[name]
|
|
81
|
+
dms_loaded = False
|
|
82
|
+
else:
|
|
83
|
+
raise ValueError(f"Unknown data type: {value}") from None
|
|
84
|
+
extra_args: dict[str, Any] = {}
|
|
85
|
+
if content := match.group("content"):
|
|
86
|
+
extra_args = dict(
|
|
87
|
+
SPLIT_ON_EQUAL_PATTERN.split(pair.strip()) for pair in SPLIT_ON_COMMA_PATTERN.split(content)
|
|
88
|
+
)
|
|
89
|
+
# Todo? Raise warning if extra_args contains keys that are not in the model fields
|
|
90
|
+
instance = cls_(**extra_args)
|
|
91
|
+
created = handler(instance)
|
|
92
|
+
# Private attributes are not validated or set. We need to set it manually
|
|
93
|
+
created._dms_loaded = dms_loaded
|
|
94
|
+
return created
|
|
47
95
|
raise ValueError(f"Cannot load {cls.__name__} from {value}")
|
|
48
96
|
|
|
49
97
|
@model_serializer(when_used="unless-none", return_type=str)
|
|
@@ -51,227 +99,285 @@ class DataType(BaseModel):
|
|
|
51
99
|
return str(self)
|
|
52
100
|
|
|
53
101
|
def __str__(self) -> str:
|
|
54
|
-
|
|
102
|
+
if self._dms_loaded:
|
|
103
|
+
base = self.dms._type
|
|
104
|
+
else:
|
|
105
|
+
base = self.model_fields["name"].default
|
|
106
|
+
return self._suffix_extra_args(base)
|
|
107
|
+
|
|
108
|
+
def _suffix_extra_args(self, base: str) -> str:
|
|
109
|
+
extra_fields: dict[str, str] = {}
|
|
110
|
+
for field_id, field_ in self.model_fields.items():
|
|
111
|
+
if field_id == "name":
|
|
112
|
+
continue
|
|
113
|
+
value = getattr(self, field_id)
|
|
114
|
+
if value is None:
|
|
115
|
+
continue
|
|
116
|
+
extra_fields[field_.alias or field_id] = str(value)
|
|
117
|
+
if extra_fields:
|
|
118
|
+
content = ",".join(f"{key}={value}" for key, value in sorted(extra_fields.items(), key=lambda x: x[0]))
|
|
119
|
+
return f"{base}({content})"
|
|
120
|
+
return base
|
|
55
121
|
|
|
56
122
|
def __eq__(self, other: Any) -> bool:
|
|
57
|
-
return isinstance(other, type(self))
|
|
123
|
+
return isinstance(other, type(self)) and str(self) == str(other)
|
|
58
124
|
|
|
59
125
|
def __hash__(self) -> int:
|
|
60
126
|
return hash(str(self))
|
|
61
127
|
|
|
62
128
|
@classmethod
|
|
63
129
|
def is_data_type(cls, value: str) -> bool:
|
|
64
|
-
|
|
130
|
+
if match := _DATATYPE_PATTERN.match(value):
|
|
131
|
+
name = match.group("name").casefold()
|
|
132
|
+
return name in _DATA_TYPE_BY_NAME or name in _DATA_TYPE_BY_DMS_TYPE
|
|
133
|
+
return False
|
|
65
134
|
|
|
66
135
|
|
|
67
136
|
class Boolean(DataType):
|
|
68
|
-
name = "boolean"
|
|
69
137
|
python = bool
|
|
70
138
|
dms = dms.Boolean
|
|
71
139
|
graphql = "Boolean"
|
|
72
140
|
xsd = "boolean"
|
|
73
141
|
sql = "BOOLEAN"
|
|
142
|
+
name: typing.Literal["boolean"] = "boolean"
|
|
74
143
|
|
|
75
144
|
|
|
76
145
|
class Float(DataType):
|
|
77
|
-
name = "float"
|
|
78
146
|
python = float
|
|
79
147
|
dms = dms.Float32
|
|
80
148
|
graphql = "Float"
|
|
81
149
|
xsd = "float"
|
|
82
150
|
sql = "FLOAT"
|
|
83
151
|
|
|
152
|
+
name: typing.Literal["float"] = "float"
|
|
153
|
+
unit: UnitEntity | None = None
|
|
154
|
+
|
|
84
155
|
|
|
85
156
|
class Double(DataType):
|
|
86
|
-
name = "double"
|
|
87
157
|
python = float
|
|
88
158
|
dms = dms.Float64
|
|
89
159
|
graphql = "Float"
|
|
90
160
|
xsd = "double"
|
|
91
161
|
sql = "FLOAT"
|
|
92
162
|
|
|
163
|
+
name: typing.Literal["double"] = "double"
|
|
164
|
+
unit: UnitEntity | None = None
|
|
165
|
+
|
|
93
166
|
|
|
94
167
|
class Integer(DataType):
|
|
95
|
-
name = "integer"
|
|
96
168
|
python = int
|
|
97
169
|
dms = dms.Int32
|
|
98
170
|
graphql = "Int"
|
|
99
171
|
xsd = "integer"
|
|
100
172
|
sql = "INTEGER"
|
|
101
173
|
|
|
174
|
+
name: typing.Literal["integer"] = "integer"
|
|
175
|
+
|
|
102
176
|
|
|
103
177
|
class NonPositiveInteger(DataType):
|
|
104
|
-
name = "nonPositiveInteger"
|
|
105
178
|
python = int
|
|
106
179
|
dms = dms.Int32
|
|
107
180
|
graphql = "Int"
|
|
108
181
|
xsd = "nonPositiveInteger"
|
|
109
182
|
sql = "INTEGER"
|
|
110
183
|
|
|
184
|
+
name: typing.Literal["nonPositiveInteger"] = "nonPositiveInteger"
|
|
185
|
+
|
|
111
186
|
|
|
112
187
|
class NonNegativeInteger(DataType):
|
|
113
|
-
name = "nonNegativeInteger"
|
|
114
188
|
python = int
|
|
115
189
|
dms = dms.Int32
|
|
116
190
|
graphql = "Int"
|
|
117
191
|
xsd = "nonNegativeInteger"
|
|
118
192
|
sql = "INTEGER"
|
|
119
193
|
|
|
194
|
+
name: typing.Literal["nonNegativeInteger"] = "nonNegativeInteger"
|
|
195
|
+
|
|
120
196
|
|
|
121
197
|
class NegativeInteger(DataType):
|
|
122
|
-
name = "negativeInteger"
|
|
123
198
|
python = int
|
|
124
199
|
dms = dms.Int32
|
|
125
200
|
graphql = "Int"
|
|
126
201
|
xsd = "negativeInteger"
|
|
127
202
|
sql = "INTEGER"
|
|
128
203
|
|
|
204
|
+
name: typing.Literal["negativeInteger"] = "negativeInteger"
|
|
205
|
+
|
|
129
206
|
|
|
130
207
|
class Long(DataType):
|
|
131
|
-
name = "long"
|
|
132
208
|
python = int
|
|
133
209
|
dms = dms.Int64
|
|
134
210
|
graphql = "Int"
|
|
135
211
|
xsd = "long"
|
|
136
212
|
sql = "BIGINT"
|
|
137
213
|
|
|
214
|
+
name: typing.Literal["long"] = "long"
|
|
215
|
+
|
|
138
216
|
|
|
139
217
|
class String(DataType):
|
|
140
|
-
name = "string"
|
|
141
218
|
python = str
|
|
142
219
|
dms = dms.Text
|
|
143
220
|
graphql = "String"
|
|
144
221
|
xsd = "string"
|
|
145
222
|
sql = "STRING"
|
|
146
223
|
|
|
224
|
+
name: typing.Literal["string"] = "string"
|
|
225
|
+
|
|
147
226
|
|
|
148
227
|
class LangString(DataType):
|
|
149
|
-
name = "langString"
|
|
150
228
|
python = str
|
|
151
229
|
dms = dms.Text
|
|
152
230
|
graphql = "String"
|
|
153
231
|
xsd = "string"
|
|
154
232
|
sql = "STRING"
|
|
155
233
|
|
|
234
|
+
name: typing.Literal["langString"] = "langString"
|
|
235
|
+
|
|
156
236
|
|
|
157
237
|
class AnyURI(DataType):
|
|
158
|
-
name = "anyURI"
|
|
159
238
|
python = str
|
|
160
239
|
dms = dms.Text
|
|
161
240
|
graphql = "String"
|
|
162
241
|
xsd = "anyURI"
|
|
163
242
|
sql = "STRING"
|
|
164
243
|
|
|
244
|
+
name: typing.Literal["anyURI"] = "anyURI"
|
|
245
|
+
|
|
165
246
|
|
|
166
247
|
class NormalizedString(DataType):
|
|
167
|
-
name = "normalizedString"
|
|
168
248
|
python = str
|
|
169
249
|
dms = dms.Text
|
|
170
250
|
graphql = "String"
|
|
171
251
|
xsd = "normalizedString"
|
|
172
252
|
sql = "STRING"
|
|
173
253
|
|
|
254
|
+
name: typing.Literal["normalizedString"] = "normalizedString"
|
|
255
|
+
|
|
174
256
|
|
|
175
257
|
class Token(DataType):
|
|
176
|
-
name = "token"
|
|
177
258
|
python = str
|
|
178
259
|
dms = dms.Text
|
|
179
260
|
graphql = "String"
|
|
180
261
|
xsd = "string"
|
|
181
262
|
sql = "STRING"
|
|
182
263
|
|
|
264
|
+
name: typing.Literal["token"] = "token"
|
|
265
|
+
|
|
183
266
|
|
|
184
267
|
class DateTime(DataType):
|
|
185
|
-
name = "dateTime"
|
|
186
268
|
python = datetime
|
|
187
269
|
dms = dms.Timestamp
|
|
188
270
|
graphql = "Timestamp"
|
|
189
271
|
xsd = "dateTimeStamp"
|
|
190
272
|
sql = "TIMESTAMP"
|
|
191
273
|
|
|
274
|
+
name: typing.Literal["dateTime"] = "dateTime"
|
|
275
|
+
|
|
192
276
|
|
|
193
277
|
class Timestamp(DataType):
|
|
194
|
-
name = "timestamp"
|
|
195
278
|
python = datetime
|
|
196
279
|
dms = dms.Timestamp
|
|
197
280
|
graphql = "Timestamp"
|
|
198
281
|
xsd = "dateTimeStamp"
|
|
199
282
|
sql = "TIMESTAMP"
|
|
200
283
|
|
|
284
|
+
name: typing.Literal["timestamp"] = "timestamp"
|
|
285
|
+
|
|
201
286
|
|
|
202
287
|
class DateTimeStamp(DataType):
|
|
203
|
-
name = "dateTimeStamp"
|
|
204
288
|
python = datetime
|
|
205
289
|
dms = dms.Timestamp
|
|
206
290
|
graphql = "Timestamp"
|
|
207
291
|
xsd = "dateTimeStamp"
|
|
208
292
|
sql = "TIMESTAMP"
|
|
209
293
|
|
|
294
|
+
name: typing.Literal["dateTimeStamp"] = "dateTimeStamp"
|
|
295
|
+
|
|
210
296
|
|
|
211
297
|
class Date(DataType):
|
|
212
|
-
name = "date"
|
|
213
298
|
python = date
|
|
214
299
|
dms = dms.Date
|
|
215
300
|
graphql = "String"
|
|
216
301
|
xsd = "date"
|
|
217
302
|
sql = "DATE"
|
|
218
303
|
|
|
304
|
+
name: typing.Literal["date"] = "date"
|
|
305
|
+
|
|
219
306
|
|
|
220
307
|
class PlainLiteral(DataType):
|
|
221
|
-
name = "PlainLiteral"
|
|
222
308
|
python = str
|
|
223
309
|
dms = dms.Text
|
|
224
310
|
graphql = "String"
|
|
225
311
|
xsd = "plainLiteral"
|
|
226
312
|
sql = "STRING"
|
|
227
313
|
|
|
314
|
+
name: typing.Literal["plainLiteral"] = "plainLiteral"
|
|
315
|
+
|
|
228
316
|
|
|
229
317
|
class Literal(DataType):
|
|
230
|
-
name = "Literal"
|
|
231
318
|
python = str
|
|
232
319
|
dms = dms.Text
|
|
233
320
|
graphql = "String"
|
|
234
321
|
xsd = "string"
|
|
235
322
|
sql = "STRING"
|
|
236
323
|
|
|
324
|
+
name: typing.Literal["Literal"] = "Literal"
|
|
325
|
+
|
|
237
326
|
|
|
238
327
|
class Timeseries(DataType):
|
|
239
|
-
name = "timeseries"
|
|
240
328
|
python = str
|
|
241
329
|
dms = dms.TimeSeriesReference
|
|
242
330
|
graphql = "TimeSeries"
|
|
243
331
|
xsd = "string"
|
|
244
332
|
sql = "STRING"
|
|
245
333
|
|
|
334
|
+
name: typing.Literal["timeseries"] = "timeseries"
|
|
335
|
+
|
|
246
336
|
|
|
247
337
|
class File(DataType):
|
|
248
|
-
name = "file"
|
|
249
338
|
python = str
|
|
250
339
|
dms = dms.FileReference
|
|
251
340
|
graphql = "File"
|
|
252
341
|
xsd = "string"
|
|
253
342
|
sql = "STRING"
|
|
254
343
|
|
|
344
|
+
name: typing.Literal["file"] = "file"
|
|
345
|
+
|
|
255
346
|
|
|
256
347
|
class Sequence(DataType):
|
|
257
|
-
name = "sequence"
|
|
258
348
|
python = str
|
|
259
349
|
dms = dms.SequenceReference
|
|
260
350
|
graphql = "Sequence"
|
|
261
351
|
xsd = "string"
|
|
262
352
|
sql = "STRING"
|
|
263
353
|
|
|
354
|
+
name: typing.Literal["sequence"] = "sequence"
|
|
355
|
+
|
|
264
356
|
|
|
265
357
|
class Json(DataType):
|
|
266
|
-
name = "json"
|
|
267
358
|
python = dict
|
|
268
359
|
dms = dms.Json
|
|
269
360
|
graphql = "Json"
|
|
270
361
|
xsd = "json"
|
|
271
362
|
sql = "STRING"
|
|
272
363
|
|
|
364
|
+
name: typing.Literal["json"] = "json"
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class Enum(DataType):
|
|
368
|
+
python = str
|
|
369
|
+
dms = DMSEnum
|
|
370
|
+
graphql = "Enum"
|
|
371
|
+
xsd = "string"
|
|
372
|
+
sql = "STRING"
|
|
373
|
+
|
|
374
|
+
name: typing.Literal["enum"] = "enum"
|
|
375
|
+
|
|
376
|
+
collection: ClassEntity
|
|
377
|
+
unknown_value: str | None = Field(None, alias="unknownValue")
|
|
378
|
+
|
|
273
379
|
|
|
274
|
-
_DATA_TYPE_BY_NAME = {cls.name.casefold(): cls for cls in DataType.__subclasses__()}
|
|
380
|
+
_DATA_TYPE_BY_NAME = {cls.model_fields["name"].default.casefold(): cls for cls in DataType.__subclasses__()}
|
|
275
381
|
_seen = set()
|
|
276
382
|
_DATA_TYPE_BY_DMS_TYPE = {
|
|
277
383
|
cls.dms._type.casefold(): cls
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
from ._rules import DMSContainer, DMSMetadata, DMSProperty, DMSRules, DMSView
|
|
2
|
-
from ._rules_input import
|
|
1
|
+
from ._rules import DMSContainer, DMSEnum, DMSMetadata, DMSNode, DMSProperty, DMSRules, DMSView
|
|
2
|
+
from ._rules_input import (
|
|
3
|
+
DMSInputContainer,
|
|
4
|
+
DMSInputEnum,
|
|
5
|
+
DMSInputMetadata,
|
|
6
|
+
DMSInputNode,
|
|
7
|
+
DMSInputProperty,
|
|
8
|
+
DMSInputRules,
|
|
9
|
+
DMSInputView,
|
|
10
|
+
)
|
|
3
11
|
from ._schema import DMSSchema, PipelineSchema
|
|
4
12
|
|
|
5
13
|
__all__ = [
|
|
@@ -9,10 +17,14 @@ __all__ = [
|
|
|
9
17
|
"DMSView",
|
|
10
18
|
"DMSProperty",
|
|
11
19
|
"DMSContainer",
|
|
20
|
+
"DMSNode",
|
|
21
|
+
"DMSEnum",
|
|
12
22
|
"PipelineSchema",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
23
|
+
"DMSInputRules",
|
|
24
|
+
"DMSInputMetadata",
|
|
25
|
+
"DMSInputView",
|
|
26
|
+
"DMSInputProperty",
|
|
27
|
+
"DMSInputContainer",
|
|
28
|
+
"DMSInputNode",
|
|
29
|
+
"DMSInputEnum",
|
|
18
30
|
]
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import warnings
|
|
2
2
|
from collections import defaultdict
|
|
3
|
-
from collections.abc import Sequence
|
|
3
|
+
from collections.abc import Collection, Sequence
|
|
4
4
|
from typing import Any, cast
|
|
5
5
|
|
|
6
6
|
from cognite.client.data_classes import data_modeling as dm
|
|
7
7
|
from cognite.client.data_classes.data_modeling.containers import BTreeIndex
|
|
8
|
+
from cognite.client.data_classes.data_modeling.data_types import EnumValue as DMSEnumValue
|
|
8
9
|
from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
|
|
9
10
|
from cognite.client.data_classes.data_modeling.views import (
|
|
10
11
|
SingleEdgeConnectionApply,
|
|
@@ -12,6 +13,7 @@ from cognite.client.data_classes.data_modeling.views import (
|
|
|
12
13
|
ViewPropertyApply,
|
|
13
14
|
)
|
|
14
15
|
|
|
16
|
+
from cognite.neat.issues.errors import NeatTypeError, ResourceNotFoundError
|
|
15
17
|
from cognite.neat.issues.warnings import NotSupportedWarning, PropertyNotFoundWarning
|
|
16
18
|
from cognite.neat.issues.warnings.user_modeling import (
|
|
17
19
|
EmptyContainerWarning,
|
|
@@ -19,20 +21,25 @@ from cognite.neat.issues.warnings.user_modeling import (
|
|
|
19
21
|
HasDataFilterOnViewWithReferencesWarning,
|
|
20
22
|
NodeTypeFilterOnParentViewWarning,
|
|
21
23
|
)
|
|
22
|
-
from cognite.neat.rules.models.
|
|
23
|
-
from cognite.neat.rules.models.data_types import DataType
|
|
24
|
+
from cognite.neat.rules.models._base_rules import DataModelType, ExtensionCategory, SchemaCompleteness
|
|
25
|
+
from cognite.neat.rules.models.data_types import DataType, Double, Enum, Float
|
|
24
26
|
from cognite.neat.rules.models.entities import (
|
|
27
|
+
ClassEntity,
|
|
25
28
|
ContainerEntity,
|
|
29
|
+
DMSFilter,
|
|
26
30
|
DMSNodeEntity,
|
|
27
31
|
DMSUnknownEntity,
|
|
32
|
+
EdgeEntity,
|
|
33
|
+
HasDataFilter,
|
|
34
|
+
NodeTypeFilter,
|
|
28
35
|
ReferenceEntity,
|
|
36
|
+
ReverseConnectionEntity,
|
|
37
|
+
UnitEntity,
|
|
29
38
|
ViewEntity,
|
|
30
|
-
ViewPropertyEntity,
|
|
31
39
|
)
|
|
32
|
-
from cognite.neat.rules.models.wrapped_entities import DMSFilter, HasDataFilter, NodeTypeFilter
|
|
33
40
|
from cognite.neat.utils.cdf.data_classes import ContainerApplyDict, NodeApplyDict, SpaceApplyDict, ViewApplyDict
|
|
34
41
|
|
|
35
|
-
from ._rules import DMSMetadata, DMSProperty, DMSRules, DMSView
|
|
42
|
+
from ._rules import DMSEnum, DMSMetadata, DMSProperty, DMSRules, DMSView
|
|
36
43
|
from ._schema import DMSSchema, PipelineSchema
|
|
37
44
|
|
|
38
45
|
|
|
@@ -111,9 +118,16 @@ class _DMSExporter:
|
|
|
111
118
|
]
|
|
112
119
|
self._update_with_properties(selected_properties, container_properties_by_id, None)
|
|
113
120
|
|
|
114
|
-
containers = self._create_containers(container_properties_by_id)
|
|
121
|
+
containers = self._create_containers(container_properties_by_id, rules.enum) # type: ignore[arg-type]
|
|
115
122
|
|
|
116
|
-
views,
|
|
123
|
+
views, view_node_type_filters = self._create_views_with_node_types(view_properties_by_id)
|
|
124
|
+
if rules.nodes:
|
|
125
|
+
node_types = NodeApplyDict(
|
|
126
|
+
[node.as_node() for node in rules.nodes]
|
|
127
|
+
+ [dm.NodeApply(node.space, node.external_id) for node in view_node_type_filters]
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
node_types = NodeApplyDict([dm.NodeApply(node.space, node.external_id) for node in view_node_type_filters])
|
|
117
131
|
|
|
118
132
|
last_schema: DMSSchema | None = None
|
|
119
133
|
if self.rules.last:
|
|
@@ -184,7 +198,7 @@ class _DMSExporter:
|
|
|
184
198
|
def _create_views_with_node_types(
|
|
185
199
|
self,
|
|
186
200
|
view_properties_by_id: dict[dm.ViewId, list[DMSProperty]],
|
|
187
|
-
) -> tuple[ViewApplyDict,
|
|
201
|
+
) -> tuple[ViewApplyDict, set[dm.NodeId]]:
|
|
188
202
|
input_views = list(self.rules.views)
|
|
189
203
|
if self.rules.last:
|
|
190
204
|
existing = {view.view.as_id() for view in input_views}
|
|
@@ -245,17 +259,19 @@ class _DMSExporter:
|
|
|
245
259
|
# as they are expected for the solution model.
|
|
246
260
|
unique_node_types.add(dm.NodeId(space=view.space, external_id=view.external_id))
|
|
247
261
|
|
|
248
|
-
return views,
|
|
249
|
-
[dm.NodeApply(space=node.space, external_id=node.external_id) for node in unique_node_types]
|
|
250
|
-
)
|
|
262
|
+
return views, unique_node_types
|
|
251
263
|
|
|
252
264
|
@classmethod
|
|
253
265
|
def _create_edge_type_from_prop(cls, prop: DMSProperty) -> dm.DirectRelationReference:
|
|
254
|
-
if isinstance(prop.
|
|
266
|
+
if isinstance(prop.connection, EdgeEntity) and prop.connection.edge_type is not None:
|
|
267
|
+
return prop.connection.edge_type.as_reference()
|
|
268
|
+
elif isinstance(prop.reference, ReferenceEntity):
|
|
255
269
|
ref_view_prop = prop.reference.as_view_property_id()
|
|
256
270
|
return cls._create_edge_type_from_view_id(cast(dm.ViewId, ref_view_prop.source), ref_view_prop.property)
|
|
257
|
-
|
|
271
|
+
elif isinstance(prop.value_type, ViewEntity):
|
|
258
272
|
return cls._create_edge_type_from_view_id(prop.view.as_id(), prop.view_property)
|
|
273
|
+
else:
|
|
274
|
+
raise NeatTypeError(f"Invalid valueType {prop.value_type!r}")
|
|
259
275
|
|
|
260
276
|
@staticmethod
|
|
261
277
|
def _create_edge_type_from_view_id(view_id: dm.ViewId, property_: str) -> dm.DirectRelationReference:
|
|
@@ -268,7 +284,12 @@ class _DMSExporter:
|
|
|
268
284
|
def _create_containers(
|
|
269
285
|
self,
|
|
270
286
|
container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]],
|
|
287
|
+
enum: Collection[DMSEnum] | None,
|
|
271
288
|
) -> ContainerApplyDict:
|
|
289
|
+
enum_values_by_collection: dict[ClassEntity, list[DMSEnum]] = defaultdict(list)
|
|
290
|
+
for enum_value in enum or []:
|
|
291
|
+
enum_values_by_collection[enum_value.collection].append(enum_value)
|
|
292
|
+
|
|
272
293
|
containers = list(self.rules.containers or [])
|
|
273
294
|
if self.rules.last:
|
|
274
295
|
existing = {container.container.as_id() for container in containers}
|
|
@@ -297,11 +318,27 @@ class _DMSExporter:
|
|
|
297
318
|
type_cls = prop.value_type.dms
|
|
298
319
|
else:
|
|
299
320
|
type_cls = dm.DirectRelation
|
|
300
|
-
|
|
321
|
+
|
|
322
|
+
args: dict[str, Any] = {}
|
|
301
323
|
if issubclass(type_cls, ListablePropertyType):
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
324
|
+
args["is_list"] = prop.is_list or False
|
|
325
|
+
if isinstance(prop.value_type, Double | Float) and isinstance(prop.value_type.unit, UnitEntity):
|
|
326
|
+
args["unit"] = prop.value_type.unit.as_reference()
|
|
327
|
+
if isinstance(prop.value_type, Enum):
|
|
328
|
+
if prop.value_type.collection not in enum_values_by_collection:
|
|
329
|
+
raise ResourceNotFoundError(
|
|
330
|
+
prop.value_type.collection, "enum collection", prop.container, "container"
|
|
331
|
+
)
|
|
332
|
+
args["unknown_value"] = prop.value_type.unknown_value
|
|
333
|
+
args["values"] = {
|
|
334
|
+
value.value: DMSEnumValue(
|
|
335
|
+
name=value.name,
|
|
336
|
+
description=value.description,
|
|
337
|
+
)
|
|
338
|
+
for value in enum_values_by_collection[prop.value_type.collection]
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
type_ = type_cls(**args)
|
|
305
342
|
container.properties[prop.container_property] = dm.ContainerProperty(
|
|
306
343
|
type=type_,
|
|
307
344
|
# If not set, nullable is True and immutable is False
|
|
@@ -463,7 +500,7 @@ class _DMSExporter:
|
|
|
463
500
|
description=prop.description,
|
|
464
501
|
**extra_args,
|
|
465
502
|
)
|
|
466
|
-
elif prop.connection
|
|
503
|
+
elif isinstance(prop.connection, EdgeEntity):
|
|
467
504
|
if isinstance(prop.value_type, ViewEntity):
|
|
468
505
|
source_view_id = prop.value_type.as_id()
|
|
469
506
|
else:
|
|
@@ -472,6 +509,9 @@ class _DMSExporter:
|
|
|
472
509
|
"If this error occurs it is a bug in NEAT, please report"
|
|
473
510
|
f"Debug Info, Invalid valueType edge: {prop.model_dump_json()}"
|
|
474
511
|
)
|
|
512
|
+
edge_source: dm.ViewId | None = None
|
|
513
|
+
if prop.connection.properties is not None:
|
|
514
|
+
edge_source = prop.connection.properties.as_id()
|
|
475
515
|
edge_cls: type[dm.EdgeConnectionApply] = dm.MultiEdgeConnectionApply
|
|
476
516
|
# If is_list is not set, we default to a MultiEdgeConnection
|
|
477
517
|
if prop.is_list is False:
|
|
@@ -483,13 +523,11 @@ class _DMSExporter:
|
|
|
483
523
|
direction="outwards",
|
|
484
524
|
name=prop.name,
|
|
485
525
|
description=prop.description,
|
|
526
|
+
edge_source=edge_source,
|
|
486
527
|
)
|
|
487
|
-
elif prop.connection
|
|
488
|
-
reverse_prop_id
|
|
489
|
-
if isinstance(prop.value_type,
|
|
490
|
-
source_view_id = prop.value_type.as_view_id()
|
|
491
|
-
reverse_prop_id = prop.value_type.property_
|
|
492
|
-
elif isinstance(prop.value_type, ViewEntity):
|
|
528
|
+
elif isinstance(prop.connection, ReverseConnectionEntity):
|
|
529
|
+
reverse_prop_id = prop.connection.property_
|
|
530
|
+
if isinstance(prop.value_type, ViewEntity):
|
|
493
531
|
source_view_id = prop.value_type.as_id()
|
|
494
532
|
else:
|
|
495
533
|
# Should have been validated.
|
|
@@ -498,6 +536,7 @@ class _DMSExporter:
|
|
|
498
536
|
f"Debug Info, Invalid valueType reverse connection: {prop.model_dump_json()}"
|
|
499
537
|
)
|
|
500
538
|
reverse_prop: DMSProperty | None = None
|
|
539
|
+
edge_source = None
|
|
501
540
|
if reverse_prop_id is not None:
|
|
502
541
|
reverse_prop = next(
|
|
503
542
|
(
|
|
@@ -507,6 +546,12 @@ class _DMSExporter:
|
|
|
507
546
|
),
|
|
508
547
|
None,
|
|
509
548
|
)
|
|
549
|
+
if (
|
|
550
|
+
reverse_prop
|
|
551
|
+
and isinstance(reverse_prop.connection, EdgeEntity)
|
|
552
|
+
and reverse_prop.connection.properties is not None
|
|
553
|
+
):
|
|
554
|
+
edge_source = reverse_prop.connection.properties.as_id()
|
|
510
555
|
|
|
511
556
|
if reverse_prop is None:
|
|
512
557
|
warnings.warn(
|
|
@@ -520,7 +565,7 @@ class _DMSExporter:
|
|
|
520
565
|
stacklevel=2,
|
|
521
566
|
)
|
|
522
567
|
|
|
523
|
-
if reverse_prop is None or reverse_prop.connection
|
|
568
|
+
if reverse_prop is None or isinstance(reverse_prop.connection, EdgeEntity):
|
|
524
569
|
inwards_edge_cls = (
|
|
525
570
|
dm.MultiEdgeConnectionApply if prop.is_list in [True, None] else SingleEdgeConnectionApply
|
|
526
571
|
)
|
|
@@ -530,6 +575,7 @@ class _DMSExporter:
|
|
|
530
575
|
name=prop.name,
|
|
531
576
|
description=prop.description,
|
|
532
577
|
direction="inwards",
|
|
578
|
+
edge_source=edge_source,
|
|
533
579
|
)
|
|
534
580
|
elif reverse_prop_id and reverse_prop and reverse_prop.connection == "direct":
|
|
535
581
|
reverse_direct_cls = (
|