cognite-neat 0.94.0__py3-none-any.whl → 0.96.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.
- cognite/neat/_constants.py +16 -0
- cognite/neat/_issues/_base.py +24 -8
- cognite/neat/_issues/errors/_resources.py +2 -2
- cognite/neat/_issues/warnings/_models.py +20 -2
- cognite/neat/_rules/exporters/_rules2excel.py +8 -2
- cognite/neat/_rules/models/dms/_rules.py +16 -0
- cognite/neat/_rules/models/dms/_rules_input.py +16 -0
- cognite/neat/_rules/models/information/_rules.py +14 -0
- cognite/neat/_rules/models/information/_rules_input.py +14 -0
- cognite/neat/_rules/transformers/__init__.py +2 -0
- cognite/neat/_rules/transformers/_converters.py +206 -37
- cognite/neat/_session/_base.py +14 -7
- cognite/neat/_session/_inspect.py +89 -0
- cognite/neat/_session/_prepare.py +65 -8
- cognite/neat/_session/_read.py +33 -4
- cognite/neat/_session/_set.py +23 -0
- cognite/neat/_session/_show.py +156 -202
- cognite/neat/_session/_state.py +24 -4
- cognite/neat/_session/_to.py +3 -0
- cognite/neat/_session/exceptions.py +9 -6
- cognite/neat/_store/_base.py +11 -8
- cognite/neat/_utils/collection_.py +4 -0
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.94.0.dist-info → cognite_neat-0.96.0.dist-info}/METADATA +3 -5
- {cognite_neat-0.94.0.dist-info → cognite_neat-0.96.0.dist-info}/RECORD +28 -26
- {cognite_neat-0.94.0.dist-info → cognite_neat-0.96.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.94.0.dist-info → cognite_neat-0.96.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.94.0.dist-info → cognite_neat-0.96.0.dist-info}/entry_points.txt +0 -0
cognite/neat/_session/_base.py
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
from typing import Literal, cast
|
|
2
2
|
|
|
3
|
-
import pandas as pd
|
|
4
3
|
from cognite.client import CogniteClient
|
|
5
4
|
|
|
5
|
+
from cognite.neat import _version
|
|
6
6
|
from cognite.neat._issues import IssueList
|
|
7
7
|
from cognite.neat._rules import importers
|
|
8
8
|
from cognite.neat._rules._shared import ReadRules
|
|
9
9
|
from cognite.neat._rules.models import DMSRules
|
|
10
|
-
from cognite.neat._rules.models._base_input import InputComponent
|
|
11
10
|
from cognite.neat._rules.models.information._rules import InformationRules
|
|
12
11
|
from cognite.neat._rules.models.information._rules_input import InformationInputRules
|
|
13
12
|
from cognite.neat._rules.transformers import ConvertToRules, VerifyAnyRules
|
|
14
13
|
|
|
14
|
+
from ._inspect import InspectAPI
|
|
15
15
|
from ._prepare import PrepareAPI
|
|
16
16
|
from ._read import ReadAPI
|
|
17
|
+
from ._set import SetAPI
|
|
17
18
|
from ._show import ShowAPI
|
|
18
19
|
from ._state import SessionState
|
|
19
20
|
from ._to import ToAPI
|
|
@@ -35,6 +36,12 @@ class NeatSession:
|
|
|
35
36
|
self.to = ToAPI(self._state, client, verbose)
|
|
36
37
|
self.prepare = PrepareAPI(self._state, verbose)
|
|
37
38
|
self.show = ShowAPI(self._state)
|
|
39
|
+
self.set = SetAPI(self._state, verbose)
|
|
40
|
+
self.inspect = InspectAPI(self._state)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def version(self) -> str:
|
|
44
|
+
return _version.__version__
|
|
38
45
|
|
|
39
46
|
def verify(self) -> IssueList:
|
|
40
47
|
output = VerifyAnyRules("continue").try_transform(self._state.input_rule)
|
|
@@ -42,6 +49,9 @@ class NeatSession:
|
|
|
42
49
|
self._state.verified_rules.append(output.rules)
|
|
43
50
|
if isinstance(output.rules, InformationRules):
|
|
44
51
|
self._state.store.add_rules(output.rules)
|
|
52
|
+
self._state.issue_lists.append(output.issues)
|
|
53
|
+
if output.issues:
|
|
54
|
+
print("You can inspect the issues with the .inspect attribute.")
|
|
45
55
|
return output.issues
|
|
46
56
|
|
|
47
57
|
def convert(self, target: Literal["dms"]) -> None:
|
|
@@ -72,13 +82,10 @@ class NeatSession:
|
|
|
72
82
|
|
|
73
83
|
output = []
|
|
74
84
|
if state.input_rules and not state.verified_rules:
|
|
75
|
-
|
|
76
|
-
table = pd.DataFrame([metadata.dump()]).T._repr_html_() # type: ignore[operator]
|
|
77
|
-
output.append(f"<H2>Raw Data Model</H2><br />{table}")
|
|
85
|
+
output.append(f"<H2>Unverified Data Model</H2><br />{state.input_rule.rules._repr_html_()}") # type: ignore
|
|
78
86
|
|
|
79
87
|
if state.verified_rules:
|
|
80
|
-
|
|
81
|
-
output.append(f"<H2>Data Model</H2><br />{table}")
|
|
88
|
+
output.append(f"<H2>Verified Data Model</H2><br />{state.last_verified_rule._repr_html_()}") # type: ignore
|
|
82
89
|
|
|
83
90
|
if state.has_store:
|
|
84
91
|
output.append(f"<H2>Instances</H2> {state.store._repr_html_()}")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import difflib
|
|
2
|
+
from typing import Literal, overload
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from cognite.neat._constants import IN_NOTEBOOK
|
|
7
|
+
from cognite.neat._issues import IssueList
|
|
8
|
+
|
|
9
|
+
from ._state import SessionState
|
|
10
|
+
from .exceptions import intercept_session_exceptions
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@intercept_session_exceptions
|
|
14
|
+
class InspectAPI:
|
|
15
|
+
def __init__(self, state: SessionState) -> None:
|
|
16
|
+
self._state = state
|
|
17
|
+
self.issues = InspectIssues(state)
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def properties(self) -> pd.DataFrame:
|
|
21
|
+
"""Returns the properties of the current data model."""
|
|
22
|
+
return self._state.last_verified_rule.properties.to_pandas()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@intercept_session_exceptions
|
|
26
|
+
class InspectIssues:
|
|
27
|
+
"""Inspect issues of the current data model."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, state: SessionState) -> None:
|
|
30
|
+
self._state = state
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def __call__(
|
|
34
|
+
self,
|
|
35
|
+
search: str | None = None,
|
|
36
|
+
return_dataframe: Literal[True] = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
|
|
37
|
+
) -> pd.DataFrame: ...
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def __call__(
|
|
41
|
+
self,
|
|
42
|
+
search: str | None = None,
|
|
43
|
+
return_dataframe: Literal[False] = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
|
|
44
|
+
) -> None: ...
|
|
45
|
+
|
|
46
|
+
def __call__(
|
|
47
|
+
self,
|
|
48
|
+
search: str | None = None,
|
|
49
|
+
return_dataframe: bool = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
|
|
50
|
+
) -> pd.DataFrame | None:
|
|
51
|
+
"""Returns the issues of the current data model."""
|
|
52
|
+
issues = self._state.last_issues
|
|
53
|
+
if not issues:
|
|
54
|
+
self._print("No issues found.")
|
|
55
|
+
|
|
56
|
+
if issues and search is not None:
|
|
57
|
+
unique_types = {type(issue).__name__ for issue in issues}
|
|
58
|
+
closest_match = set(difflib.get_close_matches(search, unique_types))
|
|
59
|
+
issues = IssueList([issue for issue in issues if type(issue).__name__ in closest_match])
|
|
60
|
+
|
|
61
|
+
if IN_NOTEBOOK:
|
|
62
|
+
from IPython.display import Markdown, display
|
|
63
|
+
|
|
64
|
+
issue_str = "\n".join(
|
|
65
|
+
[f" * **{type(issue).__name__}**: {issue.as_message(include_type=False)}" for issue in issues]
|
|
66
|
+
)
|
|
67
|
+
message = f"### {len(issues)} issues found\n\n{issue_str}"
|
|
68
|
+
display(Markdown(message))
|
|
69
|
+
|
|
70
|
+
if return_dataframe:
|
|
71
|
+
return issues.to_pandas()
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def _print(self, message: str) -> None:
|
|
75
|
+
if IN_NOTEBOOK:
|
|
76
|
+
from IPython.display import Markdown, display
|
|
77
|
+
|
|
78
|
+
display(Markdown(message))
|
|
79
|
+
else:
|
|
80
|
+
print(message)
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return self.__repr_html__()
|
|
84
|
+
|
|
85
|
+
def __repr_html__(self) -> str:
|
|
86
|
+
return (
|
|
87
|
+
"Inspect issues by calling .inspect.issues() or "
|
|
88
|
+
"search for specific issues by calling .inspect.issues('MyTypeWarning')."
|
|
89
|
+
)
|
|
@@ -9,8 +9,10 @@ from cognite.neat._rules.models.information._rules_input import InformationInput
|
|
|
9
9
|
from cognite.neat._rules.transformers import ReduceCogniteModel, ToCompliantEntities, ToExtension
|
|
10
10
|
|
|
11
11
|
from ._state import SessionState
|
|
12
|
+
from .exceptions import intercept_session_exceptions
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
@intercept_session_exceptions
|
|
14
16
|
class PrepareAPI:
|
|
15
17
|
def __init__(self, state: SessionState, verbose: bool) -> None:
|
|
16
18
|
self._state = state
|
|
@@ -18,6 +20,7 @@ class PrepareAPI:
|
|
|
18
20
|
self.data_model = DataModelPrepareAPI(state, verbose)
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
@intercept_session_exceptions
|
|
21
24
|
class DataModelPrepareAPI:
|
|
22
25
|
def __init__(self, state: SessionState, verbose: bool) -> None:
|
|
23
26
|
self._state = state
|
|
@@ -35,25 +38,79 @@ class DataModelPrepareAPI:
|
|
|
35
38
|
)
|
|
36
39
|
)
|
|
37
40
|
|
|
38
|
-
def
|
|
39
|
-
|
|
41
|
+
def to_enterprise(
|
|
42
|
+
self,
|
|
43
|
+
data_model_id: DataModelIdentifier,
|
|
44
|
+
org_name: str = "My",
|
|
45
|
+
dummy_property: str = "GUID",
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Uses the current data model as a basis to create enterprise data model
|
|
40
48
|
|
|
41
49
|
Args:
|
|
42
|
-
|
|
43
|
-
org_name: Organization name to use for the views in the
|
|
44
|
-
|
|
50
|
+
data_model_id: The enterprise data model id that is being created
|
|
51
|
+
org_name: Organization name to use for the views in the enterprise data model.
|
|
52
|
+
dummy_property: The dummy property to use as placeholder for the views in the new data model.
|
|
53
|
+
|
|
54
|
+
!!! note "Enterprise Data Model Creation"
|
|
55
|
+
Always create an enterprise data model from a Cognite Data Model as this will
|
|
56
|
+
assure all the Cognite Data Fusion applications to run smoothly, such as
|
|
57
|
+
- Search
|
|
58
|
+
- Atlas AI
|
|
59
|
+
- ...
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
if dms := self._state.last_verified_dms_rules:
|
|
63
|
+
output = ToExtension(
|
|
64
|
+
new_model_id=data_model_id,
|
|
65
|
+
org_name=org_name,
|
|
66
|
+
type_="enterprise",
|
|
67
|
+
dummy_property=dummy_property,
|
|
68
|
+
).transform(dms)
|
|
69
|
+
self._state.verified_rules.append(output.rules)
|
|
70
|
+
|
|
71
|
+
def to_solution(
|
|
72
|
+
self,
|
|
73
|
+
data_model_id: DataModelIdentifier,
|
|
74
|
+
org_name: str = "My",
|
|
75
|
+
mode: Literal["read", "write"] = "read",
|
|
76
|
+
dummy_property: str = "dummy",
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Uses the current data model as a basis to create solution data model
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
data_model_id: The solution data model id that is being created.
|
|
82
|
+
org_name: Organization name to use for the views in the new data model.
|
|
83
|
+
mode: The mode of the solution data model. Can be either "read" or "write".
|
|
84
|
+
dummy_property: The dummy property to use as placeholder for the views in the new data model.
|
|
85
|
+
|
|
86
|
+
!!! note "Solution Data Model Mode"
|
|
87
|
+
The read-only solution model will only be able to read from the existing containers
|
|
88
|
+
from the enterprise data model, therefore the solution data model will not have
|
|
89
|
+
containers in the solution data model space. Meaning the solution data model views
|
|
90
|
+
will be read-only.
|
|
91
|
+
|
|
92
|
+
The write mode will have additional containers in the solution data model space,
|
|
93
|
+
allowing in addition to reading through the solution model views, also writing to
|
|
94
|
+
the containers in the solution data model space.
|
|
45
95
|
|
|
46
96
|
"""
|
|
47
97
|
if dms := self._state.last_verified_dms_rules:
|
|
48
|
-
output = ToExtension(
|
|
98
|
+
output = ToExtension(
|
|
99
|
+
new_model_id=data_model_id,
|
|
100
|
+
org_name=org_name,
|
|
101
|
+
type_="solution",
|
|
102
|
+
mode=mode,
|
|
103
|
+
dummy_property=dummy_property,
|
|
104
|
+
).transform(dms)
|
|
49
105
|
self._state.verified_rules.append(output.rules)
|
|
50
106
|
|
|
51
|
-
def reduce(self, drop: Collection[Literal["3D", "Annotation", "BaseViews"]]) -> None:
|
|
107
|
+
def reduce(self, drop: Collection[Literal["3D", "Annotation", "BaseViews"] | str]) -> None:
|
|
52
108
|
"""This is a special method that allow you to drop parts of the data model.
|
|
53
109
|
This only applies to Cognite Data Models.
|
|
54
110
|
|
|
55
111
|
Args:
|
|
56
|
-
drop:
|
|
112
|
+
drop: What to drop from the data model. The values 3D, Annotation, and BaseViews are special values that
|
|
113
|
+
drops multiple views at once. You can also pass externalIds of views to drop individual views.
|
|
57
114
|
|
|
58
115
|
"""
|
|
59
116
|
if dms := self._state.last_verified_dms_rules:
|
cognite/neat/_session/_read.py
CHANGED
|
@@ -7,13 +7,16 @@ from cognite.client.data_classes.data_modeling import DataModelIdentifier
|
|
|
7
7
|
from cognite.neat._graph import examples as instances_examples
|
|
8
8
|
from cognite.neat._graph import extractors
|
|
9
9
|
from cognite.neat._issues import IssueList
|
|
10
|
+
from cognite.neat._issues.errors import NeatValueError
|
|
10
11
|
from cognite.neat._rules import importers
|
|
11
12
|
from cognite.neat._rules._shared import ReadRules
|
|
12
13
|
|
|
13
14
|
from ._state import SessionState
|
|
14
15
|
from ._wizard import NeatObjectType, RDFFileType, object_wizard, rdf_dm_wizard
|
|
16
|
+
from .exceptions import intercept_session_exceptions
|
|
15
17
|
|
|
16
18
|
|
|
19
|
+
@intercept_session_exceptions
|
|
17
20
|
class ReadAPI:
|
|
18
21
|
def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
|
|
19
22
|
self._state = state
|
|
@@ -23,6 +26,7 @@ class ReadAPI:
|
|
|
23
26
|
self.excel = ExcelReadAPI(state, client, verbose)
|
|
24
27
|
|
|
25
28
|
|
|
29
|
+
@intercept_session_exceptions
|
|
26
30
|
class BaseReadAPI:
|
|
27
31
|
def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
|
|
28
32
|
self._state = state
|
|
@@ -44,20 +48,44 @@ class BaseReadAPI:
|
|
|
44
48
|
elif isinstance(io, Path):
|
|
45
49
|
return io
|
|
46
50
|
else:
|
|
47
|
-
raise
|
|
51
|
+
raise NeatValueError(f"Expected str or Path, got {type(io)}")
|
|
48
52
|
|
|
49
53
|
|
|
54
|
+
@intercept_session_exceptions
|
|
50
55
|
class CDFReadAPI(BaseReadAPI):
|
|
51
|
-
def
|
|
56
|
+
def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
|
|
57
|
+
super().__init__(state, client, verbose)
|
|
58
|
+
self.classic = CDFClassicAPI(state, client, verbose)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def _get_client(self) -> CogniteClient:
|
|
52
62
|
if self._client is None:
|
|
53
|
-
raise
|
|
63
|
+
raise NeatValueError("No client provided. Please provide a client to read a data model.")
|
|
64
|
+
return self._client
|
|
54
65
|
|
|
55
|
-
|
|
66
|
+
def data_model(self, data_model_id: DataModelIdentifier) -> IssueList:
|
|
67
|
+
importer = importers.DMSImporter.from_data_model_id(self._get_client, data_model_id)
|
|
56
68
|
input_rules = importer.to_rules()
|
|
57
69
|
self._store_rules(data_model_id, input_rules, "CDF")
|
|
58
70
|
return input_rules.issues
|
|
59
71
|
|
|
60
72
|
|
|
73
|
+
@intercept_session_exceptions
|
|
74
|
+
class CDFClassicAPI(BaseReadAPI):
|
|
75
|
+
@property
|
|
76
|
+
def _get_client(self) -> CogniteClient:
|
|
77
|
+
if self._client is None:
|
|
78
|
+
raise ValueError("No client provided. Please provide a client to read a data model.")
|
|
79
|
+
return self._client
|
|
80
|
+
|
|
81
|
+
def assets(self, root_asset_external_id: str) -> None:
|
|
82
|
+
extractor = extractors.AssetsExtractor.from_hierarchy(self._get_client, root_asset_external_id)
|
|
83
|
+
self._state.store.write(extractor)
|
|
84
|
+
if self._verbose:
|
|
85
|
+
print(f"Asset hierarchy {root_asset_external_id} read successfully")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@intercept_session_exceptions
|
|
61
89
|
class ExcelReadAPI(BaseReadAPI):
|
|
62
90
|
def __call__(self, io: Any) -> IssueList:
|
|
63
91
|
filepath = self._return_filepath(io)
|
|
@@ -66,6 +94,7 @@ class ExcelReadAPI(BaseReadAPI):
|
|
|
66
94
|
return input_rules.issues
|
|
67
95
|
|
|
68
96
|
|
|
97
|
+
@intercept_session_exceptions
|
|
69
98
|
class RDFReadAPI(BaseReadAPI):
|
|
70
99
|
def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
|
|
71
100
|
super().__init__(state, client, verbose)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from cognite.client import data_modeling as dm
|
|
2
|
+
|
|
3
|
+
from cognite.neat._rules.transformers import SetIDDMSModel
|
|
4
|
+
|
|
5
|
+
from ._state import SessionState
|
|
6
|
+
from .exceptions import intercept_session_exceptions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@intercept_session_exceptions
|
|
10
|
+
class SetAPI:
|
|
11
|
+
def __init__(self, state: SessionState, verbose: bool) -> None:
|
|
12
|
+
self._state = state
|
|
13
|
+
self._verbose = verbose
|
|
14
|
+
|
|
15
|
+
def data_model_id(self, new_model_id: dm.DataModelId | tuple[str, str, str]) -> None:
|
|
16
|
+
"""Sets the data model ID of the latest verified data model."""
|
|
17
|
+
if dms := self._state.last_verified_dms_rules:
|
|
18
|
+
output = SetIDDMSModel(new_model_id).transform(dms)
|
|
19
|
+
self._state.verified_rules.append(output.rules)
|
|
20
|
+
if self._verbose:
|
|
21
|
+
print(f"Data model ID set to {new_model_id}")
|
|
22
|
+
else:
|
|
23
|
+
print("No verified DMS data model available")
|