cognite-neat 0.97.3__py3-none-any.whl → 0.98.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cognite-neat might be problematic. Click here for more details.

Files changed (65) hide show
  1. cognite/neat/_graph/loaders/__init__.py +1 -2
  2. cognite/neat/_issues/warnings/_models.py +9 -0
  3. cognite/neat/_rules/_shared.py +3 -8
  4. cognite/neat/_rules/analysis/__init__.py +1 -2
  5. cognite/neat/_rules/analysis/_base.py +2 -23
  6. cognite/neat/_rules/analysis/_dms.py +4 -10
  7. cognite/neat/_rules/analysis/_information.py +2 -10
  8. cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
  9. cognite/neat/_rules/exporters/_rules2excel.py +15 -72
  10. cognite/neat/_rules/exporters/_rules2ontology.py +4 -4
  11. cognite/neat/_rules/importers/_base.py +3 -4
  12. cognite/neat/_rules/importers/_dms2rules.py +17 -40
  13. cognite/neat/_rules/importers/_dtdl2rules/dtdl_converter.py +1 -7
  14. cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +7 -10
  15. cognite/neat/_rules/importers/_rdf/_base.py +17 -29
  16. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2classes.py +2 -2
  17. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2metadata.py +5 -10
  18. cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2properties.py +1 -2
  19. cognite/neat/_rules/importers/_rdf/_inference2rules.py +30 -18
  20. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2classes.py +2 -2
  21. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2metadata.py +5 -8
  22. cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -2
  23. cognite/neat/_rules/importers/_rdf/_shared.py +25 -140
  24. cognite/neat/_rules/importers/_spreadsheet2rules.py +10 -41
  25. cognite/neat/_rules/models/__init__.py +2 -16
  26. cognite/neat/_rules/models/_base_rules.py +98 -52
  27. cognite/neat/_rules/models/dms/_exporter.py +7 -160
  28. cognite/neat/_rules/models/dms/_rules.py +18 -126
  29. cognite/neat/_rules/models/dms/_rules_input.py +20 -48
  30. cognite/neat/_rules/models/dms/_schema.py +11 -0
  31. cognite/neat/_rules/models/dms/_validation.py +9 -107
  32. cognite/neat/_rules/models/information/_rules.py +19 -114
  33. cognite/neat/_rules/models/information/_rules_input.py +32 -41
  34. cognite/neat/_rules/models/information/_validation.py +34 -102
  35. cognite/neat/_rules/transformers/__init__.py +1 -4
  36. cognite/neat/_rules/transformers/_converters.py +18 -195
  37. cognite/neat/_rules/transformers/_mapping.py +1 -5
  38. cognite/neat/_rules/transformers/_verification.py +0 -14
  39. cognite/neat/_session/_base.py +34 -13
  40. cognite/neat/_session/_collector.py +126 -0
  41. cognite/neat/_session/_inspect.py +5 -5
  42. cognite/neat/_session/_prepare.py +4 -4
  43. cognite/neat/_session/_read.py +62 -9
  44. cognite/neat/_session/_set.py +2 -2
  45. cognite/neat/_session/_show.py +11 -11
  46. cognite/neat/_session/_to.py +24 -11
  47. cognite/neat/_session/exceptions.py +20 -3
  48. cognite/neat/_store/_provenance.py +2 -2
  49. cognite/neat/_utils/auxiliary.py +19 -0
  50. cognite/neat/_version.py +1 -1
  51. cognite/neat/_workflows/steps/data_contracts.py +2 -10
  52. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +6 -46
  53. cognite/neat/_workflows/steps/lib/current/rules_validator.py +2 -7
  54. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/METADATA +2 -1
  55. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/RECORD +58 -64
  56. cognite/neat/_graph/loaders/_rdf2asset.py +0 -416
  57. cognite/neat/_rules/analysis/_asset.py +0 -173
  58. cognite/neat/_rules/models/asset/__init__.py +0 -13
  59. cognite/neat/_rules/models/asset/_rules.py +0 -109
  60. cognite/neat/_rules/models/asset/_rules_input.py +0 -101
  61. cognite/neat/_rules/models/asset/_validation.py +0 -45
  62. cognite/neat/_rules/models/domain.py +0 -136
  63. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/LICENSE +0 -0
  64. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/WHEEL +0 -0
  65. {cognite_neat-0.97.3.dist-info → cognite_neat-0.98.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,126 @@
1
+ import os
2
+ import platform
3
+ import tempfile
4
+ import threading
5
+ import uuid
6
+ from contextlib import suppress
7
+ from functools import cached_property
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from mixpanel import Consumer, Mixpanel # type: ignore[import-untyped]
12
+
13
+ from cognite.neat._constants import IN_NOTEBOOK, IN_PYODIDE
14
+ from cognite.neat._version import __version__
15
+
16
+ _NEAT_MIXPANEL_TOKEN: str = "bd630ad149d19999df3989c3a3750c4f"
17
+
18
+
19
+ class Collector:
20
+ def __init__(self, skip_tracking: bool = False) -> None:
21
+ self.mp = Mixpanel(_NEAT_MIXPANEL_TOKEN, consumer=Consumer(api_host="api-eu.mixpanel.com"))
22
+ tmp_dir = Path(tempfile.gettempdir()).resolve()
23
+ self._opt_status_file = tmp_dir / "neat-opt-status.bin"
24
+ self._distinct_id_file = tmp_dir / "neat-distinct-id.bin"
25
+ self.skip_tracking = self.opted_out or skip_tracking
26
+
27
+ @cached_property
28
+ def _opt_status(self) -> str:
29
+ if self._opt_status_file.exists():
30
+ return self._opt_status_file.read_text()
31
+ return ""
32
+
33
+ def _bust_opt_status(self) -> None:
34
+ self.__dict__.pop("_opt_status", None)
35
+
36
+ @property
37
+ def opted_out(self) -> bool:
38
+ return self._opt_status == "opted-out"
39
+
40
+ @property
41
+ def opted_in(self) -> bool:
42
+ return self._opt_status == "opted-in"
43
+
44
+ @staticmethod
45
+ def _get_environment() -> str:
46
+ if IN_PYODIDE:
47
+ return "pyodide"
48
+ if IN_NOTEBOOK:
49
+ return "notebook"
50
+ return "python"
51
+
52
+ def track_session_command(self, command: str, *args, **kwargs) -> None:
53
+ event_information = {
54
+ "neatVersion": __version__,
55
+ "$os": platform.system(),
56
+ "pythonVersion": platform.python_version(),
57
+ "environment": self._get_environment(),
58
+ }
59
+
60
+ if len(args) > 1:
61
+ # The first argument is self.
62
+ for i, arg in enumerate(args[1:]):
63
+ event_information[f"arg{i}"] = arg
64
+
65
+ if kwargs:
66
+ for key, value in kwargs.items():
67
+ event_information[key] = self._serialize_value(value)[:500]
68
+ self._track(command, event_information)
69
+
70
+ @staticmethod
71
+ def _serialize_value(value: Any) -> str:
72
+ if isinstance(value, (str | int | float | bool)):
73
+ return str(value)
74
+ if isinstance(value, list | tuple | dict):
75
+ return str(value)
76
+ return str(type(value))
77
+
78
+ def _track(self, event_name: str, event_information: dict[str, Any]) -> bool:
79
+ if self.skip_tracking or not self.opted_in or "PYTEST_CURRENT_TEST" in os.environ:
80
+ return False
81
+
82
+ distinct_id = self.get_distinct_id()
83
+
84
+ def track() -> None:
85
+ # If we are unable to connect to Mixpanel, we don't want to crash the program
86
+ with suppress(ConnectionError):
87
+ self.mp.track(
88
+ distinct_id,
89
+ event_name,
90
+ event_information,
91
+ )
92
+
93
+ thread = threading.Thread(
94
+ target=track,
95
+ daemon=False,
96
+ )
97
+ thread.start()
98
+ return True
99
+
100
+ def get_distinct_id(self) -> str:
101
+ if self._distinct_id_file.exists():
102
+ return self._distinct_id_file.read_text()
103
+
104
+ distinct_id = f"{platform.system()}-{platform.python_version()}-{uuid.uuid4()!s}"
105
+ self._distinct_id_file.write_text(distinct_id)
106
+ with suppress(ConnectionError):
107
+ self.mp.people_set(
108
+ distinct_id,
109
+ {
110
+ "$os": platform.system(),
111
+ "$python_version": platform.python_version(),
112
+ "$distinct_id": distinct_id,
113
+ },
114
+ )
115
+ return distinct_id
116
+
117
+ def enable(self) -> None:
118
+ self._opt_status_file.write_text("opted-in")
119
+ self._bust_opt_status()
120
+
121
+ def disable(self) -> None:
122
+ self._opt_status_file.write_text("opted-out")
123
+ self._bust_opt_status()
124
+
125
+
126
+ _COLLECTOR = Collector()
@@ -9,10 +9,10 @@ from cognite.neat._issues import IssueList
9
9
  from cognite.neat._utils.upload import UploadResult, UploadResultCore, UploadResultList
10
10
 
11
11
  from ._state import SessionState
12
- from .exceptions import intercept_session_exceptions
12
+ from .exceptions import session_class_wrapper
13
13
 
14
14
 
15
- @intercept_session_exceptions
15
+ @session_class_wrapper
16
16
  class InspectAPI:
17
17
  def __init__(self, state: SessionState) -> None:
18
18
  self._state = state
@@ -25,7 +25,7 @@ class InspectAPI:
25
25
  return self._state.data_model.last_verified_rule[1].properties.to_pandas()
26
26
 
27
27
 
28
- @intercept_session_exceptions
28
+ @session_class_wrapper
29
29
  class InspectIssues:
30
30
  """Inspect issues of the current data model."""
31
31
 
@@ -92,14 +92,14 @@ class InspectIssues:
92
92
  )
93
93
 
94
94
 
95
- @intercept_session_exceptions
95
+ @session_class_wrapper
96
96
  class InspectOutcome:
97
97
  def __init__(self, state: SessionState) -> None:
98
98
  self.data_model = InspectUploadOutcome(lambda: state.data_model.last_outcome)
99
99
  self.instances = InspectUploadOutcome(lambda: state.instances.last_outcome)
100
100
 
101
101
 
102
- @intercept_session_exceptions
102
+ @session_class_wrapper
103
103
  class InspectUploadOutcome:
104
104
  def __init__(self, get_last_outcome: Callable[[], UploadResultList]) -> None:
105
105
  self._get_last_outcome = get_last_outcome
@@ -12,10 +12,10 @@ from cognite.neat._rules.transformers import ReduceCogniteModel, ToCompliantEnti
12
12
  from cognite.neat._store._provenance import Change
13
13
 
14
14
  from ._state import SessionState
15
- from .exceptions import NeatSessionError, intercept_session_exceptions
15
+ from .exceptions import NeatSessionError, session_class_wrapper
16
16
 
17
17
 
18
- @intercept_session_exceptions
18
+ @session_class_wrapper
19
19
  class PrepareAPI:
20
20
  def __init__(self, state: SessionState, verbose: bool) -> None:
21
21
  self._state = state
@@ -24,7 +24,7 @@ class PrepareAPI:
24
24
  self.instances = InstancePrepareAPI(state, verbose)
25
25
 
26
26
 
27
- @intercept_session_exceptions
27
+ @session_class_wrapper
28
28
  class InstancePrepareAPI:
29
29
  def __init__(self, state: SessionState, verbose: bool) -> None:
30
30
  self._state = state
@@ -95,7 +95,7 @@ class InstancePrepareAPI:
95
95
  return type_uri[0], property_uri[0]
96
96
 
97
97
 
98
- @intercept_session_exceptions
98
+ @session_class_wrapper
99
99
  class DataModelPrepareAPI:
100
100
  def __init__(self, state: SessionState, verbose: bool) -> None:
101
101
  self._state = state
@@ -1,17 +1,19 @@
1
1
  import tempfile
2
2
  from datetime import datetime, timezone
3
3
  from pathlib import Path
4
- from typing import Any
4
+ from typing import Any, Literal
5
5
 
6
6
  from cognite.client import CogniteClient
7
7
  from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier
8
8
 
9
+ from cognite.neat._constants import COGNITE_SPACES
9
10
  from cognite.neat._graph import examples as instances_examples
10
11
  from cognite.neat._graph import extractors
11
12
  from cognite.neat._issues import IssueList
12
13
  from cognite.neat._issues.errors import NeatValueError
13
14
  from cognite.neat._rules import importers
14
15
  from cognite.neat._rules._shared import ReadRules
16
+ from cognite.neat._rules.importers import BaseImporter
15
17
  from cognite.neat._store._provenance import Activity as ProvenanceActivity
16
18
  from cognite.neat._store._provenance import Change
17
19
  from cognite.neat._store._provenance import Entity as ProvenanceEntity
@@ -20,10 +22,10 @@ from cognite.neat._utils.reader import GitHubReader, NeatReader, PathReader
20
22
  from ._state import SessionState
21
23
  from ._wizard import NeatObjectType, RDFFileType, object_wizard, rdf_dm_wizard
22
24
  from .engine import import_engine
23
- from .exceptions import NeatSessionError, intercept_session_exceptions
25
+ from .exceptions import NeatSessionError, session_class_wrapper
24
26
 
25
27
 
26
- @intercept_session_exceptions
28
+ @session_class_wrapper
27
29
  class ReadAPI:
28
30
  def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
29
31
  self._state = state
@@ -32,9 +34,10 @@ class ReadAPI:
32
34
  self.rdf = RDFReadAPI(state, client, verbose)
33
35
  self.excel = ExcelReadAPI(state, client, verbose)
34
36
  self.csv = CSVReadAPI(state, client, verbose)
37
+ self.yaml = YamlReadAPI(state, client, verbose)
35
38
 
36
39
 
37
- @intercept_session_exceptions
40
+ @session_class_wrapper
38
41
  class BaseReadAPI:
39
42
  def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
40
43
  self._state = state
@@ -62,7 +65,7 @@ class BaseReadAPI:
62
65
  raise NeatValueError(f"Expected str or Path, got {type(io)}")
63
66
 
64
67
 
65
- @intercept_session_exceptions
68
+ @session_class_wrapper
66
69
  class CDFReadAPI(BaseReadAPI):
67
70
  def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
68
71
  super().__init__(state, client, verbose)
@@ -107,7 +110,7 @@ class CDFReadAPI(BaseReadAPI):
107
110
  return self._store_rules(rules, change)
108
111
 
109
112
 
110
- @intercept_session_exceptions
113
+ @session_class_wrapper
111
114
  class CDFClassicAPI(BaseReadAPI):
112
115
  @property
113
116
  def _get_client(self) -> CogniteClient:
@@ -122,7 +125,7 @@ class CDFClassicAPI(BaseReadAPI):
122
125
  print(f"Asset hierarchy {root_asset_external_id} read successfully")
123
126
 
124
127
 
125
- @intercept_session_exceptions
128
+ @session_class_wrapper
126
129
  class ExcelReadAPI(BaseReadAPI):
127
130
  def __call__(self, io: Any) -> IssueList:
128
131
  reader = NeatReader.create(io)
@@ -146,7 +149,57 @@ class ExcelReadAPI(BaseReadAPI):
146
149
  return input_rules.issues
147
150
 
148
151
 
149
- @intercept_session_exceptions
152
+ @session_class_wrapper
153
+ class YamlReadAPI(BaseReadAPI):
154
+ def __call__(self, io: Any, format: Literal["neat", "toolkit"] = "neat") -> IssueList:
155
+ reader = NeatReader.create(io)
156
+ if not isinstance(reader, PathReader):
157
+ raise NeatValueError("Only file paths are supported for YAML files")
158
+ start = datetime.now(timezone.utc)
159
+ importer: BaseImporter
160
+ if format == "neat":
161
+ importer = importers.YAMLImporter.from_file(reader.path)
162
+ elif format == "toolkit":
163
+ if reader.path.is_file():
164
+ dms_importer = importers.DMSImporter.from_zip_file(reader.path)
165
+ elif reader.path.is_dir():
166
+ dms_importer = importers.DMSImporter.from_directory(reader.path)
167
+ else:
168
+ raise NeatValueError(f"Unsupported YAML format: {format}")
169
+ ref_containers = dms_importer.root_schema.referenced_container()
170
+ if system_container_ids := [
171
+ container_id for container_id in ref_containers if container_id.space in COGNITE_SPACES
172
+ ]:
173
+ if self._client is None:
174
+ raise NeatSessionError(
175
+ "No client provided. You are referencing Cognite containers in your data model, "
176
+ "NEAT needs a client to lookup the container definitions. "
177
+ "Please set the client in the session, NeatSession(client=client)."
178
+ )
179
+ system_containers = self._client.data_modeling.containers.retrieve(system_container_ids)
180
+ dms_importer.update_referenced_containers(system_containers)
181
+
182
+ importer = dms_importer
183
+ else:
184
+ raise NeatValueError(f"Unsupported YAML format: {format}")
185
+ input_rules: ReadRules = importer.to_rules()
186
+
187
+ end = datetime.now(timezone.utc)
188
+
189
+ if input_rules.rules:
190
+ change = Change.from_rules_activity(
191
+ input_rules,
192
+ importer.agent,
193
+ start,
194
+ end,
195
+ description=f"YAML file {reader!s} read as unverified data model",
196
+ )
197
+ self._store_rules(input_rules, change)
198
+
199
+ return input_rules.issues
200
+
201
+
202
+ @session_class_wrapper
150
203
  class CSVReadAPI(BaseReadAPI):
151
204
  def __call__(self, io: Any, type: str, primary_key: str) -> None:
152
205
  reader = NeatReader.create(io)
@@ -167,7 +220,7 @@ class CSVReadAPI(BaseReadAPI):
167
220
  self._state.instances.store.write(extractor)
168
221
 
169
222
 
170
- @intercept_session_exceptions
223
+ @session_class_wrapper
171
224
  class RDFReadAPI(BaseReadAPI):
172
225
  def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
173
226
  super().__init__(state, client, verbose)
@@ -7,10 +7,10 @@ from cognite.neat._rules.transformers import SetIDDMSModel
7
7
  from cognite.neat._store._provenance import Change
8
8
 
9
9
  from ._state import SessionState
10
- from .exceptions import NeatSessionError, intercept_session_exceptions
10
+ from .exceptions import NeatSessionError, session_class_wrapper
11
11
 
12
12
 
13
- @intercept_session_exceptions
13
+ @session_class_wrapper
14
14
  class SetAPI:
15
15
  def __init__(self, state: SessionState, verbose: bool) -> None:
16
16
  self._state = state
@@ -16,10 +16,10 @@ from cognite.neat._session.exceptions import NeatSessionError
16
16
  from cognite.neat._utils.rdf_ import remove_namespace_from_uri
17
17
 
18
18
  from ._state import SessionState
19
- from .exceptions import intercept_session_exceptions
19
+ from .exceptions import session_class_wrapper
20
20
 
21
21
 
22
- @intercept_session_exceptions
22
+ @session_class_wrapper
23
23
  class ShowAPI:
24
24
  def __init__(self, state: SessionState) -> None:
25
25
  self._state = state
@@ -27,7 +27,7 @@ class ShowAPI:
27
27
  self.instances = ShowInstanceAPI(self._state)
28
28
 
29
29
 
30
- @intercept_session_exceptions
30
+ @session_class_wrapper
31
31
  class ShowBaseAPI:
32
32
  def __init__(self, state: SessionState) -> None:
33
33
  self._state = state
@@ -63,7 +63,7 @@ class ShowBaseAPI:
63
63
  return net.show(name)
64
64
 
65
65
 
66
- @intercept_session_exceptions
66
+ @session_class_wrapper
67
67
  class ShowDataModelAPI(ShowBaseAPI):
68
68
  def __init__(self, state: SessionState) -> None:
69
69
  super().__init__(state)
@@ -111,7 +111,7 @@ class ShowDataModelAPI(ShowBaseAPI):
111
111
  di_graph.add_edge(
112
112
  prop_.view.suffix,
113
113
  prop_.value_type.suffix,
114
- label=prop_.name or prop_.property_,
114
+ label=prop_.name or prop_.view_property,
115
115
  )
116
116
 
117
117
  return di_graph
@@ -145,7 +145,7 @@ class ShowDataModelAPI(ShowBaseAPI):
145
145
  return di_graph
146
146
 
147
147
 
148
- @intercept_session_exceptions
148
+ @session_class_wrapper
149
149
  class ShowDataModelImplementsAPI(ShowBaseAPI):
150
150
  def __init__(self, state: SessionState) -> None:
151
151
  super().__init__(state)
@@ -206,27 +206,27 @@ class ShowDataModelImplementsAPI(ShowBaseAPI):
206
206
  # if possible use human readable label coming from the view name
207
207
 
208
208
  # add subClassOff as edges
209
- if class_.parent:
209
+ if class_.implements:
210
210
  if not di_graph.has_node(class_.class_.suffix):
211
211
  di_graph.add_node(
212
212
  class_.class_.suffix,
213
213
  label=class_.name or class_.class_.suffix,
214
214
  )
215
215
 
216
- for parent in class_.parent:
216
+ for parent in class_.implements:
217
217
  if not di_graph.has_node(parent.suffix):
218
218
  di_graph.add_node(parent.suffix, label=parent.suffix)
219
219
  di_graph.add_edge(
220
220
  class_.class_.suffix,
221
221
  parent.suffix,
222
- label="subClassOf",
222
+ label="implements",
223
223
  dashes=True,
224
224
  )
225
225
 
226
226
  return di_graph
227
227
 
228
228
 
229
- @intercept_session_exceptions
229
+ @session_class_wrapper
230
230
  class ShowDataModelProvenanceAPI(ShowBaseAPI):
231
231
  def __init__(self, state: SessionState) -> None:
232
232
  super().__init__(state)
@@ -286,7 +286,7 @@ class ShowDataModelProvenanceAPI(ShowBaseAPI):
286
286
  return remove_namespace_from_uri(thing)
287
287
 
288
288
 
289
- @intercept_session_exceptions
289
+ @session_class_wrapper
290
290
  class ShowInstanceAPI(ShowBaseAPI):
291
291
  def __init__(self, state: SessionState) -> None:
292
292
  super().__init__(state)
@@ -12,10 +12,10 @@ from cognite.neat._rules._shared import VerifiedRules
12
12
  from cognite.neat._utils.upload import UploadResultCore, UploadResultList
13
13
 
14
14
  from ._state import SessionState
15
- from .exceptions import NeatSessionError, intercept_session_exceptions
15
+ from .exceptions import NeatSessionError, session_class_wrapper
16
16
 
17
17
 
18
- @intercept_session_exceptions
18
+ @session_class_wrapper
19
19
  class ToAPI:
20
20
  def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
21
21
  self._state = state
@@ -46,21 +46,34 @@ class ToAPI:
46
46
  return None
47
47
 
48
48
  @overload
49
- def yaml(self, io: None) -> str: ...
49
+ def yaml(self, io: None, format: Literal["neat"] = "neat") -> str: ...
50
50
 
51
51
  @overload
52
- def yaml(self, io: Any) -> None: ...
53
-
54
- def yaml(self, io: Any | None = None) -> str | None:
55
- exporter = exporters.YAMLExporter()
56
- if io is None:
57
- return exporter.export(self._state.data_model.last_verified_rule[1])
52
+ def yaml(self, io: Any, format: Literal["neat", "toolkit"] = "neat") -> None: ...
53
+
54
+ def yaml(self, io: Any | None = None, format: Literal["neat", "toolkit"] = "neat") -> str | None:
55
+ if format == "neat":
56
+ exporter = exporters.YAMLExporter()
57
+ last_verified = self._state.data_model.last_verified_rule[1]
58
+ if io is None:
59
+ return exporter.export(last_verified)
60
+
61
+ exporter.export_to_file(last_verified, Path(io))
62
+ elif format == "toolkit":
63
+ if io is None or not isinstance(io, str | Path):
64
+ raise NeatSessionError(
65
+ "Please provide a zip file or directory path to write the YAML files to."
66
+ "This is required for the 'toolkit' format."
67
+ )
68
+ dms_rule = self._state.data_model.last_verified_dms_rules[1]
69
+ exporters.DMSExporter().export_to_file(dms_rule, Path(io))
70
+ else:
71
+ raise NeatSessionError("Please provide a valid format. {['neat', 'toolkit']}")
58
72
 
59
- exporter.export_to_file(self._state.data_model.last_verified_rule[1], Path(io))
60
73
  return None
61
74
 
62
75
 
63
- @intercept_session_exceptions
76
+ @session_class_wrapper
64
77
  class CDFToAPI:
65
78
  def __init__(self, state: SessionState, client: CogniteClient | None, verbose: bool) -> None:
66
79
  self._client = client
@@ -2,6 +2,8 @@ import functools
2
2
  from collections.abc import Callable
3
3
  from typing import Any
4
4
 
5
+ from ._collector import _COLLECTOR
6
+
5
7
  try:
6
8
  from rich import print
7
9
 
@@ -16,9 +18,10 @@ class NeatSessionError(Exception):
16
18
  ...
17
19
 
18
20
 
19
- def _intercept_session_exceptions(func: Callable):
21
+ def _session_method_wrapper(func: Callable, cls_name: str):
20
22
  @functools.wraps(func)
21
23
  def wrapper(*args: Any, **kwargs: Any):
24
+ _COLLECTOR.track_session_command(f"{cls_name}.{func.__name__}", *args, **kwargs)
22
25
  try:
23
26
  return func(*args, **kwargs)
24
27
  except NeatSessionError as e:
@@ -40,7 +43,21 @@ def _intercept_session_exceptions(func: Callable):
40
43
  return wrapper
41
44
 
42
45
 
43
- def intercept_session_exceptions(cls: type):
46
+ def session_class_wrapper(cls: type):
47
+ """This decorator wraps all methods of a class.
48
+
49
+ It should be used with all composition classes used with the NeatSession class.
50
+
51
+ It does the following:
52
+ * Intercepts all NeatSession exceptions and prints them in a user-friendly way.
53
+ * Collects user metrics.
54
+
55
+ Args:
56
+ cls: NeatSession composition class
57
+
58
+ Returns:
59
+ cls: NeatSession composition class with all methods wrapped
60
+ """
44
61
  to_check = [cls]
45
62
  while to_check:
46
63
  cls = to_check.pop()
@@ -49,7 +66,7 @@ def intercept_session_exceptions(cls: type):
49
66
  continue
50
67
  attr = getattr(cls, attr_name)
51
68
  if callable(attr):
52
- setattr(cls, attr_name, _intercept_session_exceptions(attr))
69
+ setattr(cls, attr_name, _session_method_wrapper(attr, cls.__name__))
53
70
  elif isinstance(attr, type):
54
71
  to_check.append(attr)
55
72
  return cls
@@ -93,14 +93,14 @@ class Entity:
93
93
  return cls(
94
94
  was_attributed_to=agent,
95
95
  was_generated_by=activity,
96
- id_=rules.id_,
96
+ id_=rules.metadata.identifier,
97
97
  )
98
98
 
99
99
  elif isinstance(rules, ReadRules | JustRules) and rules.rules is not None:
100
100
  return cls(
101
101
  was_attributed_to=agent,
102
102
  was_generated_by=activity,
103
- id_=rules.rules.id_,
103
+ id_=rules.rules.metadata.identifier,
104
104
  )
105
105
  else:
106
106
  return cls(
@@ -140,3 +140,22 @@ def string_to_ideal_type(input_string: str) -> int | bool | float | datetime | s
140
140
  except ValueError:
141
141
  # Return the input string if no conversion is possible
142
142
  return input_string
143
+
144
+
145
+ def get_parameters_by_method(obj: object, prefix: str = "") -> dict[str, dict[str, type]]:
146
+ annotation_by_method = {}
147
+ namespace_dict = {**dict(obj.__class__.__dict__.items()), **vars(obj)}
148
+ for name, value in namespace_dict.items():
149
+ if name != "__call__" and (name.startswith("_") or isinstance(value, property)):
150
+ continue
151
+ if callable(value) and type(value).__name__ == "function":
152
+ annotation_by_method[f"{prefix}{name}"] = get_parameters(value)
153
+ elif isinstance(value, object) and type(value).__module__ != "builtins":
154
+ sub_prefix = f"{prefix}{name}." if prefix else f"{name}."
155
+ annotation_by_method.update(get_parameters_by_method(value, sub_prefix))
156
+ return annotation_by_method
157
+
158
+
159
+ def get_parameters(obj: Callable) -> dict[str, type]:
160
+ annotations = inspect.get_annotations(obj)
161
+ return {name: annotations[name] for name in annotations if name != "return"}
cognite/neat/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.97.3"
1
+ __version__ = "0.98.0"
2
2
  __engine__ = "^1.0.3"
@@ -10,9 +10,7 @@ from cognite.client.data_classes import (
10
10
  from cognite.client.data_classes.data_modeling import EdgeApply, NodeApply
11
11
 
12
12
  from cognite.neat._rules.models import (
13
- AssetRules,
14
13
  DMSRules,
15
- DomainRules,
16
14
  InformationRules,
17
15
  )
18
16
  from cognite.neat._store import NeatGraphStore
@@ -20,19 +18,13 @@ from cognite.neat._workflows.steps.step_model import DataContract
20
18
 
21
19
 
22
20
  class MultiRuleData(DataContract):
23
- domain: DomainRules | None = None
24
21
  information: InformationRules | None = None
25
- asset: AssetRules | None = None
26
22
  dms: DMSRules | None = None
27
23
 
28
24
  @classmethod
29
- def from_rules(cls, rules: DomainRules | InformationRules | AssetRules | DMSRules):
30
- if isinstance(rules, DomainRules):
31
- return cls(domain=rules)
32
- elif isinstance(rules, InformationRules):
25
+ def from_rules(cls, rules: InformationRules | DMSRules):
26
+ if isinstance(rules, InformationRules):
33
27
  return cls(information=rules)
34
- elif isinstance(rules, AssetRules):
35
- return cls(asset=rules)
36
28
  elif isinstance(rules, DMSRules):
37
29
  return cls(dms=rules)
38
30
  else: