cognite-neat 0.126.0__py3-none-any.whl → 0.126.1__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.
- cognite/neat/_client/__init__.py +4 -0
- cognite/neat/_client/api.py +8 -0
- cognite/neat/_client/client.py +19 -0
- cognite/neat/_client/config.py +40 -0
- cognite/neat/_client/containers_api.py +73 -0
- cognite/neat/_client/data_classes.py +10 -0
- cognite/neat/_client/data_model_api.py +63 -0
- cognite/neat/_client/spaces_api.py +67 -0
- cognite/neat/_client/views_api.py +82 -0
- cognite/neat/_data_model/_analysis.py +127 -0
- cognite/neat/_data_model/_constants.py +59 -0
- cognite/neat/_data_model/_shared.py +46 -0
- cognite/neat/_data_model/deployer/__init__.py +0 -0
- cognite/neat/_data_model/deployer/_differ.py +113 -0
- cognite/neat/_data_model/deployer/_differ_container.py +354 -0
- cognite/neat/_data_model/deployer/_differ_data_model.py +29 -0
- cognite/neat/_data_model/deployer/_differ_space.py +9 -0
- cognite/neat/_data_model/deployer/_differ_view.py +194 -0
- cognite/neat/_data_model/deployer/data_classes.py +176 -0
- cognite/neat/_data_model/exporters/__init__.py +4 -0
- cognite/neat/_data_model/exporters/_base.py +6 -1
- cognite/neat/_data_model/exporters/_table_exporter/__init__.py +0 -0
- cognite/neat/_data_model/exporters/_table_exporter/exporter.py +106 -0
- cognite/neat/_data_model/exporters/_table_exporter/workbook.py +414 -0
- cognite/neat/_data_model/exporters/_table_exporter/writer.py +391 -0
- cognite/neat/_data_model/importers/__init__.py +2 -1
- cognite/neat/_data_model/importers/_api_importer.py +88 -0
- cognite/neat/_data_model/importers/_table_importer/data_classes.py +48 -8
- cognite/neat/_data_model/importers/_table_importer/importer.py +74 -5
- cognite/neat/_data_model/importers/_table_importer/reader.py +63 -7
- cognite/neat/_data_model/models/dms/__init__.py +17 -1
- cognite/neat/_data_model/models/dms/_base.py +12 -8
- cognite/neat/_data_model/models/dms/_constants.py +1 -1
- cognite/neat/_data_model/models/dms/_constraints.py +2 -1
- cognite/neat/_data_model/models/dms/_container.py +5 -5
- cognite/neat/_data_model/models/dms/_data_model.py +3 -3
- cognite/neat/_data_model/models/dms/_data_types.py +8 -1
- cognite/neat/_data_model/models/dms/_http.py +18 -0
- cognite/neat/_data_model/models/dms/_indexes.py +2 -1
- cognite/neat/_data_model/models/dms/_references.py +17 -4
- cognite/neat/_data_model/models/dms/_space.py +11 -7
- cognite/neat/_data_model/models/dms/_view_property.py +7 -4
- cognite/neat/_data_model/models/dms/_views.py +16 -6
- cognite/neat/_data_model/validation/__init__.py +0 -0
- cognite/neat/_data_model/validation/_base.py +16 -0
- cognite/neat/_data_model/validation/dms/__init__.py +9 -0
- cognite/neat/_data_model/validation/dms/_orchestrator.py +68 -0
- cognite/neat/_data_model/validation/dms/_validators.py +139 -0
- cognite/neat/_exceptions.py +15 -3
- cognite/neat/_issues.py +39 -6
- cognite/neat/_session/__init__.py +3 -0
- cognite/neat/_session/_physical.py +88 -0
- cognite/neat/_session/_session.py +34 -25
- cognite/neat/_session/_wrappers.py +61 -0
- cognite/neat/_state_machine/__init__.py +10 -0
- cognite/neat/{_session/_state_machine → _state_machine}/_base.py +11 -1
- cognite/neat/_state_machine/_states.py +53 -0
- cognite/neat/_store/__init__.py +3 -0
- cognite/neat/_store/_provenance.py +55 -0
- cognite/neat/_store/_store.py +124 -0
- cognite/neat/_utils/_reader.py +194 -0
- cognite/neat/_utils/http_client/__init__.py +14 -20
- cognite/neat/_utils/http_client/_client.py +22 -61
- cognite/neat/_utils/http_client/_data_classes.py +167 -268
- cognite/neat/_utils/text.py +6 -0
- cognite/neat/_utils/useful_types.py +23 -2
- cognite/neat/_version.py +1 -1
- cognite/neat/v0/core/_data_model/importers/_rdf/_shared.py +2 -2
- {cognite_neat-0.126.0.dist-info → cognite_neat-0.126.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.126.0.dist-info → cognite_neat-0.126.1.dist-info}/RECORD +72 -38
- cognite/neat/_data_model/exporters/_table_exporter.py +0 -35
- cognite/neat/_session/_state_machine/__init__.py +0 -23
- cognite/neat/_session/_state_machine/_states.py +0 -150
- {cognite_neat-0.126.0.dist-info → cognite_neat-0.126.1.dist-info}/WHEEL +0 -0
- {cognite_neat-0.126.0.dist-info → cognite_neat-0.126.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from cognite.neat._client.config import NeatClientConfig
|
|
2
|
+
from cognite.neat._utils.http_client import HTTPClient
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class NeatAPI:
|
|
6
|
+
def __init__(self, neat_config: NeatClientConfig, http_client: HTTPClient) -> None:
|
|
7
|
+
self._config = neat_config
|
|
8
|
+
self._http_client = http_client
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from cognite.client import ClientConfig, CogniteClient
|
|
2
|
+
|
|
3
|
+
from cognite.neat._utils.http_client import HTTPClient
|
|
4
|
+
|
|
5
|
+
from .config import NeatClientConfig
|
|
6
|
+
from .containers_api import ContainersAPI
|
|
7
|
+
from .data_model_api import DataModelsAPI
|
|
8
|
+
from .spaces_api import SpacesAPI
|
|
9
|
+
from .views_api import ViewsAPI
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NeatClient:
|
|
13
|
+
def __init__(self, cognite_client_or_config: CogniteClient | ClientConfig) -> None:
|
|
14
|
+
self.config = NeatClientConfig(cognite_client_or_config)
|
|
15
|
+
http_client = HTTPClient(self.config)
|
|
16
|
+
self.data_models = DataModelsAPI(self.config, http_client)
|
|
17
|
+
self.views = ViewsAPI(self.config, http_client)
|
|
18
|
+
self.containers = ContainersAPI(self.config, http_client)
|
|
19
|
+
self.spaces = SpacesAPI(self.config, http_client)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from cognite.client import ClientConfig, CogniteClient
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NeatClientConfig(ClientConfig):
|
|
5
|
+
def __init__(self, cognite_client_or_config: CogniteClient | ClientConfig):
|
|
6
|
+
config = (
|
|
7
|
+
cognite_client_or_config.config
|
|
8
|
+
if isinstance(cognite_client_or_config, CogniteClient)
|
|
9
|
+
else cognite_client_or_config
|
|
10
|
+
)
|
|
11
|
+
super().__init__(
|
|
12
|
+
client_name=config.client_name,
|
|
13
|
+
project=config.project,
|
|
14
|
+
credentials=config.credentials,
|
|
15
|
+
api_subversion=config.api_subversion,
|
|
16
|
+
base_url=config.base_url,
|
|
17
|
+
max_workers=config.max_workers,
|
|
18
|
+
headers=config.headers,
|
|
19
|
+
timeout=config.timeout,
|
|
20
|
+
file_transfer_timeout=config.file_transfer_timeout,
|
|
21
|
+
debug=config.debug,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def create_api_url(self, endpoint: str) -> str:
|
|
25
|
+
"""Create a full API URL for the given endpoint.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
endpoint (str): The API endpoint to append to the base URL.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str: The full API URL.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
>>> config = NeatClientConfig(cluster="bluefield", project="my_project", ...)
|
|
35
|
+
>>> config.create_api_url("/models/instances")
|
|
36
|
+
"https://bluefield.cognitedata.com/api/v1/my_project/models/instances"
|
|
37
|
+
"""
|
|
38
|
+
if not endpoint.startswith("/"):
|
|
39
|
+
endpoint = f"/{endpoint}"
|
|
40
|
+
return f"{self.base_url}/api/v1/projects/{self.project}{endpoint}"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from cognite.neat._data_model.models.dms import ContainerReference, ContainerResponse
|
|
4
|
+
from cognite.neat._utils.http_client import ItemIDBody, ItemsRequest, ParametersRequest
|
|
5
|
+
from cognite.neat._utils.useful_types import PrimitiveType
|
|
6
|
+
|
|
7
|
+
from .api import NeatAPI
|
|
8
|
+
from .data_classes import PagedResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ContainersAPI(NeatAPI):
|
|
12
|
+
def retrieve(
|
|
13
|
+
self,
|
|
14
|
+
items: list[ContainerReference],
|
|
15
|
+
) -> list[ContainerResponse]:
|
|
16
|
+
"""Retrieve containers by their identifiers.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
items: List of (space, external_id) tuples identifying the containers to retrieve.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List of ContainerResponse objects.
|
|
23
|
+
"""
|
|
24
|
+
if not items:
|
|
25
|
+
return []
|
|
26
|
+
if len(items) > 1000:
|
|
27
|
+
raise ValueError("Cannot retrieve more than 1000 containers at once.")
|
|
28
|
+
|
|
29
|
+
result = self._http_client.request_with_retries(
|
|
30
|
+
ItemsRequest(
|
|
31
|
+
endpoint_url=self._config.create_api_url("/models/containers/byids"),
|
|
32
|
+
method="POST",
|
|
33
|
+
body=ItemIDBody(items=items),
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
result.raise_for_status()
|
|
37
|
+
result = PagedResponse[ContainerResponse].model_validate_json(result.success_response.body)
|
|
38
|
+
return result.items
|
|
39
|
+
|
|
40
|
+
def list(
|
|
41
|
+
self,
|
|
42
|
+
space: str | None = None,
|
|
43
|
+
include_global: bool = False,
|
|
44
|
+
limit: int = 10,
|
|
45
|
+
) -> list[ContainerResponse]:
|
|
46
|
+
"""List containers in CDF Project.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
space: If specified, only containers in this space are returned.
|
|
50
|
+
include_global: If True, include global containers.
|
|
51
|
+
limit: Maximum number of containers to return. Max is 1000.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of ContainerResponse objects.
|
|
55
|
+
"""
|
|
56
|
+
if limit > 1000:
|
|
57
|
+
raise ValueError("Pagination is not (yet) supported for listing containers. The maximum limit is 1000.")
|
|
58
|
+
parameters: dict[str, PrimitiveType] = {
|
|
59
|
+
"includeGlobal": include_global,
|
|
60
|
+
"limit": limit,
|
|
61
|
+
}
|
|
62
|
+
if space is not None:
|
|
63
|
+
parameters["space"] = space
|
|
64
|
+
result = self._http_client.request_with_retries(
|
|
65
|
+
ParametersRequest(
|
|
66
|
+
endpoint_url=self._config.create_api_url("/models/containers"),
|
|
67
|
+
method="GET",
|
|
68
|
+
parameters=parameters,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
result.raise_for_status()
|
|
72
|
+
result = PagedResponse[ContainerResponse].model_validate_json(result.success_response.body)
|
|
73
|
+
return result.items
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from cognite.neat._data_model.models.dms import DataModelReference, DataModelResponse
|
|
2
|
+
from cognite.neat._utils.http_client import ItemIDBody, ItemsRequest, ParametersRequest
|
|
3
|
+
from cognite.neat._utils.useful_types import PrimitiveType
|
|
4
|
+
|
|
5
|
+
from .api import NeatAPI
|
|
6
|
+
from .data_classes import PagedResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DataModelsAPI(NeatAPI):
|
|
10
|
+
def retrieve(
|
|
11
|
+
self,
|
|
12
|
+
items: list[DataModelReference],
|
|
13
|
+
) -> list[DataModelResponse]:
|
|
14
|
+
"""Retrieve data models by their identifiers.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
items: List of data models references identifying the data models to retrieve.
|
|
18
|
+
Returns:
|
|
19
|
+
List of DataModelResponse objects.
|
|
20
|
+
"""
|
|
21
|
+
if not items:
|
|
22
|
+
return []
|
|
23
|
+
if len(items) > 1000:
|
|
24
|
+
raise ValueError("Cannot retrieve more than 1000 containers at once.")
|
|
25
|
+
|
|
26
|
+
result = self._http_client.request_with_retries(
|
|
27
|
+
ItemsRequest(
|
|
28
|
+
endpoint_url=self._config.create_api_url("/models/datamodels/byids"),
|
|
29
|
+
method="POST",
|
|
30
|
+
body=ItemIDBody(items=items),
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
result.raise_for_status()
|
|
34
|
+
result = PagedResponse[DataModelResponse].model_validate_json(result.success_response.body)
|
|
35
|
+
return result.items
|
|
36
|
+
|
|
37
|
+
def list(
|
|
38
|
+
self,
|
|
39
|
+
space: str | None = None,
|
|
40
|
+
all_versions: bool = False,
|
|
41
|
+
include_global: bool = False,
|
|
42
|
+
limit: int = 10,
|
|
43
|
+
) -> list[DataModelResponse]:
|
|
44
|
+
"""List data models in CDF Project."""
|
|
45
|
+
if limit > 1000:
|
|
46
|
+
raise ValueError("Pagination is not (yet) supported for listing data models. The maximum limit is 1000.")
|
|
47
|
+
parameters: dict[str, PrimitiveType] = {
|
|
48
|
+
"allVersions": all_versions,
|
|
49
|
+
"includeGlobal": include_global,
|
|
50
|
+
"limit": limit,
|
|
51
|
+
}
|
|
52
|
+
if space is not None:
|
|
53
|
+
parameters["space"] = space
|
|
54
|
+
result = self._http_client.request_with_retries(
|
|
55
|
+
ParametersRequest(
|
|
56
|
+
endpoint_url=self._config.create_api_url("/models/datamodels"),
|
|
57
|
+
method="GET",
|
|
58
|
+
parameters=parameters,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
result.raise_for_status()
|
|
62
|
+
result = PagedResponse[DataModelResponse].model_validate_json(result.success_response.body)
|
|
63
|
+
return result.items
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from cognite.neat._data_model.models.dms import SpaceResponse
|
|
4
|
+
from cognite.neat._data_model.models.dms._references import SpaceReference
|
|
5
|
+
from cognite.neat._utils.http_client import ItemIDBody, ItemsRequest, ParametersRequest
|
|
6
|
+
from cognite.neat._utils.useful_types import PrimitiveType
|
|
7
|
+
|
|
8
|
+
from .api import NeatAPI
|
|
9
|
+
from .data_classes import PagedResponse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SpacesAPI(NeatAPI):
|
|
13
|
+
def retrieve(self, spaces: list[SpaceReference]) -> list[SpaceResponse]:
|
|
14
|
+
"""Retrieve spaces by their identifiers.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
spaces: List of space identifiers to retrieve.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
List of SpaceResponse objects.
|
|
21
|
+
"""
|
|
22
|
+
if not spaces:
|
|
23
|
+
return []
|
|
24
|
+
if len(spaces) > 1000:
|
|
25
|
+
raise ValueError("Cannot retrieve more than 1000 spaces at once.")
|
|
26
|
+
|
|
27
|
+
result = self._http_client.request_with_retries(
|
|
28
|
+
ItemsRequest(
|
|
29
|
+
endpoint_url=self._config.create_api_url("/models/spaces/byids"),
|
|
30
|
+
method="POST",
|
|
31
|
+
body=ItemIDBody(items=spaces),
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
result.raise_for_status()
|
|
35
|
+
result = PagedResponse[SpaceResponse].model_validate_json(result.success_response.body)
|
|
36
|
+
return result.items
|
|
37
|
+
|
|
38
|
+
def list(
|
|
39
|
+
self,
|
|
40
|
+
include_global: bool = False,
|
|
41
|
+
limit: int = 10,
|
|
42
|
+
) -> list[SpaceResponse]:
|
|
43
|
+
"""List spaces in CDF Project.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
include_global: If True, include global spaces.
|
|
47
|
+
limit: Maximum number of spaces to return. Max is 1000.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
List of SpaceResponse objects.
|
|
51
|
+
"""
|
|
52
|
+
if limit > 1000:
|
|
53
|
+
raise ValueError("Pagination is not (yet) supported for listing spaces. The maximum limit is 1000.")
|
|
54
|
+
parameters: dict[str, PrimitiveType] = {
|
|
55
|
+
"includeGlobal": include_global,
|
|
56
|
+
"limit": limit,
|
|
57
|
+
}
|
|
58
|
+
result = self._http_client.request_with_retries(
|
|
59
|
+
ParametersRequest(
|
|
60
|
+
endpoint_url=self._config.create_api_url("/models/spaces"),
|
|
61
|
+
method="GET",
|
|
62
|
+
parameters=parameters,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
result.raise_for_status()
|
|
66
|
+
result = PagedResponse[SpaceResponse].model_validate_json(result.success_response.body)
|
|
67
|
+
return result.items
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from cognite.neat._data_model.models.dms import ViewReference, ViewResponse
|
|
4
|
+
from cognite.neat._utils.http_client import ItemIDBody, ItemsRequest, ParametersRequest
|
|
5
|
+
from cognite.neat._utils.useful_types import PrimitiveType
|
|
6
|
+
|
|
7
|
+
from .api import NeatAPI
|
|
8
|
+
from .data_classes import PagedResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ViewsAPI(NeatAPI):
|
|
12
|
+
def retrieve(
|
|
13
|
+
self,
|
|
14
|
+
items: list[ViewReference],
|
|
15
|
+
include_inherited_properties: bool = True,
|
|
16
|
+
) -> list[ViewResponse]:
|
|
17
|
+
"""Retrieve views by their identifiers.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
items: List of (space, external_id, version) tuples identifying the views to retrieve.
|
|
21
|
+
include_inherited_properties: If True, include properties inherited from parent views.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
List of ViewResponse objects.
|
|
25
|
+
"""
|
|
26
|
+
if not items:
|
|
27
|
+
return []
|
|
28
|
+
if len(items) > 1000:
|
|
29
|
+
raise ValueError("Cannot retrieve more than 1000 views at once.")
|
|
30
|
+
|
|
31
|
+
result = self._http_client.request_with_retries(
|
|
32
|
+
ItemsRequest(
|
|
33
|
+
endpoint_url=self._config.create_api_url("/models/views/byids"),
|
|
34
|
+
method="POST",
|
|
35
|
+
body=ItemIDBody(items=items),
|
|
36
|
+
parameters={"includeInheritedProperties": include_inherited_properties},
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
result.raise_for_status()
|
|
40
|
+
result = PagedResponse[ViewResponse].model_validate_json(result.success_response.body)
|
|
41
|
+
return result.items
|
|
42
|
+
|
|
43
|
+
def list(
|
|
44
|
+
self,
|
|
45
|
+
space: str | None = None,
|
|
46
|
+
all_versions: bool = False,
|
|
47
|
+
include_inherited_properties: bool = True,
|
|
48
|
+
include_global: bool = False,
|
|
49
|
+
limit: int = 10,
|
|
50
|
+
) -> list[ViewResponse]:
|
|
51
|
+
"""List views in CDF Project.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
space: If specified, only views in this space are returned.
|
|
55
|
+
all_versions: If True, return all versions. If False, only return the latest version.
|
|
56
|
+
include_inherited_properties: If True, include properties inherited from parent views.
|
|
57
|
+
include_global: If True, include global views.
|
|
58
|
+
limit: Maximum number of views to return. Max is 1000.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of ViewResponse objects.
|
|
62
|
+
"""
|
|
63
|
+
if limit > 1000:
|
|
64
|
+
raise ValueError("Pagination is not (yet) supported for listing views. The maximum limit is 1000.")
|
|
65
|
+
parameters: dict[str, PrimitiveType] = {
|
|
66
|
+
"allVersions": all_versions,
|
|
67
|
+
"includeInheritedProperties": include_inherited_properties,
|
|
68
|
+
"includeGlobal": include_global,
|
|
69
|
+
"limit": limit,
|
|
70
|
+
}
|
|
71
|
+
if space is not None:
|
|
72
|
+
parameters["space"] = space
|
|
73
|
+
result = self._http_client.request_with_retries(
|
|
74
|
+
ParametersRequest(
|
|
75
|
+
endpoint_url=self._config.create_api_url("/models/views"),
|
|
76
|
+
method="GET",
|
|
77
|
+
parameters=parameters,
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
result.raise_for_status()
|
|
81
|
+
result = PagedResponse[ViewResponse].model_validate_json(result.success_response.body)
|
|
82
|
+
return result.items
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from graphlib import TopologicalSorter
|
|
2
|
+
|
|
3
|
+
from cognite.neat._data_model.models.dms._references import ContainerReference, ViewReference
|
|
4
|
+
from cognite.neat._data_model.models.dms._schema import RequestSchema
|
|
5
|
+
from cognite.neat._data_model.models.dms._view_property import (
|
|
6
|
+
EdgeProperty,
|
|
7
|
+
ReverseDirectRelationProperty,
|
|
8
|
+
ViewCorePropertyRequest,
|
|
9
|
+
)
|
|
10
|
+
from cognite.neat._data_model.models.dms._views import ViewRequest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DataModelAnalysis:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
physical: RequestSchema | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
self._physical = physical
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def physical(self) -> RequestSchema:
|
|
22
|
+
if self._physical is None:
|
|
23
|
+
raise ValueError("Physical Data Model is required for this analysis")
|
|
24
|
+
return self._physical
|
|
25
|
+
|
|
26
|
+
def referenced_views(self, include_connection_end_node_types: bool = False) -> set[ViewReference]:
|
|
27
|
+
"""Get all referenced views in the physical data model."""
|
|
28
|
+
referenced_views = set()
|
|
29
|
+
|
|
30
|
+
for view in self.physical.views:
|
|
31
|
+
referenced_views.add(view.as_reference())
|
|
32
|
+
if view.implements:
|
|
33
|
+
for implement in view.implements:
|
|
34
|
+
referenced_views.add(implement)
|
|
35
|
+
|
|
36
|
+
if include_connection_end_node_types:
|
|
37
|
+
referenced_views |= set(self.connection_end_node_types.values())
|
|
38
|
+
|
|
39
|
+
return referenced_views
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def referenced_containers(self) -> set[ContainerReference]:
|
|
43
|
+
"""Get all referenced containers in the physical data model."""
|
|
44
|
+
referenced_containers = set()
|
|
45
|
+
|
|
46
|
+
for view in self.physical.views:
|
|
47
|
+
for property_ in view.properties.values():
|
|
48
|
+
if isinstance(property_, ViewCorePropertyRequest):
|
|
49
|
+
referenced_containers.add(property_.container)
|
|
50
|
+
|
|
51
|
+
for container in self.physical.containers:
|
|
52
|
+
referenced_containers.add(container.as_reference())
|
|
53
|
+
|
|
54
|
+
return referenced_containers
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def view_ancestors(self) -> dict[ViewReference, set[ViewReference]]:
|
|
58
|
+
"""Get a mapping of each view to its ancestors in the physical data model."""
|
|
59
|
+
implements_by_view = self.implements_by_view
|
|
60
|
+
|
|
61
|
+
# Topological sort to ensure that concepts include all ancestors
|
|
62
|
+
for view in list(TopologicalSorter(implements_by_view).static_order()):
|
|
63
|
+
if view not in implements_by_view:
|
|
64
|
+
continue
|
|
65
|
+
implements_by_view[view] |= {
|
|
66
|
+
grand_parent
|
|
67
|
+
for parent in implements_by_view[view]
|
|
68
|
+
for grand_parent in implements_by_view.get(parent, set())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return implements_by_view
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def implements_by_view(self) -> dict[ViewReference, set[ViewReference]]:
|
|
75
|
+
"""Get a mapping of each view to the views it implements."""
|
|
76
|
+
implements_mapping: dict[ViewReference, set[ViewReference]] = {}
|
|
77
|
+
|
|
78
|
+
for view in self.physical.views:
|
|
79
|
+
view_ref = view.as_reference()
|
|
80
|
+
if view_ref not in implements_mapping:
|
|
81
|
+
implements_mapping[view_ref] = set()
|
|
82
|
+
if view.implements:
|
|
83
|
+
for implement in view.implements:
|
|
84
|
+
implements_mapping[view_ref].add(implement)
|
|
85
|
+
|
|
86
|
+
return implements_mapping
|
|
87
|
+
|
|
88
|
+
def view_by_reference(self, include_inherited_properties: bool = True) -> dict[ViewReference, ViewRequest]:
|
|
89
|
+
"""Get a mapping of view references to their corresponding ViewRequest objects."""
|
|
90
|
+
view_ancestors = self.view_ancestors
|
|
91
|
+
|
|
92
|
+
view_by_reference: dict[ViewReference, ViewRequest] = {
|
|
93
|
+
view.as_reference(): view.model_copy(deep=True) for view in self.physical.views
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if include_inherited_properties:
|
|
97
|
+
for ref, view in view_by_reference.items():
|
|
98
|
+
for ancestor in view_ancestors.get(ref, set()):
|
|
99
|
+
if ancestor_view := view_by_reference.get(ancestor):
|
|
100
|
+
if ancestor_view.properties:
|
|
101
|
+
view.properties.update(ancestor_view.properties)
|
|
102
|
+
|
|
103
|
+
return view_by_reference
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def connection_end_node_types(self) -> dict[tuple[ViewReference, str], ViewReference]:
|
|
107
|
+
"""Get a mapping of view references to their corresponding ViewRequest objects."""
|
|
108
|
+
view_by_reference = self.view_by_reference(include_inherited_properties=False)
|
|
109
|
+
connection_end_node_types: dict[tuple[ViewReference, str], ViewReference] = {}
|
|
110
|
+
|
|
111
|
+
for view_ref, view in view_by_reference.items():
|
|
112
|
+
if not view.properties:
|
|
113
|
+
continue
|
|
114
|
+
for prop_ref, property_ in view.properties.items():
|
|
115
|
+
# direct relation
|
|
116
|
+
if isinstance(property_, ViewCorePropertyRequest) and property_.source:
|
|
117
|
+
connection_end_node_types[(view_ref, prop_ref)] = property_.source
|
|
118
|
+
|
|
119
|
+
# reverse direct relation
|
|
120
|
+
if isinstance(property_, ReverseDirectRelationProperty) and property_.source:
|
|
121
|
+
connection_end_node_types[(view_ref, prop_ref)] = property_.source
|
|
122
|
+
|
|
123
|
+
# edge property
|
|
124
|
+
if isinstance(property_, EdgeProperty) and property_.source:
|
|
125
|
+
connection_end_node_types[(view_ref, prop_ref)] = property_.source
|
|
126
|
+
|
|
127
|
+
return connection_end_node_types
|
|
@@ -1,3 +1,62 @@
|
|
|
1
1
|
from ._identifiers import NameSpace
|
|
2
2
|
|
|
3
3
|
XML_SCHEMA_NAMESPACE = NameSpace("http://www.w3.org/2001/XMLSchema#")
|
|
4
|
+
|
|
5
|
+
CDF_CDM_SPACE = "cdf_cdm"
|
|
6
|
+
CDF_CDM_VERSION = "v1"
|
|
7
|
+
|
|
8
|
+
COGNITE_CONCEPTS_MAIN = (
|
|
9
|
+
"CogniteAsset",
|
|
10
|
+
"CogniteEquipment",
|
|
11
|
+
"CogniteActivity",
|
|
12
|
+
"CogniteTimeSeries",
|
|
13
|
+
"CogniteFile",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
COGNITE_CONCEPTS_INTERFACES = (
|
|
17
|
+
"CogniteDescribable",
|
|
18
|
+
"CogniteSourceable",
|
|
19
|
+
"CogniteSchedulable",
|
|
20
|
+
"CogniteVisualizable",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
COGNITE_CONCEPTS_CONFIGURATIONS = (
|
|
24
|
+
"CogniteSourceSystem",
|
|
25
|
+
"CogniteUnit",
|
|
26
|
+
"CogniteAssetClass",
|
|
27
|
+
"CogniteAssetType",
|
|
28
|
+
"CogniteEquipmentType",
|
|
29
|
+
"CogniteFileCategory",
|
|
30
|
+
)
|
|
31
|
+
COGNITE_CONCEPTS_ANNOTATIONS = (
|
|
32
|
+
"CogniteAnnotation",
|
|
33
|
+
"CogniteDiagramAnnotation",
|
|
34
|
+
)
|
|
35
|
+
COGNITE_CONCEPTS_3D = (
|
|
36
|
+
"CogniteCubeMap",
|
|
37
|
+
"CogniteCADRevision",
|
|
38
|
+
"CognitePointCloudVolume",
|
|
39
|
+
"Cognite360ImageAnnotation",
|
|
40
|
+
"Cognite3DObject",
|
|
41
|
+
"Cognite3DRevision",
|
|
42
|
+
"Cognite360Image",
|
|
43
|
+
"Cognite360ImageCollection",
|
|
44
|
+
"Cognite360ImageStation",
|
|
45
|
+
"CognitePointCloudModel",
|
|
46
|
+
"Cognite3DTransformation",
|
|
47
|
+
"Cognite360ImageModel",
|
|
48
|
+
"Cognite3DModel",
|
|
49
|
+
"CogniteCADModel",
|
|
50
|
+
"CognitePointCloudRevision",
|
|
51
|
+
"CogniteCADNode",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
COGNITE_CONCEPTS: tuple[str, ...] = (
|
|
55
|
+
*COGNITE_CONCEPTS_MAIN,
|
|
56
|
+
*COGNITE_CONCEPTS_INTERFACES,
|
|
57
|
+
*COGNITE_CONCEPTS_CONFIGURATIONS,
|
|
58
|
+
*COGNITE_CONCEPTS_ANNOTATIONS,
|
|
59
|
+
*COGNITE_CONCEPTS_3D,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
COGNITE_SPACES = (CDF_CDM_SPACE,)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from cognite.neat._client.client import NeatClient
|
|
5
|
+
from cognite.neat._issues import IssueList
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OnSuccess(ABC):
|
|
9
|
+
"""Abstract base class for post-activity success handlers."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, client: NeatClient | None = None) -> None:
|
|
12
|
+
self._client = client
|
|
13
|
+
self._issues: IssueList = IssueList()
|
|
14
|
+
self._results: list = []
|
|
15
|
+
self._has_run = False
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def issues(self) -> IssueList:
|
|
19
|
+
"""List of issues found during the success handler execution."""
|
|
20
|
+
if not self._has_run:
|
|
21
|
+
raise RuntimeError("OnSuccess handler has not been run yet.")
|
|
22
|
+
return self._issues
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def results(self) -> list:
|
|
26
|
+
"""List of results produced during the success handler execution."""
|
|
27
|
+
if not self._has_run:
|
|
28
|
+
raise RuntimeError("OnSuccess handler has not been run yet.")
|
|
29
|
+
return self._results
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def run(self, data_model: Any) -> None:
|
|
33
|
+
"""Execute the success handler on the data model."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class OnSuccessIssuesChecker(OnSuccess):
|
|
38
|
+
"""Abstract base class for post-activity success handlers that check for issues of the data model."""
|
|
39
|
+
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OnSuccessResultProducer(OnSuccess):
|
|
44
|
+
"""Abstract base class for post-activity success handlers that produce desired outcomes using the data model."""
|
|
45
|
+
|
|
46
|
+
...
|
|
File without changes
|