cognite-neat 0.75.8__py3-none-any.whl → 0.76.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/app/api/configuration.py +4 -9
- cognite/neat/app/api/routers/configuration.py +2 -1
- cognite/neat/app/api/routers/crud.py +5 -5
- cognite/neat/app/api/routers/data_exploration.py +3 -1
- cognite/neat/app/api/routers/rules.py +3 -3
- cognite/neat/app/api/routers/workflows.py +3 -3
- cognite/neat/app/ui/neat-app/build/asset-manifest.json +3 -3
- cognite/neat/app/ui/neat-app/build/index.html +1 -1
- cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js → main.ec7f72e2.js} +3 -3
- cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js.map → main.ec7f72e2.js.map} +1 -1
- cognite/neat/config.py +147 -12
- cognite/neat/constants.py +1 -0
- cognite/neat/graph/exceptions.py +1 -2
- cognite/neat/legacy/graph/exceptions.py +1 -2
- cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +1 -2
- cognite/neat/legacy/graph/loaders/_asset_loader.py +8 -13
- cognite/neat/legacy/graph/loaders/_base.py +2 -4
- cognite/neat/legacy/graph/loaders/_exceptions.py +1 -3
- cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +4 -8
- cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +2 -4
- cognite/neat/legacy/graph/loaders/rdf_to_dms.py +2 -4
- cognite/neat/legacy/graph/loaders/validator.py +1 -1
- cognite/neat/legacy/graph/transformations/transformer.py +1 -2
- cognite/neat/legacy/rules/exporters/_rules2dms.py +1 -2
- cognite/neat/legacy/rules/exporters/_validation.py +4 -8
- cognite/neat/legacy/rules/importers/_base.py +0 -4
- cognite/neat/legacy/rules/importers/_dms2rules.py +0 -2
- cognite/neat/legacy/rules/models/rdfpath.py +1 -2
- cognite/neat/legacy/workflows/examples/Export_DMS/workflow.yaml +89 -0
- cognite/neat/legacy/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +152 -0
- cognite/neat/legacy/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +139 -0
- cognite/neat/legacy/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +270 -0
- cognite/neat/legacy/workflows/examples/Import_DMS/workflow.yaml +65 -0
- cognite/neat/legacy/workflows/examples/Ontology_to_Data_Model/workflow.yaml +116 -0
- cognite/neat/legacy/workflows/examples/Validate_Rules/workflow.yaml +67 -0
- cognite/neat/legacy/workflows/examples/Validate_Solution_Model/workflow.yaml +64 -0
- cognite/neat/legacy/workflows/examples/Visualize_Data_Model_Using_Mock_Graph/workflow.yaml +95 -0
- cognite/neat/legacy/workflows/examples/Visualize_Semantic_Data_Model/workflow.yaml +111 -0
- cognite/neat/rules/exporters/_models.py +3 -0
- cognite/neat/rules/exporters/_rules2dms.py +46 -4
- cognite/neat/rules/exporters/_rules2excel.py +2 -11
- cognite/neat/rules/exporters/_validation.py +6 -8
- cognite/neat/rules/importers/_base.py +8 -4
- cognite/neat/rules/importers/_dms2rules.py +321 -129
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +2 -4
- cognite/neat/rules/importers/_dtdl2rules/spec.py +2 -4
- cognite/neat/rules/importers/_owl2rules/_owl2rules.py +2 -4
- cognite/neat/rules/importers/_spreadsheet2rules.py +18 -16
- cognite/neat/rules/importers/_yaml2rules.py +2 -4
- cognite/neat/rules/issues/base.py +3 -0
- cognite/neat/rules/issues/dms.py +144 -58
- cognite/neat/rules/issues/fileread.py +41 -0
- cognite/neat/rules/issues/formatters.py +3 -1
- cognite/neat/rules/issues/importing.py +155 -0
- cognite/neat/rules/issues/spreadsheet.py +12 -9
- cognite/neat/rules/models/entities.py +30 -8
- cognite/neat/rules/models/rdfpath.py +1 -2
- cognite/neat/rules/models/rules/_base.py +5 -6
- cognite/neat/rules/models/rules/_dms_architect_rules.py +494 -333
- cognite/neat/rules/models/rules/_dms_rules_write.py +43 -52
- cognite/neat/rules/models/rules/_dms_schema.py +112 -22
- cognite/neat/rules/models/rules/_domain_rules.py +5 -0
- cognite/neat/rules/models/rules/_information_rules.py +13 -6
- cognite/neat/rules/models/wrapped_entities.py +166 -0
- cognite/neat/utils/cdf_loaders/_data_modeling.py +3 -1
- cognite/neat/utils/cdf_loaders/_ingestion.py +2 -4
- cognite/neat/utils/spreadsheet.py +2 -4
- cognite/neat/utils/utils.py +2 -4
- cognite/neat/workflows/base.py +5 -5
- cognite/neat/workflows/manager.py +32 -22
- cognite/neat/workflows/model.py +3 -3
- cognite/neat/workflows/steps/lib/__init__.py +0 -7
- cognite/neat/workflows/steps/lib/current/__init__.py +6 -0
- cognite/neat/workflows/steps/lib/{rules_exporter.py → current/rules_exporter.py} +8 -8
- cognite/neat/workflows/steps/lib/{rules_importer.py → current/rules_importer.py} +4 -4
- cognite/neat/workflows/steps/lib/io/__init__.py +1 -0
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_contextualization.py +2 -2
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_extractor.py +9 -9
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_loader.py +9 -9
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_store.py +4 -4
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_transformer.py +2 -2
- cognite/neat/workflows/steps/lib/{v1 → legacy}/rules_exporter.py +15 -17
- cognite/neat/workflows/steps/lib/{v1 → legacy}/rules_importer.py +7 -7
- cognite/neat/workflows/steps/step_model.py +5 -9
- cognite/neat/workflows/steps_registry.py +20 -11
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/RECORD +98 -86
- cognite/neat/app/api/data_classes/configuration.py +0 -121
- /cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js.LICENSE.txt → main.ec7f72e2.js.LICENSE.txt} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_extractor.py → current/graph_extractor.py} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_loader.py → current/graph_loader.py} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_store.py → current/graph_store.py} +0 -0
- /cognite/neat/workflows/steps/lib/{rules_validator.py → current/rules_validator.py} +0 -0
- /cognite/neat/workflows/steps/lib/{io_steps.py → io/io_steps.py} +0 -0
- /cognite/neat/workflows/steps/lib/{v1 → legacy}/__init__.py +0 -0
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/entry_points.txt +0 -0
|
@@ -46,14 +46,12 @@ class YAMLImporter(BaseImporter):
|
|
|
46
46
|
return cls(yaml.safe_load(filepath.read_text()), filepaths=[filepath])
|
|
47
47
|
|
|
48
48
|
@overload
|
|
49
|
-
def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> Rules:
|
|
50
|
-
...
|
|
49
|
+
def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> Rules: ...
|
|
51
50
|
|
|
52
51
|
@overload
|
|
53
52
|
def to_rules(
|
|
54
53
|
self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
|
|
55
|
-
) -> tuple[Rules | None, IssueList]:
|
|
56
|
-
...
|
|
54
|
+
) -> tuple[Rules | None, IssueList]: ...
|
|
57
55
|
|
|
58
56
|
def to_rules(
|
|
59
57
|
self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
|
|
@@ -78,6 +78,9 @@ class NeatValidationError(ValidationIssue, ABC):
|
|
|
78
78
|
all_errors.append(DefaultPydanticError.from_pydantic_error(error))
|
|
79
79
|
return all_errors
|
|
80
80
|
|
|
81
|
+
def as_exception(self) -> Exception:
|
|
82
|
+
return ValueError(self.message())
|
|
83
|
+
|
|
81
84
|
|
|
82
85
|
@dataclass(frozen=True)
|
|
83
86
|
class DefaultPydanticError(NeatValidationError):
|
cognite/neat/rules/issues/dms.py
CHANGED
|
@@ -16,15 +16,16 @@ __all__ = [
|
|
|
16
16
|
"MissingParentViewError",
|
|
17
17
|
"MissingSourceViewError",
|
|
18
18
|
"MissingEdgeViewError",
|
|
19
|
-
"DuplicatedViewInDataModelError",
|
|
20
19
|
"DirectRelationMissingSourceWarning",
|
|
20
|
+
"ViewModelVersionNotMatchingWarning",
|
|
21
|
+
"ViewModelSpaceNotMatchingWarning",
|
|
22
|
+
"DuplicatedViewInDataModelError",
|
|
21
23
|
"ContainerPropertyUsedMultipleTimesError",
|
|
22
|
-
"DirectRelationListWarning",
|
|
23
|
-
"ReverseOfDirectRelationListWarning",
|
|
24
24
|
"EmptyContainerWarning",
|
|
25
|
-
"
|
|
25
|
+
"UnsupportedConnectionWarning",
|
|
26
26
|
"MultipleReferenceWarning",
|
|
27
27
|
"HasDataFilterOnNoPropertiesViewWarning",
|
|
28
|
+
"ReverseRelationMissingOtherSideWarning",
|
|
28
29
|
"NodeTypeFilterOnParentViewWarning",
|
|
29
30
|
"ChangingContainerError",
|
|
30
31
|
"ChangingViewError",
|
|
@@ -32,13 +33,11 @@ __all__ = [
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
@dataclass(frozen=True)
|
|
35
|
-
class DMSSchemaError(NeatValidationError, ABC):
|
|
36
|
-
...
|
|
36
|
+
class DMSSchemaError(NeatValidationError, ABC): ...
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
@dataclass(frozen=True)
|
|
40
|
-
class DMSSchemaWarning(ValidationWarning, ABC):
|
|
41
|
-
...
|
|
40
|
+
class DMSSchemaWarning(ValidationWarning, ABC): ...
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
@dataclass(frozen=True)
|
|
@@ -204,6 +203,53 @@ class DirectRelationMissingSourceWarning(DMSSchemaWarning):
|
|
|
204
203
|
return output
|
|
205
204
|
|
|
206
205
|
|
|
206
|
+
@dataclass(frozen=True)
|
|
207
|
+
class ViewModelVersionNotMatchingWarning(DMSSchemaWarning):
|
|
208
|
+
description = "The view model version does not match the data model version"
|
|
209
|
+
fix = "Update the view model version to match the data model version"
|
|
210
|
+
error_name: ClassVar[str] = "ViewModelVersionNotMatching"
|
|
211
|
+
view_ids: list[dm.ViewId]
|
|
212
|
+
data_model_version: str
|
|
213
|
+
|
|
214
|
+
def message(self) -> str:
|
|
215
|
+
return (
|
|
216
|
+
f"The version in the views {self.view_ids} does not match the version in the data model "
|
|
217
|
+
f"{self.data_model_version}. This is not recommended as it easily leads to confusion and errors. "
|
|
218
|
+
f"Views are very cheap and we recommend you update the version of the views to match the data"
|
|
219
|
+
f" model version, irrespective of whether the views have changed or not."
|
|
220
|
+
" The approach of having same version of model components as the model itself "
|
|
221
|
+
" is a globally recognized data modeling practice."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def dump(self) -> dict[str, Any]:
|
|
225
|
+
output = super().dump()
|
|
226
|
+
output["view_id"] = [view_id.dump() for view_id in self.view_ids]
|
|
227
|
+
output["data_model_version"] = self.data_model_version
|
|
228
|
+
return output
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass(frozen=True)
|
|
232
|
+
class ViewModelSpaceNotMatchingWarning(DMSSchemaWarning):
|
|
233
|
+
description = "The view model space does not match the data model space"
|
|
234
|
+
fix = "Update the view model space to match the data model space"
|
|
235
|
+
error_name: ClassVar[str] = "ViewModelSpaceNotMatching"
|
|
236
|
+
view_ids: list[dm.ViewId]
|
|
237
|
+
data_model_space: str
|
|
238
|
+
|
|
239
|
+
def message(self) -> str:
|
|
240
|
+
return (
|
|
241
|
+
f"The space in the views {self.view_ids} does not match the space in the data model "
|
|
242
|
+
f"{self.data_model_space}. This is not recommended as it easily leads to confusion and errors. "
|
|
243
|
+
f"Views are very cheap and we recommend you always have views in the same space as the data model."
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def dump(self) -> dict[str, Any]:
|
|
247
|
+
output = super().dump()
|
|
248
|
+
output["view_id"] = [view_id.dump() for view_id in self.view_ids]
|
|
249
|
+
output["data_model_space"] = self.data_model_space
|
|
250
|
+
return output
|
|
251
|
+
|
|
252
|
+
|
|
207
253
|
@dataclass(frozen=True)
|
|
208
254
|
class ContainerPropertyUsedMultipleTimesError(DMSSchemaError):
|
|
209
255
|
description = "The container property is used multiple times by the same view property"
|
|
@@ -300,92 +346,62 @@ class ChangingViewError(DMSSchemaError):
|
|
|
300
346
|
|
|
301
347
|
|
|
302
348
|
@dataclass(frozen=True)
|
|
303
|
-
class
|
|
304
|
-
description = "The container
|
|
305
|
-
fix = "
|
|
306
|
-
error_name: ClassVar[str] = "
|
|
307
|
-
view_id: dm.ViewId
|
|
349
|
+
class EmptyContainerWarning(DMSSchemaWarning):
|
|
350
|
+
description = "The container is empty"
|
|
351
|
+
fix = "Add data to the container"
|
|
352
|
+
error_name: ClassVar[str] = "EmptyContainerWarning"
|
|
308
353
|
container_id: dm.ContainerId
|
|
309
|
-
property: str
|
|
310
354
|
|
|
311
355
|
def message(self) -> str:
|
|
312
356
|
return (
|
|
313
|
-
f"The
|
|
314
|
-
|
|
315
|
-
f"the view {self.view_id}.{self.property} instead"
|
|
357
|
+
f"The container {self.container_id} is empty. Is this intended? Skipping this container "
|
|
358
|
+
"in the data model."
|
|
316
359
|
)
|
|
317
360
|
|
|
318
361
|
def dump(self) -> dict[str, Any]:
|
|
319
362
|
output = super().dump()
|
|
320
|
-
output["view_id"] = self.view_id.dump()
|
|
321
363
|
output["container_id"] = self.container_id.dump()
|
|
322
|
-
output["property"] = self.property
|
|
323
364
|
return output
|
|
324
365
|
|
|
325
366
|
|
|
326
367
|
@dataclass(frozen=True)
|
|
327
|
-
class
|
|
328
|
-
description =
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
fix = "Make the property into a multiedge connection instead"
|
|
332
|
-
error_name: ClassVar[str] = "ReverseOfDirectRelationListWarning"
|
|
368
|
+
class UnsupportedConnectionWarning(DMSSchemaWarning):
|
|
369
|
+
description = "The connection type is not supported by neat"
|
|
370
|
+
fix = "Change the connection to a supported type"
|
|
371
|
+
error_name: ClassVar[str] = "UnsupportedConnectionWarning"
|
|
333
372
|
view_id: dm.ViewId
|
|
334
373
|
property: str
|
|
374
|
+
connection: str
|
|
335
375
|
|
|
336
376
|
def message(self) -> str:
|
|
337
377
|
return (
|
|
338
|
-
f"The
|
|
339
|
-
|
|
340
|
-
"will be converted from a reverse direct relation to an MultiEdgeConnection instead"
|
|
378
|
+
f"The connection {self.connection} in {self.view_id}.{self.property} is not supported."
|
|
379
|
+
"This property will be ignored."
|
|
341
380
|
)
|
|
342
381
|
|
|
343
382
|
def dump(self) -> dict[str, Any]:
|
|
344
383
|
output = super().dump()
|
|
345
384
|
output["view_id"] = self.view_id.dump()
|
|
346
385
|
output["property"] = self.property
|
|
386
|
+
output["connection"] = self.connection
|
|
347
387
|
return output
|
|
348
388
|
|
|
349
389
|
|
|
350
390
|
@dataclass(frozen=True)
|
|
351
|
-
class
|
|
352
|
-
description = "The
|
|
353
|
-
fix = "Add
|
|
354
|
-
error_name: ClassVar[str] = "
|
|
355
|
-
container_id: dm.ContainerId
|
|
356
|
-
|
|
357
|
-
def message(self) -> str:
|
|
358
|
-
return (
|
|
359
|
-
f"The container {self.container_id} is empty. Is this intended? Skipping this container "
|
|
360
|
-
"in the data model."
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
def dump(self) -> dict[str, Any]:
|
|
364
|
-
output = super().dump()
|
|
365
|
-
output["container_id"] = self.container_id.dump()
|
|
366
|
-
return output
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
@dataclass(frozen=True)
|
|
370
|
-
class UnsupportedRelationWarning(DMSSchemaWarning):
|
|
371
|
-
description = "The relatio type is not supported by neat"
|
|
372
|
-
fix = "Change the relation to a supported type"
|
|
373
|
-
error_name: ClassVar[str] = "UnsupportedRelationWarning"
|
|
391
|
+
class ReverseRelationMissingOtherSideWarning(DMSSchemaWarning):
|
|
392
|
+
description = "The relation is missing the other side"
|
|
393
|
+
fix = "Add the other side of the relation"
|
|
394
|
+
error_name: ClassVar[str] = "ReverseRelationMissingOtherSideWarning"
|
|
374
395
|
view_id: dm.ViewId
|
|
375
396
|
property: str
|
|
376
|
-
relation: str
|
|
377
397
|
|
|
378
398
|
def message(self) -> str:
|
|
379
|
-
return
|
|
380
|
-
f"The relation {self.relation} in {self.view_id}.{self.property} is not supported."
|
|
381
|
-
"This property will be ignored."
|
|
382
|
-
)
|
|
399
|
+
return f"The reverse relation specified in {self.view_id}.{self.property} is missing the other side."
|
|
383
400
|
|
|
384
401
|
def dump(self) -> dict[str, Any]:
|
|
385
402
|
output = super().dump()
|
|
386
403
|
output["view_id"] = self.view_id.dump()
|
|
387
404
|
output["property"] = self.property
|
|
388
|
-
output["relation"] = self.relation
|
|
389
405
|
return output
|
|
390
406
|
|
|
391
407
|
|
|
@@ -429,7 +445,7 @@ class HasDataFilterOnNoPropertiesViewWarning(DMSSchemaWarning):
|
|
|
429
445
|
@dataclass(frozen=True)
|
|
430
446
|
class NodeTypeFilterOnParentViewWarning(DMSSchemaWarning):
|
|
431
447
|
description = (
|
|
432
|
-
"Setting a node type filter on a parent view. This is
|
|
448
|
+
"Setting a node type filter on a parent view. This is not "
|
|
433
449
|
"recommended as parent views are typically used for multiple type of nodes."
|
|
434
450
|
)
|
|
435
451
|
fix = "Use a HasData filter instead"
|
|
@@ -446,3 +462,73 @@ class NodeTypeFilterOnParentViewWarning(DMSSchemaWarning):
|
|
|
446
462
|
output = super().dump()
|
|
447
463
|
output["view_id"] = self.view_id.dump()
|
|
448
464
|
return output
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
@dataclass(frozen=True)
|
|
468
|
+
class HasDataFilterOnViewWithReferencesWarning(DMSSchemaWarning):
|
|
469
|
+
description = (
|
|
470
|
+
"Setting a hasData filter on a solution view which reference other containers is not recommended."
|
|
471
|
+
"This will lead to no nodes being returned when querying the solution view."
|
|
472
|
+
)
|
|
473
|
+
fix = "Use a node type filter instead"
|
|
474
|
+
error_name: ClassVar[str] = "HasDataFilterOnReferencedViewWarning"
|
|
475
|
+
|
|
476
|
+
view_id: dm.ViewId
|
|
477
|
+
references: list[dm.ViewId]
|
|
478
|
+
|
|
479
|
+
def message(self) -> str:
|
|
480
|
+
return (
|
|
481
|
+
f"Setting a hasData filter on view {self.view_id} which references other views {self.references}. "
|
|
482
|
+
"This is not recommended as it will lead to no nodes being returned when querying the solution view."
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
def dump(self) -> dict[str, Any]:
|
|
486
|
+
output = super().dump()
|
|
487
|
+
output["view_id"] = self.view_id.dump()
|
|
488
|
+
output["references"] = [view.dump() for view in sorted(self.references, key=lambda x: x.as_tuple())]
|
|
489
|
+
return output
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@dataclass(frozen=True)
|
|
493
|
+
class OtherDataModelsInSpaceWarning(DMSSchemaWarning):
|
|
494
|
+
description = "The space contains other data models"
|
|
495
|
+
fix = "Move the data models to their respective spaces."
|
|
496
|
+
error_name: ClassVar[str] = "OtherDataModelsInSpaceWarning"
|
|
497
|
+
space: str
|
|
498
|
+
data_models: list[dm.DataModelId]
|
|
499
|
+
|
|
500
|
+
def message(self) -> str:
|
|
501
|
+
return (
|
|
502
|
+
f"The space {self.space} contains data models from other spaces: {self.data_models}. {self.fix}"
|
|
503
|
+
" It is recommended to only have one data model per space. This avoid potential conflicts and "
|
|
504
|
+
"makes it easier to manage the data models."
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def dump(self) -> dict[str, Any]:
|
|
508
|
+
output = super().dump()
|
|
509
|
+
output["space"] = self.space
|
|
510
|
+
output["data_models"] = [data_model.dump() for data_model in self.data_models]
|
|
511
|
+
return output
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
@dataclass(frozen=True)
|
|
515
|
+
class SolutionOnTopOfSolutionModelWarning(DMSSchemaWarning):
|
|
516
|
+
description = "The data model is a solution on top of another solution"
|
|
517
|
+
fix = "Use the base solution as the data model"
|
|
518
|
+
error_name: ClassVar[str] = "SolutionOnTopOfSolutionModelWarning"
|
|
519
|
+
data_model: dm.DataModelId
|
|
520
|
+
base_data_model: dm.DataModelId
|
|
521
|
+
|
|
522
|
+
def message(self) -> str:
|
|
523
|
+
return (
|
|
524
|
+
f"The data model {self.data_model} is a solution model on top of another solution {self.base_data_model} "
|
|
525
|
+
"model. This is not recommended as it can lead to confusion and errors. It is very hard to "
|
|
526
|
+
"maintain a nested structure of solution models. Instead, only build solution models on "
|
|
527
|
+
"top of enterprise models."
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
def dump(self) -> dict[str, Any]:
|
|
531
|
+
output = super().dump()
|
|
532
|
+
output["data_model"] = self.data_model.dump()
|
|
533
|
+
output["base_data_model"] = self.base_data_model.dump()
|
|
534
|
+
return output
|
|
@@ -9,10 +9,13 @@ __all__ = [
|
|
|
9
9
|
"InvalidFileFormatWarning",
|
|
10
10
|
"UnsupportedSpecWarning",
|
|
11
11
|
"UnknownItemWarning",
|
|
12
|
+
"FailedLoadWarning",
|
|
12
13
|
"BugInImporterWarning",
|
|
13
14
|
"FileReadError",
|
|
14
15
|
"FileNotFoundError",
|
|
15
16
|
"FileNotAFileError",
|
|
17
|
+
"InvalidFileFormatError",
|
|
18
|
+
"FailedStringLoadError",
|
|
16
19
|
]
|
|
17
20
|
|
|
18
21
|
|
|
@@ -96,6 +99,26 @@ class UnknownItemWarning(FileReadWarning):
|
|
|
96
99
|
return output
|
|
97
100
|
|
|
98
101
|
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class FailedLoadWarning(FileReadWarning):
|
|
104
|
+
description = "The file content is invalid"
|
|
105
|
+
fix = "Check if the file content is valid"
|
|
106
|
+
|
|
107
|
+
expected_format: str
|
|
108
|
+
error_message: str
|
|
109
|
+
|
|
110
|
+
def message(self) -> str:
|
|
111
|
+
return (
|
|
112
|
+
f"Failed to load {self.filepath.name}. Expected format: {self.expected_format}. Error: {self.error_message}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def dump(self) -> dict[str, str | None]:
|
|
116
|
+
output = super().dump()
|
|
117
|
+
output["expected_format"] = self.expected_format
|
|
118
|
+
output["error_message"] = self.error_message
|
|
119
|
+
return output
|
|
120
|
+
|
|
121
|
+
|
|
99
122
|
@dataclass(frozen=True)
|
|
100
123
|
class BugInImporterWarning(FileReadWarning):
|
|
101
124
|
description = "A bug was raised during reading."
|
|
@@ -143,6 +166,24 @@ class InvalidFileFormatError(FileReadError):
|
|
|
143
166
|
return f"{self.filepath} is not in the expected format. Expected format: {self.expected_format}."
|
|
144
167
|
|
|
145
168
|
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class FailedStringLoadError(NeatValidationError):
|
|
171
|
+
description = "The file content is invalid"
|
|
172
|
+
fix = "Check if the file content is valid"
|
|
173
|
+
|
|
174
|
+
expected_format: str
|
|
175
|
+
error_message: str
|
|
176
|
+
|
|
177
|
+
def message(self) -> str:
|
|
178
|
+
return f"Failed to load string. Expected format: {self.expected_format}. Error: {self.error_message}"
|
|
179
|
+
|
|
180
|
+
def dump(self) -> dict[str, str | None]:
|
|
181
|
+
output = super().dump()
|
|
182
|
+
output["expected_format"] = self.expected_format
|
|
183
|
+
output["error_message"] = self.error_message
|
|
184
|
+
return output
|
|
185
|
+
|
|
186
|
+
|
|
146
187
|
@dataclass(frozen=True)
|
|
147
188
|
class NoFilesFoundError(FileReadError):
|
|
148
189
|
description = "No files were found in the directory"
|
|
@@ -79,5 +79,7 @@ class BasicHTML(Formatter):
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
FORMATTER_BY_NAME: dict[str, type[Formatter]] = {
|
|
82
|
-
subclass.__name__: subclass
|
|
82
|
+
subclass.__name__: subclass # type: ignore[type-abstract]
|
|
83
|
+
for subclass in Formatter.__subclasses__()
|
|
84
|
+
if ABC not in subclass.__bases__ # type: ignore[type-abstract]
|
|
83
85
|
}
|
|
@@ -10,11 +10,19 @@ __all__ = [
|
|
|
10
10
|
"IgnoredComponentWarning",
|
|
11
11
|
"UnknownPropertyWarning",
|
|
12
12
|
"UnknownValueTypeWarning",
|
|
13
|
+
"MissingContainerWarning",
|
|
14
|
+
"MissingContainerPropertyWarning",
|
|
15
|
+
"MultipleDataModelsWarning",
|
|
16
|
+
"UnknownPropertyTypeWarning",
|
|
17
|
+
"FailedToInferValueTypeWarning",
|
|
18
|
+
"UnknownContainerConstraintWarning",
|
|
19
|
+
"NoDataModelError",
|
|
13
20
|
"ModelImportError",
|
|
14
21
|
"InvalidComponentError",
|
|
15
22
|
"MissingParentDefinitionError",
|
|
16
23
|
"MissingIdentifierError",
|
|
17
24
|
"UnsupportedPropertyTypeError",
|
|
25
|
+
"APIError",
|
|
18
26
|
]
|
|
19
27
|
|
|
20
28
|
|
|
@@ -132,12 +140,159 @@ class UnknownValueTypeWarning(ModelImportWarning):
|
|
|
132
140
|
)
|
|
133
141
|
|
|
134
142
|
|
|
143
|
+
@dataclass(frozen=True)
|
|
144
|
+
class MultipleDataModelsWarning(ModelImportWarning):
|
|
145
|
+
description = "Multiple data models detected. This is not supported."
|
|
146
|
+
fix = "Remove the extra data models."
|
|
147
|
+
|
|
148
|
+
data_models: list[str]
|
|
149
|
+
|
|
150
|
+
def message(self) -> str:
|
|
151
|
+
return f"Multiple data models detected: {self.data_models}. Will only import the first one."
|
|
152
|
+
|
|
153
|
+
def dump(self) -> dict[str, list[str]]:
|
|
154
|
+
return {"data_models": self.data_models}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(frozen=True)
|
|
158
|
+
class MissingContainerWarning(ModelImportWarning):
|
|
159
|
+
description = "Missing container definition."
|
|
160
|
+
fix = "Add a container definition."
|
|
161
|
+
|
|
162
|
+
view_id: str
|
|
163
|
+
property_: str
|
|
164
|
+
container_id: str
|
|
165
|
+
|
|
166
|
+
def message(self) -> str:
|
|
167
|
+
return (
|
|
168
|
+
f"Container '{self.container_id}' is missing. "
|
|
169
|
+
f"Will skip property '{self.property_}' of view '{self.view_id}'."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def dump(self) -> dict[str, str]:
|
|
173
|
+
return {"view_id": self.view_id, "property": self.property_, "container_id": self.container_id}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass(frozen=True)
|
|
177
|
+
class MissingContainerPropertyWarning(ModelImportWarning):
|
|
178
|
+
description = "Missing container property definition."
|
|
179
|
+
fix = "Add a container property definition."
|
|
180
|
+
|
|
181
|
+
view_id: str
|
|
182
|
+
property_: str
|
|
183
|
+
container_id: str
|
|
184
|
+
|
|
185
|
+
def message(self) -> str:
|
|
186
|
+
return (
|
|
187
|
+
f"Container '{self.container_id}' is missing property '{self.property_}'. "
|
|
188
|
+
f"This property will be skipped for view '{self.view_id}'."
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def dump(self) -> dict[str, str]:
|
|
192
|
+
return {"view_id": self.view_id, "property": self.property_, "container_id": self.container_id}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(frozen=True)
|
|
196
|
+
class UnknownPropertyTypeWarning(ModelImportWarning):
|
|
197
|
+
description = "Unknown property type. This will be ignored in the imports."
|
|
198
|
+
fix = "Set to a supported property type."
|
|
199
|
+
view_id: str
|
|
200
|
+
property_id: str
|
|
201
|
+
property_type: str
|
|
202
|
+
|
|
203
|
+
def message(self) -> str:
|
|
204
|
+
return (
|
|
205
|
+
f"Unknown property type '{self.property_type}' for property '{self.property_id}' "
|
|
206
|
+
f"of view '{self.view_id}'. This will be ignored in the imports."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def dump(self) -> dict[str, str]:
|
|
210
|
+
return {"view_id": self.view_id, "property_id": self.property_id, "property_type": self.property_type}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@dataclass(frozen=True)
|
|
214
|
+
class UnknownContainerConstraintWarning(ModelImportWarning):
|
|
215
|
+
description = "Unknown container constraint. This will be ignored in the imports."
|
|
216
|
+
fix = "Set to a supported container constraint."
|
|
217
|
+
container_id: str
|
|
218
|
+
property_id: str
|
|
219
|
+
constraint: str
|
|
220
|
+
|
|
221
|
+
def message(self) -> str:
|
|
222
|
+
return (
|
|
223
|
+
f"Unknown container constraint '{self.constraint}' for property '{self.property_id}' of container "
|
|
224
|
+
f"'{self.container_id}'. This will be ignored in the imports."
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def dump(self) -> dict[str, str]:
|
|
228
|
+
return {"container_id": self.container_id, "property_id": self.property_id, "constraint": self.constraint}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass(frozen=True)
|
|
232
|
+
class FailedToInferValueTypeWarning(ModelImportWarning):
|
|
233
|
+
description = "Failed to infer value type. This will be ignored in the imports."
|
|
234
|
+
fix = "Set to a supported value type."
|
|
235
|
+
view_id: str
|
|
236
|
+
property_id: str
|
|
237
|
+
|
|
238
|
+
def message(self) -> str:
|
|
239
|
+
return (
|
|
240
|
+
f"Failed to infer value type for property '{self.property_id}' of view '{self.view_id}'. "
|
|
241
|
+
f"This will be ignored in the imports."
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def dump(self) -> dict[str, str]:
|
|
245
|
+
return {"view_id": self.view_id, "property_id": self.property_id}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@dataclass(frozen=True)
|
|
249
|
+
class APIWarning(ModelImportWarning):
|
|
250
|
+
description = "An error was raised."
|
|
251
|
+
fix = "No fix is available."
|
|
252
|
+
|
|
253
|
+
error_message: str
|
|
254
|
+
|
|
255
|
+
def message(self) -> str:
|
|
256
|
+
return self.error_message
|
|
257
|
+
|
|
258
|
+
def dump(self) -> dict[str, str]:
|
|
259
|
+
return {"error_message": self.error_message}
|
|
260
|
+
|
|
261
|
+
|
|
135
262
|
@dataclass(frozen=True)
|
|
136
263
|
class ModelImportError(NeatValidationError, ABC):
|
|
137
264
|
description = "An error was raised during importing."
|
|
138
265
|
fix = "No fix is available."
|
|
139
266
|
|
|
140
267
|
|
|
268
|
+
@dataclass(frozen=True)
|
|
269
|
+
class NoDataModelError(ModelImportError):
|
|
270
|
+
description = "No data model found.."
|
|
271
|
+
fix = "Check if the data model exists in the source."
|
|
272
|
+
|
|
273
|
+
error_message: str
|
|
274
|
+
|
|
275
|
+
def message(self) -> str:
|
|
276
|
+
return self.error_message
|
|
277
|
+
|
|
278
|
+
def dump(self) -> dict[str, str]:
|
|
279
|
+
return {"error_message": self.error_message}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass(frozen=True)
|
|
283
|
+
class APIError(ModelImportError):
|
|
284
|
+
description = "An error was raised during importing."
|
|
285
|
+
fix = "No fix is available."
|
|
286
|
+
|
|
287
|
+
error_message: str
|
|
288
|
+
|
|
289
|
+
def message(self) -> str:
|
|
290
|
+
return self.error_message
|
|
291
|
+
|
|
292
|
+
def dump(self) -> dict[str, str]:
|
|
293
|
+
return {"error_message": self.error_message}
|
|
294
|
+
|
|
295
|
+
|
|
141
296
|
@dataclass(frozen=True)
|
|
142
297
|
class InvalidComponentError(ModelImportError, ABC):
|
|
143
298
|
description = "This is a base class for all errors related invalid component definitions"
|
|
@@ -176,15 +176,18 @@ class InvalidRowUnknownSheetError(InvalidRowError):
|
|
|
176
176
|
) -> Self:
|
|
177
177
|
sheet_name, _, row, column, *__ = error["loc"]
|
|
178
178
|
reader = (read_info_by_sheet or {}).get(str(sheet_name), SpreadsheetRead())
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
179
|
+
try:
|
|
180
|
+
return cls(
|
|
181
|
+
column=str(column),
|
|
182
|
+
row=reader.adjusted_row_number(int(row)),
|
|
183
|
+
actual_sheet_name=str(sheet_name),
|
|
184
|
+
type=error["type"],
|
|
185
|
+
msg=error["msg"],
|
|
186
|
+
input=error.get("input"),
|
|
187
|
+
url=str(url) if (url := error.get("url")) else None,
|
|
188
|
+
)
|
|
189
|
+
except ValueError:
|
|
190
|
+
return DefaultPydanticError.from_pydantic_error(error) # type: ignore[return-value]
|
|
188
191
|
|
|
189
192
|
def dump(self) -> dict[str, Any]:
|
|
190
193
|
output = super().dump()
|