cognite-neat 0.105.2__py3-none-any.whl → 0.107.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 (54) hide show
  1. cognite/neat/_config.py +6 -260
  2. cognite/neat/_graph/extractors/__init__.py +5 -1
  3. cognite/neat/_graph/extractors/_base.py +32 -0
  4. cognite/neat/_graph/extractors/_classic_cdf/_base.py +42 -16
  5. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +78 -8
  6. cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +2 -0
  7. cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +10 -3
  8. cognite/neat/_graph/extractors/_dms.py +48 -14
  9. cognite/neat/_graph/extractors/_dms_graph.py +149 -0
  10. cognite/neat/_graph/extractors/_rdf_file.py +32 -5
  11. cognite/neat/_graph/loaders/_rdf2dms.py +119 -20
  12. cognite/neat/_graph/queries/_construct.py +1 -1
  13. cognite/neat/_graph/transformers/__init__.py +5 -0
  14. cognite/neat/_graph/transformers/_base.py +13 -9
  15. cognite/neat/_graph/transformers/_classic_cdf.py +141 -44
  16. cognite/neat/_graph/transformers/_rdfpath.py +4 -4
  17. cognite/neat/_graph/transformers/_value_type.py +54 -44
  18. cognite/neat/_issues/warnings/_external.py +1 -1
  19. cognite/neat/_rules/analysis/_base.py +1 -1
  20. cognite/neat/_rules/analysis/_information.py +14 -13
  21. cognite/neat/_rules/catalog/__init__.py +1 -0
  22. cognite/neat/_rules/catalog/classic_model.xlsx +0 -0
  23. cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
  24. cognite/neat/_rules/importers/_dms2rules.py +7 -5
  25. cognite/neat/_rules/importers/_rdf/_inference2rules.py +5 -3
  26. cognite/neat/_rules/models/_base_rules.py +0 -12
  27. cognite/neat/_rules/models/_types.py +5 -0
  28. cognite/neat/_rules/models/dms/_rules.py +50 -2
  29. cognite/neat/_rules/models/information/_rules.py +48 -5
  30. cognite/neat/_rules/models/information/_rules_input.py +1 -1
  31. cognite/neat/_rules/models/mapping/_classic2core.py +4 -5
  32. cognite/neat/_rules/models/mapping/_classic2core.yaml +70 -58
  33. cognite/neat/_rules/transformers/__init__.py +4 -0
  34. cognite/neat/_rules/transformers/_converters.py +209 -62
  35. cognite/neat/_rules/transformers/_mapping.py +3 -2
  36. cognite/neat/_session/_base.py +8 -13
  37. cognite/neat/_session/_inspect.py +6 -2
  38. cognite/neat/_session/_mapping.py +22 -13
  39. cognite/neat/_session/_prepare.py +9 -57
  40. cognite/neat/_session/_read.py +96 -29
  41. cognite/neat/_session/_set.py +9 -0
  42. cognite/neat/_session/_state.py +10 -1
  43. cognite/neat/_session/_to.py +51 -15
  44. cognite/neat/_session/exceptions.py +7 -3
  45. cognite/neat/_store/_graph_store.py +85 -39
  46. cognite/neat/_store/_rules_store.py +22 -0
  47. cognite/neat/_utils/auth.py +2 -0
  48. cognite/neat/_utils/collection_.py +32 -11
  49. cognite/neat/_version.py +1 -1
  50. {cognite_neat-0.105.2.dist-info → cognite_neat-0.107.0.dist-info}/METADATA +2 -8
  51. {cognite_neat-0.105.2.dist-info → cognite_neat-0.107.0.dist-info}/RECORD +54 -52
  52. {cognite_neat-0.105.2.dist-info → cognite_neat-0.107.0.dist-info}/WHEEL +1 -1
  53. {cognite_neat-0.105.2.dist-info → cognite_neat-0.107.0.dist-info}/LICENSE +0 -0
  54. {cognite_neat-0.105.2.dist-info → cognite_neat-0.107.0.dist-info}/entry_points.txt +0 -0
@@ -4,11 +4,9 @@ from typing import Any, Literal, cast
4
4
  from cognite.client.data_classes.data_modeling import DataModelIdentifier
5
5
  from rdflib import URIRef
6
6
 
7
- from cognite.neat._client import NeatClient
8
7
  from cognite.neat._constants import (
9
8
  get_default_prefixes_and_namespaces,
10
9
  )
11
- from cognite.neat._graph import extractors
12
10
  from cognite.neat._graph.transformers import (
13
11
  AttachPropertyFromTargetToSource,
14
12
  ConnectionToLiteral,
@@ -25,7 +23,6 @@ from cognite.neat._issues import IssueList
25
23
  from cognite.neat._issues.errors import NeatValueError
26
24
  from cognite.neat._rules.models.dms import DMSValidation
27
25
  from cognite.neat._rules.transformers import (
28
- AddClassImplements,
29
26
  IncludeReferenced,
30
27
  PrefixEntities,
31
28
  ReduceCogniteModel,
@@ -47,10 +44,10 @@ class PrepareAPI:
47
44
  inferring a data model or exporting the knowledge graph to a desired destination.
48
45
  """
49
46
 
50
- def __init__(self, client: NeatClient | None, state: SessionState, verbose: bool) -> None:
47
+ def __init__(self, state: SessionState, verbose: bool) -> None:
51
48
  self._state = state
52
49
  self._verbose = verbose
53
- self.data_model = DataModelPrepareAPI(client, state, verbose)
50
+ self.data_model = DataModelPrepareAPI(state, verbose)
54
51
  self.instances = InstancePrepareAPI(state, verbose)
55
52
 
56
53
 
@@ -349,41 +346,6 @@ class InstancePrepareAPI:
349
346
  transformer = ConnectionToLiteral(subject_type, subject_predicate)
350
347
  self._state.instances.store.transform(transformer)
351
348
 
352
- def classic_to_core(self) -> None:
353
- """Prepares extracted CDF classic graph for the Core Data model.
354
-
355
- !!! note "This method bundles several graph transformers which"
356
- - Convert relationships to edges
357
- - Convert TimeSeries.type from bool to enum
358
- - Convert all properties 'source' to a connection to SourceSystem
359
- - Convert all properties 'labels' from a connection to a string
360
-
361
- Example:
362
- Apply classic to core transformations:
363
- ```python
364
- neat.prepare.instances.classic_to_core()
365
- ```
366
- """
367
- self.relationships_as_edges()
368
- self.convert_data_type(
369
- ("TimeSeries", "isString"), convert=lambda is_string: "string" if is_string else "numeric"
370
- )
371
- self.property_to_type((None, "source"), "SourceSystem", "name")
372
- for type_ in [
373
- extractors.EventsExtractor._default_rdf_type,
374
- extractors.AssetsExtractor._default_rdf_type,
375
- extractors.FilesExtractor._default_rdf_type,
376
- ]:
377
- try:
378
- subject_type, subject_predicate = self._get_type_and_property_uris(type_, "labels")
379
- except NeatValueError:
380
- # If the type_.labels does not exist, continue. This is not an error, it just means that the
381
- # Labels is not used in the graph for that type.
382
- continue
383
- else:
384
- transformer = ConnectionToLiteral(subject_type, subject_predicate)
385
- self._state.instances.store.transform(transformer)
386
-
387
349
 
388
350
  @session_class_wrapper
389
351
  class DataModelPrepareAPI:
@@ -391,8 +353,7 @@ class DataModelPrepareAPI:
391
353
  to a desired destination.
392
354
  """
393
355
 
394
- def __init__(self, client: NeatClient | None, state: SessionState, verbose: bool) -> None:
395
- self._client = client
356
+ def __init__(self, state: SessionState, verbose: bool) -> None:
396
357
  self._state = state
397
358
  self._verbose = verbose
398
359
 
@@ -507,14 +468,15 @@ class DataModelPrepareAPI:
507
468
  self._state.rule_store.last_verified_dms_rules
508
469
  ).imported_views_and_containers_ids()
509
470
  transformers: list[RulesTransformer] = []
510
- if (view_ids or container_ids) and self._client is None:
471
+ client = self._state.client
472
+ if (view_ids or container_ids) and client is None:
511
473
  raise NeatSessionError(
512
474
  "No client provided. You are referencing unknown views and containers in your data model, "
513
475
  "NEAT needs a client to lookup the definitions. "
514
476
  "Please set the client in the session, NeatSession(client=client)."
515
477
  )
516
- elif (view_ids or container_ids) and self._client:
517
- transformers.append(IncludeReferenced(self._client, include_properties=True))
478
+ elif (view_ids or container_ids) and client:
479
+ transformers.append(IncludeReferenced(client, include_properties=True))
518
480
 
519
481
  transformers.append(
520
482
  ToDataProductModel(
@@ -539,20 +501,10 @@ class DataModelPrepareAPI:
539
501
 
540
502
  def include_referenced(self) -> IssueList:
541
503
  """Include referenced views and containers in the data model."""
542
- if self._client is None:
504
+ if self._state.client is None:
543
505
  raise NeatSessionError(
544
506
  "No client provided. You are referencing unknown views and containers in your data model, "
545
507
  "NEAT needs a client to lookup the definitions. "
546
508
  "Please set the client in the session, NeatSession(client=client)."
547
509
  )
548
- return self._state.rule_transform(IncludeReferenced(self._client))
549
-
550
- def add_implements_to_classes(self, suffix: Literal["Edge"], implements: str = "Edge") -> IssueList:
551
- """All classes with the suffix will have the implements property set to the given value.
552
-
553
- Args:
554
- suffix: The suffix of the classes to add the implements property to.
555
- implements: The value of the implements property to set.
556
-
557
- """
558
- return self._state.rule_transform(AddClassImplements(implements, suffix))
510
+ return self._state.rule_transform(IncludeReferenced(self._state.client))
@@ -1,15 +1,19 @@
1
1
  from typing import Any, Literal, cast
2
2
 
3
3
  from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier
4
+ from cognite.client.utils.useful_types import SequenceNotStr
4
5
 
5
6
  from cognite.neat._client import NeatClient
7
+ from cognite.neat._constants import CLASSIC_CDF_NAMESPACE
6
8
  from cognite.neat._graph import examples as instances_examples
7
9
  from cognite.neat._graph import extractors
10
+ from cognite.neat._graph.transformers import ConvertLiteral, LiteralToEntity, LookupRelationshipSourceTarget
8
11
  from cognite.neat._issues import IssueList
9
12
  from cognite.neat._issues.errors import NeatValueError
10
13
  from cognite.neat._issues.warnings import MissingCogniteClientWarning
11
14
  from cognite.neat._rules import catalog, importers
12
15
  from cognite.neat._rules.importers import BaseImporter
16
+ from cognite.neat._rules.transformers import ClassicPrepareCore
13
17
  from cognite.neat._utils.reader import NeatReader
14
18
 
15
19
  from ._state import SessionState
@@ -22,23 +26,38 @@ from .exceptions import NeatSessionError, session_class_wrapper
22
26
  class ReadAPI:
23
27
  """Read from a data source into NeatSession graph store."""
24
28
 
25
- def __init__(self, state: SessionState, client: NeatClient | None, verbose: bool) -> None:
29
+ def __init__(self, state: SessionState, verbose: bool) -> None:
26
30
  self._state = state
27
31
  self._verbose = verbose
28
- self.cdf = CDFReadAPI(state, client, verbose)
29
- self.rdf = RDFReadAPI(state, client, verbose)
30
- self.excel = ExcelReadAPI(state, client, verbose)
31
- self.csv = CSVReadAPI(state, client, verbose)
32
- self.yaml = YamlReadAPI(state, client, verbose)
33
- self.xml = XMLReadAPI(state, client, verbose)
32
+ self.cdf = CDFReadAPI(state, verbose)
33
+ self.rdf = RDFReadAPI(state, verbose)
34
+ self.excel = ExcelReadAPI(state, verbose)
35
+ self.csv = CSVReadAPI(state, verbose)
36
+ self.yaml = YamlReadAPI(state, verbose)
37
+ self.xml = XMLReadAPI(state, verbose)
38
+
39
+ def session(self, io: Any) -> None:
40
+ """Reads a Neat Session from a zip file.
41
+
42
+ Args:
43
+ io: file path to the Neat Session
44
+
45
+ Example:
46
+ ```python
47
+ neat.read.session("path_to_neat_session")
48
+ ```
49
+ """
50
+ reader = NeatReader.create(io)
51
+ path = reader.materialize_path()
52
+
53
+ self._state.instances.store.write(extractors.RdfFileExtractor.from_zip(path))
34
54
 
35
55
 
36
56
  @session_class_wrapper
37
57
  class BaseReadAPI:
38
- def __init__(self, state: SessionState, client: NeatClient | None, verbose: bool) -> None:
58
+ def __init__(self, state: SessionState, verbose: bool) -> None:
39
59
  self._state = state
40
60
  self._verbose = verbose
41
- self._client = client
42
61
 
43
62
 
44
63
  @session_class_wrapper
@@ -48,15 +67,15 @@ class CDFReadAPI(BaseReadAPI):
48
67
 
49
68
  """
50
69
 
51
- def __init__(self, state: SessionState, client: NeatClient | None, verbose: bool) -> None:
52
- super().__init__(state, client, verbose)
53
- self.classic = CDFClassicAPI(state, client, verbose)
70
+ def __init__(self, state: SessionState, verbose: bool) -> None:
71
+ super().__init__(state, verbose)
72
+ self.classic = CDFClassicAPI(state, verbose)
54
73
 
55
74
  @property
56
75
  def _get_client(self) -> NeatClient:
57
- if self._client is None:
76
+ if self._state.client is None:
58
77
  raise NeatValueError("No client provided. Please provide a client to read a data model.")
59
- return self._client
78
+ return self._state.client
60
79
 
61
80
  def data_model(self, data_model_id: DataModelIdentifier) -> IssueList:
62
81
  """Reads a Data Model from CDF to the knowledge graph.
@@ -79,6 +98,24 @@ class CDFReadAPI(BaseReadAPI):
79
98
  importer = importers.DMSImporter.from_data_model_id(self._get_client, data_model_id)
80
99
  return self._state.rule_import(importer)
81
100
 
101
+ def graph(
102
+ self, data_model_id: DataModelIdentifier, instance_space: str | SequenceNotStr[str] | None = None
103
+ ) -> IssueList:
104
+ """Reads a knowledge graph from Cognite Data Fusion (CDF).
105
+
106
+ Args:
107
+ data_model_id: Tuple of strings with the id of a CDF Data Model.
108
+ instance_space: The instance spaces to extract. If None, all instance spaces are extracted.
109
+
110
+ Returns:
111
+ IssueList: A list of issues that occurred during the extraction.
112
+
113
+ """
114
+ extractor = extractors.DMSGraphExtractor.from_data_model_id(
115
+ data_model_id, self._get_client, instance_space=instance_space
116
+ )
117
+ return self._state.write_graph(extractor)
118
+
82
119
 
83
120
  @session_class_wrapper
84
121
  class CDFClassicAPI(BaseReadAPI):
@@ -89,11 +126,11 @@ class CDFClassicAPI(BaseReadAPI):
89
126
 
90
127
  @property
91
128
  def _get_client(self) -> NeatClient:
92
- if self._client is None:
129
+ if self._state.client is None:
93
130
  raise ValueError("No client provided. Please provide a client to read a data model.")
94
- return self._client
131
+ return self._state.client
95
132
 
96
- def graph(self, root_asset_external_id: str, limit_per_type: int | None = None) -> None:
133
+ def graph(self, root_asset_external_id: str, limit_per_type: int | None = None) -> IssueList:
97
134
  """Reads the classic knowledge graph from CDF.
98
135
 
99
136
  The Classic Graph consists of the following core resource type.
@@ -129,14 +166,45 @@ class CDFClassicAPI(BaseReadAPI):
129
166
  root_asset_external_id: The external id of the root asset
130
167
  limit_per_type: The maximum number of nodes to extract per core node type. If None, all nodes are extracted.
131
168
 
169
+ Returns:
170
+ IssueList: A list of issues that occurred during the extraction.
171
+
172
+ Example:
173
+ ```python
174
+ neat.read.cdf.graph("root_asset_external_id")
175
+ ```
132
176
  """
177
+ namespace = CLASSIC_CDF_NAMESPACE
133
178
  extractor = extractors.ClassicGraphExtractor(
134
- self._get_client, root_asset_external_id=root_asset_external_id, limit_per_type=limit_per_type
179
+ self._get_client,
180
+ root_asset_external_id=root_asset_external_id,
181
+ limit_per_type=limit_per_type,
182
+ namespace=namespace,
183
+ prefix="Classic",
135
184
  )
136
-
137
- self._state.instances.store.write(extractor)
138
- if self._verbose:
139
- print(f"Classic Graph {root_asset_external_id} read successfully")
185
+ issues = self._state.write_graph(extractor)
186
+ issues.action = "Read Classic Graph"
187
+ if issues:
188
+ print("Use the .inspect.issues() for more details")
189
+
190
+ # Converting the instances from classic to core
191
+ self._state.instances.store.transform(LookupRelationshipSourceTarget(namespace, "Classic"))
192
+ self._state.instances.store.transform(
193
+ ConvertLiteral(
194
+ namespace["ClassicTimeSeries"],
195
+ namespace["isString"],
196
+ lambda is_string: "string" if is_string else "numeric",
197
+ )
198
+ )
199
+ self._state.instances.store.transform(
200
+ LiteralToEntity(None, namespace["source"], "ClassicSourceSystem", "name"),
201
+ )
202
+ # Updating the information model.
203
+ self._state.rule_store.transform(ClassicPrepareCore(namespace))
204
+ # Update the instance store with the latest rules
205
+ information_rules = self._state.rule_store.last_verified_information_rules
206
+ self._state.instances.store.rules = information_rules
207
+ return issues
140
208
 
141
209
 
142
210
  @session_class_wrapper
@@ -153,9 +221,9 @@ class ExcelReadAPI(BaseReadAPI):
153
221
  ```
154
222
  """
155
223
 
156
- def __init__(self, state: SessionState, client: NeatClient | None, verbose: bool) -> None:
157
- super().__init__(state, client, verbose)
158
- self.examples = ExcelExampleAPI(state, client, verbose)
224
+ def __init__(self, state: SessionState, verbose: bool) -> None:
225
+ super().__init__(state, verbose)
226
+ self.examples = ExcelExampleAPI(state, verbose)
159
227
 
160
228
  def __call__(self, io: Any) -> IssueList:
161
229
  """Reads a Neat Excel Rules sheet to the graph store. The rules sheet may stem from an Information architect,
@@ -201,7 +269,7 @@ class YamlReadAPI(BaseReadAPI):
201
269
  if format == "neat":
202
270
  importer = importers.YAMLImporter.from_file(path, source_name=f"{reader!s}")
203
271
  elif format == "toolkit":
204
- dms_importer = importers.DMSImporter.from_path(path, self._client)
272
+ dms_importer = importers.DMSImporter.from_path(path, self._state.client)
205
273
  if dms_importer.issue_list.has_warning_type(MissingCogniteClientWarning):
206
274
  raise NeatSessionError(
207
275
  "No client provided. You are referencing Cognite containers in your data model, "
@@ -316,8 +384,8 @@ class RDFReadAPI(BaseReadAPI):
316
384
  io: file path or url to the RDF source
317
385
  """
318
386
 
319
- def __init__(self, state: SessionState, client: NeatClient | None, verbose: bool) -> None:
320
- super().__init__(state, client, verbose)
387
+ def __init__(self, state: SessionState, verbose: bool) -> None:
388
+ super().__init__(state, verbose)
321
389
  self.examples = RDFExamples(state)
322
390
 
323
391
  def ontology(self, io: Any) -> IssueList:
@@ -387,7 +455,6 @@ class RDFExamples:
387
455
  def __init__(self, state: SessionState) -> None:
388
456
  self._state = state
389
457
 
390
- @property
391
458
  def nordic44(self) -> IssueList:
392
459
  """Reads the Nordic 44 knowledge graph into the NeatSession graph store."""
393
460
  self._state.instances.store.write(extractors.RdfFileExtractor(instances_examples.nordic44_knowledge_graph))
@@ -1,5 +1,7 @@
1
+ from cognite.client import CogniteClient
1
2
  from cognite.client import data_modeling as dm
2
3
 
4
+ from cognite.neat._client import NeatClient
3
5
  from cognite.neat._constants import COGNITE_MODELS
4
6
  from cognite.neat._issues import IssueList
5
7
  from cognite.neat._rules.models import DMSRules
@@ -35,3 +37,10 @@ class SetAPI:
35
37
  " due to temporarily issue with the reverse direct relation interpretation"
36
38
  )
37
39
  return self._state.rule_transform(SetIDDMSModel(new_model_id))
40
+
41
+ def client(self, client: CogniteClient) -> None:
42
+ """Sets the client to be used in the session."""
43
+ self._state.client = NeatClient(client)
44
+ if self._verbose:
45
+ print(f"Client set to {self._state.client.config.project} CDF project.")
46
+ return None
@@ -1,6 +1,8 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import Literal, cast
3
3
 
4
+ from cognite.neat._client import NeatClient
5
+ from cognite.neat._graph.extractors import KnowledgeGraphExtractor
4
6
  from cognite.neat._issues import IssueList
5
7
  from cognite.neat._rules.importers import BaseImporter, InferenceImporter
6
8
  from cognite.neat._rules.models import DMSRules, InformationRules
@@ -15,10 +17,11 @@ from .exceptions import NeatSessionError
15
17
 
16
18
 
17
19
  class SessionState:
18
- def __init__(self, store_type: Literal["memory", "oxigraph"]) -> None:
20
+ def __init__(self, store_type: Literal["memory", "oxigraph"], client: NeatClient | None = None) -> None:
19
21
  self.instances = InstancesState(store_type)
20
22
  self.rule_store = NeatRulesStore()
21
23
  self.last_reference: DMSRules | InformationRules | None = None
24
+ self.client = client
22
25
 
23
26
  def rule_transform(self, *transformer: RulesTransformer) -> IssueList:
24
27
  if not transformer:
@@ -60,6 +63,12 @@ class SessionState:
60
63
  issues.hint = "Use the .inspect.issues() for more details."
61
64
  return issues
62
65
 
66
+ def write_graph(self, extractor: KnowledgeGraphExtractor) -> IssueList:
67
+ self.instances.store.write(extractor)
68
+ issue_list = self.rule_store.import_graph(extractor)
69
+ self.instances.store.add_rules(self.rule_store.last_verified_information_rules)
70
+ return issue_list
71
+
63
72
 
64
73
  @dataclass
65
74
  class InstancesState:
@@ -1,10 +1,11 @@
1
+ import warnings
2
+ import zipfile
1
3
  from collections.abc import Collection
2
4
  from pathlib import Path
3
5
  from typing import Any, Literal, overload
4
6
 
5
7
  from cognite.client.data_classes.data_modeling import SpaceApply
6
8
 
7
- from cognite.neat._client import NeatClient
8
9
  from cognite.neat._constants import COGNITE_MODELS
9
10
  from cognite.neat._graph import loaders
10
11
  from cognite.neat._rules import exporters
@@ -25,10 +26,10 @@ class ToAPI:
25
26
 
26
27
  """
27
28
 
28
- def __init__(self, state: SessionState, client: NeatClient | None, verbose: bool) -> None:
29
+ def __init__(self, state: SessionState, verbose: bool) -> None:
29
30
  self._state = state
30
31
  self._verbose = verbose
31
- self.cdf = CDFToAPI(state, client, verbose)
32
+ self.cdf = CDFToAPI(state, verbose)
32
33
 
33
34
  def excel(
34
35
  self,
@@ -81,11 +82,47 @@ class ToAPI:
81
82
  exporter = exporters.ExcelExporter(styling="maximal", reference_rules_with_prefix=reference_rules_with_prefix)
82
83
  return self._state.rule_store.export_to_file(exporter, Path(io))
83
84
 
85
+ def session(self, io: Any) -> None:
86
+ """Export the current session to a file.
87
+
88
+ Args:
89
+ io: The file path to file-like object to write the session to.
90
+
91
+ Example:
92
+ Export the session to a file
93
+ ```python
94
+ session_file_name = "neat_session.zip"
95
+ neat.to.session(session_file_name)
96
+ ```
97
+ """
98
+ filepath = Path(io)
99
+ if filepath.suffix not in {".zip"}:
100
+ warnings.warn("File extension is not .zip, adding it to the file name", stacklevel=2)
101
+ filepath = filepath.with_suffix(".zip")
102
+
103
+ filepath.parent.mkdir(exist_ok=True, parents=True)
104
+
105
+ with zipfile.ZipFile(filepath, "w") as zip_ref:
106
+ zip_ref.writestr(
107
+ "neat-session/instances/instances.ttl",
108
+ self._state.instances.store.serialize(),
109
+ )
110
+
84
111
  @overload
85
- def yaml(self, io: None, format: Literal["neat"] = "neat", skip_system_spaces: bool = True) -> str: ...
112
+ def yaml(
113
+ self,
114
+ io: None,
115
+ format: Literal["neat"] = "neat",
116
+ skip_system_spaces: bool = True,
117
+ ) -> str: ...
86
118
 
87
119
  @overload
88
- def yaml(self, io: Any, format: Literal["neat", "toolkit"] = "neat", skip_system_spaces: bool = True) -> None: ...
120
+ def yaml(
121
+ self,
122
+ io: Any,
123
+ format: Literal["neat", "toolkit"] = "neat",
124
+ skip_system_spaces: bool = True,
125
+ ) -> None: ...
89
126
 
90
127
  def yaml(
91
128
  self, io: Any | None = None, format: Literal["neat", "toolkit"] = "neat", skip_system_spaces: bool = True
@@ -153,8 +190,7 @@ class ToAPI:
153
190
  class CDFToAPI:
154
191
  """Write a verified Data Model and Instances to CDF."""
155
192
 
156
- def __init__(self, state: SessionState, client: NeatClient | None, verbose: bool) -> None:
157
- self._client = client
193
+ def __init__(self, state: SessionState, verbose: bool) -> None:
158
194
  self._state = state
159
195
  self._verbose = verbose
160
196
 
@@ -166,9 +202,9 @@ class CDFToAPI:
166
202
  Note this space is required to be different than the space with the data model.
167
203
 
168
204
  """
169
- if not self._client:
205
+ if not self._state.client:
170
206
  raise NeatSessionError("No CDF client provided!")
171
-
207
+ client = self._state.client
172
208
  space = space or f"{self._state.rule_store.last_verified_dms_rules.metadata.space}_instances"
173
209
 
174
210
  if space and space == self._state.rule_store.last_verified_dms_rules.metadata.space:
@@ -176,16 +212,16 @@ class CDFToAPI:
176
212
  elif not PATTERNS.space_compliance.match(str(space)):
177
213
  raise NeatSessionError("Please provide a valid space name. {PATTERNS.space_compliance.pattern}")
178
214
 
179
- if not self._client.data_modeling.spaces.retrieve(space):
180
- self._client.data_modeling.spaces.apply(SpaceApply(space=space))
215
+ if not client.data_modeling.spaces.retrieve(space):
216
+ client.data_modeling.spaces.apply(SpaceApply(space=space))
181
217
 
182
218
  loader = loaders.DMSLoader.from_rules(
183
219
  self._state.rule_store.last_verified_dms_rules,
184
220
  self._state.instances.store,
185
221
  instance_space=space,
186
- client=self._client,
222
+ client=client,
187
223
  )
188
- result = loader.load_into_cdf(self._client)
224
+ result = loader.load_into_cdf(client)
189
225
  self._state.instances.outcome.append(result)
190
226
  print("You can inspect the details with the .inspect.outcome.instances(...) method.")
191
227
  return result
@@ -219,9 +255,9 @@ class CDFToAPI:
219
255
 
220
256
  exporter = exporters.DMSExporter(existing=existing, export_components=components, drop_data=drop_data)
221
257
 
222
- if not self._client:
258
+ if not self._state.client:
223
259
  raise NeatSessionError("No client provided!")
224
260
 
225
- result = self._state.rule_store.export_to_cdf(exporter, self._client, dry_run)
261
+ result = self._state.rule_store.export_to_cdf(exporter, self._state.client, dry_run)
226
262
  print("You can inspect the details with the .inspect.outcome.data_model(...) method.")
227
263
  return result
@@ -2,17 +2,21 @@ import functools
2
2
  from collections.abc import Callable
3
3
  from typing import Any
4
4
 
5
- from cognite.neat._issues.errors import CDFMissingClientError
5
+ from cognite.neat._issues.errors import CDFMissingClientError, NeatImportError
6
6
 
7
7
  from ._collector import _COLLECTOR
8
8
 
9
9
  try:
10
10
  from rich import print
11
+ from rich.markup import escape
11
12
 
12
13
  _PREFIX = "[bold red][ERROR][/bold red]"
13
14
  except ImportError:
14
15
  _PREFIX = "[ERROR]"
15
16
 
17
+ def escape(x: Any, *_: Any, **__: Any) -> Any: # type: ignore[misc]
18
+ return x
19
+
16
20
 
17
21
  class NeatSessionError(Exception):
18
22
  """Base class for all exceptions raised by the NeatSession class."""
@@ -29,8 +33,8 @@ def _session_method_wrapper(func: Callable, cls_name: str):
29
33
  except NeatSessionError as e:
30
34
  action = _get_action()
31
35
  print(f"{_PREFIX} Cannot {action}: {e}")
32
- except CDFMissingClientError as e:
33
- print(f"{_PREFIX} {e.as_message()}")
36
+ except (CDFMissingClientError, NeatImportError) as e:
37
+ print(f"{_PREFIX} {escape(e.as_message())}")
34
38
  except ModuleNotFoundError as e:
35
39
  if e.name == "neatengine":
36
40
  action = _get_action()