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.

Files changed (99) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/app/api/configuration.py +4 -9
  3. cognite/neat/app/api/routers/configuration.py +2 -1
  4. cognite/neat/app/api/routers/crud.py +5 -5
  5. cognite/neat/app/api/routers/data_exploration.py +3 -1
  6. cognite/neat/app/api/routers/rules.py +3 -3
  7. cognite/neat/app/api/routers/workflows.py +3 -3
  8. cognite/neat/app/ui/neat-app/build/asset-manifest.json +3 -3
  9. cognite/neat/app/ui/neat-app/build/index.html +1 -1
  10. cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js → main.ec7f72e2.js} +3 -3
  11. cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js.map → main.ec7f72e2.js.map} +1 -1
  12. cognite/neat/config.py +147 -12
  13. cognite/neat/constants.py +1 -0
  14. cognite/neat/graph/exceptions.py +1 -2
  15. cognite/neat/legacy/graph/exceptions.py +1 -2
  16. cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +1 -2
  17. cognite/neat/legacy/graph/loaders/_asset_loader.py +8 -13
  18. cognite/neat/legacy/graph/loaders/_base.py +2 -4
  19. cognite/neat/legacy/graph/loaders/_exceptions.py +1 -3
  20. cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +4 -8
  21. cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +2 -4
  22. cognite/neat/legacy/graph/loaders/rdf_to_dms.py +2 -4
  23. cognite/neat/legacy/graph/loaders/validator.py +1 -1
  24. cognite/neat/legacy/graph/transformations/transformer.py +1 -2
  25. cognite/neat/legacy/rules/exporters/_rules2dms.py +1 -2
  26. cognite/neat/legacy/rules/exporters/_validation.py +4 -8
  27. cognite/neat/legacy/rules/importers/_base.py +0 -4
  28. cognite/neat/legacy/rules/importers/_dms2rules.py +0 -2
  29. cognite/neat/legacy/rules/models/rdfpath.py +1 -2
  30. cognite/neat/legacy/workflows/examples/Export_DMS/workflow.yaml +89 -0
  31. cognite/neat/legacy/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +152 -0
  32. cognite/neat/legacy/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +139 -0
  33. cognite/neat/legacy/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +270 -0
  34. cognite/neat/legacy/workflows/examples/Import_DMS/workflow.yaml +65 -0
  35. cognite/neat/legacy/workflows/examples/Ontology_to_Data_Model/workflow.yaml +116 -0
  36. cognite/neat/legacy/workflows/examples/Validate_Rules/workflow.yaml +67 -0
  37. cognite/neat/legacy/workflows/examples/Validate_Solution_Model/workflow.yaml +64 -0
  38. cognite/neat/legacy/workflows/examples/Visualize_Data_Model_Using_Mock_Graph/workflow.yaml +95 -0
  39. cognite/neat/legacy/workflows/examples/Visualize_Semantic_Data_Model/workflow.yaml +111 -0
  40. cognite/neat/rules/exporters/_models.py +3 -0
  41. cognite/neat/rules/exporters/_rules2dms.py +46 -4
  42. cognite/neat/rules/exporters/_rules2excel.py +2 -11
  43. cognite/neat/rules/exporters/_validation.py +6 -8
  44. cognite/neat/rules/importers/_base.py +8 -4
  45. cognite/neat/rules/importers/_dms2rules.py +321 -129
  46. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +2 -4
  47. cognite/neat/rules/importers/_dtdl2rules/spec.py +2 -4
  48. cognite/neat/rules/importers/_owl2rules/_owl2rules.py +2 -4
  49. cognite/neat/rules/importers/_spreadsheet2rules.py +18 -16
  50. cognite/neat/rules/importers/_yaml2rules.py +2 -4
  51. cognite/neat/rules/issues/base.py +3 -0
  52. cognite/neat/rules/issues/dms.py +144 -58
  53. cognite/neat/rules/issues/fileread.py +41 -0
  54. cognite/neat/rules/issues/formatters.py +3 -1
  55. cognite/neat/rules/issues/importing.py +155 -0
  56. cognite/neat/rules/issues/spreadsheet.py +12 -9
  57. cognite/neat/rules/models/entities.py +30 -8
  58. cognite/neat/rules/models/rdfpath.py +1 -2
  59. cognite/neat/rules/models/rules/_base.py +5 -6
  60. cognite/neat/rules/models/rules/_dms_architect_rules.py +494 -333
  61. cognite/neat/rules/models/rules/_dms_rules_write.py +43 -52
  62. cognite/neat/rules/models/rules/_dms_schema.py +112 -22
  63. cognite/neat/rules/models/rules/_domain_rules.py +5 -0
  64. cognite/neat/rules/models/rules/_information_rules.py +13 -6
  65. cognite/neat/rules/models/wrapped_entities.py +166 -0
  66. cognite/neat/utils/cdf_loaders/_data_modeling.py +3 -1
  67. cognite/neat/utils/cdf_loaders/_ingestion.py +2 -4
  68. cognite/neat/utils/spreadsheet.py +2 -4
  69. cognite/neat/utils/utils.py +2 -4
  70. cognite/neat/workflows/base.py +5 -5
  71. cognite/neat/workflows/manager.py +32 -22
  72. cognite/neat/workflows/model.py +3 -3
  73. cognite/neat/workflows/steps/lib/__init__.py +0 -7
  74. cognite/neat/workflows/steps/lib/current/__init__.py +6 -0
  75. cognite/neat/workflows/steps/lib/{rules_exporter.py → current/rules_exporter.py} +8 -8
  76. cognite/neat/workflows/steps/lib/{rules_importer.py → current/rules_importer.py} +4 -4
  77. cognite/neat/workflows/steps/lib/io/__init__.py +1 -0
  78. cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_contextualization.py +2 -2
  79. cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_extractor.py +9 -9
  80. cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_loader.py +9 -9
  81. cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_store.py +4 -4
  82. cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_transformer.py +2 -2
  83. cognite/neat/workflows/steps/lib/{v1 → legacy}/rules_exporter.py +15 -17
  84. cognite/neat/workflows/steps/lib/{v1 → legacy}/rules_importer.py +7 -7
  85. cognite/neat/workflows/steps/step_model.py +5 -9
  86. cognite/neat/workflows/steps_registry.py +20 -11
  87. {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/METADATA +1 -1
  88. {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/RECORD +98 -86
  89. cognite/neat/app/api/data_classes/configuration.py +0 -121
  90. /cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js.LICENSE.txt → main.ec7f72e2.js.LICENSE.txt} +0 -0
  91. /cognite/neat/workflows/steps/lib/{graph_extractor.py → current/graph_extractor.py} +0 -0
  92. /cognite/neat/workflows/steps/lib/{graph_loader.py → current/graph_loader.py} +0 -0
  93. /cognite/neat/workflows/steps/lib/{graph_store.py → current/graph_store.py} +0 -0
  94. /cognite/neat/workflows/steps/lib/{rules_validator.py → current/rules_validator.py} +0 -0
  95. /cognite/neat/workflows/steps/lib/{io_steps.py → io/io_steps.py} +0 -0
  96. /cognite/neat/workflows/steps/lib/{v1 → legacy}/__init__.py +0 -0
  97. {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/LICENSE +0 -0
  98. {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/WHEEL +0 -0
  99. {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):
@@ -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
- "UnsupportedRelationWarning",
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 DirectRelationListWarning(DMSSchemaWarning):
304
- description = "The container property is set to a direct relation list, which is not supported by the CDF API"
305
- fix = "Make the property into a multiedge connection instead"
306
- error_name: ClassVar[str] = "DirectRelationListWarning"
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 property in {self.container_id}.{self.property} is a list of direct relations. "
314
- f"This is not supported by the API, so it will be converted to an MultiEdgeConnection on"
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 ReverseOfDirectRelationListWarning(DMSSchemaWarning):
328
- description = (
329
- "The view property is set to a reverse of a direct relation list, which is not supported by the CDF API"
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 property pointed to be {self.view_id}.{self.property} is a list of direct relations. "
339
- f"This is not supported by the API, so the {self.view_id}.{self.property} "
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 EmptyContainerWarning(DMSSchemaWarning):
352
- description = "The container is empty"
353
- fix = "Add data to the container"
354
- error_name: ClassVar[str] = "EmptyContainerWarning"
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 no "
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 for subclass in Formatter.__subclasses__() if ABC not in subclass.__bases__ # type: ignore[type-abstract]
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
- return cls(
180
- column=str(column),
181
- row=reader.adjusted_row_number(int(row)),
182
- actual_sheet_name=str(sheet_name),
183
- type=error["type"],
184
- msg=error["msg"],
185
- input=error.get("input"),
186
- url=str(url) if (url := error.get("url")) else None,
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()