cognite-neat 0.88.0__py3-none-any.whl → 0.88.2__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/routers/configuration.py +1 -1
  3. cognite/neat/app/ui/neat-app/build/asset-manifest.json +7 -7
  4. cognite/neat/app/ui/neat-app/build/index.html +1 -1
  5. cognite/neat/app/ui/neat-app/build/static/css/{main.38a62222.css → main.72e3d92e.css} +2 -2
  6. cognite/neat/app/ui/neat-app/build/static/css/main.72e3d92e.css.map +1 -0
  7. cognite/neat/app/ui/neat-app/build/static/js/main.5a52cf09.js +3 -0
  8. cognite/neat/app/ui/neat-app/build/static/js/{main.ec7f72e2.js.LICENSE.txt → main.5a52cf09.js.LICENSE.txt} +0 -9
  9. cognite/neat/app/ui/neat-app/build/static/js/main.5a52cf09.js.map +1 -0
  10. cognite/neat/config.py +44 -27
  11. cognite/neat/exceptions.py +8 -2
  12. cognite/neat/graph/extractors/_classic_cdf/_assets.py +21 -73
  13. cognite/neat/graph/extractors/_classic_cdf/_base.py +102 -0
  14. cognite/neat/graph/extractors/_classic_cdf/_events.py +46 -42
  15. cognite/neat/graph/extractors/_classic_cdf/_files.py +41 -45
  16. cognite/neat/graph/extractors/_classic_cdf/_labels.py +75 -52
  17. cognite/neat/graph/extractors/_classic_cdf/_relationships.py +49 -27
  18. cognite/neat/graph/extractors/_classic_cdf/_sequences.py +47 -50
  19. cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +47 -49
  20. cognite/neat/graph/loaders/_base.py +4 -4
  21. cognite/neat/graph/loaders/_rdf2asset.py +12 -14
  22. cognite/neat/graph/loaders/_rdf2dms.py +14 -10
  23. cognite/neat/graph/queries/_base.py +22 -29
  24. cognite/neat/graph/queries/_shared.py +1 -1
  25. cognite/neat/graph/stores/_base.py +19 -11
  26. cognite/neat/graph/transformers/_rdfpath.py +3 -2
  27. cognite/neat/issues/__init__.py +16 -0
  28. cognite/neat/{issues.py → issues/_base.py} +78 -2
  29. cognite/neat/issues/errors/external.py +21 -0
  30. cognite/neat/issues/errors/properties.py +75 -0
  31. cognite/neat/issues/errors/resources.py +123 -0
  32. cognite/neat/issues/errors/schema.py +0 -0
  33. cognite/neat/{rules/issues → issues}/formatters.py +9 -9
  34. cognite/neat/issues/neat_warnings/__init__.py +2 -0
  35. cognite/neat/issues/neat_warnings/identifier.py +27 -0
  36. cognite/neat/issues/neat_warnings/models.py +22 -0
  37. cognite/neat/issues/neat_warnings/properties.py +77 -0
  38. cognite/neat/issues/neat_warnings/resources.py +125 -0
  39. cognite/neat/rules/exporters/_rules2dms.py +3 -2
  40. cognite/neat/rules/exporters/_rules2ontology.py +28 -20
  41. cognite/neat/rules/exporters/_validation.py +15 -21
  42. cognite/neat/rules/importers/__init__.py +7 -3
  43. cognite/neat/rules/importers/_base.py +3 -3
  44. cognite/neat/rules/importers/_dms2rules.py +39 -18
  45. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +44 -53
  46. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +6 -5
  47. cognite/neat/rules/importers/_rdf/__init__.py +0 -0
  48. cognite/neat/rules/importers/_rdf/_imf2rules/__init__.py +3 -0
  49. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +82 -0
  50. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +34 -0
  51. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +123 -0
  52. cognite/neat/rules/importers/{_owl2rules/_owl2rules.py → _rdf/_imf2rules/_imf2rules.py} +15 -11
  53. cognite/neat/rules/importers/{_inference2rules.py → _rdf/_inference2rules.py} +1 -1
  54. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +57 -0
  55. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2metadata.py +68 -0
  56. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +59 -0
  57. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +76 -0
  58. cognite/neat/rules/importers/_rdf/_shared.py +586 -0
  59. cognite/neat/rules/importers/_spreadsheet2rules.py +31 -28
  60. cognite/neat/rules/importers/_yaml2rules.py +2 -1
  61. cognite/neat/rules/issues/__init__.py +1 -5
  62. cognite/neat/rules/issues/base.py +2 -21
  63. cognite/neat/rules/issues/dms.py +20 -134
  64. cognite/neat/rules/issues/ontology.py +298 -0
  65. cognite/neat/rules/issues/spreadsheet.py +51 -3
  66. cognite/neat/rules/issues/tables.py +72 -0
  67. cognite/neat/rules/models/_rdfpath.py +4 -4
  68. cognite/neat/rules/models/_types/_field.py +14 -21
  69. cognite/neat/rules/models/asset/_validation.py +1 -1
  70. cognite/neat/rules/models/dms/_schema.py +53 -30
  71. cognite/neat/rules/models/dms/_validation.py +2 -2
  72. cognite/neat/rules/models/entities.py +3 -0
  73. cognite/neat/rules/models/information/_rules.py +5 -4
  74. cognite/neat/rules/models/information/_validation.py +1 -1
  75. cognite/neat/utils/rdf_.py +17 -9
  76. cognite/neat/utils/regex_patterns.py +52 -0
  77. cognite/neat/workflows/steps/lib/current/rules_importer.py +73 -1
  78. cognite/neat/workflows/steps/lib/current/rules_validator.py +19 -7
  79. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/METADATA +2 -6
  80. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/RECORD +85 -72
  81. cognite/neat/app/ui/neat-app/build/static/css/main.38a62222.css.map +0 -1
  82. cognite/neat/app/ui/neat-app/build/static/js/main.ec7f72e2.js +0 -3
  83. cognite/neat/app/ui/neat-app/build/static/js/main.ec7f72e2.js.map +0 -1
  84. cognite/neat/graph/issues/loader.py +0 -104
  85. cognite/neat/graph/stores/_oxrdflib.py +0 -247
  86. cognite/neat/rules/exceptions.py +0 -2972
  87. cognite/neat/rules/importers/_owl2rules/_owl2classes.py +0 -215
  88. cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +0 -213
  89. cognite/neat/rules/importers/_owl2rules/_owl2properties.py +0 -203
  90. cognite/neat/rules/issues/importing.py +0 -408
  91. cognite/neat/rules/models/_types/_base.py +0 -16
  92. cognite/neat/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +0 -152
  93. cognite/neat/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +0 -139
  94. cognite/neat/workflows/examples/Ontology_to_Data_Model/workflow.yaml +0 -116
  95. /cognite/neat/{graph/issues → issues/errors}/__init__.py +0 -0
  96. /cognite/neat/rules/importers/{_owl2rules → _rdf/_owl2rules}/__init__.py +0 -0
  97. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/LICENSE +0 -0
  98. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/WHEEL +0 -0
  99. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from cognite.neat.issues import NeatError
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class FailedAuthorizationError(NeatError):
9
+ description = "Missing authorization for {action}: {reason}"
10
+
11
+ action: str
12
+ reason: str
13
+
14
+ def message(self) -> str:
15
+ return self.description.format(action=self.action, reason=self.reason)
16
+
17
+ def dump(self) -> dict[str, Any]:
18
+ output = super().dump()
19
+ output["action"] = self.action
20
+ output["reason"] = self.reason
21
+ return output
@@ -0,0 +1,75 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Generic
3
+
4
+ from .resources import ResourceError, T_Identifier, T_ReferenceIdentifier
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class PropertyNotFoundError(ResourceError[T_Identifier]):
9
+ """The {resource_type} with identifier {identifier} does not have a property {property_name}"""
10
+
11
+ property_name: str
12
+
13
+ def message(self) -> str:
14
+ return (self.__doc__ or "").format(
15
+ resource_type=self.resource_type, identifier=repr(self.identifier), property_name=self.property_name
16
+ )
17
+
18
+ def dump(self) -> dict[str, Any]:
19
+ output = super().dump()
20
+ output["property_name"] = self.property_name
21
+ return output
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class ReferredPropertyNotFoundError(ResourceError, Generic[T_Identifier, T_ReferenceIdentifier]):
26
+ """The {resource_type} with identifier {identifier} does not have a property {property_name} referred
27
+ to by {referred_type} {referred_by} does not exist
28
+ """
29
+
30
+ fix = "Ensure the {resource_type} {identifier} has a {property_name} property"
31
+
32
+ referred_by: T_ReferenceIdentifier
33
+ referred_type: str
34
+ property_name: str
35
+
36
+ def message(self) -> str:
37
+ return (self.__doc__ or "").format(
38
+ resource_type=self.resource_type,
39
+ identifier=repr(self.identifier),
40
+ referred_type=self.referred_type,
41
+ referred_by=repr(self.referred_by),
42
+ property_name=self.property_name,
43
+ )
44
+
45
+ def dump(self) -> dict[str, Any]:
46
+ output = super().dump()
47
+ output["resource_type"] = self.resource_type
48
+ output["identifier"] = self.identifier
49
+ output["referred_by"] = self.referred_by
50
+ output["referred_type"] = self.referred_type
51
+ output["property_name"] = self.property_name
52
+ return output
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class PropertyTypeNotSupportedError(ResourceError[T_Identifier]):
57
+ """The {resource_type} with identifier {identifier} has a property {property_name}
58
+ of unsupported type {property_type}"""
59
+
60
+ property_name: str
61
+ property_type: str
62
+
63
+ def message(self) -> str:
64
+ return (self.__doc__ or "").format(
65
+ resource_type=self.resource_type,
66
+ identifier=repr(self.identifier),
67
+ property_name=self.property_name,
68
+ property_type=self.property_type,
69
+ )
70
+
71
+ def dump(self) -> dict[str, Any]:
72
+ output = super().dump()
73
+ output["property_name"] = self.property_name
74
+ output["property_type"] = self.property_type
75
+ return output
@@ -0,0 +1,123 @@
1
+ from collections.abc import Hashable
2
+ from dataclasses import dataclass
3
+ from typing import Any, Generic, TypeVar
4
+
5
+ from cognite.neat.issues import NeatError
6
+
7
+ T_Identifier = TypeVar("T_Identifier", bound=Hashable)
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ResourceError(NeatError, Generic[T_Identifier]):
12
+ """Base class for resource errors"""
13
+
14
+ identifier: T_Identifier
15
+ resource_type: str
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ResourceNotFoundError(ResourceError[T_Identifier]):
20
+ """The {resource_type} with identifier {identifier} is missing: {reason}"""
21
+
22
+ fix = "Check the {resource_type} {identifier} and try again."
23
+ reason: str
24
+
25
+ def message(self) -> str:
26
+ return (self.__doc__ or "").format(
27
+ resource_type=self.resource_type, identifier=repr(self.identifier), reason=self.reason
28
+ )
29
+
30
+ def dump(self) -> dict[str, Any]:
31
+ output = super().dump()
32
+ output["resource_type"] = self.resource_type
33
+ output["identifier"] = self.identifier
34
+ output["reason"] = self.reason
35
+ return output
36
+
37
+
38
+ T_ReferenceIdentifier = TypeVar("T_ReferenceIdentifier", bound=Hashable)
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class ReferredResourceNotFoundError(ResourceError, Generic[T_Identifier, T_ReferenceIdentifier]):
43
+ """The {resource_type} with identifier {identifier} referred by {referred_type} {referred_by} does not exist"""
44
+
45
+ fix = "Create the {resource_type}"
46
+
47
+ referred_by: T_ReferenceIdentifier
48
+ referred_type: str
49
+
50
+ def message(self) -> str:
51
+ return (self.__doc__ or "").format(
52
+ resource_type=self.resource_type,
53
+ identifier=repr(self.identifier),
54
+ referred_type=self.referred_type,
55
+ referred_by=repr(self.referred_by),
56
+ )
57
+
58
+ def dump(self) -> dict[str, Any]:
59
+ output = super().dump()
60
+ output["resource_type"] = self.resource_type
61
+ output["identifier"] = self.identifier
62
+ output["referred_by"] = self.referred_by
63
+ output["referred_type"] = self.referred_type
64
+ return output
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class FailedConvertError(NeatError):
69
+ description = "Failed to convert the {identifier} to {target_format}: {reason}"
70
+ fix = "Check the error message and correct the rules."
71
+ identifier: str
72
+ target_format: str
73
+ reason: str
74
+
75
+ def message(self) -> str:
76
+ return self.description.format(identifier=self.identifier, target_format=self.target_format, reason=self.reason)
77
+
78
+ def dump(self) -> dict[str, Any]:
79
+ output = super().dump()
80
+ output["identifier"] = self.identifier
81
+ output["targetFormat"] = self.target_format
82
+ output["reason"] = self.reason
83
+ return output
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class InvalidResourceError(NeatError):
88
+ """The {resource_type} with identifier {identifier} is invalid and will be skipped. {reason}"""
89
+
90
+ fix = "Check the error message and correct the instance."
91
+
92
+ resource_type: str
93
+ identifier: str
94
+ reason: str
95
+
96
+ def message(self) -> str:
97
+ return (self.__doc__ or "").format(
98
+ resource_type=self.resource_type, identifier=self.identifier, reason=self.reason
99
+ )
100
+
101
+ def dump(self) -> dict[str, Any]:
102
+ output = super().dump()
103
+ output["type"] = self.resource_type
104
+ output["identifier"] = self.identifier
105
+ output["reason"] = self.reason
106
+ return output
107
+
108
+
109
+ @dataclass(frozen=True)
110
+ class MissingIdentifierError(NeatError):
111
+ """The {resource_type} with name {name} is missing an identifier."""
112
+
113
+ resource_type: str
114
+ name: str | None = None
115
+
116
+ def message(self) -> str:
117
+ return (self.__doc__ or "").format(resource_type=self.resource_type, name=self.name or "unknown")
118
+
119
+ def dump(self) -> dict[str, Any]:
120
+ output = super().dump()
121
+ output["type"] = self.resource_type
122
+ output["name"] = self.name
123
+ return output
File without changes
@@ -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 IssueList, NeatValidationError, ValidationWarning
6
+ from ._base import NeatError, NeatIssueList, NeatWarning
7
7
 
8
8
  __all__ = ["Formatter", "BasicHTML", "FORMATTER_BY_NAME"]
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: IssueList) -> str:
16
+ def create_report(self, issues: NeatIssueList) -> 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: IssueList, file_or_dir_path: Path | None = None) -> None:
23
+ def write_to_file(self, issues: NeatIssueList, 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,9 +41,9 @@ 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: IssueList) -> str:
45
- errors = [issue for issue in issues if isinstance(issue, NeatValidationError)]
46
- warnings_ = [issue for issue in issues if isinstance(issue, ValidationWarning)]
44
+ def create_report(self, issues: NeatIssueList) -> str:
45
+ errors = [issue for issue in issues if isinstance(issue, NeatError)]
46
+ warnings_ = [issue for issue in issues if isinstance(issue, NeatWarning)]
47
47
  self._doc.clear()
48
48
  self._body = ET.SubElement(self._doc, "body")
49
49
  h1 = ET.SubElement(self._body, "h1")
@@ -61,11 +61,11 @@ class BasicHTML(Formatter):
61
61
 
62
62
  return ET.tostring(self._doc, encoding="unicode")
63
63
 
64
- def _write_errors_or_warnings(self, issues: list[NeatValidationError] | list[ValidationWarning]) -> None:
65
- issue_name = "errors" if isinstance(issues[0], NeatValidationError) else "warnings"
64
+ def _write_errors_or_warnings(self, issues: list[NeatError] | list[NeatWarning]) -> None:
65
+ issue_name = "errors" if isinstance(issues[0], NeatError) else "warnings"
66
66
  main_categories = {base_ for issue in issues for base_ in type(issue).__bases__}
67
67
  for category in main_categories:
68
- issues_in_category: list[NeatValidationError] | list[ValidationWarning] = [ # type: ignore[assignment]
68
+ issues_in_category: list[NeatError] | list[NeatWarning] = [ # type: ignore[assignment]
69
69
  issue for issue in issues if isinstance(issue, category)
70
70
  ]
71
71
  h3 = ET.SubElement(self._body, "h3")
@@ -0,0 +1,2 @@
1
+ """All warnings raised by the neat package are defined here. Note this module is called 'neat_warnings' instead
2
+ of 'warnings' to avoid conflicts with the built-in Python warnings module."""
@@ -0,0 +1,27 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from cognite.neat.issues import NeatWarning
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class RegexViolationWarning(NeatWarning):
9
+ """The value '{value}' of {identifier} does not match the {pattern_name} pattern '{pattern}'"""
10
+
11
+ value: str
12
+ pattern: str
13
+ identifier: str
14
+ pattern_name: str
15
+
16
+ def message(self) -> str:
17
+ return (self.__doc__ or "").format(
18
+ value=self.value, pattern=self.pattern, identifier=self.identifier, pattern_name=self.pattern_name
19
+ )
20
+
21
+ def dump(self) -> dict[str, Any]:
22
+ output = super().dump()
23
+ output["value"] = self.value
24
+ output["pattern"] = self.pattern
25
+ output["identifier"] = self.identifier
26
+ output["pattern_name"] = self.pattern_name
27
+ return output
@@ -0,0 +1,22 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from cognite.neat.issues import NeatWarning
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class InvalidClassWarning(NeatWarning):
9
+ description = "The class {class_name} is invalid and will be skipped. {reason}"
10
+ fix = "Check the error message and correct the class."
11
+
12
+ class_name: str
13
+ reason: str
14
+
15
+ def message(self) -> str:
16
+ return self.description.format(class_name=self.class_name, reason=self.reason)
17
+
18
+ def dump(self) -> dict[str, Any]:
19
+ output = super().dump()
20
+ output["class_name"] = self.class_name
21
+ output["reason"] = self.reason
22
+ return output
@@ -0,0 +1,77 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Generic
3
+
4
+ from .resources import ResourceWarning, T_Identifier, T_ReferenceIdentifier
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class PropertyTypeNotSupportedWarning(ResourceWarning[T_Identifier]):
9
+ """The {resource_type} with identifier {identifier} has a property {property_name}
10
+ of unsupported type {property_type}. This will be ignored."""
11
+
12
+ property_name: str
13
+ property_type: str
14
+
15
+ def message(self) -> str:
16
+ return (self.__doc__ or "").format(
17
+ resource_type=self.resource_type,
18
+ identifier=repr(self.identifier),
19
+ property_name=self.property_name,
20
+ property_type=self.property_type,
21
+ )
22
+
23
+ def dump(self) -> dict[str, Any]:
24
+ output = super().dump()
25
+ output["property_name"] = self.property_name
26
+ output["property_type"] = self.property_type
27
+ return output
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class ReferredPropertyNotFoundWarning(ResourceWarning, Generic[T_Identifier, T_ReferenceIdentifier]):
32
+ """The {resource_type} with identifier {identifier} does not have a property {property_name} referred
33
+ to by {referred_type} {referred_by} does not exist. This will be ignored.
34
+ """
35
+
36
+ fix = "Ensure the {resource_type} {identifier} has a {property_name} property"
37
+
38
+ referred_by: T_ReferenceIdentifier
39
+ referred_type: str
40
+ property_name: str
41
+
42
+ def message(self) -> str:
43
+ return (self.__doc__ or "").format(
44
+ resource_type=self.resource_type,
45
+ identifier=repr(self.identifier),
46
+ referred_type=self.referred_type,
47
+ referred_by=repr(self.referred_by),
48
+ property_name=self.property_name,
49
+ )
50
+
51
+ def dump(self) -> dict[str, Any]:
52
+ output = super().dump()
53
+ output["resource_type"] = self.resource_type
54
+ output["identifier"] = self.identifier
55
+ output["referred_by"] = self.referred_by
56
+ output["referred_type"] = self.referred_type
57
+ output["property_name"] = self.property_name
58
+ return output
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class PropertyRedefinedWarning(ResourceWarning[T_Identifier]):
63
+ """The {resource_type} with identifier {identifier} has a property {property_name} redefined."""
64
+
65
+ property_id: str
66
+
67
+ def message(self) -> str:
68
+ return (self.__doc__ or "").format(
69
+ resource_type=self.resource_type, identifier=repr(self.identifier), property_name=self.property_id
70
+ )
71
+
72
+ def dump(self) -> dict[str, Any]:
73
+ output = super().dump()
74
+ output["property_id"] = self.property_id
75
+ output["resource_type"] = self.resource_type
76
+ output["identifier"] = self.identifier
77
+ return output
@@ -0,0 +1,125 @@
1
+ from collections.abc import Hashable
2
+ from dataclasses import dataclass
3
+ from typing import Any, Generic, TypeVar
4
+
5
+ from cognite.neat.issues import NeatWarning
6
+
7
+ T_Identifier = TypeVar("T_Identifier", bound=Hashable)
8
+
9
+ T_ReferenceIdentifier = TypeVar("T_ReferenceIdentifier", bound=Hashable)
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class ResourceWarning(NeatWarning, Generic[T_Identifier]):
14
+ """Base class for resource warnings"""
15
+
16
+ identifier: T_Identifier
17
+ resource_type: str
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class ResourceNotFoundWarning(ResourceWarning[T_Identifier]):
22
+ """The {resource_type} with identifier {identifier} is missing: {reason}. This will be ignored."""
23
+
24
+ fix = "Check the {resource_type} {identifier} and try again."
25
+ reason: str
26
+
27
+ def message(self) -> str:
28
+ return (self.__doc__ or "").format(
29
+ resource_type=self.resource_type, identifier=repr(self.identifier), reason=self.reason
30
+ )
31
+
32
+ def dump(self) -> dict[str, Any]:
33
+ output = super().dump()
34
+ output["resource_type"] = self.resource_type
35
+ output["identifier"] = self.identifier
36
+ output["reason"] = self.reason
37
+ return output
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class ReferredResourceNotFoundWarning(ResourceWarning, Generic[T_Identifier, T_ReferenceIdentifier]):
42
+ """The {resource_type} with identifier {identifier} referred by {referred_type} {referred_by} does not exist.
43
+ This will be ignored."""
44
+
45
+ fix = "Create the {resource_type}"
46
+
47
+ referred_by: T_ReferenceIdentifier
48
+ referred_type: str
49
+
50
+ def message(self) -> str:
51
+ return (self.__doc__ or "").format(
52
+ resource_type=self.resource_type,
53
+ identifier=repr(self.identifier),
54
+ referred_type=self.referred_type,
55
+ referred_by=repr(self.referred_by),
56
+ )
57
+
58
+ def dump(self) -> dict[str, Any]:
59
+ output = super().dump()
60
+ output["resource_type"] = self.resource_type
61
+ output["identifier"] = self.identifier
62
+ output["referred_by"] = self.referred_by
63
+ output["referred_type"] = self.referred_type
64
+ return output
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class MultipleResourcesWarning(NeatWarning, Generic[T_Identifier]):
69
+ """Multiple resources of type {resource_type} with identifiers {resources} were found. This will be ignored."""
70
+
71
+ fix = "Remove the duplicate resources"
72
+
73
+ resources: frozenset[T_Identifier]
74
+ resource_type: str
75
+
76
+ def message(self) -> str:
77
+ return (self.__doc__ or "").format(
78
+ resource_type=self.resource_type,
79
+ resources=self.resources,
80
+ )
81
+
82
+ def dump(self) -> dict[str, Any]:
83
+ output = super().dump()
84
+ output["resource_type"] = self.resource_type
85
+ output["resources"] = self.resources
86
+ return output
87
+
88
+
89
+ @dataclass(frozen=True)
90
+ class FailedLoadingResourcesWarning(NeatWarning, Generic[T_Identifier]):
91
+ """Failed to load resources of type {resource_type} with identifiers {resources}. Continuing without
92
+ these resources."""
93
+
94
+ extra = "The error was: {error}"
95
+
96
+ fix = "Check the error."
97
+
98
+ resources: frozenset[T_Identifier]
99
+ resource_type: str
100
+ error: str | None = None
101
+
102
+ def message(self) -> str:
103
+ return (self.__doc__ or "").format(
104
+ resource_type=self.resource_type,
105
+ resources=self.resources,
106
+ ) + (self.extra.format(error=self.error) if self.error else "")
107
+
108
+ def dump(self) -> dict[str, Any]:
109
+ output = super().dump()
110
+ output["resource_type"] = self.resource_type
111
+ output["resources"] = self.resources
112
+ return output
113
+
114
+
115
+ class ResourceTypeNotSupportedWarning(ResourceWarning[T_Identifier]):
116
+ """The {resource_type} with identifier {identifier} is not supported. This will be ignored."""
117
+
118
+ def message(self) -> str:
119
+ return (self.__doc__ or "").format(resource_type=self.resource_type, identifier=repr(self.identifier))
120
+
121
+ def dump(self) -> dict[str, Any]:
122
+ output = super().dump()
123
+ output["resource_type"] = self.resource_type
124
+ output["identifier"] = self.identifier
125
+ return output
@@ -15,9 +15,10 @@ from cognite.client.data_classes.data_modeling import (
15
15
  )
16
16
  from cognite.client.exceptions import CogniteAPIError
17
17
 
18
+ from cognite.neat.issues import IssueList
19
+ from cognite.neat.issues.neat_warnings.resources import FailedLoadingResourcesWarning
18
20
  from cognite.neat.rules import issues
19
21
  from cognite.neat.rules._shared import Rules
20
- from cognite.neat.rules.issues import IssueList
21
22
  from cognite.neat.rules.models import InformationRules
22
23
  from cognite.neat.rules.models.dms import DMSRules, DMSSchema, PipelineSchema
23
24
  from cognite.neat.utils.cdf.loaders import (
@@ -313,7 +314,7 @@ class DMSExporter(CDFExporter[DMSSchema]):
313
314
  try:
314
315
  data_models = loader.client.data_modeling.data_models.list(space=space, limit=25, all_versions=False)
315
316
  except CogniteAPIError as e:
316
- warnings.warn(issues.importing.APIWarning(str(e)), stacklevel=2)
317
+ warnings.warn(FailedLoadingResourcesWarning[str](frozenset({space}), "Space", str(e)), stacklevel=2)
317
318
  return []
318
319
  else:
319
320
  return [
@@ -9,8 +9,19 @@ from rdflib import DCTERMS, OWL, RDF, RDFS, XSD, BNode, Graph, Literal, Namespac
9
9
  from rdflib.collection import Collection as GraphCollection
10
10
 
11
11
  from cognite.neat.constants import DEFAULT_NAMESPACE as NEAT_NAMESPACE
12
- from cognite.neat.rules import exceptions
13
12
  from cognite.neat.rules.analysis import InformationAnalysis
13
+ from cognite.neat.rules.issues.ontology import (
14
+ MetadataSheetNamespaceNotDefinedError,
15
+ MissingDataModelPrefixOrNamespaceWarning,
16
+ OntologyMultiDefinitionPropertyWarning,
17
+ OntologyMultiDomainPropertyWarning,
18
+ OntologyMultiLabeledPropertyWarning,
19
+ OntologyMultiRangePropertyWarning,
20
+ OntologyMultiTypePropertyWarning,
21
+ PrefixMissingError,
22
+ PropertiesDefinedMultipleTimesError,
23
+ PropertyDefinitionsNotForSamePropertyError,
24
+ )
14
25
  from cognite.neat.rules.models import DMSRules
15
26
  from cognite.neat.rules.models.data_types import DataType
16
27
  from cognite.neat.rules.models.entities import ClassEntity, EntityTypes
@@ -102,13 +113,15 @@ class Ontology(OntologyModel):
102
113
 
103
114
  properties_redefined, redefinition_warnings = are_properties_redefined(rules, return_report=True)
104
115
  if properties_redefined:
105
- raise exceptions.PropertiesDefinedMultipleTimes(report=generate_exception_report(redefinition_warnings))
116
+ raise PropertiesDefinedMultipleTimesError(
117
+ report=generate_exception_report(redefinition_warnings)
118
+ ).as_exception()
106
119
 
107
120
  if rules.prefixes is None:
108
- raise exceptions.PrefixMissing()
121
+ raise PrefixMissingError().as_exception()
109
122
 
110
123
  if rules.metadata.namespace is None:
111
- raise exceptions.MissingDataModelPrefixOrNamespace()
124
+ raise MissingDataModelPrefixOrNamespaceWarning()
112
125
 
113
126
  class_dict = InformationAnalysis(rules).as_class_dict()
114
127
  return cls(
@@ -172,7 +185,7 @@ class Ontology(OntologyModel):
172
185
  owl.bind(prefix, namespace)
173
186
 
174
187
  if self.metadata.namespace is None:
175
- raise exceptions.MetadataSheetNamespaceNotDefined()
188
+ raise MetadataSheetNamespaceNotDefinedError().as_exception()
176
189
 
177
190
  owl.add((URIRef(self.metadata.namespace), RDF.type, OWL.Ontology))
178
191
  for property_ in self.properties:
@@ -221,7 +234,7 @@ class OWLMetadata(InformationMetadata):
221
234
  def triples(self) -> list[tuple]:
222
235
  # Mandatory triples originating from Metadata mandatory fields
223
236
  if self.namespace is None:
224
- raise exceptions.MetadataSheetNamespaceNotDefined()
237
+ raise MetadataSheetNamespaceNotDefinedError().as_exception()
225
238
  triples: list[tuple] = [
226
239
  (URIRef(self.namespace), DCTERMS.hasVersion, Literal(self.version)),
227
240
  (URIRef(self.namespace), OWL.versionInfo, Literal(self.version)),
@@ -319,7 +332,7 @@ class OWLProperty(OntologyModel):
319
332
  """Here list of properties is a list of properties with the same id, but different definitions."""
320
333
 
321
334
  if not cls.same_property_id(definitions):
322
- raise exceptions.PropertyDefinitionsNotForSameProperty()
335
+ raise PropertyDefinitionsNotForSamePropertyError().as_exception()
323
336
 
324
337
  owl_property = cls.model_construct(
325
338
  id_=namespace[definitions[0].property_],
@@ -350,10 +363,9 @@ class OWLProperty(OntologyModel):
350
363
  def is_multi_type(cls, v, info: ValidationInfo):
351
364
  if len(v) > 1:
352
365
  warnings.warn(
353
- exceptions.OntologyMultiTypeProperty(
366
+ OntologyMultiTypePropertyWarning(
354
367
  remove_namespace_from_uri(info.data["id_"]), [remove_namespace_from_uri(t) for t in v]
355
- ).message,
356
- category=exceptions.OntologyMultiTypeProperty,
368
+ ),
357
369
  stacklevel=2,
358
370
  )
359
371
  return v
@@ -362,10 +374,9 @@ class OWLProperty(OntologyModel):
362
374
  def is_multi_range(cls, v, info: ValidationInfo):
363
375
  if len(v) > 1:
364
376
  warnings.warn(
365
- exceptions.OntologyMultiRangeProperty(
377
+ OntologyMultiRangePropertyWarning(
366
378
  remove_namespace_from_uri(info.data["id_"]), [remove_namespace_from_uri(t) for t in v]
367
- ).message,
368
- category=exceptions.OntologyMultiRangeProperty,
379
+ ),
369
380
  stacklevel=2,
370
381
  )
371
382
  return v
@@ -374,10 +385,9 @@ class OWLProperty(OntologyModel):
374
385
  def is_multi_domain(cls, v, info: ValidationInfo):
375
386
  if len(v) > 1:
376
387
  warnings.warn(
377
- exceptions.OntologyMultiDomainProperty(
388
+ OntologyMultiDomainPropertyWarning(
378
389
  remove_namespace_from_uri(info.data["id_"]), [remove_namespace_from_uri(t) for t in v]
379
- ).message,
380
- category=exceptions.OntologyMultiDomainProperty,
390
+ ),
381
391
  stacklevel=2,
382
392
  )
383
393
  return v
@@ -386,8 +396,7 @@ class OWLProperty(OntologyModel):
386
396
  def has_multi_name(cls, v, info: ValidationInfo):
387
397
  if len(v) > 1:
388
398
  warnings.warn(
389
- exceptions.OntologyMultiLabeledProperty(remove_namespace_from_uri(info.data["id_"]), v).message,
390
- category=exceptions.OntologyMultiLabeledProperty,
399
+ OntologyMultiLabeledPropertyWarning(remove_namespace_from_uri(info.data["id_"]), v),
391
400
  stacklevel=2,
392
401
  )
393
402
  return v
@@ -396,8 +405,7 @@ class OWLProperty(OntologyModel):
396
405
  def has_multi_comment(cls, v, info: ValidationInfo):
397
406
  if len(v) > 1:
398
407
  warnings.warn(
399
- exceptions.OntologyMultiDefinitionProperty(remove_namespace_from_uri(info.data["id_"])).message,
400
- category=exceptions.OntologyMultiDefinitionProperty,
408
+ OntologyMultiDefinitionPropertyWarning(remove_namespace_from_uri(info.data["id_"])),
401
409
  stacklevel=2,
402
410
  )
403
411
  return v