cognite-neat 1.0.31__py3-none-any.whl → 1.0.32__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.
- cognite/neat/_client/data_classes.py +32 -0
- cognite/neat/_client/statistics_api.py +28 -1
- cognite/neat/_data_model/_analysis.py +3 -0
- cognite/neat/_data_model/_constants.py +4 -0
- cognite/neat/_data_model/{validation/dms → rules}/_base.py +11 -5
- cognite/neat/_data_model/rules/cdf/__init__.py +3 -0
- cognite/neat/_data_model/rules/cdf/_base.py +5 -0
- cognite/neat/_data_model/rules/cdf/_orchestrator.py +56 -0
- cognite/neat/_data_model/rules/cdf/_spaces.py +47 -0
- cognite/neat/_data_model/{validation → rules}/dms/__init__.py +2 -2
- cognite/neat/_data_model/{validation → rules}/dms/_ai_readiness.py +17 -17
- cognite/neat/_data_model/rules/dms/_base.py +5 -0
- cognite/neat/_data_model/{validation → rules}/dms/_connections.py +23 -23
- cognite/neat/_data_model/{validation → rules}/dms/_consistency.py +3 -3
- cognite/neat/_data_model/{validation → rules}/dms/_containers.py +9 -9
- cognite/neat/_data_model/{validation → rules}/dms/_limits.py +14 -14
- cognite/neat/_data_model/{validation → rules}/dms/_orchestrator.py +7 -7
- cognite/neat/_data_model/{validation → rules}/dms/_performance.py +7 -7
- cognite/neat/_data_model/{validation → rules}/dms/_views.py +7 -7
- cognite/neat/_session/_cdf.py +15 -1
- cognite/neat/_session/_physical.py +6 -6
- cognite/neat/_session/_wrappers.py +1 -1
- cognite/neat/_state_machine/_states.py +1 -1
- cognite/neat/_store/_store.py +19 -1
- cognite/neat/_version.py +1 -1
- {cognite_neat-1.0.31.dist-info → cognite_neat-1.0.32.dist-info}/METADATA +1 -1
- {cognite_neat-1.0.31.dist-info → cognite_neat-1.0.32.dist-info}/RECORD +29 -24
- {cognite_neat-1.0.31.dist-info → cognite_neat-1.0.32.dist-info}/WHEEL +1 -1
- /cognite/neat/_data_model/{validation → rules}/__init__.py +0 -0
|
@@ -2,6 +2,8 @@ from typing import Generic, TypeVar
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
+
from cognite.neat._utils.useful_types import BaseModelObject
|
|
6
|
+
|
|
5
7
|
T = TypeVar("T", bound=BaseModel)
|
|
6
8
|
|
|
7
9
|
|
|
@@ -42,3 +44,33 @@ class StatisticsResponse(BaseModel, populate_by_name=True):
|
|
|
42
44
|
concurrent_read_limit: int = Field(alias="concurrentReadLimit")
|
|
43
45
|
concurrent_write_limit: int = Field(alias="concurrentWriteLimit")
|
|
44
46
|
concurrent_delete_limit: int = Field(alias="concurrentDeleteLimit")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SpaceStatisticsItem(BaseModelObject, populate_by_name=True):
|
|
50
|
+
"""Individual space statistics item."""
|
|
51
|
+
|
|
52
|
+
space: str
|
|
53
|
+
containers: int
|
|
54
|
+
views: int
|
|
55
|
+
data_models: int
|
|
56
|
+
edges: int
|
|
57
|
+
soft_deleted_edges: int
|
|
58
|
+
nodes: int
|
|
59
|
+
soft_deleted_nodes: int
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_empty(self) -> bool:
|
|
63
|
+
"""Check if the space has zero usage."""
|
|
64
|
+
return (
|
|
65
|
+
self.containers == 0 and self.views == 0 and self.data_models == 0 and self.edges == 0 and self.nodes == 0
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SpaceStatisticsResponse(BaseModelObject, populate_by_name=True):
|
|
70
|
+
"""Response model for space statistics endpoint."""
|
|
71
|
+
|
|
72
|
+
items: list[SpaceStatisticsItem]
|
|
73
|
+
|
|
74
|
+
def empty_spaces(self) -> list[str]:
|
|
75
|
+
"""Get a list of space identifiers that have zero usage."""
|
|
76
|
+
return [item.space for item in self.items if item.is_empty]
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
1
3
|
from cognite.neat._utils.http_client import HTTPClient, ParametersRequest
|
|
4
|
+
from cognite.neat._utils.http_client._data_classes import SimpleBodyRequest
|
|
2
5
|
|
|
3
6
|
from .config import NeatClientConfig
|
|
4
|
-
from .data_classes import StatisticsResponse
|
|
7
|
+
from .data_classes import SpaceStatisticsResponse, StatisticsResponse
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
class StatisticsAPI:
|
|
@@ -27,3 +30,27 @@ class StatisticsAPI:
|
|
|
27
30
|
result.raise_for_status()
|
|
28
31
|
result = StatisticsResponse.model_validate_json(result.success_response.body)
|
|
29
32
|
return result
|
|
33
|
+
|
|
34
|
+
def space_statistics(self, spaces: list[str]) -> SpaceStatisticsResponse:
|
|
35
|
+
"""Retrieve space-wise usage data and limits.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
spaces: List of space identifiers to retrieve statistics for.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
SpaceStatisticsResponse object.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
body = {"items": [{"space": space} for space in spaces]}
|
|
45
|
+
|
|
46
|
+
result = self._http_client.request_with_retries(
|
|
47
|
+
SimpleBodyRequest(
|
|
48
|
+
endpoint_url=self._config.create_api_url("/models/statistics/spaces/byids"),
|
|
49
|
+
method="POST",
|
|
50
|
+
body=json.dumps(body),
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
result.raise_for_status()
|
|
55
|
+
result = SpaceStatisticsResponse.model_validate_json(result.success_response.body)
|
|
56
|
+
return result
|
|
@@ -8,6 +8,7 @@ from typing import Literal, TypeAlias, TypeVar
|
|
|
8
8
|
import networkx as nx
|
|
9
9
|
from pyparsing import cached_property
|
|
10
10
|
|
|
11
|
+
from cognite.neat._client.data_classes import SpaceStatisticsResponse
|
|
11
12
|
from cognite.neat._data_model._constants import COGNITE_SPACES
|
|
12
13
|
from cognite.neat._data_model._snapshot import SchemaSnapshot
|
|
13
14
|
from cognite.neat._data_model.models.dms._constraints import RequiresConstraintDefinition
|
|
@@ -70,9 +71,11 @@ class ValidationResources:
|
|
|
70
71
|
local: SchemaSnapshot,
|
|
71
72
|
cdf: SchemaSnapshot,
|
|
72
73
|
limits: SchemaLimits | None = None,
|
|
74
|
+
space_statistics: SpaceStatisticsResponse | None = None,
|
|
73
75
|
) -> None:
|
|
74
76
|
self._modus_operandi = modus_operandi
|
|
75
77
|
self.limits = limits or SchemaLimits()
|
|
78
|
+
self.space_statistics = space_statistics
|
|
76
79
|
|
|
77
80
|
self.local = local
|
|
78
81
|
self.cdf = cdf
|
|
@@ -67,8 +67,12 @@ COGNITE_SPACES = (
|
|
|
67
67
|
"cdf_apm",
|
|
68
68
|
"cdf_apps_shared",
|
|
69
69
|
"cdf_cdm_3d",
|
|
70
|
+
"cdf_time_series_data",
|
|
71
|
+
"cdf_cdm_units",
|
|
70
72
|
)
|
|
71
73
|
|
|
74
|
+
COGNITE_APP_SPACES = ("CommentInstanceSpace", "IndustrialCanvasInstanceSpace", "SolutionTagsInstanceSpace", "scene")
|
|
75
|
+
|
|
72
76
|
# Defaults from https://docs.cognite.com/cdf/dm/dm_reference/dm_limits_and_restrictions#list-size-limits
|
|
73
77
|
|
|
74
78
|
DEFAULT_MAX_LIST_SIZE = 1000
|
|
@@ -2,15 +2,17 @@ from abc import ABC, abstractmethod
|
|
|
2
2
|
from typing import ClassVar
|
|
3
3
|
|
|
4
4
|
from cognite.neat._data_model._analysis import ValidationResources
|
|
5
|
+
from cognite.neat._data_model.models.dms._schema import RequestSchema
|
|
5
6
|
from cognite.neat._issues import ConsistencyError, Recommendation
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
class
|
|
9
|
-
"""
|
|
9
|
+
class NeatRule(ABC):
|
|
10
|
+
"""Rules for data model principles."""
|
|
10
11
|
|
|
11
12
|
code: ClassVar[str]
|
|
12
13
|
issue_type: ClassVar[type[ConsistencyError] | type[Recommendation]]
|
|
13
14
|
alpha: ClassVar[bool] = False
|
|
15
|
+
fixable: ClassVar[bool] = False
|
|
14
16
|
|
|
15
17
|
def __init__(
|
|
16
18
|
self,
|
|
@@ -19,7 +21,11 @@ class DataModelValidator(ABC):
|
|
|
19
21
|
self.validation_resources = validation_resources
|
|
20
22
|
|
|
21
23
|
@abstractmethod
|
|
22
|
-
def
|
|
23
|
-
"""Execute
|
|
24
|
-
# do something with data model
|
|
24
|
+
def validate(self) -> list[ConsistencyError] | list[Recommendation] | list[ConsistencyError | Recommendation]:
|
|
25
|
+
"""Execute rule validation."""
|
|
25
26
|
...
|
|
27
|
+
|
|
28
|
+
def fix(self) -> RequestSchema:
|
|
29
|
+
"""Fix the issues found by the validator producing a fixed object."""
|
|
30
|
+
|
|
31
|
+
raise NotImplementedError("This rule does not implement fix()")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from cognite.neat._client.data_classes import SpaceStatisticsResponse
|
|
4
|
+
from cognite.neat._data_model._analysis import ValidationResources
|
|
5
|
+
from cognite.neat._data_model._shared import OnSuccessIssuesChecker
|
|
6
|
+
from cognite.neat._data_model._snapshot import SchemaSnapshot
|
|
7
|
+
from cognite.neat._data_model.models.dms._limits import SchemaLimits
|
|
8
|
+
from cognite.neat._utils.auxiliary import get_concrete_subclasses
|
|
9
|
+
|
|
10
|
+
from ._base import CDFRule
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CDFRulesOrchestrator(OnSuccessIssuesChecker):
|
|
14
|
+
"""CDF rules orchestrator, used to execute CDF rules on an entire CDF snapshot."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
limits: SchemaLimits,
|
|
19
|
+
space_statistics: SpaceStatisticsResponse,
|
|
20
|
+
can_run_validator: Callable[[str, type], bool] | None = None,
|
|
21
|
+
enable_alpha_validators: bool = False,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
self._limits = limits
|
|
25
|
+
self._can_run_validator = can_run_validator or (lambda code, issue_type: True) # type: ignore
|
|
26
|
+
self._has_run = False
|
|
27
|
+
self._enable_alpha_validators = enable_alpha_validators
|
|
28
|
+
self._space_statistics = space_statistics
|
|
29
|
+
|
|
30
|
+
def run(self, cdf_snapshot: SchemaSnapshot) -> None:
|
|
31
|
+
"""Run quality assessment on the DMS data model."""
|
|
32
|
+
|
|
33
|
+
validation_resources = self._gather_validation_resources(cdf_snapshot)
|
|
34
|
+
|
|
35
|
+
# Initialize all validators
|
|
36
|
+
validators: list[CDFRule] = [validator(validation_resources) for validator in get_concrete_subclasses(CDFRule)]
|
|
37
|
+
|
|
38
|
+
# Run validators
|
|
39
|
+
for validator in validators:
|
|
40
|
+
if validator.alpha and not self._enable_alpha_validators:
|
|
41
|
+
continue
|
|
42
|
+
if self._can_run_validator(validator.code, validator.issue_type):
|
|
43
|
+
self._issues.extend(validator.validate())
|
|
44
|
+
|
|
45
|
+
self._has_run = True
|
|
46
|
+
|
|
47
|
+
def _gather_validation_resources(self, cdf_snapshot: SchemaSnapshot) -> ValidationResources:
|
|
48
|
+
# we do not want to modify the original request schema during validation
|
|
49
|
+
|
|
50
|
+
return ValidationResources(
|
|
51
|
+
cdf=cdf_snapshot,
|
|
52
|
+
local=cdf_snapshot,
|
|
53
|
+
limits=self._limits,
|
|
54
|
+
space_statistics=self._space_statistics,
|
|
55
|
+
modus_operandi="rebuild",
|
|
56
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from cognite.neat._data_model._constants import COGNITE_APP_SPACES, COGNITE_SPACES
|
|
2
|
+
from cognite.neat._data_model.rules.cdf._base import CDFRule
|
|
3
|
+
from cognite.neat._issues import Recommendation
|
|
4
|
+
|
|
5
|
+
BASE_CODE = "NEAT-CDF-SPACES"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EmptySpaces(CDFRule):
|
|
9
|
+
"""Rule that checks for empty spaces in CDF.
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
This rule checks if there are any empty spaces in CDF.
|
|
13
|
+
|
|
14
|
+
## Why is this bad?
|
|
15
|
+
CDF projects typically have limits of 100 spaces, and having empty spaces can waste these valuable resources.
|
|
16
|
+
Also, empty spaces can lead to confusion and mismanagement of resources within the CDF environment.
|
|
17
|
+
They may indicate incomplete configurations or unused resources that could be cleaned up.
|
|
18
|
+
|
|
19
|
+
## Example
|
|
20
|
+
A space `iamempty` with no associated resources such as Views, Containers or Data Models.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
code = f"{BASE_CODE}-001"
|
|
26
|
+
issue_type = Recommendation
|
|
27
|
+
|
|
28
|
+
def validate(self) -> list[Recommendation]:
|
|
29
|
+
issues: list[Recommendation] = []
|
|
30
|
+
|
|
31
|
+
if not self.validation_resources.space_statistics:
|
|
32
|
+
return issues
|
|
33
|
+
|
|
34
|
+
empty_spaces = set(self.validation_resources.space_statistics.empty_spaces()) - set(
|
|
35
|
+
COGNITE_APP_SPACES + COGNITE_SPACES
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
for space in empty_spaces:
|
|
39
|
+
issues.append(
|
|
40
|
+
Recommendation(
|
|
41
|
+
message=f"Space '{space}' is empty and has no associated resources.",
|
|
42
|
+
code=self.code,
|
|
43
|
+
fix="Consider removing the empty space to maintain a clean CDF environment.",
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return issues
|
|
@@ -36,7 +36,7 @@ from ._limits import (
|
|
|
36
36
|
ViewImplementsCountIsOutOfLimits,
|
|
37
37
|
ViewPropertyCountIsOutOfLimits,
|
|
38
38
|
)
|
|
39
|
-
from ._orchestrator import
|
|
39
|
+
from ._orchestrator import DmsDataModelRulesOrchestrator
|
|
40
40
|
from ._performance import (
|
|
41
41
|
MissingRequiresConstraint,
|
|
42
42
|
SuboptimalRequiresConstraint,
|
|
@@ -53,7 +53,7 @@ __all__ = [
|
|
|
53
53
|
"DataModelMissingDescription",
|
|
54
54
|
"DataModelMissingName",
|
|
55
55
|
"DataModelViewCountIsOutOfLimits",
|
|
56
|
-
"
|
|
56
|
+
"DmsDataModelRulesOrchestrator",
|
|
57
57
|
"EnumerationMissingDescription",
|
|
58
58
|
"EnumerationMissingName",
|
|
59
59
|
"ExternalContainerDoesNotExist",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Validators for checking if data model is AI-ready."""
|
|
2
2
|
|
|
3
3
|
from cognite.neat._data_model.models.dms._data_types import EnumProperty
|
|
4
|
-
from cognite.neat._data_model.
|
|
4
|
+
from cognite.neat._data_model.rules.dms._base import DataModelRule
|
|
5
5
|
from cognite.neat._issues import Recommendation
|
|
6
6
|
|
|
7
7
|
BASE_CODE = "NEAT-DMS-AI-READINESS"
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class DataModelMissingName(
|
|
10
|
+
class DataModelMissingName(DataModelRule):
|
|
11
11
|
"""Validates that data model has a human-readable name.
|
|
12
12
|
|
|
13
13
|
## What it does
|
|
@@ -26,7 +26,7 @@ class DataModelMissingName(DataModelValidator):
|
|
|
26
26
|
code = f"{BASE_CODE}-001"
|
|
27
27
|
issue_type = Recommendation
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def validate(self) -> list[Recommendation]:
|
|
30
30
|
recommendations: list[Recommendation] = []
|
|
31
31
|
|
|
32
32
|
if not self.validation_resources.merged_data_model.name:
|
|
@@ -41,7 +41,7 @@ class DataModelMissingName(DataModelValidator):
|
|
|
41
41
|
return recommendations
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
class DataModelMissingDescription(
|
|
44
|
+
class DataModelMissingDescription(DataModelRule):
|
|
45
45
|
"""Validates that data model has a human-readable description.
|
|
46
46
|
|
|
47
47
|
## What it does
|
|
@@ -65,7 +65,7 @@ class DataModelMissingDescription(DataModelValidator):
|
|
|
65
65
|
code = f"{BASE_CODE}-002"
|
|
66
66
|
issue_type = Recommendation
|
|
67
67
|
|
|
68
|
-
def
|
|
68
|
+
def validate(self) -> list[Recommendation]:
|
|
69
69
|
recommendations: list[Recommendation] = []
|
|
70
70
|
|
|
71
71
|
if not self.validation_resources.merged_data_model.description:
|
|
@@ -80,7 +80,7 @@ class DataModelMissingDescription(DataModelValidator):
|
|
|
80
80
|
return recommendations
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
class ViewMissingName(
|
|
83
|
+
class ViewMissingName(DataModelRule):
|
|
84
84
|
"""Validates that a View has a human-readable name.
|
|
85
85
|
|
|
86
86
|
## What it does
|
|
@@ -100,7 +100,7 @@ class ViewMissingName(DataModelValidator):
|
|
|
100
100
|
code = f"{BASE_CODE}-003"
|
|
101
101
|
issue_type = Recommendation
|
|
102
102
|
|
|
103
|
-
def
|
|
103
|
+
def validate(self) -> list[Recommendation]:
|
|
104
104
|
recommendations: list[Recommendation] = []
|
|
105
105
|
|
|
106
106
|
for view_ref in self.validation_resources.merged_data_model.views or []:
|
|
@@ -121,7 +121,7 @@ class ViewMissingName(DataModelValidator):
|
|
|
121
121
|
return recommendations
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
class ViewMissingDescription(
|
|
124
|
+
class ViewMissingDescription(DataModelRule):
|
|
125
125
|
"""Validates that a View has a human-readable description.
|
|
126
126
|
|
|
127
127
|
## What it does
|
|
@@ -151,7 +151,7 @@ class ViewMissingDescription(DataModelValidator):
|
|
|
151
151
|
code = f"{BASE_CODE}-004"
|
|
152
152
|
issue_type = Recommendation
|
|
153
153
|
|
|
154
|
-
def
|
|
154
|
+
def validate(self) -> list[Recommendation]:
|
|
155
155
|
recommendations: list[Recommendation] = []
|
|
156
156
|
|
|
157
157
|
for view_ref in self.validation_resources.merged_data_model.views or []:
|
|
@@ -172,7 +172,7 @@ class ViewMissingDescription(DataModelValidator):
|
|
|
172
172
|
return recommendations
|
|
173
173
|
|
|
174
174
|
|
|
175
|
-
class ViewPropertyMissingName(
|
|
175
|
+
class ViewPropertyMissingName(DataModelRule):
|
|
176
176
|
"""Validates that a view property has a human-readable name.
|
|
177
177
|
|
|
178
178
|
## What it does
|
|
@@ -192,7 +192,7 @@ class ViewPropertyMissingName(DataModelValidator):
|
|
|
192
192
|
code = f"{BASE_CODE}-005"
|
|
193
193
|
issue_type = Recommendation
|
|
194
194
|
|
|
195
|
-
def
|
|
195
|
+
def validate(self) -> list[Recommendation]:
|
|
196
196
|
recommendations: list[Recommendation] = []
|
|
197
197
|
|
|
198
198
|
for view_ref in self.validation_resources.merged_data_model.views or []:
|
|
@@ -217,7 +217,7 @@ class ViewPropertyMissingName(DataModelValidator):
|
|
|
217
217
|
return recommendations
|
|
218
218
|
|
|
219
219
|
|
|
220
|
-
class ViewPropertyMissingDescription(
|
|
220
|
+
class ViewPropertyMissingDescription(DataModelRule):
|
|
221
221
|
"""Validates that a View property has a human-readable description.
|
|
222
222
|
|
|
223
223
|
## What it does
|
|
@@ -248,7 +248,7 @@ class ViewPropertyMissingDescription(DataModelValidator):
|
|
|
248
248
|
code = f"{BASE_CODE}-006"
|
|
249
249
|
issue_type = Recommendation
|
|
250
250
|
|
|
251
|
-
def
|
|
251
|
+
def validate(self) -> list[Recommendation]:
|
|
252
252
|
recommendations: list[Recommendation] = []
|
|
253
253
|
|
|
254
254
|
for view_ref in self.validation_resources.merged_data_model.views or []:
|
|
@@ -273,7 +273,7 @@ class ViewPropertyMissingDescription(DataModelValidator):
|
|
|
273
273
|
return recommendations
|
|
274
274
|
|
|
275
275
|
|
|
276
|
-
class EnumerationMissingName(
|
|
276
|
+
class EnumerationMissingName(DataModelRule):
|
|
277
277
|
"""Validates that an enumeration has a human-readable name.
|
|
278
278
|
|
|
279
279
|
## What it does
|
|
@@ -293,7 +293,7 @@ class EnumerationMissingName(DataModelValidator):
|
|
|
293
293
|
code = f"{BASE_CODE}-007"
|
|
294
294
|
issue_type = Recommendation
|
|
295
295
|
|
|
296
|
-
def
|
|
296
|
+
def validate(self) -> list[Recommendation]:
|
|
297
297
|
recommendations: list[Recommendation] = []
|
|
298
298
|
|
|
299
299
|
for container_ref in self.validation_resources.merged.containers:
|
|
@@ -322,7 +322,7 @@ class EnumerationMissingName(DataModelValidator):
|
|
|
322
322
|
return recommendations
|
|
323
323
|
|
|
324
324
|
|
|
325
|
-
class EnumerationMissingDescription(
|
|
325
|
+
class EnumerationMissingDescription(DataModelRule):
|
|
326
326
|
"""Validates that an enumeration value has a human-readable description.
|
|
327
327
|
|
|
328
328
|
## What it does
|
|
@@ -353,7 +353,7 @@ class EnumerationMissingDescription(DataModelValidator):
|
|
|
353
353
|
code = f"{BASE_CODE}-008"
|
|
354
354
|
issue_type = Recommendation
|
|
355
355
|
|
|
356
|
-
def
|
|
356
|
+
def validate(self) -> list[Recommendation]:
|
|
357
357
|
recommendations: list[Recommendation] = []
|
|
358
358
|
|
|
359
359
|
for container_ref in self.validation_resources.merged.containers:
|
|
@@ -9,13 +9,13 @@ from cognite.neat._data_model.models.dms._references import (
|
|
|
9
9
|
ViewReference,
|
|
10
10
|
)
|
|
11
11
|
from cognite.neat._data_model.models.dms._view_property import ViewCorePropertyRequest
|
|
12
|
-
from cognite.neat._data_model.
|
|
12
|
+
from cognite.neat._data_model.rules.dms._base import DataModelRule
|
|
13
13
|
from cognite.neat._issues import ConsistencyError, Recommendation
|
|
14
14
|
|
|
15
15
|
BASE_CODE = "NEAT-DMS-CONNECTIONS"
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class ConnectionValueTypeUnexisting(
|
|
18
|
+
class ConnectionValueTypeUnexisting(DataModelRule):
|
|
19
19
|
"""Validates that connection value types exist.
|
|
20
20
|
|
|
21
21
|
## What it does
|
|
@@ -33,7 +33,7 @@ class ConnectionValueTypeUnexisting(DataModelValidator):
|
|
|
33
33
|
code = f"{BASE_CODE}-001"
|
|
34
34
|
issue_type = ConsistencyError
|
|
35
35
|
|
|
36
|
-
def
|
|
36
|
+
def validate(self) -> list[ConsistencyError]:
|
|
37
37
|
errors: list[ConsistencyError] = []
|
|
38
38
|
|
|
39
39
|
for (view, property_), value_type in self.validation_resources.connection_end_node_types.items():
|
|
@@ -57,7 +57,7 @@ class ConnectionValueTypeUnexisting(DataModelValidator):
|
|
|
57
57
|
return errors
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
class ConnectionValueTypeUndefined(
|
|
60
|
+
class ConnectionValueTypeUndefined(DataModelRule):
|
|
61
61
|
"""Validates that connection value types are not None, i.e. undefined.
|
|
62
62
|
|
|
63
63
|
## What it does
|
|
@@ -77,7 +77,7 @@ class ConnectionValueTypeUndefined(DataModelValidator):
|
|
|
77
77
|
code = f"{BASE_CODE}-002"
|
|
78
78
|
issue_type = Recommendation
|
|
79
79
|
|
|
80
|
-
def
|
|
80
|
+
def validate(self) -> list[Recommendation]:
|
|
81
81
|
recommendations: list[Recommendation] = []
|
|
82
82
|
|
|
83
83
|
for (view, property_), value_type in self.validation_resources.connection_end_node_types.items():
|
|
@@ -128,7 +128,7 @@ def _normalize_through_reference(
|
|
|
128
128
|
return through
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
class ReverseConnectionSourceViewMissing(
|
|
131
|
+
class ReverseConnectionSourceViewMissing(DataModelRule):
|
|
132
132
|
"""Validates that source view referenced in reverse connection exist.
|
|
133
133
|
|
|
134
134
|
## What it does
|
|
@@ -146,7 +146,7 @@ class ReverseConnectionSourceViewMissing(DataModelValidator):
|
|
|
146
146
|
code = f"{BASE_CODE}-REVERSE-001"
|
|
147
147
|
issue_type = ConsistencyError
|
|
148
148
|
|
|
149
|
-
def
|
|
149
|
+
def validate(self) -> list[ConsistencyError]:
|
|
150
150
|
errors: list[ConsistencyError] = []
|
|
151
151
|
|
|
152
152
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -172,7 +172,7 @@ class ReverseConnectionSourceViewMissing(DataModelValidator):
|
|
|
172
172
|
return errors
|
|
173
173
|
|
|
174
174
|
|
|
175
|
-
class ReverseConnectionSourcePropertyMissing(
|
|
175
|
+
class ReverseConnectionSourcePropertyMissing(DataModelRule):
|
|
176
176
|
"""Validates that source property referenced in reverse connections exist.
|
|
177
177
|
|
|
178
178
|
## What it does
|
|
@@ -191,7 +191,7 @@ class ReverseConnectionSourcePropertyMissing(DataModelValidator):
|
|
|
191
191
|
code = f"{BASE_CODE}-REVERSE-002"
|
|
192
192
|
issue_type = ConsistencyError
|
|
193
193
|
|
|
194
|
-
def
|
|
194
|
+
def validate(self) -> list[ConsistencyError]:
|
|
195
195
|
errors: list[ConsistencyError] = []
|
|
196
196
|
|
|
197
197
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -224,7 +224,7 @@ class ReverseConnectionSourcePropertyMissing(DataModelValidator):
|
|
|
224
224
|
return errors
|
|
225
225
|
|
|
226
226
|
|
|
227
|
-
class ReverseConnectionSourcePropertyWrongType(
|
|
227
|
+
class ReverseConnectionSourcePropertyWrongType(DataModelRule):
|
|
228
228
|
"""Validates that source property for the reverse connections is a direct relation.
|
|
229
229
|
|
|
230
230
|
## What it does
|
|
@@ -243,7 +243,7 @@ class ReverseConnectionSourcePropertyWrongType(DataModelValidator):
|
|
|
243
243
|
code = f"{BASE_CODE}-REVERSE-003"
|
|
244
244
|
issue_type = ConsistencyError
|
|
245
245
|
|
|
246
|
-
def
|
|
246
|
+
def validate(self) -> list[ConsistencyError]:
|
|
247
247
|
errors: list[ConsistencyError] = []
|
|
248
248
|
|
|
249
249
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -280,7 +280,7 @@ class ReverseConnectionSourcePropertyWrongType(DataModelValidator):
|
|
|
280
280
|
return errors
|
|
281
281
|
|
|
282
282
|
|
|
283
|
-
class ReverseConnectionContainerMissing(
|
|
283
|
+
class ReverseConnectionContainerMissing(DataModelRule):
|
|
284
284
|
"""Validates that the container referenced by the reverse connection source properties exist.
|
|
285
285
|
|
|
286
286
|
## What it does
|
|
@@ -298,7 +298,7 @@ class ReverseConnectionContainerMissing(DataModelValidator):
|
|
|
298
298
|
code = f"{BASE_CODE}-REVERSE-004"
|
|
299
299
|
issue_type = ConsistencyError
|
|
300
300
|
|
|
301
|
-
def
|
|
301
|
+
def validate(self) -> list[ConsistencyError]:
|
|
302
302
|
errors: list[ConsistencyError] = []
|
|
303
303
|
|
|
304
304
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -344,7 +344,7 @@ class ReverseConnectionContainerMissing(DataModelValidator):
|
|
|
344
344
|
return errors
|
|
345
345
|
|
|
346
346
|
|
|
347
|
-
class ReverseConnectionContainerPropertyMissing(
|
|
347
|
+
class ReverseConnectionContainerPropertyMissing(DataModelRule):
|
|
348
348
|
"""Validates that container property referenced by the reverse connections exists.
|
|
349
349
|
|
|
350
350
|
## What it does
|
|
@@ -363,7 +363,7 @@ class ReverseConnectionContainerPropertyMissing(DataModelValidator):
|
|
|
363
363
|
code = f"{BASE_CODE}-REVERSE-005"
|
|
364
364
|
issue_type = ConsistencyError
|
|
365
365
|
|
|
366
|
-
def
|
|
366
|
+
def validate(self) -> list[ConsistencyError]:
|
|
367
367
|
errors: list[ConsistencyError] = []
|
|
368
368
|
|
|
369
369
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -412,7 +412,7 @@ class ReverseConnectionContainerPropertyMissing(DataModelValidator):
|
|
|
412
412
|
return errors
|
|
413
413
|
|
|
414
414
|
|
|
415
|
-
class ReverseConnectionContainerPropertyWrongType(
|
|
415
|
+
class ReverseConnectionContainerPropertyWrongType(DataModelRule):
|
|
416
416
|
"""Validates that the container property used in reverse connection is the direct relations.
|
|
417
417
|
|
|
418
418
|
## What it does
|
|
@@ -431,7 +431,7 @@ class ReverseConnectionContainerPropertyWrongType(DataModelValidator):
|
|
|
431
431
|
code = f"{BASE_CODE}-REVERSE-006"
|
|
432
432
|
issue_type = ConsistencyError
|
|
433
433
|
|
|
434
|
-
def
|
|
434
|
+
def validate(self) -> list[ConsistencyError]:
|
|
435
435
|
errors: list[ConsistencyError] = []
|
|
436
436
|
|
|
437
437
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -483,7 +483,7 @@ class ReverseConnectionContainerPropertyWrongType(DataModelValidator):
|
|
|
483
483
|
return errors
|
|
484
484
|
|
|
485
485
|
|
|
486
|
-
class ReverseConnectionTargetMissing(
|
|
486
|
+
class ReverseConnectionTargetMissing(DataModelRule):
|
|
487
487
|
"""Validates that the direct connection in reverse connection pair have target views specified.
|
|
488
488
|
|
|
489
489
|
## What it does
|
|
@@ -501,7 +501,7 @@ class ReverseConnectionTargetMissing(DataModelValidator):
|
|
|
501
501
|
code = f"{BASE_CODE}-REVERSE-007"
|
|
502
502
|
issue_type = Recommendation
|
|
503
503
|
|
|
504
|
-
def
|
|
504
|
+
def validate(self) -> list[Recommendation]:
|
|
505
505
|
recommendations: list[Recommendation] = []
|
|
506
506
|
|
|
507
507
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -546,7 +546,7 @@ class ReverseConnectionTargetMissing(DataModelValidator):
|
|
|
546
546
|
return recommendations
|
|
547
547
|
|
|
548
548
|
|
|
549
|
-
class ReverseConnectionPointsToAncestor(
|
|
549
|
+
class ReverseConnectionPointsToAncestor(DataModelRule):
|
|
550
550
|
"""Validates that direct connections point to specific views rather than ancestors.
|
|
551
551
|
|
|
552
552
|
## What it does
|
|
@@ -565,7 +565,7 @@ class ReverseConnectionPointsToAncestor(DataModelValidator):
|
|
|
565
565
|
code = f"{BASE_CODE}-REVERSE-008"
|
|
566
566
|
issue_type = Recommendation
|
|
567
567
|
|
|
568
|
-
def
|
|
568
|
+
def validate(self) -> list[Recommendation]:
|
|
569
569
|
recommendations: list[Recommendation] = []
|
|
570
570
|
|
|
571
571
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -613,7 +613,7 @@ class ReverseConnectionPointsToAncestor(DataModelValidator):
|
|
|
613
613
|
return recommendations
|
|
614
614
|
|
|
615
615
|
|
|
616
|
-
class ReverseConnectionTargetMismatch(
|
|
616
|
+
class ReverseConnectionTargetMismatch(DataModelRule):
|
|
617
617
|
"""Validates that direct connections point to the correct target views.
|
|
618
618
|
|
|
619
619
|
## What it does
|
|
@@ -632,7 +632,7 @@ class ReverseConnectionTargetMismatch(DataModelValidator):
|
|
|
632
632
|
code = f"{BASE_CODE}-REVERSE-009"
|
|
633
633
|
issue_type = Recommendation
|
|
634
634
|
|
|
635
|
-
def
|
|
635
|
+
def validate(self) -> list[Recommendation]:
|
|
636
636
|
recommendations: list[Recommendation] = []
|
|
637
637
|
|
|
638
638
|
for (target_view_ref, reverse_prop_name), (
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Validators checking for consistency issues in data model."""
|
|
2
2
|
|
|
3
3
|
from cognite.neat._data_model._constants import COGNITE_SPACES
|
|
4
|
-
from cognite.neat._data_model.
|
|
4
|
+
from cognite.neat._data_model.rules.dms._base import DataModelRule
|
|
5
5
|
from cognite.neat._issues import Recommendation
|
|
6
6
|
|
|
7
7
|
BASE_CODE = "NEAT-DMS-CONSISTENCY"
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class ViewSpaceVersionInconsistentWithDataModel(
|
|
10
|
+
class ViewSpaceVersionInconsistentWithDataModel(DataModelRule):
|
|
11
11
|
"""Validates that views have consistent space and version with the data model.
|
|
12
12
|
|
|
13
13
|
## What it does
|
|
@@ -25,7 +25,7 @@ class ViewSpaceVersionInconsistentWithDataModel(DataModelValidator):
|
|
|
25
25
|
code = f"{BASE_CODE}-001"
|
|
26
26
|
issue_type = Recommendation
|
|
27
27
|
|
|
28
|
-
def
|
|
28
|
+
def validate(self) -> list[Recommendation]:
|
|
29
29
|
recommendations: list[Recommendation] = []
|
|
30
30
|
|
|
31
31
|
if not self.validation_resources.merged_data_model.views:
|