cognite-neat 0.88.1__py3-none-any.whl → 0.88.3__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.
Files changed (103) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/graph/__init__.py +0 -3
  3. cognite/neat/graph/loaders/_base.py +6 -6
  4. cognite/neat/graph/loaders/_rdf2asset.py +28 -31
  5. cognite/neat/graph/loaders/_rdf2dms.py +24 -15
  6. cognite/neat/issues/__init__.py +14 -0
  7. cognite/neat/issues/_base.py +415 -0
  8. cognite/neat/issues/errors/__init__.py +72 -0
  9. cognite/neat/issues/errors/_external.py +67 -0
  10. cognite/neat/issues/errors/_general.py +28 -0
  11. cognite/neat/issues/errors/_properties.py +62 -0
  12. cognite/neat/issues/errors/_resources.py +111 -0
  13. cognite/neat/issues/errors/_workflow.py +36 -0
  14. cognite/neat/{rules/issues → issues}/formatters.py +10 -10
  15. cognite/neat/issues/warnings/__init__.py +66 -0
  16. cognite/neat/issues/warnings/_external.py +40 -0
  17. cognite/neat/issues/warnings/_general.py +29 -0
  18. cognite/neat/issues/warnings/_models.py +92 -0
  19. cognite/neat/issues/warnings/_properties.py +44 -0
  20. cognite/neat/issues/warnings/_resources.py +55 -0
  21. cognite/neat/issues/warnings/user_modeling.py +113 -0
  22. cognite/neat/rules/_shared.py +10 -2
  23. cognite/neat/rules/exporters/_base.py +6 -6
  24. cognite/neat/rules/exporters/_rules2dms.py +19 -11
  25. cognite/neat/rules/exporters/_rules2excel.py +4 -4
  26. cognite/neat/rules/exporters/_rules2ontology.py +74 -51
  27. cognite/neat/rules/exporters/_rules2yaml.py +3 -3
  28. cognite/neat/rules/exporters/_validation.py +11 -96
  29. cognite/neat/rules/importers/__init__.py +7 -3
  30. cognite/neat/rules/importers/_base.py +9 -13
  31. cognite/neat/rules/importers/_dms2rules.py +42 -24
  32. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +49 -53
  33. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +31 -23
  34. cognite/neat/rules/importers/_dtdl2rules/spec.py +7 -0
  35. cognite/neat/rules/importers/_rdf/_imf2rules/__init__.py +3 -0
  36. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +82 -0
  37. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +34 -0
  38. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +123 -0
  39. cognite/neat/rules/importers/{_owl2rules/_owl2rules.py → _rdf/_imf2rules/_imf2rules.py} +24 -18
  40. cognite/neat/rules/importers/{_inference2rules.py → _rdf/_inference2rules.py} +9 -9
  41. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +58 -0
  42. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2metadata.py +68 -0
  43. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +60 -0
  44. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +76 -0
  45. cognite/neat/rules/importers/_rdf/_shared.py +586 -0
  46. cognite/neat/rules/importers/_spreadsheet2rules.py +35 -22
  47. cognite/neat/rules/importers/_yaml2rules.py +23 -21
  48. cognite/neat/rules/models/_constants.py +2 -1
  49. cognite/neat/rules/models/_rdfpath.py +4 -4
  50. cognite/neat/rules/models/_types/_field.py +9 -11
  51. cognite/neat/rules/models/asset/_rules.py +1 -3
  52. cognite/neat/rules/models/asset/_validation.py +14 -10
  53. cognite/neat/rules/models/dms/_converter.py +2 -4
  54. cognite/neat/rules/models/dms/_exporter.py +30 -8
  55. cognite/neat/rules/models/dms/_rules.py +23 -7
  56. cognite/neat/rules/models/dms/_schema.py +94 -62
  57. cognite/neat/rules/models/dms/_validation.py +105 -66
  58. cognite/neat/rules/models/entities.py +3 -0
  59. cognite/neat/rules/models/information/_converter.py +2 -2
  60. cognite/neat/rules/models/information/_rules.py +7 -8
  61. cognite/neat/rules/models/information/_validation.py +48 -25
  62. cognite/neat/rules/transformers/__init__.py +0 -0
  63. cognite/neat/rules/transformers/_base.py +15 -0
  64. cognite/neat/utils/auxiliary.py +2 -35
  65. cognite/neat/utils/text.py +17 -0
  66. cognite/neat/workflows/base.py +4 -4
  67. cognite/neat/workflows/cdf_store.py +3 -3
  68. cognite/neat/workflows/steps/data_contracts.py +1 -1
  69. cognite/neat/workflows/steps/lib/current/graph_extractor.py +3 -3
  70. cognite/neat/workflows/steps/lib/current/graph_loader.py +2 -2
  71. cognite/neat/workflows/steps/lib/current/graph_store.py +1 -1
  72. cognite/neat/workflows/steps/lib/current/rules_exporter.py +10 -10
  73. cognite/neat/workflows/steps/lib/current/rules_importer.py +78 -6
  74. cognite/neat/workflows/steps/lib/current/rules_validator.py +20 -9
  75. cognite/neat/workflows/steps/lib/io/io_steps.py +5 -5
  76. cognite/neat/workflows/steps_registry.py +4 -5
  77. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.3.dist-info}/METADATA +1 -1
  78. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.3.dist-info}/RECORD +86 -77
  79. cognite/neat/exceptions.py +0 -145
  80. cognite/neat/graph/exceptions.py +0 -90
  81. cognite/neat/graph/issues/loader.py +0 -104
  82. cognite/neat/issues.py +0 -158
  83. cognite/neat/rules/importers/_owl2rules/_owl2classes.py +0 -215
  84. cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +0 -209
  85. cognite/neat/rules/importers/_owl2rules/_owl2properties.py +0 -203
  86. cognite/neat/rules/issues/__init__.py +0 -26
  87. cognite/neat/rules/issues/base.py +0 -82
  88. cognite/neat/rules/issues/dms.py +0 -683
  89. cognite/neat/rules/issues/fileread.py +0 -197
  90. cognite/neat/rules/issues/importing.py +0 -423
  91. cognite/neat/rules/issues/ontology.py +0 -298
  92. cognite/neat/rules/issues/spreadsheet.py +0 -563
  93. cognite/neat/rules/issues/spreadsheet_file.py +0 -151
  94. cognite/neat/rules/issues/tables.py +0 -72
  95. cognite/neat/workflows/_exceptions.py +0 -41
  96. /cognite/neat/{graph/issues → rules/importers/_rdf}/__init__.py +0 -0
  97. /cognite/neat/rules/importers/{_owl2rules → _rdf/_owl2rules}/__init__.py +0 -0
  98. /cognite/neat/{graph/stores → store}/__init__.py +0 -0
  99. /cognite/neat/{graph/stores → store}/_base.py +0 -0
  100. /cognite/neat/{graph/stores → store}/_provenance.py +0 -0
  101. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.3.dist-info}/LICENSE +0 -0
  102. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.3.dist-info}/WHEEL +0 -0
  103. {cognite_neat-0.88.1.dist-info → cognite_neat-0.88.3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,415 @@
1
+ import sys
2
+ import warnings
3
+ from abc import ABC
4
+ from collections import UserList
5
+ from collections.abc import Collection, Hashable, Iterable, Sequence
6
+ from dataclasses import dataclass, fields
7
+ from functools import total_ordering
8
+ from pathlib import Path
9
+ from types import UnionType
10
+ from typing import Any, ClassVar, Literal, TypeAlias, TypeVar, get_args, get_origin
11
+ from warnings import WarningMessage
12
+
13
+ import pandas as pd
14
+ from cognite.client.data_classes.data_modeling import ContainerId, ViewId
15
+ from pydantic_core import ErrorDetails
16
+
17
+ from cognite.neat.utils.spreadsheet import SpreadsheetRead
18
+ from cognite.neat.utils.text import humanize_collection, to_camel, to_snake
19
+
20
+ if sys.version_info < (3, 11):
21
+ from exceptiongroup import ExceptionGroup
22
+ from typing_extensions import Self
23
+ else:
24
+ from typing import Self
25
+
26
+
27
+ __all__ = [
28
+ "NeatIssue",
29
+ "NeatError",
30
+ "NeatWarning",
31
+ "DefaultWarning",
32
+ "NeatIssueList",
33
+ "MultiValueError",
34
+ ]
35
+
36
+ T_Identifier = TypeVar("T_Identifier", bound=Hashable)
37
+
38
+ T_ReferenceIdentifier = TypeVar("T_ReferenceIdentifier", bound=Hashable)
39
+
40
+ ResourceType: TypeAlias = (
41
+ Literal[
42
+ "view",
43
+ "container",
44
+ "view property",
45
+ "container property",
46
+ "space",
47
+ "class",
48
+ "asset",
49
+ "relationship",
50
+ "data model",
51
+ "edge",
52
+ "node",
53
+ "unknown",
54
+ ]
55
+ # String to handle all unknown types in different importers.
56
+ | str
57
+ )
58
+
59
+
60
+ @total_ordering
61
+ @dataclass(frozen=True)
62
+ class NeatIssue:
63
+ """This is the base class for all exceptions and warnings (issues) used in Neat."""
64
+
65
+ extra: ClassVar[str | None] = None
66
+ fix: ClassVar[str | None] = None
67
+
68
+ def as_message(self) -> str:
69
+ """Return a human-readable message for the issue."""
70
+ template = self.__doc__
71
+ if not template:
72
+ return "Missing"
73
+ variables, has_all_optional = self._get_variables()
74
+
75
+ msg = template.format(**variables)
76
+ if self.extra and has_all_optional:
77
+ msg += "\n" + self.extra.format(**variables)
78
+ if self.fix:
79
+ msg += f"\nFix: {self.fix.format(**variables)}"
80
+ name = type(self).__name__
81
+ return f"{name}: {msg}"
82
+
83
+ def _get_variables(self) -> tuple[dict[str, str], bool]:
84
+ variables: dict[str, str] = {}
85
+ has_all_optional = True
86
+ for name, var_ in vars(self).items():
87
+ if var_ is None:
88
+ has_all_optional = False
89
+ elif isinstance(var_, str):
90
+ variables[name] = var_
91
+ elif isinstance(var_, Path):
92
+ variables[name] = var_.as_posix()
93
+ elif isinstance(var_, Collection):
94
+ variables[name] = humanize_collection(var_)
95
+ else:
96
+ variables[name] = repr(var_)
97
+ return variables, has_all_optional
98
+
99
+ def dump(self) -> dict[str, Any]:
100
+ """Return a dictionary representation of the issue."""
101
+ variables = vars(self)
102
+ output = {to_camel(key): self._dump_value(value) for key, value in variables.items() if value is not None}
103
+ output["NeatIssue"] = type(self).__name__
104
+ return output
105
+
106
+ @classmethod
107
+ def _dump_value(cls, value: Any) -> list | int | bool | float | str | dict:
108
+ if isinstance(value, str | int | bool | float):
109
+ return value
110
+ elif isinstance(value, frozenset):
111
+ return [cls._dump_value(item) for item in value]
112
+ elif isinstance(value, Path):
113
+ return value.as_posix()
114
+ elif isinstance(value, tuple):
115
+ return [cls._dump_value(item) for item in value]
116
+ elif isinstance(value, ViewId | ContainerId):
117
+ return value.dump(camel_case=True, include_type=True)
118
+ raise ValueError(f"Unsupported type: {type(value)}")
119
+
120
+ @classmethod
121
+ def load(cls, data: dict[str, Any]) -> "NeatIssue":
122
+ """Create an instance of the issue from a dictionary."""
123
+ from cognite.neat.issues.errors import _NEAT_ERRORS_BY_NAME, NeatValueError
124
+ from cognite.neat.issues.warnings import _NEAT_WARNINGS_BY_NAME
125
+
126
+ if "NeatIssue" not in data:
127
+ raise NeatValueError("The data does not contain a NeatIssue key.")
128
+ issue_type = data.pop("NeatIssue")
129
+ args = {to_snake(key): value for key, value in data.items()}
130
+ if issue_type in _NEAT_ERRORS_BY_NAME:
131
+ return cls._load_values(_NEAT_ERRORS_BY_NAME[issue_type], args)
132
+ elif issue_type in _NEAT_WARNINGS_BY_NAME:
133
+ return cls._load_values(_NEAT_WARNINGS_BY_NAME[issue_type], args)
134
+ else:
135
+ raise NeatValueError(f"Unknown issue type: {issue_type}")
136
+
137
+ @classmethod
138
+ def _load_values(cls, neat_issue_cls: "type[NeatIssue]", data: dict[str, Any]) -> "NeatIssue":
139
+ args: dict[str, Any] = {}
140
+ for f in fields(neat_issue_cls):
141
+ if f.name not in data:
142
+ continue
143
+ value = data[f.name]
144
+ args[f.name] = cls._load_value(f.type, value)
145
+ return neat_issue_cls(**args)
146
+
147
+ @classmethod
148
+ def _load_value(cls, type_: type, value: Any) -> Any:
149
+ if isinstance(type_, UnionType) or get_origin(type_) is UnionType:
150
+ args = get_args(type_)
151
+ return cls._load_value(args[0], value)
152
+ elif type_ is frozenset or get_origin(type_) is frozenset:
153
+ subtype = get_args(type_)[0]
154
+ return frozenset(cls._load_value(subtype, item) for item in value)
155
+ elif type_ is Path:
156
+ return Path(value)
157
+ elif type_ is tuple or get_origin(type_) is tuple:
158
+ subtype = get_args(type_)[0]
159
+ return tuple(cls._load_value(subtype, item) for item in value)
160
+ elif type_ is ViewId:
161
+ return ViewId.load(value)
162
+ elif type_ is ContainerId:
163
+ return ContainerId.load(value)
164
+ return value
165
+
166
+ def __lt__(self, other: "NeatIssue") -> bool:
167
+ if not isinstance(other, NeatIssue):
168
+ return NotImplemented
169
+ return (type(self).__name__, self.as_message()) < (type(other).__name__, other.as_message())
170
+
171
+ def __eq__(self, other: object) -> bool:
172
+ if not isinstance(other, NeatIssue):
173
+ return NotImplemented
174
+ return (type(self).__name__, self.as_message()) == (type(other).__name__, other.as_message())
175
+
176
+
177
+ @dataclass(frozen=True)
178
+ class NeatError(NeatIssue, Exception):
179
+ """This is the base class for all exceptions (errors) used in Neat."""
180
+
181
+ @classmethod
182
+ def from_pydantic_errors(cls, errors: list[ErrorDetails], **kwargs) -> "list[NeatError]":
183
+ """Convert a list of pydantic errors to a list of Error instances.
184
+
185
+ This is intended to be overridden in subclasses to handle specific error types.
186
+ """
187
+ all_errors: list[NeatError] = []
188
+ read_info_by_sheet = kwargs.get("read_info_by_sheet")
189
+
190
+ for error in errors:
191
+ ctx = error.get("ctx")
192
+ if isinstance(ctx, dict) and isinstance(multi_error := ctx.get("error"), MultiValueError):
193
+ if read_info_by_sheet:
194
+ for caught_error in multi_error.errors:
195
+ cls._adjust_row_numbers(caught_error, read_info_by_sheet) # type: ignore[arg-type]
196
+ all_errors.extend(multi_error.errors) # type: ignore[arg-type]
197
+ elif isinstance(ctx, dict) and isinstance(single_error := ctx.get("error"), NeatError):
198
+ if read_info_by_sheet:
199
+ cls._adjust_row_numbers(single_error, read_info_by_sheet)
200
+ all_errors.append(single_error)
201
+ elif len(error["loc"]) >= 4 and read_info_by_sheet:
202
+ all_errors.append(RowError.from_pydantic_error(error, read_info_by_sheet))
203
+ else:
204
+ all_errors.append(DefaultPydanticError.from_pydantic_error(error))
205
+ return all_errors
206
+
207
+ @staticmethod
208
+ def _adjust_row_numbers(caught_error: "NeatError", read_info_by_sheet: dict[str, SpreadsheetRead]) -> None:
209
+ from cognite.neat.issues.errors._properties import PropertyDefinitionDuplicatedError
210
+ from cognite.neat.issues.errors._resources import ResourceNotDefinedError
211
+
212
+ reader = read_info_by_sheet.get("Properties", SpreadsheetRead())
213
+
214
+ if isinstance(caught_error, PropertyDefinitionDuplicatedError) and caught_error.location_name == "rows":
215
+ adjusted_row_number = (
216
+ tuple(
217
+ reader.adjusted_row_number(row_no) if isinstance(row_no, int) else row_no
218
+ for row_no in caught_error.locations or []
219
+ )
220
+ or None
221
+ )
222
+ # The error is frozen, so we have to use __setattr__ to change the row number
223
+ object.__setattr__(caught_error, "locations", adjusted_row_number)
224
+ elif isinstance(caught_error, RowError):
225
+ # Adjusting the row number to the actual row number in the spreadsheet
226
+ new_row = reader.adjusted_row_number(caught_error.row)
227
+ # The error is frozen, so we have to use __setattr__ to change the row number
228
+ object.__setattr__(caught_error, "row", new_row)
229
+ elif isinstance(caught_error, ResourceNotDefinedError):
230
+ if isinstance(caught_error.row_number, int) and caught_error.sheet_name == "Properties":
231
+ new_row = reader.adjusted_row_number(caught_error.row_number)
232
+ object.__setattr__(caught_error, "row_number", new_row)
233
+
234
+
235
+ @dataclass(frozen=True)
236
+ class DefaultPydanticError(NeatError, ValueError):
237
+ """{type}: {msg} [loc={loc}]"""
238
+
239
+ type: str
240
+ loc: tuple[int | str, ...]
241
+ msg: str
242
+
243
+ @classmethod
244
+ def from_pydantic_error(cls, error: ErrorDetails) -> "DefaultPydanticError":
245
+ return cls(
246
+ type=error["type"],
247
+ loc=error["loc"],
248
+ msg=error["msg"],
249
+ )
250
+
251
+ def as_message(self) -> str:
252
+ if self.loc and len(self.loc) == 1:
253
+ return f"{self.loc[0]} sheet: {self.msg}"
254
+ elif self.loc and len(self.loc) == 2:
255
+ return f"{self.loc[0]} sheet field/column <{self.loc[1]}>: {self.msg}"
256
+ else:
257
+ return self.msg
258
+
259
+
260
+ @dataclass(frozen=True)
261
+ class RowError(NeatError, ValueError):
262
+ """In {sheet_name}, row={row}, column={column}: {msg}. [type={type}, input_value={input}]"""
263
+
264
+ extra = "For further information visit {url}"
265
+
266
+ sheet_name: str
267
+ column: str
268
+ row: int
269
+ type: str
270
+ msg: str
271
+ input: Any
272
+ url: str | None = None
273
+
274
+ @classmethod
275
+ def from_pydantic_error(
276
+ cls,
277
+ error: ErrorDetails,
278
+ read_info_by_sheet: dict[str, SpreadsheetRead] | None = None,
279
+ ) -> Self:
280
+ sheet_name, _, row, column, *__ = error["loc"]
281
+ reader = (read_info_by_sheet or {}).get(str(sheet_name), SpreadsheetRead())
282
+ return cls(
283
+ sheet_name=str(sheet_name),
284
+ column=str(column),
285
+ row=reader.adjusted_row_number(int(row)),
286
+ type=error["type"],
287
+ msg=error["msg"],
288
+ input=error.get("input"),
289
+ url=str(url) if (url := error.get("url")) else None,
290
+ )
291
+
292
+ def as_message(self) -> str:
293
+ input_str = str(self.input) if self.input is not None else ""
294
+ input_str = input_str[:50] + "..." if len(input_str) > 50 else input_str
295
+ output = (
296
+ f"In {self.sheet_name}, row={self.row}, column={self.column}: {self.msg}. "
297
+ f"[type={self.type}, input_value={input_str}]"
298
+ )
299
+ if self.url:
300
+ output += f" For further information visit {self.url}"
301
+ return output
302
+
303
+
304
+ @dataclass(frozen=True)
305
+ class NeatWarning(NeatIssue, UserWarning):
306
+ """This is the base class for all warnings used in Neat."""
307
+
308
+ @classmethod
309
+ def from_warning(cls, warning: WarningMessage) -> "NeatWarning":
310
+ """Create a NeatWarning from a WarningMessage."""
311
+ return DefaultWarning.from_warning_message(warning)
312
+
313
+
314
+ @dataclass(frozen=True)
315
+ class DefaultWarning(NeatWarning):
316
+ """{category}: {warning}"""
317
+
318
+ extra = "Source: {source}"
319
+
320
+ warning: str
321
+ category: str
322
+ source: str | None = None
323
+
324
+ @classmethod
325
+ def from_warning_message(cls, warning: WarningMessage) -> NeatWarning:
326
+ if isinstance(warning.message, NeatWarning):
327
+ return warning.message
328
+
329
+ return cls(
330
+ warning=str(warning.message),
331
+ category=warning.category.__name__,
332
+ source=warning.source,
333
+ )
334
+
335
+ def as_message(self) -> str:
336
+ return str(self.warning)
337
+
338
+
339
+ T_NeatIssue = TypeVar("T_NeatIssue", bound=NeatIssue)
340
+
341
+
342
+ class NeatIssueList(UserList[T_NeatIssue], ABC):
343
+ """This is a generic list of NeatIssues."""
344
+
345
+ def __init__(self, issues: Sequence[T_NeatIssue] | None = None, title: str | None = None):
346
+ super().__init__(issues or [])
347
+ self.title = title
348
+
349
+ @property
350
+ def errors(self) -> Self:
351
+ """Return all the errors in this list."""
352
+ return type(self)([issue for issue in self if isinstance(issue, NeatError)]) # type: ignore[misc]
353
+
354
+ @property
355
+ def has_errors(self) -> bool:
356
+ """Return True if this list contains any errors."""
357
+ return any(isinstance(issue, NeatError) for issue in self)
358
+
359
+ @property
360
+ def warnings(self) -> Self:
361
+ """Return all the warnings in this list."""
362
+ return type(self)([issue for issue in self if isinstance(issue, NeatWarning)]) # type: ignore[misc]
363
+
364
+ def as_errors(self) -> ExceptionGroup:
365
+ """Return an ExceptionGroup with all the errors in this list."""
366
+ return ExceptionGroup(
367
+ "Operation failed",
368
+ [issue for issue in self if isinstance(issue, NeatError)],
369
+ )
370
+
371
+ def trigger_warnings(self) -> None:
372
+ """Trigger all warnings in this list."""
373
+ for warning in [issue for issue in self if isinstance(issue, NeatWarning)]:
374
+ warnings.warn(warning, stacklevel=2)
375
+
376
+ def to_pandas(self) -> pd.DataFrame:
377
+ """Return a pandas DataFrame representation of this list."""
378
+ return pd.DataFrame([issue.dump() for issue in self])
379
+
380
+ def _repr_html_(self) -> str | None:
381
+ return self.to_pandas()._repr_html_() # type: ignore[operator]
382
+
383
+ def as_exception(self) -> "MultiValueError":
384
+ """Return a MultiValueError with all the errors in this list."""
385
+ return MultiValueError(self.errors)
386
+
387
+
388
+ class MultiValueError(ValueError):
389
+ """This is a container for multiple errors.
390
+
391
+ It is used in the pydantic field_validator/model_validator to collect multiple errors, which
392
+ can then be caught in a try-except block and returned as an IssueList.
393
+
394
+ """
395
+
396
+ def __init__(self, errors: Sequence[NeatIssue]):
397
+ self.errors = list(errors)
398
+
399
+
400
+ class IssueList(NeatIssueList[NeatIssue]):
401
+ """This is a list of NeatIssues."""
402
+
403
+ ...
404
+
405
+
406
+ T_Cls = TypeVar("T_Cls")
407
+
408
+
409
+ def _get_subclasses(cls_: type[T_Cls], include_base: bool = False) -> Iterable[type[T_Cls]]:
410
+ """Get all subclasses of a class."""
411
+ if include_base:
412
+ yield cls_
413
+ for s in cls_.__subclasses__():
414
+ yield s
415
+ yield from _get_subclasses(s, False)
@@ -0,0 +1,72 @@
1
+ from cognite.neat.issues._base import DefaultPydanticError, NeatError, RowError, _get_subclasses
2
+
3
+ from ._external import (
4
+ AuthorizationError,
5
+ FileMissingRequiredFieldError,
6
+ FileNotAFileError,
7
+ FileNotFoundNeatError,
8
+ FileReadError,
9
+ FileTypeUnexpectedError,
10
+ NeatYamlError,
11
+ )
12
+ from ._general import NeatImportError, NeatValueError, RegexViolationError
13
+ from ._properties import (
14
+ PropertyDefinitionDuplicatedError,
15
+ PropertyDefinitionError,
16
+ PropertyMappingDuplicatedError,
17
+ PropertyNotFoundError,
18
+ PropertyTypeNotSupportedError,
19
+ )
20
+ from ._resources import (
21
+ ResourceChangedError,
22
+ ResourceConvertionError,
23
+ ResourceCreationError,
24
+ ResourceDuplicatedError,
25
+ ResourceError,
26
+ ResourceMissingIdentifierError,
27
+ ResourceNotDefinedError,
28
+ ResourceNotFoundError,
29
+ ResourceRetrievalError,
30
+ )
31
+ from ._workflow import (
32
+ WorkflowConfigurationNotSetError,
33
+ WorkFlowMissingDataError,
34
+ WorkflowStepNotInitializedError,
35
+ WorkflowStepOutputError,
36
+ )
37
+
38
+ __all__ = [
39
+ "NeatError",
40
+ "NeatValueError",
41
+ "NeatImportError",
42
+ "RegexViolationError",
43
+ "AuthorizationError",
44
+ "NeatYamlError",
45
+ "FileReadError",
46
+ "ResourceCreationError",
47
+ "FileNotFoundNeatError",
48
+ "FileMissingRequiredFieldError",
49
+ "PropertyDefinitionError",
50
+ "PropertyTypeNotSupportedError",
51
+ "PropertyNotFoundError",
52
+ "PropertyDefinitionDuplicatedError",
53
+ "ResourceChangedError",
54
+ "ResourceDuplicatedError",
55
+ "ResourceRetrievalError",
56
+ "ResourceNotFoundError",
57
+ "ResourceError",
58
+ "ResourceNotDefinedError",
59
+ "ResourceMissingIdentifierError",
60
+ "ResourceConvertionError",
61
+ "WorkflowConfigurationNotSetError",
62
+ "WorkFlowMissingDataError",
63
+ "WorkflowStepNotInitializedError",
64
+ "WorkflowStepOutputError",
65
+ "FileTypeUnexpectedError",
66
+ "FileNotAFileError",
67
+ "DefaultPydanticError",
68
+ "PropertyMappingDuplicatedError",
69
+ "RowError",
70
+ ]
71
+
72
+ _NEAT_ERRORS_BY_NAME = {error.__name__: error for error in _get_subclasses(NeatError, include_base=True)}
@@ -0,0 +1,67 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from yaml import YAMLError
5
+
6
+ from cognite.neat.issues import NeatError
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class AuthorizationError(NeatError, RuntimeError):
11
+ """Missing authorization for {action}: {reason}"""
12
+
13
+ action: str
14
+ reason: str
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class FileReadError(NeatError, RuntimeError):
19
+ """Error when reading file, {filepath}: {reason}"""
20
+
21
+ fix = "Is the {filepath} open in another program? Is the file corrupted?"
22
+ filepath: Path
23
+ reason: str
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class FileNotFoundNeatError(NeatError, FileNotFoundError):
28
+ """File {filepath} not found"""
29
+
30
+ fix = "Make sure to provide a valid file"
31
+ filepath: Path
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class FileMissingRequiredFieldError(NeatError, ValueError):
36
+ """Missing required {field_name} in {filepath}: {field}"""
37
+
38
+ filepath: Path
39
+ field_name: str
40
+ field: str
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class NeatYamlError(NeatError, YAMLError):
45
+ """Invalid YAML: {reason}"""
46
+
47
+ extra = "Expected format: {expected_format}"
48
+ fix = "Check if the file is a valid YAML file"
49
+
50
+ reason: str
51
+ expected_format: str | None = None
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class FileTypeUnexpectedError(NeatError, TypeError):
56
+ """Unexpected file type: {filepath}. Expected format: {expected_format}"""
57
+
58
+ filepath: Path
59
+ expected_format: frozenset[str]
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class FileNotAFileError(NeatError, FileNotFoundError):
64
+ """{filepath} is not a file"""
65
+
66
+ fix = "Make sure to provide a valid file"
67
+ filepath: Path
@@ -0,0 +1,28 @@
1
+ from dataclasses import dataclass
2
+
3
+ from cognite.neat.issues import NeatError
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class NeatValueError(NeatError, ValueError):
8
+ """{raw_message}"""
9
+
10
+ raw_message: str
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class RegexViolationError(NeatError, ValueError):
15
+ """Value, {value} failed regex, {regex}, validation. Make sure that the name follows the regex pattern."""
16
+
17
+ value: str
18
+ regex: str
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class NeatImportError(NeatError, ImportError):
23
+ """The functionality requires {module}. You can include it
24
+ in your neat installation with `pip install "cognite-neat[{neat_extra}]"`.
25
+ """
26
+
27
+ module: str
28
+ neat_extra: str
@@ -0,0 +1,62 @@
1
+ from dataclasses import dataclass
2
+ from typing import Generic
3
+
4
+ from cognite.neat.issues._base import ResourceType
5
+
6
+ from ._resources import ResourceError, T_Identifier, T_ReferenceIdentifier
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class PropertyError(ResourceError[T_Identifier]):
11
+ """Base class for property errors {resource_type} with identifier {identifier}.{property_name}"""
12
+
13
+ property_name: str
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class PropertyNotFoundError(PropertyError, Generic[T_Identifier, T_ReferenceIdentifier]):
18
+ """The {resource_type} with identifier {identifier} does not have a property {property_name}"""
19
+
20
+ extra = "referred to by {referred_type} {referred_by} does not exist"
21
+ fix = "Ensure the {resource_type} {identifier} has a {property_name} property"
22
+
23
+ referred_by: T_ReferenceIdentifier | None = None
24
+ referred_type: ResourceType | None = None
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class PropertyTypeNotSupportedError(PropertyError[T_Identifier]):
29
+ """The {resource_type} with identifier {identifier} has a property {property_name}
30
+ of unsupported type {property_type}"""
31
+
32
+ property_type: str
33
+
34
+
35
+ # This is a generic error that should be used sparingly
36
+ @dataclass(frozen=True)
37
+ class PropertyDefinitionError(PropertyError[T_Identifier]):
38
+ """Invalid property definition for {resource_type} {identifier}.{property_name}: {reason}"""
39
+
40
+ reason: str
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class PropertyDefinitionDuplicatedError(PropertyError[T_Identifier]):
45
+ """The {resource_type} with identifier {identifier} has multiple definitions for the property {property_name}
46
+ with values {property_values}
47
+ """
48
+
49
+ extra = "in locations {locations} with name {location_name}"
50
+
51
+ property_values: frozenset[str | int | float | bool | None | tuple[str | int | float | bool | None, ...]]
52
+ locations: tuple[str | int, ...] | None = None
53
+ location_name: str | None = None
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class PropertyMappingDuplicatedError(PropertyError[T_Identifier], Generic[T_Identifier, T_ReferenceIdentifier]):
58
+ """The {resource_type} with identifier {identifier}.{property_name} is mapped to by: {mappings}. Ensure
59
+ that only one {mapping_type} maps to {resource_type} {identifier}.{property_name}"""
60
+
61
+ mappings: frozenset[T_ReferenceIdentifier]
62
+ mapping_type: ResourceType