cognite-neat 0.109.4__py3-none-any.whl → 0.110.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 (67) hide show
  1. cognite/neat/_alpha.py +2 -0
  2. cognite/neat/_client/_api/schema.py +17 -1
  3. cognite/neat/_client/data_classes/schema.py +3 -3
  4. cognite/neat/_constants.py +11 -0
  5. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +9 -10
  6. cognite/neat/_graph/extractors/_iodd.py +3 -3
  7. cognite/neat/_graph/extractors/_mock_graph_generator.py +9 -7
  8. cognite/neat/_graph/loaders/_rdf2dms.py +285 -346
  9. cognite/neat/_graph/queries/_base.py +28 -92
  10. cognite/neat/_graph/transformers/__init__.py +1 -3
  11. cognite/neat/_graph/transformers/_rdfpath.py +2 -49
  12. cognite/neat/_issues/__init__.py +1 -6
  13. cognite/neat/_issues/_base.py +21 -252
  14. cognite/neat/_issues/_contextmanagers.py +46 -0
  15. cognite/neat/_issues/_factory.py +61 -0
  16. cognite/neat/_issues/errors/__init__.py +18 -4
  17. cognite/neat/_issues/errors/_wrapper.py +81 -3
  18. cognite/neat/_issues/formatters.py +4 -4
  19. cognite/neat/_issues/warnings/__init__.py +3 -2
  20. cognite/neat/_issues/warnings/_properties.py +8 -0
  21. cognite/neat/_rules/_constants.py +9 -0
  22. cognite/neat/_rules/_shared.py +3 -2
  23. cognite/neat/_rules/analysis/__init__.py +2 -3
  24. cognite/neat/_rules/analysis/_base.py +450 -258
  25. cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
  26. cognite/neat/_rules/exporters/_rules2excel.py +2 -8
  27. cognite/neat/_rules/exporters/_rules2instance_template.py +2 -2
  28. cognite/neat/_rules/exporters/_rules2ontology.py +5 -4
  29. cognite/neat/_rules/importers/_base.py +2 -47
  30. cognite/neat/_rules/importers/_dms2rules.py +7 -10
  31. cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +2 -2
  32. cognite/neat/_rules/importers/_rdf/_inference2rules.py +59 -25
  33. cognite/neat/_rules/importers/_rdf/_shared.py +1 -1
  34. cognite/neat/_rules/importers/_spreadsheet2rules.py +12 -9
  35. cognite/neat/_rules/models/dms/_rules.py +3 -1
  36. cognite/neat/_rules/models/dms/_rules_input.py +4 -0
  37. cognite/neat/_rules/models/dms/_validation.py +14 -4
  38. cognite/neat/_rules/models/entities/_loaders.py +1 -1
  39. cognite/neat/_rules/models/entities/_multi_value.py +2 -2
  40. cognite/neat/_rules/models/information/_rules.py +18 -17
  41. cognite/neat/_rules/models/information/_rules_input.py +2 -1
  42. cognite/neat/_rules/models/information/_validation.py +3 -1
  43. cognite/neat/_rules/transformers/__init__.py +8 -2
  44. cognite/neat/_rules/transformers/_converters.py +228 -43
  45. cognite/neat/_rules/transformers/_verification.py +5 -10
  46. cognite/neat/_session/_base.py +4 -4
  47. cognite/neat/_session/_prepare.py +12 -0
  48. cognite/neat/_session/_read.py +21 -17
  49. cognite/neat/_session/_show.py +11 -123
  50. cognite/neat/_session/_state.py +0 -2
  51. cognite/neat/_session/_subset.py +64 -0
  52. cognite/neat/_session/_to.py +63 -12
  53. cognite/neat/_store/_graph_store.py +5 -246
  54. cognite/neat/_utils/rdf_.py +2 -2
  55. cognite/neat/_utils/spreadsheet.py +44 -1
  56. cognite/neat/_utils/text.py +51 -32
  57. cognite/neat/_version.py +1 -1
  58. {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/METADATA +1 -1
  59. {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/RECORD +62 -64
  60. {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/WHEEL +1 -1
  61. cognite/neat/_graph/queries/_construct.py +0 -187
  62. cognite/neat/_graph/queries/_shared.py +0 -173
  63. cognite/neat/_rules/analysis/_dms.py +0 -57
  64. cognite/neat/_rules/analysis/_information.py +0 -249
  65. cognite/neat/_rules/models/_rdfpath.py +0 -372
  66. {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/LICENSE +0 -0
  67. {cognite_neat-0.109.4.dist-info → cognite_neat-0.110.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,46 @@
1
+ import warnings
2
+ from collections.abc import Iterator
3
+ from contextlib import contextmanager
4
+
5
+ from pydantic import ValidationError
6
+
7
+ from cognite.neat._utils.spreadsheet import SpreadsheetRead
8
+
9
+ from ._base import IssueList, MultiValueError, NeatError
10
+ from ._factory import from_pydantic_errors, from_warning
11
+
12
+
13
+ @contextmanager
14
+ def catch_warnings() -> Iterator[IssueList]:
15
+ """Catch warnings and append them to the issues list."""
16
+ issues = IssueList()
17
+ with warnings.catch_warnings(record=True) as warning_logger:
18
+ warnings.simplefilter("always")
19
+ try:
20
+ yield issues
21
+ finally:
22
+ if warning_logger:
23
+ issues.extend([from_warning(warning) for warning in warning_logger])
24
+
25
+
26
+ @contextmanager
27
+ def catch_issues(read_info_by_sheet: dict[str, SpreadsheetRead] | None = None) -> Iterator[IssueList]:
28
+ """This is an internal help function to handle issues and warnings.
29
+
30
+ Args:
31
+ read_info_by_sheet (dict[str, SpreadsheetRead]): The read information by sheet. This is used to adjust
32
+ the row numbers in the errors/warnings.
33
+
34
+ Returns:
35
+ IssueList: The list of issues.
36
+
37
+ """
38
+ with catch_warnings() as issues:
39
+ try:
40
+ yield issues
41
+ except ValidationError as e:
42
+ issues.extend(from_pydantic_errors(e.errors(), read_info_by_sheet))
43
+ except NeatError as single:
44
+ issues.append(single)
45
+ except MultiValueError as multi:
46
+ issues.extend(multi.errors)
@@ -0,0 +1,61 @@
1
+ from typing import cast
2
+ from warnings import WarningMessage
3
+
4
+ from pydantic_core import ErrorDetails
5
+
6
+ from cognite.neat._issues._base import NeatError, NeatWarning
7
+ from cognite.neat._utils.spreadsheet import SpreadsheetRead
8
+
9
+ from .errors import NeatValueError, SpreadsheetError
10
+ from .warnings import NeatValueWarning
11
+
12
+
13
+ def from_pydantic_errors(
14
+ errors: list[ErrorDetails], read_info_by_sheet: dict[str, SpreadsheetRead] | None = None
15
+ ) -> list[NeatError]:
16
+ read_info_by_sheet = read_info_by_sheet or {}
17
+ return [
18
+ _from_pydantic_error(error, read_info_by_sheet)
19
+ for error in errors
20
+ # Skip the error for SheetList, as it is not relevant for the user. This is an
21
+ # internal class used to have helper methods for a lists as .to_pandas()
22
+ if not (error["type"] == "is_instance_of" and error["loc"][1] == "is-instance[SheetList]")
23
+ ]
24
+
25
+
26
+ def from_warning(warning: WarningMessage) -> NeatWarning:
27
+ if isinstance(warning.message, NeatWarning):
28
+ return warning.message
29
+ message = f"{warning.category.__name__}: {warning.message!s}"
30
+ if warning.source:
31
+ message += f" Source: {warning.source}"
32
+ return NeatValueWarning(message)
33
+
34
+
35
+ def _from_pydantic_error(error: ErrorDetails, read_info_by_sheet: dict[str, SpreadsheetRead]) -> NeatError:
36
+ neat_error = _create_neat_value_error(error)
37
+ location = error["loc"]
38
+ return SpreadsheetError.create(location, neat_error, read_info_by_sheet.get(cast(str, location[0])))
39
+
40
+
41
+ def _create_neat_value_error(error: ErrorDetails) -> NeatValueError:
42
+ if (ctx := error.get("ctx")) and (neat_error := ctx.get("error")) and isinstance(neat_error, NeatError):
43
+ # Is already a NeatError
44
+ return neat_error
45
+ return _pydantic_to_neat_error(error)
46
+
47
+
48
+ def _pydantic_to_neat_error(error: ErrorDetails) -> NeatValueError:
49
+ error_type = error["type"]
50
+ input_value = error["input"]
51
+ match error_type:
52
+ # See https://docs.pydantic.dev/latest/errors/validation_errors/ for all possible error types:
53
+ case error_type if error_type.endswith("_type") | error_type.endswith("_parsing"):
54
+ if input_value is None:
55
+ return NeatValueError("value is missing.")
56
+ expected_type = error_type.removesuffix("_type").removesuffix("_parsing")
57
+ return NeatValueError(f"Expected a {expected_type} type, got {input_value!r}")
58
+ case _:
59
+ # The above cases overwrite the human-readable message from pydantic.
60
+ # Motivation for overwriting is that pydantic is developer-oriented and while neat is SME-oriented.
61
+ return NeatValueError(f"{error['msg']} got '{input_value}'")
@@ -1,4 +1,4 @@
1
- from cognite.neat._issues._base import DefaultPydanticError, NeatError, RowError, _get_subclasses
1
+ from cognite.neat._issues._base import NeatError, _get_subclasses
2
2
 
3
3
  from ._external import (
4
4
  AuthorizationError,
@@ -31,12 +31,23 @@ from ._resources import (
31
31
  ResourceNotFoundError,
32
32
  ResourceRetrievalError,
33
33
  )
34
- from ._wrapper import MetadataValueError
34
+ from ._wrapper import (
35
+ ClassValueError,
36
+ ContainerValueError,
37
+ EnumValueError,
38
+ MetadataValueError,
39
+ NodeValueError,
40
+ PropertyValueError,
41
+ SpreadsheetError,
42
+ ViewValueError,
43
+ )
35
44
 
36
45
  __all__ = [
37
46
  "AuthorizationError",
38
47
  "CDFMissingClientError",
39
- "DefaultPydanticError",
48
+ "ClassValueError",
49
+ "ContainerValueError",
50
+ "EnumValueError",
40
51
  "FileMissingRequiredFieldError",
41
52
  "FileNotAFileError",
42
53
  "FileNotFoundNeatError",
@@ -48,12 +59,14 @@ __all__ = [
48
59
  "NeatTypeError",
49
60
  "NeatValueError",
50
61
  "NeatYamlError",
62
+ "NodeValueError",
51
63
  "OxigraphStorageLockedError",
52
64
  "PropertyDefinitionDuplicatedError",
53
65
  "PropertyDefinitionError",
54
66
  "PropertyMappingDuplicatedError",
55
67
  "PropertyNotFoundError",
56
68
  "PropertyTypeNotSupportedError",
69
+ "PropertyValueError",
57
70
  "RegexViolationError",
58
71
  "ResourceChangedError",
59
72
  "ResourceConversionError",
@@ -65,7 +78,8 @@ __all__ = [
65
78
  "ResourceNotFoundError",
66
79
  "ResourceRetrievalError",
67
80
  "ReversedConnectionNotFeasibleError",
68
- "RowError",
81
+ "SpreadsheetError",
82
+ "ViewValueError",
69
83
  ]
70
84
 
71
85
  _NEAT_ERRORS_BY_NAME = {error.__name__: error for error in _get_subclasses(NeatError, include_base=True)}
@@ -1,11 +1,89 @@
1
+ from abc import ABC
1
2
  from dataclasses import dataclass
3
+ from typing import ClassVar, cast
2
4
 
3
5
  from cognite.neat._issues import NeatError
6
+ from cognite.neat._utils.spreadsheet import SpreadsheetRead
4
7
 
5
8
 
6
9
  @dataclass(unsafe_hash=True)
7
- class MetadataValueError(NeatError, ValueError):
8
- """Field {field_name} - {error}"""
10
+ class SpreadsheetError(NeatError, ValueError, ABC):
11
+ """In row {row}: {error}"""
9
12
 
10
- field_name: str
13
+ _name: ClassVar[str] = ""
11
14
  error: NeatError
15
+
16
+ @classmethod
17
+ def create(
18
+ cls, location: tuple[int | str, ...], error: NeatError, spreadsheet: SpreadsheetRead | None = None
19
+ ) -> "SpreadsheetError":
20
+ spreadsheet_name = cast(str, location[0])
21
+ if spreadsheet_name not in ERROR_CLS_BY_SPREADSHEET_NAME:
22
+ # This happens for the metadata sheet, which are individual fields
23
+ return MetadataValueError(error, field_name=spreadsheet_name)
24
+
25
+ error_cls = ERROR_CLS_BY_SPREADSHEET_NAME[spreadsheet_name]
26
+ row, column = cast(tuple[int, str], location[2:4])
27
+
28
+ if spreadsheet:
29
+ row = spreadsheet.adjusted_row_number(row)
30
+
31
+ return error_cls(
32
+ row=row,
33
+ error=error,
34
+ column=column,
35
+ )
36
+
37
+
38
+ @dataclass(unsafe_hash=True)
39
+ class SpreadsheetListError(SpreadsheetError, ABC):
40
+ """In row {row}, column '{column}': {error}"""
41
+
42
+ row: int
43
+ column: str
44
+
45
+
46
+ @dataclass(unsafe_hash=True)
47
+ class MetadataValueError(SpreadsheetError):
48
+ """In field {field_name}: {error}"""
49
+
50
+ _type: ClassVar[str] = "Metadata"
51
+ field_name: str
52
+
53
+
54
+ @dataclass(unsafe_hash=True)
55
+ class ViewValueError(SpreadsheetListError):
56
+ _name = "Views"
57
+
58
+
59
+ @dataclass(unsafe_hash=True)
60
+ class ContainerValueError(SpreadsheetListError):
61
+ _name = "Containers"
62
+
63
+
64
+ @dataclass(unsafe_hash=True)
65
+ class PropertyValueError(SpreadsheetListError):
66
+ _name = "Properties"
67
+
68
+
69
+ @dataclass(unsafe_hash=True)
70
+ class ClassValueError(SpreadsheetListError):
71
+ _name = "Classes"
72
+
73
+
74
+ @dataclass(unsafe_hash=True)
75
+ class EnumValueError(SpreadsheetListError):
76
+ _name = "Enum"
77
+
78
+
79
+ @dataclass(unsafe_hash=True)
80
+ class NodeValueError(SpreadsheetListError):
81
+ _name = "Nodes"
82
+
83
+
84
+ ERROR_CLS_BY_SPREADSHEET_NAME = {cls_._name: cls_ for cls_ in SpreadsheetListError.__subclasses__()}
85
+
86
+ # Efficient way to set docstring for all classes
87
+ for _cls in ERROR_CLS_BY_SPREADSHEET_NAME.values():
88
+ _cls.__doc__ = SpreadsheetListError.__doc__
89
+ del _cls
@@ -3,7 +3,7 @@ import xml.etree.ElementTree as ET
3
3
  from abc import ABC, abstractmethod
4
4
  from pathlib import Path
5
5
 
6
- from ._base import NeatError, NeatIssueList, NeatWarning
6
+ from ._base import IssueList, NeatError, NeatWarning
7
7
 
8
8
  __all__ = ["FORMATTER_BY_NAME", "BasicHTML", "Formatter"]
9
9
 
@@ -13,14 +13,14 @@ class Formatter(ABC):
13
13
  default_file_prefix: str = "validation_report"
14
14
 
15
15
  @abstractmethod
16
- def create_report(self, issues: NeatIssueList) -> str:
16
+ def create_report(self, issues: IssueList) -> str:
17
17
  raise NotImplementedError()
18
18
 
19
19
  @property
20
20
  def default_file_name(self) -> str:
21
21
  return f"{self.default_file_prefix}_{type(self).__name__.lower()}{self.file_suffix}"
22
22
 
23
- def write_to_file(self, issues: NeatIssueList, file_or_dir_path: Path | None = None) -> None:
23
+ def write_to_file(self, issues: IssueList, file_or_dir_path: Path | None = None) -> None:
24
24
  if file_or_dir_path is None:
25
25
  file_or_dir_path = Path(self.default_file_name)
26
26
  elif file_or_dir_path.is_dir():
@@ -41,7 +41,7 @@ class BasicHTML(Formatter):
41
41
  self._doc = ET.Element("html")
42
42
  self._body = ET.SubElement(self._doc, "body")
43
43
 
44
- def create_report(self, issues: NeatIssueList) -> str:
44
+ def create_report(self, issues: IssueList) -> str:
45
45
  errors = [issue for issue in issues if isinstance(issue, NeatError)]
46
46
  warnings_ = [issue for issue in issues if isinstance(issue, NeatWarning)]
47
47
  self._doc.clear()
@@ -2,7 +2,7 @@
2
2
  conflicts with the built-in Python warnings module. However, it is expected to always be used in an absolute
3
3
  import, and should thus not cause a naming conflict."""
4
4
 
5
- from cognite.neat._issues._base import DefaultWarning, NeatWarning, _get_subclasses
5
+ from cognite.neat._issues._base import NeatWarning, _get_subclasses
6
6
 
7
7
  from . import user_modeling
8
8
  from ._external import (
@@ -29,6 +29,7 @@ from ._properties import (
29
29
  PropertyDataTypeConversionWarning,
30
30
  PropertyDefinitionDuplicatedWarning,
31
31
  PropertyDirectRelationLimitWarning,
32
+ PropertyMultipleValueWarning,
32
33
  PropertyNotFoundWarning,
33
34
  PropertyOverwritingWarning,
34
35
  PropertyTypeNotSupportedWarning,
@@ -49,7 +50,6 @@ __all__ = [
49
50
  "CDFAuthWarning",
50
51
  "CDFMaxIterationsWarning",
51
52
  "CDFNotSupportedWarning",
52
- "DefaultWarning",
53
53
  "FileItemNotSupportedWarning",
54
54
  "FileMissingRequiredFieldWarning",
55
55
  "FileReadWarning",
@@ -65,6 +65,7 @@ __all__ = [
65
65
  "PropertyDataTypeConversionWarning",
66
66
  "PropertyDefinitionDuplicatedWarning",
67
67
  "PropertyDirectRelationLimitWarning",
68
+ "PropertyMultipleValueWarning",
68
69
  "PropertyNotFoundWarning",
69
70
  "PropertyOverwritingWarning",
70
71
  "PropertyTypeNotSupportedWarning",
@@ -80,3 +80,11 @@ class PropertyDirectRelationLimitWarning(PropertyWarning[T_Identifier]):
80
80
  resource_type = "view"
81
81
 
82
82
  limit: int = DMS_DIRECT_RELATION_LIST_LIMIT
83
+
84
+
85
+ @dataclass(unsafe_hash=True)
86
+ class PropertyMultipleValueWarning(PropertyWarning[T_Identifier]):
87
+ """The {resource_type} with identifier {identifier} has a property {property_name} with multiple values.
88
+ Selecting the first value {value}, the rest will be ignored."""
89
+
90
+ value: str
@@ -185,3 +185,12 @@ class _Patterns:
185
185
 
186
186
 
187
187
  PATTERNS = _Patterns()
188
+
189
+
190
+ def get_internal_properties() -> set[str]:
191
+ return {
192
+ "physical",
193
+ "logical",
194
+ "conceptual",
195
+ "Neat ID",
196
+ }
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any, Generic, TypeAlias, TypeVar
2
+ from typing import Generic, TypeAlias, TypeVar
3
3
 
4
4
  from cognite.neat._rules.models import (
5
5
  DMSRules,
@@ -7,6 +7,7 @@ from cognite.neat._rules.models import (
7
7
  )
8
8
  from cognite.neat._rules.models.dms._rules_input import DMSInputRules
9
9
  from cognite.neat._rules.models.information._rules_input import InformationInputRules
10
+ from cognite.neat._utils.spreadsheet import SpreadsheetRead
10
11
 
11
12
  VerifiedRules: TypeAlias = InformationRules | DMSRules
12
13
 
@@ -20,7 +21,7 @@ class ReadRules(Generic[T_InputRules]):
20
21
  """This represents a rules that has been read."""
21
22
 
22
23
  rules: T_InputRules | None
23
- read_context: dict[str, Any]
24
+ read_context: dict[str, SpreadsheetRead]
24
25
 
25
26
  @classmethod
26
27
  def display_type_name(cls) -> str:
@@ -1,4 +1,3 @@
1
- from ._dms import DMSAnalysis
2
- from ._information import InformationAnalysis
1
+ from ._base import RulesAnalysis
3
2
 
4
- __all__ = ["DMSAnalysis", "InformationAnalysis"]
3
+ __all__ = ["RulesAnalysis"]