cognite-neat 0.84.1__py3-none-any.whl → 0.85.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.
Files changed (37) hide show
  1. cognite/neat/__init__.py +2 -1
  2. cognite/neat/_shared.py +17 -1
  3. cognite/neat/_version.py +1 -1
  4. cognite/neat/graph/extractors/__init__.py +22 -0
  5. cognite/neat/graph/extractors/_base.py +5 -0
  6. cognite/neat/graph/extractors/_classic_cdf/_assets.py +7 -0
  7. cognite/neat/graph/extractors/_classic_cdf/_events.py +7 -0
  8. cognite/neat/graph/extractors/_classic_cdf/_labels.py +7 -0
  9. cognite/neat/graph/extractors/_classic_cdf/_relationships.py +7 -0
  10. cognite/neat/graph/extractors/_classic_cdf/_sequences.py +7 -0
  11. cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +7 -0
  12. cognite/neat/graph/extractors/_dexpi.py +1 -1
  13. cognite/neat/graph/extractors/_rdf_file.py +8 -0
  14. cognite/neat/graph/loaders/__init__.py +19 -0
  15. cognite/neat/graph/loaders/_base.py +22 -45
  16. cognite/neat/graph/loaders/_rdf2dms.py +21 -9
  17. cognite/neat/graph/stores/_base.py +10 -0
  18. cognite/neat/graph/stores/_provenance.py +11 -3
  19. cognite/neat/rules/exporters/__init__.py +23 -1
  20. cognite/neat/rules/exporters/_base.py +5 -0
  21. cognite/neat/rules/exporters/_rules2dms.py +1 -1
  22. cognite/neat/rules/exporters/_rules2ontology.py +6 -0
  23. cognite/neat/rules/importers/__init__.py +20 -0
  24. cognite/neat/rules/importers/_base.py +5 -0
  25. cognite/neat/rules/importers/_dms2rules.py +10 -0
  26. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +1 -1
  27. cognite/neat/rules/importers/_inference2rules.py +5 -1
  28. cognite/neat/rules/importers/_spreadsheet2rules.py +17 -0
  29. cognite/neat/utils/auth.py +298 -0
  30. cognite/neat/utils/auxiliary.py +24 -0
  31. cognite/neat/utils/upload.py +5 -42
  32. {cognite_neat-0.84.1.dist-info → cognite_neat-0.85.0.dist-info}/METADATA +3 -1
  33. {cognite_neat-0.84.1.dist-info → cognite_neat-0.85.0.dist-info}/RECORD +37 -36
  34. /cognite/neat/utils/{xml.py → xml_.py} +0 -0
  35. {cognite_neat-0.84.1.dist-info → cognite_neat-0.85.0.dist-info}/LICENSE +0 -0
  36. {cognite_neat-0.84.1.dist-info → cognite_neat-0.85.0.dist-info}/WHEEL +0 -0
  37. {cognite_neat-0.84.1.dist-info → cognite_neat-0.85.0.dist-info}/entry_points.txt +0 -0
cognite/neat/__init__.py CHANGED
@@ -1,3 +1,4 @@
1
1
  from ._version import __version__
2
+ from .utils.auth import get_cognite_client
2
3
 
3
- __all__ = ["__version__"]
4
+ __all__ = ["__version__", "get_cognite_client"]
cognite/neat/_shared.py CHANGED
@@ -17,8 +17,24 @@ class NeatObject:
17
17
  """Return a dictionary representation of the object."""
18
18
  raise NotImplementedError()
19
19
 
20
+ def _repr_html_(self) -> str:
21
+ return pd.Series(self.dump(aggregate=True)).to_frame(name="value")._repr_html_()
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class FrozenNeatObject:
26
+ """A frozen neat object can be dumped to a dictionary."""
27
+
28
+ @abstractmethod
29
+ def dump(self, aggregate: bool = True) -> dict[str, Any]:
30
+ """Return a dictionary representation of the object."""
31
+ raise NotImplementedError()
32
+
33
+ def _repr_html_(self) -> str:
34
+ return pd.Series(self.dump(aggregate=True)).to_frame(name="value")._repr_html_()
35
+
20
36
 
21
- T_NeatObject = TypeVar("T_NeatObject", bound=NeatObject)
37
+ T_NeatObject = TypeVar("T_NeatObject", bound=NeatObject | FrozenNeatObject)
22
38
 
23
39
 
24
40
  class NeatList(list, Sequence[T_NeatObject]):
cognite/neat/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.84.1"
1
+ __version__ = "0.85.0"
@@ -1,3 +1,4 @@
1
+ from ._base import BaseExtractor
1
2
  from ._classic_cdf._assets import AssetsExtractor
2
3
  from ._classic_cdf._events import EventsExtractor
3
4
  from ._classic_cdf._files import FilesExtractor
@@ -10,6 +11,7 @@ from ._mock_graph_generator import MockGraphGenerator
10
11
  from ._rdf_file import RdfFileExtractor
11
12
 
12
13
  __all__ = [
14
+ "BaseExtractor",
13
15
  "AssetsExtractor",
14
16
  "MockGraphGenerator",
15
17
  "RelationshipsExtractor",
@@ -35,3 +37,23 @@ TripleExtractors = (
35
37
  | RdfFileExtractor
36
38
  | DexpiExtractor
37
39
  )
40
+
41
+
42
+ def _repr_html_() -> str:
43
+ import pandas as pd
44
+
45
+ table = pd.DataFrame( # type: ignore[operator]
46
+ [
47
+ {
48
+ "Extractor": name,
49
+ "Description": globals()[name].__doc__.strip().split("\n")[0] if globals()[name].__doc__ else "Missing",
50
+ }
51
+ for name in __all__
52
+ if name != "BaseExtractor"
53
+ ]
54
+ )._repr_html_()
55
+
56
+ return (
57
+ "<strong>Extractor</strong> An extractor is used to read data from "
58
+ f"a source into Neat's internal triple storage. <br />{table}"
59
+ )
@@ -2,6 +2,7 @@ from abc import abstractmethod
2
2
  from collections.abc import Iterable
3
3
 
4
4
  from cognite.neat.graph.models import Triple
5
+ from cognite.neat.utils.auxiliary import class_html_doc
5
6
 
6
7
 
7
8
  class BaseExtractor:
@@ -12,3 +13,7 @@ class BaseExtractor:
12
13
  @abstractmethod
13
14
  def extract(self) -> Iterable[Triple]:
14
15
  raise NotImplementedError()
16
+
17
+ @classmethod
18
+ def _repr_html_(cls) -> str:
19
+ return class_html_doc(cls)
@@ -14,6 +14,13 @@ from cognite.neat.utils.utils import create_sha256_hash, string_to_ideal_type
14
14
 
15
15
 
16
16
  class AssetsExtractor(BaseExtractor):
17
+ """Extract data from Cognite Data Fusions Assets into Neat.
18
+
19
+ Args:
20
+ assets (Iterable[Asset]): An iterable of assets.
21
+ namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
22
+ """
23
+
17
24
  def __init__(
18
25
  self,
19
26
  assets: Iterable[Asset],
@@ -15,6 +15,13 @@ from cognite.neat.utils.utils import string_to_ideal_type
15
15
 
16
16
 
17
17
  class EventsExtractor(BaseExtractor):
18
+ """Extract data from Cognite Data Fusions Events into Neat.
19
+
20
+ Args:
21
+ events (Iterable[Event]): An iterable of events.
22
+ namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
23
+ """
24
+
18
25
  def __init__(
19
26
  self,
20
27
  events: Iterable[Event],
@@ -14,6 +14,13 @@ from cognite.neat.utils.utils import create_sha256_hash
14
14
 
15
15
 
16
16
  class LabelsExtractor(BaseExtractor):
17
+ """Extract data from Cognite Data Fusions Labels into Neat.
18
+
19
+ Args:
20
+ labels (Iterable[LabelDefinition]): An iterable of labels.
21
+ namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
22
+ """
23
+
17
24
  def __init__(
18
25
  self,
19
26
  labels: Iterable[LabelDefinition],
@@ -15,6 +15,13 @@ from cognite.neat.utils.utils import create_sha256_hash
15
15
 
16
16
 
17
17
  class RelationshipsExtractor(BaseExtractor):
18
+ """Extract data from Cognite Data Fusions Relationships into Neat.
19
+
20
+ Args:
21
+ relationships (Iterable[Asset]): An iterable of relationships.
22
+ namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
23
+ """
24
+
18
25
  def __init__(
19
26
  self,
20
27
  relationships: Iterable[Relationship],
@@ -15,6 +15,13 @@ from cognite.neat.utils.utils import string_to_ideal_type
15
15
 
16
16
 
17
17
  class SequencesExtractor(BaseExtractor):
18
+ """Extract data from Cognite Data Fusions Sequences into Neat.
19
+
20
+ Args:
21
+ sequence (Iterable[Sequence]): An iterable of sequences.
22
+ namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
23
+ """
24
+
18
25
  def __init__(
19
26
  self,
20
27
  sequence: Iterable[Sequence],
@@ -15,6 +15,13 @@ from cognite.neat.utils.utils import string_to_ideal_type
15
15
 
16
16
 
17
17
  class TimeSeriesExtractor(BaseExtractor):
18
+ """Extract data from Cognite Data Fusions TimeSeries into Neat.
19
+
20
+ Args:
21
+ timeseries (Iterable[TimeSeries]): An iterable of timeseries.
22
+ namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
23
+ """
24
+
18
25
  def __init__(
19
26
  self,
20
27
  timeseries: Iterable[TimeSeries],
@@ -10,7 +10,7 @@ from cognite.neat.constants import DEFAULT_NAMESPACE
10
10
  from cognite.neat.graph.extractors._base import BaseExtractor
11
11
  from cognite.neat.graph.models import Triple
12
12
  from cognite.neat.utils.utils import as_neat_compliant_uri
13
- from cognite.neat.utils.xml import get_children, iterate_tree
13
+ from cognite.neat.utils.xml_ import get_children, iterate_tree
14
14
 
15
15
  DEXPI = Namespace("http://sandbox.dexpi.org/rdl/")
16
16
  QUDT = Namespace("https://qudt.org/vocab/unit/")
@@ -7,6 +7,14 @@ from cognite.neat.graph.extractors._base import BaseExtractor
7
7
 
8
8
 
9
9
  class RdfFileExtractor(BaseExtractor):
10
+ """Extract data from RDF files into Neat.
11
+
12
+ Args:
13
+ filepath (Path): The path to the RDF file.
14
+ mime_type (MIMETypes, optional): The MIME type of the RDF file. Defaults to "application/rdf+xml".
15
+ base_uri (URIRef, optional): The base URI to use. Defaults to None.
16
+ """
17
+
10
18
  def __init__(
11
19
  self,
12
20
  filepath: Path,
@@ -2,3 +2,22 @@ from ._base import BaseLoader, CDFLoader
2
2
  from ._rdf2dms import DMSLoader
3
3
 
4
4
  __all__ = ["BaseLoader", "CDFLoader", "DMSLoader"]
5
+
6
+
7
+ def _repr_html_() -> str:
8
+ import pandas as pd
9
+
10
+ table = pd.DataFrame( # type: ignore[operator]
11
+ [
12
+ {
13
+ "Loader": name,
14
+ "Description": globals()[name].__doc__.strip().split("\n")[0] if globals()[name].__doc__ else "Missing",
15
+ }
16
+ for name in __all__
17
+ if name not in ("BaseLoader", "CDFLoader")
18
+ ]
19
+ )._repr_html_()
20
+
21
+ return (
22
+ "<strong>Loader</strong> A loader writes data from Neat's triple storage into a target system" f"<br />{table}"
23
+ )
@@ -1,7 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
- from collections.abc import Iterable
2
+ from collections.abc import Hashable, Iterable
3
3
  from pathlib import Path
4
- from typing import ClassVar, Generic, Literal, TypeVar, overload
4
+ from typing import ClassVar, Generic, TypeVar
5
5
 
6
6
  from cognite.client import CogniteClient
7
7
  from cognite.client.data_classes.capabilities import Capability
@@ -9,7 +9,8 @@ from cognite.client.data_classes.capabilities import Capability
9
9
  from cognite.neat.graph import NeatGraphStore
10
10
  from cognite.neat.graph.issues.loader import FailedAuthorizationError
11
11
  from cognite.neat.issues import NeatIssue, NeatIssueList
12
- from cognite.neat.utils.upload import UploadDiffsID, UploadResultIDs
12
+ from cognite.neat.utils.auxiliary import class_html_doc
13
+ from cognite.neat.utils.upload import UploadResult, UploadResultList
13
14
 
14
15
  T_Output = TypeVar("T_Output")
15
16
 
@@ -34,49 +35,25 @@ class BaseLoader(ABC, Generic[T_Output]):
34
35
  """Load the graph with data."""
35
36
  pass
36
37
 
38
+ @classmethod
39
+ def _repr_html_(cls) -> str:
40
+ return class_html_doc(cls)
41
+
37
42
 
38
43
  class CDFLoader(BaseLoader[T_Output]):
39
44
  _UPLOAD_BATCH_SIZE: ClassVar[int] = 1000
40
45
 
41
- @overload
42
- def load_into_cdf_iterable(
43
- self, client: CogniteClient, return_diffs: Literal[False] = False, dry_run: bool = False
44
- ) -> Iterable[UploadResultIDs]: ...
45
-
46
- @overload
47
- def load_into_cdf_iterable(
48
- self, client: CogniteClient, return_diffs: Literal[True], dry_run: bool = False
49
- ) -> Iterable[UploadDiffsID]: ...
50
-
51
- def load_into_cdf_iterable(
52
- self, client: CogniteClient, return_diffs: bool = False, dry_run: bool = False
53
- ) -> Iterable[UploadResultIDs] | Iterable[UploadDiffsID]:
54
- yield from self._load_into_cdf_iterable(client, return_diffs, dry_run)
55
-
56
- @overload
57
- def load_into_cdf(
58
- self, client: CogniteClient, return_diffs: Literal[False] = False, dry_run: bool = False
59
- ) -> list[UploadResultIDs]: ...
60
-
61
- @overload
62
- def load_into_cdf(
63
- self, client: CogniteClient, return_diffs: Literal[True], dry_run: bool = False
64
- ) -> list[UploadDiffsID]: ...
65
-
66
- def load_into_cdf(
67
- self, client: CogniteClient, return_diffs: bool = False, dry_run: bool = False
68
- ) -> list[UploadResultIDs] | list[UploadDiffsID]:
69
- return list(self._load_into_cdf_iterable(client, return_diffs, dry_run)) # type: ignore[return-value]
70
-
71
- def _load_into_cdf_iterable(
72
- self, client: CogniteClient, return_diffs: bool = False, dry_run: bool = False
73
- ) -> Iterable[UploadResultIDs] | Iterable[UploadDiffsID]:
46
+ def load_into_cdf(self, client: CogniteClient, dry_run: bool = False) -> UploadResultList:
47
+ return UploadResultList(self.load_into_cdf_iterable(client, dry_run))
48
+
49
+ def load_into_cdf_iterable(self, client: CogniteClient, dry_run: bool = False) -> Iterable[UploadResult]:
74
50
  missing_capabilities = client.iam.verify_capabilities(self._get_required_capabilities())
75
- result_cls = UploadDiffsID if return_diffs else UploadResultIDs
76
51
  if missing_capabilities:
77
- result = result_cls(name=type(self).__name__)
78
- result.issues.append(FailedAuthorizationError(action="Upload to CDF", reason=str(missing_capabilities)))
79
- yield result
52
+ upload_result = UploadResult[Hashable](name=type(self).__name__)
53
+ upload_result.issues.append(
54
+ FailedAuthorizationError(action="Upload to CDF", reason=str(missing_capabilities))
55
+ )
56
+ yield upload_result
80
57
  return
81
58
 
82
59
  issues = NeatIssueList[NeatIssue]()
@@ -88,10 +65,11 @@ class CDFLoader(BaseLoader[T_Output]):
88
65
  items.append(result)
89
66
 
90
67
  if len(items) >= self._UPLOAD_BATCH_SIZE:
91
- yield self._upload_to_cdf(client, items, return_diffs, dry_run, issues)
92
- items.clear()
68
+ yield self._upload_to_cdf(client, items, dry_run, issues)
69
+ issues = NeatIssueList[NeatIssue]()
70
+ items = []
93
71
  if items:
94
- yield self._upload_to_cdf(client, items, return_diffs, dry_run, issues)
72
+ yield self._upload_to_cdf(client, items, dry_run, issues)
95
73
 
96
74
  @abstractmethod
97
75
  def _get_required_capabilities(self) -> list[Capability]:
@@ -102,8 +80,7 @@ class CDFLoader(BaseLoader[T_Output]):
102
80
  self,
103
81
  client: CogniteClient,
104
82
  items: list[T_Output],
105
- return_diffs: bool,
106
83
  dry_run: bool,
107
84
  read_issues: NeatIssueList,
108
- ) -> UploadResultIDs | UploadDiffsID:
85
+ ) -> UploadResult:
109
86
  raise NotImplementedError
@@ -10,6 +10,7 @@ from cognite.client import CogniteClient
10
10
  from cognite.client import data_modeling as dm
11
11
  from cognite.client.data_classes.capabilities import Capability, DataModelInstancesAcl
12
12
  from cognite.client.data_classes.data_modeling import ViewId
13
+ from cognite.client.data_classes.data_modeling.ids import InstanceId
13
14
  from cognite.client.data_classes.data_modeling.views import SingleEdgeConnection
14
15
  from cognite.client.exceptions import CogniteAPIError
15
16
  from pydantic import ValidationInfo, create_model, field_validator
@@ -21,13 +22,24 @@ from cognite.neat.graph.stores import NeatGraphStore
21
22
  from cognite.neat.issues import NeatIssue, NeatIssueList
22
23
  from cognite.neat.rules.models import DMSRules
23
24
  from cognite.neat.rules.models.data_types import _DATA_TYPE_BY_DMS_TYPE
24
- from cognite.neat.utils.upload import UploadDiffsID
25
+ from cognite.neat.utils.upload import UploadResult
25
26
  from cognite.neat.utils.utils import create_sha256_hash
26
27
 
27
28
  from ._base import CDFLoader
28
29
 
29
30
 
30
31
  class DMSLoader(CDFLoader[dm.InstanceApply]):
32
+ """Load data from Cognite Data Fusions Data Modeling Service (DMS) into Neat.
33
+
34
+ Args:
35
+ graph_store (NeatGraphStore): The graph store to load the data into.
36
+ data_model (dm.DataModel[dm.View] | None): The data model to load.
37
+ instance_space (str): The instance space to load the data into.
38
+ class_by_view_id (dict[ViewId, str] | None): A mapping from view id to class name. Defaults to None.
39
+ creat_issues (Sequence[NeatIssue] | None): A list of issues that occurred during reading. Defaults to None.
40
+ tracker (type[Tracker] | None): The tracker to use. Defaults to None.
41
+ """
42
+
31
43
  def __init__(
32
44
  self,
33
45
  graph_store: NeatGraphStore,
@@ -243,11 +255,10 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
243
255
  self,
244
256
  client: CogniteClient,
245
257
  items: list[dm.InstanceApply],
246
- return_diffs: bool,
247
258
  dry_run: bool,
248
259
  read_issues: NeatIssueList,
249
- ) -> UploadDiffsID:
250
- result = UploadDiffsID(name=type(self).__name__, issues=read_issues)
260
+ ) -> UploadResult:
261
+ result = UploadResult[InstanceId](name=type(self).__name__, issues=read_issues)
251
262
  try:
252
263
  nodes = [item for item in items if isinstance(item, dm.NodeApply)]
253
264
  edges = [item for item in items if isinstance(item, dm.EdgeApply)]
@@ -260,16 +271,17 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
260
271
  )
261
272
  except CogniteAPIError as e:
262
273
  result.error_messages.append(str(e))
263
- result.failed.append([repr(instance.as_id()) for instance in items]) # type: ignore[arg-type, attr-defined]
274
+ result.failed_upserted.update(item.as_id() for item in e.failed + e.unknown)
275
+ result.created.update(item.as_id() for item in e.successful)
264
276
  else:
265
277
  for instance in itertools.chain(upserted.nodes, upserted.edges):
266
278
  if instance.was_modified and instance.created_time == instance.last_updated_time:
267
- result.created.append(repr(instance.as_id()))
279
+ result.created.add(instance.as_id())
268
280
  elif instance.was_modified:
269
- result.changed.append(repr(instance.as_id()))
281
+ result.changed.add(instance.as_id())
270
282
  else:
271
- result.unchanged.append(repr(instance.as_id()))
272
- return result if return_diffs else result.as_upload_result_ids() # type: ignore[return-value]
283
+ result.unchanged.add(instance.as_id())
284
+ return result
273
285
 
274
286
 
275
287
  def _triples2dictionary(
@@ -282,3 +282,13 @@ class NeatGraphStore:
282
282
  description=transformer.description,
283
283
  )
284
284
  )
285
+
286
+ def _repr_html_(self) -> str:
287
+ provenance = self.provenance._repr_html_()
288
+
289
+ return (
290
+ f"<strong>{type(self).__name__}</strong> A graph store is a container for storing triples. "
291
+ "It can be queried and transformed to extract information.<br />"
292
+ "<strong>Provenance</strong> Provenance is a record of changes that have occurred in the graph store.<br />"
293
+ f"{provenance}"
294
+ )
@@ -8,7 +8,6 @@
8
8
 
9
9
 
10
10
  import uuid
11
- from collections import UserList
12
11
  from collections.abc import Sequence
13
12
  from dataclasses import dataclass
14
13
  from datetime import datetime
@@ -16,6 +15,7 @@ from typing import TypeVar
16
15
 
17
16
  from rdflib import PROV, RDF, Literal, URIRef
18
17
 
18
+ from cognite.neat._shared import FrozenNeatObject, NeatList
19
19
  from cognite.neat.constants import DEFAULT_NAMESPACE
20
20
 
21
21
 
@@ -64,7 +64,7 @@ class Entity:
64
64
 
65
65
 
66
66
  @dataclass(frozen=True)
67
- class Change:
67
+ class Change(FrozenNeatObject):
68
68
  agent: Agent
69
69
  activity: Activity
70
70
  entity: Entity
@@ -81,11 +81,19 @@ class Change:
81
81
  entity = Entity(was_generated_by=activity, was_attributed_to=agent)
82
82
  return cls(agent, activity, entity, description)
83
83
 
84
+ def dump(self, aggregate: bool = True) -> dict[str, str]:
85
+ return {
86
+ "Agent": self.agent.id_,
87
+ "Activity": self.activity.id_,
88
+ "Entity": self.entity.id_,
89
+ "Description": self.description,
90
+ }
91
+
84
92
 
85
93
  T_Change = TypeVar("T_Change", bound=Change)
86
94
 
87
95
 
88
- class Provenance(UserList[T_Change]):
96
+ class Provenance(NeatList[Change]):
89
97
  def __init__(self, changes: Sequence[T_Change] | None = None):
90
98
  super().__init__(changes or [])
91
99
 
@@ -1,9 +1,11 @@
1
- from ._rules2dms import CDFExporter, DMSExporter
1
+ from ._base import BaseExporter, CDFExporter
2
+ from ._rules2dms import DMSExporter
2
3
  from ._rules2excel import ExcelExporter
3
4
  from ._rules2ontology import GraphExporter, OWLExporter, SemanticDataModelExporter, SHACLExporter
4
5
  from ._rules2yaml import YAMLExporter
5
6
 
6
7
  __all__ = [
8
+ "BaseExporter",
7
9
  "DMSExporter",
8
10
  "CDFExporter",
9
11
  "SemanticDataModelExporter",
@@ -13,3 +15,23 @@ __all__ = [
13
15
  "ExcelExporter",
14
16
  "YAMLExporter",
15
17
  ]
18
+
19
+
20
+ def _repr_html_() -> str:
21
+ import pandas as pd
22
+
23
+ table = pd.DataFrame( # type: ignore[operator]
24
+ [
25
+ {
26
+ "Exporter": name,
27
+ "Description": globals()[name].__doc__.strip().split("\n")[0] if globals()[name].__doc__ else "Missing",
28
+ }
29
+ for name in __all__
30
+ if name not in ("BaseExporter", "CDFExporter", "GraphExporter")
31
+ ]
32
+ )._repr_html_()
33
+
34
+ return (
35
+ "<strong>Exporter</strong> An exporter converts Neat's representation of a data model called <em>Rules</em>"
36
+ f" into a schema/data model for a target format.<br />{table}"
37
+ )
@@ -7,6 +7,7 @@ from cognite.client import CogniteClient
7
7
 
8
8
  from cognite.neat.rules._shared import Rules
9
9
  from cognite.neat.rules.models import DMSRules, InformationRules, RoleTypes
10
+ from cognite.neat.utils.auxiliary import class_html_doc
10
11
  from cognite.neat.utils.upload import UploadResult, UploadResultList
11
12
 
12
13
  T_Export = TypeVar("T_Export")
@@ -34,6 +35,10 @@ class BaseExporter(ABC, Generic[T_Export]):
34
35
  else:
35
36
  raise NotImplementedError(f"Role {output_role} is not supported for {type(rules).__name__} rules")
36
37
 
38
+ @classmethod
39
+ def _repr_html_(cls) -> str:
40
+ return class_html_doc(cls, include_factory_methods=False)
41
+
37
42
 
38
43
  class CDFExporter(BaseExporter[T_Export]):
39
44
  @abstractmethod
@@ -39,7 +39,7 @@ Component: TypeAlias = Literal["all", "spaces", "data_models", "views", "contain
39
39
 
40
40
 
41
41
  class DMSExporter(CDFExporter[DMSSchema]):
42
- """Class for exporting rules object to CDF Data Model Storage (DMS).
42
+ """Export rules to Cognite Data Fusion's Data Model Storage (DMS) service.
43
43
 
44
44
  Args:
45
45
  export_components (frozenset[Literal["all", "spaces", "data_models", "views", "containers"]], optional):
@@ -39,16 +39,22 @@ class GraphExporter(BaseExporter[Graph], ABC):
39
39
 
40
40
 
41
41
  class OWLExporter(GraphExporter):
42
+ """Exports rules to an OWL ontology."""
43
+
42
44
  def export(self, rules: Rules) -> Graph:
43
45
  return Ontology.from_rules(rules).as_owl()
44
46
 
45
47
 
46
48
  class SHACLExporter(GraphExporter):
49
+ """Exports rules to a SHACL graph."""
50
+
47
51
  def export(self, rules: Rules) -> Graph:
48
52
  return Ontology.from_rules(rules).as_shacl()
49
53
 
50
54
 
51
55
  class SemanticDataModelExporter(GraphExporter):
56
+ """Exports rules to a semantic data model."""
57
+
52
58
  def export(self, rules: Rules) -> Graph:
53
59
  return Ontology.from_rules(rules).as_semantic_data_model()
54
60
 
@@ -16,3 +16,23 @@ __all__ = [
16
16
  "YAMLImporter",
17
17
  "InferenceImporter",
18
18
  ]
19
+
20
+
21
+ def _repr_html_() -> str:
22
+ import pandas as pd
23
+
24
+ table = pd.DataFrame( # type: ignore[operator]
25
+ [
26
+ {
27
+ "Importer": name,
28
+ "Description": globals()[name].__doc__.strip().split("\n")[0] if globals()[name].__doc__ else "Missing",
29
+ }
30
+ for name in __all__
31
+ if name != "BaseImporter"
32
+ ]
33
+ )._repr_html_()
34
+
35
+ return (
36
+ "<strong>Importer</strong> An importer reads data/schema/data model from a source"
37
+ f" and converts it into Neat's representation of a data model called <em>Rules</em>.<br />{table}"
38
+ )
@@ -12,6 +12,7 @@ from rdflib import Namespace
12
12
  from cognite.neat.rules._shared import Rules
13
13
  from cognite.neat.rules.issues.base import IssueList, NeatValidationError, ValidationWarning
14
14
  from cognite.neat.rules.models import AssetRules, DMSRules, InformationRules, RoleTypes
15
+ from cognite.neat.utils.auxiliary import class_html_doc
15
16
 
16
17
 
17
18
  class BaseImporter(ABC):
@@ -79,6 +80,10 @@ class BaseImporter(ABC):
79
80
  "description": f"Imported using {type(self).__name__}",
80
81
  }
81
82
 
83
+ @classmethod
84
+ def _repr_html_(cls) -> str:
85
+ return class_html_doc(cls)
86
+
82
87
 
83
88
  class _FutureResult:
84
89
  def __init__(self) -> None:
@@ -46,6 +46,16 @@ from cognite.neat.rules.models.entities import (
46
46
 
47
47
 
48
48
  class DMSImporter(BaseImporter):
49
+ """Imports a Data Model from Cognite Data Fusion.
50
+
51
+ Args:
52
+ schema: The schema containing the data model.
53
+ read_issues: A list of issues that occurred during the import.
54
+ metadata: Metadata for the data model.
55
+ ref_metadata: Metadata for the reference data model.
56
+
57
+ """
58
+
49
59
  def __init__(
50
60
  self,
51
61
  schema: DMSSchema,
@@ -18,7 +18,7 @@ from cognite.neat.utils.text import to_pascal
18
18
 
19
19
 
20
20
  class DTDLImporter(BaseImporter):
21
- """Importer for DTDL (Digital Twin Definition Language).
21
+ """Importer from Azure Digital Twin - DTDL (Digital Twin Definition Language).
22
22
 
23
23
  This importer supports DTDL v2.0 and v3.0.
24
24
 
@@ -33,7 +33,11 @@ INSTANCE_PROPERTIES_DEFINITION = """SELECT ?property (count(?property) as ?occur
33
33
 
34
34
 
35
35
  class InferenceImporter(BaseImporter):
36
- """Rules inference through analysis of knowledge graph provided in various formats.
36
+ """Infers rules from a triple store.
37
+
38
+ Rules inference through analysis of knowledge graph provided in various formats.
39
+ Use the factory methods to create an triples store from sources such as
40
+ RDF files, JSON files, YAML files, XML files, or directly from a graph store.
37
41
 
38
42
  Args:
39
43
  issue_list: Issue list to store issues
@@ -205,6 +205,12 @@ class SpreadsheetReader:
205
205
 
206
206
 
207
207
  class ExcelImporter(BaseImporter):
208
+ """Import rules from an Excel file.
209
+
210
+ Args:
211
+ filepath (Path): The path to the Excel file.
212
+ """
213
+
208
214
  def __init__(self, filepath: Path):
209
215
  self.filepath = filepath
210
216
 
@@ -286,6 +292,17 @@ class ExcelImporter(BaseImporter):
286
292
 
287
293
 
288
294
  class GoogleSheetImporter(BaseImporter):
295
+ """Import rules from a Google Sheet.
296
+
297
+ .. warning::
298
+
299
+ This importer is experimental and may not work as expected.
300
+
301
+ Args:
302
+ sheet_id (str): The Google Sheet ID.
303
+ skiprows (int): The number of rows to skip when reading the Google Sheet.
304
+ """
305
+
289
306
  def __init__(self, sheet_id: str, skiprows: int = 1):
290
307
  self.sheet_id = sheet_id
291
308
  self.skiprows = skiprows
@@ -0,0 +1,298 @@
1
+ import os
2
+ import subprocess
3
+ from contextlib import suppress
4
+ from dataclasses import dataclass, fields
5
+ from pathlib import Path
6
+ from typing import Literal, TypeAlias, get_args
7
+
8
+ from cognite.client import CogniteClient
9
+ from cognite.client.credentials import CredentialProvider, OAuthClientCredentials, OAuthInteractive, Token
10
+
11
+ from cognite.neat import _version
12
+ from cognite.neat.utils.auxiliary import local_import
13
+
14
+ __all__ = ["get_cognite_client"]
15
+
16
+ _LOGIN_FLOW: TypeAlias = Literal["infer", "client_credentials", "interactive", "token"]
17
+ _VALID_LOGIN_FLOWS = get_args(_LOGIN_FLOW)
18
+ _CLIENT_NAME = f"CogniteNeat:{_version.__version__}"
19
+
20
+
21
+ def get_cognite_client() -> CogniteClient:
22
+ with suppress(KeyError):
23
+ variables = _EnvironmentVariables.create_from_environ()
24
+ return variables.get_client()
25
+
26
+ repo_root = _repo_root()
27
+ if repo_root:
28
+ with suppress(KeyError, FileNotFoundError, TypeError):
29
+ variables = _from_dotenv(repo_root / ".env")
30
+ client = variables.get_client()
31
+ print("Found .env file in repository root. Loaded variables from .env file.")
32
+ return client
33
+ variables = _prompt_user()
34
+ if repo_root and _env_in_gitignore(repo_root):
35
+ local_import("rich", "jupyter")
36
+ from rich.prompt import Prompt
37
+
38
+ env_file = repo_root / ".env"
39
+ answer = Prompt.ask(
40
+ "Do you store the variables in an .env file in the repository root for easy reuse?", choices=["y", "n"]
41
+ )
42
+ if env_file.exists():
43
+ answer = Prompt.ask(f"{env_file} already exists. Overwrite?", choices=["y", "n"])
44
+ if answer == "y":
45
+ env_file.write_text(variables.create_env_file())
46
+ print("Created .env file in repository root.")
47
+
48
+ return variables.get_client()
49
+
50
+
51
+ @dataclass
52
+ class _EnvironmentVariables:
53
+ CDF_CLUSTER: str
54
+ CDF_PROJECT: str
55
+ LOGIN_FLOW: _LOGIN_FLOW = "infer"
56
+ IDP_CLIENT_ID: str | None = None
57
+ IDP_CLIENT_SECRET: str | None = None
58
+ TOKEN: str | None = None
59
+
60
+ IDP_TENANT_ID: str | None = None
61
+ IDP_TOKEN_URL: str | None = None
62
+
63
+ CDF_URL: str | None = None
64
+ IDP_AUDIENCE: str | None = None
65
+ IDP_SCOPES: str | None = None
66
+ IDP_AUTHORITY_URL: str | None = None
67
+
68
+ def __post_init__(self):
69
+ if self.LOGIN_FLOW.lower() not in _VALID_LOGIN_FLOWS:
70
+ raise ValueError(f"LOGIN_FLOW must be one of {_VALID_LOGIN_FLOWS}")
71
+
72
+ @property
73
+ def cdf_url(self) -> str:
74
+ return self.CDF_URL or f"https://{self.CDF_CLUSTER}.cognitedata.com"
75
+
76
+ @property
77
+ def idp_token_url(self) -> str:
78
+ if self.IDP_TOKEN_URL:
79
+ return self.IDP_TOKEN_URL
80
+ if not self.IDP_TENANT_ID:
81
+ raise KeyError("IDP_TENANT_ID or IDP_TOKEN_URL must be set in the environment.")
82
+ return f"https://login.microsoftonline.com/{self.IDP_TENANT_ID}/oauth2/v2.0/token"
83
+
84
+ @property
85
+ def idp_audience(self) -> str:
86
+ return self.IDP_AUDIENCE or f"https://{self.CDF_CLUSTER}.cognitedata.com"
87
+
88
+ @property
89
+ def idp_scopes(self) -> list[str]:
90
+ if self.IDP_SCOPES:
91
+ return self.IDP_SCOPES.split()
92
+ return [f"https://{self.CDF_CLUSTER}.cognitedata.com/.default"]
93
+
94
+ @property
95
+ def idp_authority_url(self) -> str:
96
+ if self.IDP_AUTHORITY_URL:
97
+ return self.IDP_AUTHORITY_URL
98
+ if not self.IDP_TENANT_ID:
99
+ raise KeyError("IDP_TENANT_ID or IDP_AUTHORITY_URL must be set in the environment.")
100
+ return f"https://login.microsoftonline.com/{self.IDP_TENANT_ID}"
101
+
102
+ @classmethod
103
+ def create_from_environ(cls) -> "_EnvironmentVariables":
104
+ if "CDF_CLUSTER" not in os.environ or "CDF_PROJECT" not in os.environ:
105
+ raise KeyError("CDF_CLUSTER and CDF_PROJECT must be set in the environment.", "CDF_CLUSTER", "CDF_PROJECT")
106
+
107
+ return cls(
108
+ CDF_CLUSTER=os.environ["CDF_CLUSTER"],
109
+ CDF_PROJECT=os.environ["CDF_PROJECT"],
110
+ LOGIN_FLOW=os.environ.get("LOGIN_FLOW", "infer"), # type: ignore[arg-type]
111
+ IDP_CLIENT_ID=os.environ.get("IDP_CLIENT_ID"),
112
+ IDP_CLIENT_SECRET=os.environ.get("IDP_CLIENT_SECRET"),
113
+ TOKEN=os.environ.get("TOKEN"),
114
+ CDF_URL=os.environ.get("CDF_URL"),
115
+ IDP_TOKEN_URL=os.environ.get("IDP_TOKEN_URL"),
116
+ IDP_TENANT_ID=os.environ.get("IDP_TENANT_ID"),
117
+ IDP_AUDIENCE=os.environ.get("IDP_AUDIENCE"),
118
+ IDP_SCOPES=os.environ.get("IDP_SCOPES"),
119
+ IDP_AUTHORITY_URL=os.environ.get("IDP_AUTHORITY_URL"),
120
+ )
121
+
122
+ def get_credentials(self) -> CredentialProvider:
123
+ method_by_flow = {
124
+ "client_credentials": self.get_oauth_client_credentials,
125
+ "interactive": self.get_oauth_interactive,
126
+ "token": self.get_token,
127
+ }
128
+ if self.LOGIN_FLOW in method_by_flow:
129
+ return method_by_flow[self.LOGIN_FLOW]()
130
+ key_options: list[tuple[str, ...]] = []
131
+ for method in method_by_flow.values():
132
+ try:
133
+ return method()
134
+ except KeyError as e:
135
+ key_options += e.args[1:]
136
+ raise KeyError(
137
+ f"LOGIN_FLOW={self.LOGIN_FLOW} requires one of the following environment set variables to be set.",
138
+ *key_options,
139
+ )
140
+
141
+ def get_oauth_client_credentials(self) -> OAuthClientCredentials:
142
+ if not self.IDP_CLIENT_ID or not self.IDP_CLIENT_SECRET:
143
+ raise KeyError(
144
+ "IDP_CLIENT_ID and IDP_CLIENT_SECRET must be set in the environment.",
145
+ "IDP_CLIENT_ID",
146
+ "IDP_CLIENT_SECRET",
147
+ )
148
+ return OAuthClientCredentials(
149
+ client_id=self.IDP_CLIENT_ID,
150
+ client_secret=self.IDP_CLIENT_SECRET,
151
+ token_url=self.idp_token_url,
152
+ audience=self.idp_audience,
153
+ scopes=self.idp_scopes,
154
+ )
155
+
156
+ def get_oauth_interactive(self) -> OAuthInteractive:
157
+ if not self.IDP_CLIENT_ID:
158
+ raise KeyError("IDP_CLIENT_ID must be set in the environment.", "IDP_CLIENT_ID")
159
+ return OAuthInteractive(
160
+ client_id=self.IDP_CLIENT_ID,
161
+ authority_url=self.idp_authority_url,
162
+ redirect_port=53_000,
163
+ scopes=self.idp_scopes,
164
+ )
165
+
166
+ def get_token(self) -> Token:
167
+ if not self.TOKEN:
168
+ raise KeyError("TOKEN must be set in the environment", "TOKEN")
169
+ return Token(self.TOKEN)
170
+
171
+ def get_client(self) -> CogniteClient:
172
+ return CogniteClient.default(
173
+ self.CDF_PROJECT, self.CDF_CLUSTER, credentials=self.get_credentials(), client_name=_CLIENT_NAME
174
+ )
175
+
176
+ def create_env_file(self) -> str:
177
+ lines: list[str] = []
178
+ first_optional = True
179
+ for field in fields(self):
180
+ is_optional = hasattr(self, field.name.lower())
181
+ if is_optional and first_optional:
182
+ lines.append(
183
+ "# The below variables are the defaults, they are automatically " "constructed unless they are set."
184
+ )
185
+ first_optional = False
186
+ name = field.name.lower() if is_optional else field.name
187
+ value = getattr(self, name)
188
+ if value is not None:
189
+ if isinstance(value, list):
190
+ value = " ".join(value)
191
+ lines.append(f"{field.name}={value}")
192
+ return "\n".join(lines)
193
+
194
+
195
+ def _from_dotenv(evn_file: Path) -> _EnvironmentVariables:
196
+ if not evn_file.exists():
197
+ raise FileNotFoundError(f"{evn_file} does not exist.")
198
+ content = evn_file.read_text()
199
+ valid_variables = {f.name for f in fields(_EnvironmentVariables)}
200
+ variables: dict[str, str] = {}
201
+ for line in content.splitlines():
202
+ if line.startswith("#") or not line:
203
+ continue
204
+ key, value = line.split("=", 1)
205
+ if key in valid_variables:
206
+ variables[key] = value
207
+ return _EnvironmentVariables(**variables) # type: ignore[arg-type]
208
+
209
+
210
+ def _prompt_user() -> _EnvironmentVariables:
211
+ local_import("rich", "jupyter")
212
+ from rich.prompt import Prompt
213
+
214
+ try:
215
+ variables = _EnvironmentVariables.create_from_environ()
216
+ continue_ = Prompt.ask(
217
+ f"Use environment variables for CDF Cluster '{variables.CDF_CLUSTER}' "
218
+ f"and Project '{variables.CDF_PROJECT}'? [y/n]",
219
+ choices=["y", "n"],
220
+ default="y",
221
+ )
222
+ if continue_ == "n":
223
+ variables = _prompt_cluster_and_project()
224
+ except KeyError:
225
+ variables = _prompt_cluster_and_project()
226
+
227
+ login_flow = Prompt.ask("Login flow", choices=[f for f in _VALID_LOGIN_FLOWS if f != "infer"])
228
+ variables.LOGIN_FLOW = login_flow # type: ignore[assignment]
229
+ if login_flow == "token":
230
+ token = Prompt.ask("Enter token")
231
+ variables.TOKEN = token
232
+ return variables
233
+
234
+ variables.IDP_CLIENT_ID = Prompt.ask("Enter IDP Client ID")
235
+ if login_flow == "client_credentials":
236
+ variables.IDP_CLIENT_SECRET = Prompt.ask("Enter IDP Client Secret", password=True)
237
+ tenant_id = Prompt.ask("Enter IDP_TENANT_ID (leave empty to enter IDP_TOKEN_URL instead)")
238
+ if tenant_id:
239
+ variables.IDP_TENANT_ID = tenant_id
240
+ else:
241
+ token_url = Prompt.ask("Enter IDP_TOKEN_URL")
242
+ variables.IDP_TOKEN_URL = token_url
243
+ optional = ["IDP_AUDIENCE", "IDP_SCOPES"]
244
+ else:
245
+ optional = ["IDP_TENANT_ID", "IDP_SCOPES"]
246
+
247
+ defaults = "".join(f"\n - {name}: {getattr(variables, name.lower())}" for name in optional)
248
+ use_defaults = Prompt.ask(
249
+ f"Use default values for the following variables?{defaults}", choices=["y", "n"], default="y"
250
+ )
251
+ if use_defaults:
252
+ return variables
253
+ for name in optional:
254
+ value = Prompt.ask(f"Enter {name}")
255
+ setattr(variables, name, value)
256
+ return variables
257
+
258
+
259
+ def _prompt_cluster_and_project() -> _EnvironmentVariables:
260
+ from rich.prompt import Prompt
261
+
262
+ cluster = Prompt.ask("Enter CDF Cluster (example 'greenfield', 'bluefield', 'westeurope-1)")
263
+ project = Prompt.ask("Enter CDF Project")
264
+ return _EnvironmentVariables(cluster, project)
265
+
266
+
267
+ def _is_notebook() -> bool:
268
+ try:
269
+ shell = get_ipython().__class__.__name__ # type: ignore[name-defined]
270
+ if shell == "ZMQInteractiveShell":
271
+ return True # Jupyter notebook or qtconsole
272
+ elif shell == "TerminalInteractiveShell":
273
+ return False # Terminal running IPython
274
+ else:
275
+ return False # Other type (?)
276
+ except NameError:
277
+ return False # Probably standard Python interpreter
278
+
279
+
280
+ def _repo_root() -> Path | None:
281
+ with suppress(Exception):
282
+ result = subprocess.run("git rev-parse --show-toplevel".split(), stdout=subprocess.PIPE)
283
+ return Path(result.stdout.decode().strip())
284
+ return None
285
+
286
+
287
+ def _env_in_gitignore(repo_root: Path) -> bool:
288
+ ignore_file = repo_root / ".gitignore"
289
+ if not ignore_file.exists():
290
+ return False
291
+ else:
292
+ ignored = {line.strip() for line in ignore_file.read_text().splitlines()}
293
+ return ".env" in ignored or "*.env" in ignored
294
+
295
+
296
+ if __name__ == "__main__":
297
+ c = _prompt_user().get_client()
298
+ print(c.iam.token.inspect())
@@ -1,4 +1,6 @@
1
1
  import importlib
2
+ import inspect
3
+ from collections.abc import Callable
2
4
  from types import ModuleType
3
5
 
4
6
  from cognite.neat.exceptions import NeatImportError
@@ -9,3 +11,25 @@ def local_import(module: str, extra: str) -> ModuleType:
9
11
  return importlib.import_module(module)
10
12
  except ImportError as e:
11
13
  raise NeatImportError(module.split(".")[0], extra) from e
14
+
15
+
16
+ def get_classmethods(cls: type) -> list[Callable]:
17
+ return [
18
+ func for _, func in inspect.getmembers(cls, lambda x: inspect.ismethod(x) and not x.__name__.startswith("_"))
19
+ ]
20
+
21
+
22
+ def class_html_doc(cls: type, include_factory_methods: bool = True) -> str:
23
+ if cls.__doc__:
24
+ docstring = cls.__doc__.split("Args:")[0].strip().replace("\n", "<br />")
25
+ else:
26
+ docstring = "Missing Description"
27
+ if include_factory_methods:
28
+ factory_methods = get_classmethods(cls)
29
+ if factory_methods:
30
+ factory_methods_str = "".join(f"<li><em>.{m.__name__}</em></li>" for m in factory_methods)
31
+ docstring += (
32
+ f"<br /><strong>Available factory methods:</strong><br />"
33
+ f'<ul style="list-style-type:circle;">{factory_methods_str}</ul>'
34
+ )
35
+ return f"<h3>{cls.__name__}</h3><p>{docstring}</p>"
@@ -41,15 +41,18 @@ class UploadResult(UploadResultCore, Generic[T_ID]):
41
41
  unchanged: set[T_ID] = field(default_factory=set)
42
42
  skipped: set[T_ID] = field(default_factory=set)
43
43
  failed_created: set[T_ID] = field(default_factory=set)
44
+ failed_upserted: set[T_ID] = field(default_factory=set)
44
45
  failed_changed: set[T_ID] = field(default_factory=set)
45
46
  failed_deleted: set[T_ID] = field(default_factory=set)
46
47
 
47
48
  @property
48
49
  def failed(self) -> int:
49
- return len(self.failed_created) + len(self.failed_changed) + len(self.failed_deleted)
50
+ return (
51
+ len(self.failed_created) + len(self.failed_changed) + len(self.failed_deleted) + len(self.failed_upserted)
52
+ )
50
53
 
51
54
  @property
52
- def total(self) -> int:
55
+ def success(self) -> int:
53
56
  return len(self.created) + len(self.deleted) + len(self.changed) + len(self.unchanged) + len(self.skipped)
54
57
 
55
58
  def dump(self, aggregate: bool = True) -> dict[str, Any]:
@@ -84,43 +87,3 @@ class UploadResult(UploadResultCore, Generic[T_ID]):
84
87
  continue
85
88
  lines.append(f"{key}: {value}")
86
89
  return f"{self.name.title()}: {', '.join(lines)}"
87
-
88
-
89
- @dataclass
90
- class UploadResultIDs(UploadResultCore):
91
- success: list[str] = field(default_factory=list)
92
- failed: list[str] = field(default_factory=list)
93
-
94
- def dump(self, aggregate: bool = True) -> dict[str, Any]:
95
- output = super().dump(aggregate)
96
- if self.success:
97
- output["success"] = len(self.success) if aggregate else self.success
98
- if self.failed:
99
- output["failed"] = len(self.failed) if aggregate else self.failed
100
- return output
101
-
102
-
103
- @dataclass
104
- class UploadDiffsID(UploadResultCore):
105
- created: list[str] = field(default_factory=list)
106
- changed: list[str] = field(default_factory=list)
107
- unchanged: list[str] = field(default_factory=list)
108
- failed: list[str] = field(default_factory=list)
109
-
110
- def as_upload_result_ids(self) -> UploadResultIDs:
111
- result = UploadResultIDs(name=self.name, error_messages=self.error_messages, issues=self.issues)
112
- result.success = self.created + self.changed + self.unchanged
113
- result.failed = self.failed
114
- return result
115
-
116
- def dump(self, aggregate: bool = True) -> dict[str, Any]:
117
- output = super().dump(aggregate)
118
- if self.created:
119
- output["created"] = len(self.created) if aggregate else self.created
120
- if self.changed:
121
- output["changed"] = len(self.changed) if aggregate else self.changed
122
- if self.unchanged:
123
- output["unchanged"] = len(self.unchanged) if aggregate else self.unchanged
124
- if self.failed:
125
- output["failed"] = len(self.failed) if aggregate else self.failed
126
- return output
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cognite-neat
3
- Version: 0.84.1
3
+ Version: 0.85.0
4
4
  Summary: Knowledge graph transformation
5
5
  Home-page: https://cognite-neat.readthedocs-hosted.com/
6
6
  License: Apache-2.0
@@ -16,6 +16,7 @@ Provides-Extra: all
16
16
  Provides-Extra: docs
17
17
  Provides-Extra: google
18
18
  Provides-Extra: graphql
19
+ Provides-Extra: jupyter
19
20
  Provides-Extra: oxi
20
21
  Provides-Extra: service
21
22
  Requires-Dist: PyYAML
@@ -46,6 +47,7 @@ Requires-Dist: pymdown-extensions ; extra == "docs"
46
47
  Requires-Dist: pyoxigraph (==0.3.19) ; extra == "oxi" or extra == "all"
47
48
  Requires-Dist: rdflib
48
49
  Requires-Dist: requests
50
+ Requires-Dist: rich[jupyter] (>=13.7.1,<14.0.0) ; extra == "jupyter"
49
51
  Requires-Dist: schedule (>=1,<2) ; extra == "service" or extra == "all"
50
52
  Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
51
53
  Requires-Dist: typing_extensions (>=4.8,<5.0) ; python_version < "3.11"
@@ -1,6 +1,6 @@
1
- cognite/neat/__init__.py,sha256=v-rRiDOgZ3sQSMQKq0vgUQZvpeOkoHFXissAx6Ktg84,61
2
- cognite/neat/_shared.py,sha256=afQiTM0SvIKqeBRTvpfwwIvZL7QMQevt4F7lqRagAFg,968
3
- cognite/neat/_version.py,sha256=V73U5VqNMlTo-QuWNLjECQI8_59YjD0cHrTa5BEP1lg,23
1
+ cognite/neat/__init__.py,sha256=AiexNcHdAHFbrrbo9c65gtil1dqx_SGraDH1PSsXjKE,126
2
+ cognite/neat/_shared.py,sha256=RSaHm2eJceTlvb-hMMe4nHgoHdPYDfN3XcxDXo24k3A,1530
3
+ cognite/neat/_version.py,sha256=oS_t2ihHSRa_TQs1XBgfsFFpaFOEDZAndDmjccnIF4g,23
4
4
  cognite/neat/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  cognite/neat/app/api/asgi/metrics.py,sha256=nxFy7L5cChTI0a-zkCiJ59Aq8yLuIJp5c9Dg0wRXtV0,152
6
6
  cognite/neat/app/api/configuration.py,sha256=2U5M6M252swvQPQyooA1EBzFUZNtcTmuSaywfJDgckM,4232
@@ -54,34 +54,34 @@ cognite/neat/graph/examples/Knowledge-Graph-Nordic44.xml,sha256=U2Ns-M4LRjT1fBkh
54
54
  cognite/neat/graph/examples/__init__.py,sha256=yAjHVY3b5jOjmbW-iLbhvu7BG014TpGi3K4igkDqW5I,368
55
55
  cognite/neat/graph/examples/skos-capturing-sheet-wind-topics.xlsx,sha256=CV_yK5ZSbYS_ktfIZUPD8Sevs47zpswLXQUDFkGE4Gw,45798
56
56
  cognite/neat/graph/exceptions.py,sha256=R6pyOH774n9w2x_X_nrUr8OMAdjJMf_XPIqAvxIQaWo,3401
57
- cognite/neat/graph/extractors/__init__.py,sha256=ozXL6ZLK36wp3uX4UACRVs6rbvynQg2JQlDgL1UM1Wk,1025
58
- cognite/neat/graph/extractors/_base.py,sha256=TOXDnlqske8DgnJwA0THDVRgmR79Acjm56yF0E-2w7I,356
57
+ cognite/neat/graph/extractors/__init__.py,sha256=nXcNp6i3-1HteIkr8Ujxk4b09W5jk27Q3eWuwjcnGnM,1647
58
+ cognite/neat/graph/extractors/_base.py,sha256=8IWygpkQTwo0UOmbbwWVI7540_klTVdUVX2JjVPFRIs,498
59
59
  cognite/neat/graph/extractors/_classic_cdf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- cognite/neat/graph/extractors/_classic_cdf/_assets.py,sha256=6e_glBuYl6ArBabOblERQ57g55qsiA0UHrmHVvBqk_s,3895
61
- cognite/neat/graph/extractors/_classic_cdf/_events.py,sha256=Z0vPcyOz4mCwY0Dqa5wAQZjczO1dbTUGM0X4Y10NLGQ,3995
60
+ cognite/neat/graph/extractors/_classic_cdf/_assets.py,sha256=Hu-RoTBhn4LFm38G51L5tc0MVy4-zr1POHWyrIB-vUc,4130
61
+ cognite/neat/graph/extractors/_classic_cdf/_events.py,sha256=SGZWKCxppECIQkwQs5M2e_SoF-eGilCW2KiyXk2PmzM,4230
62
62
  cognite/neat/graph/extractors/_classic_cdf/_files.py,sha256=-6nCkXUCAnDsv4eDFDEiQ-U4SGhmW1VLxZJFUcszqjU,4831
63
- cognite/neat/graph/extractors/_classic_cdf/_labels.py,sha256=nGQyrOZrZzxniIKWROzS1jYrLDT861NARyi-VM_u9-0,2587
64
- cognite/neat/graph/extractors/_classic_cdf/_relationships.py,sha256=w16hu_REIFEVEenhdxEInOmgCouZaBwxXSlxl9_7vTA,5398
65
- cognite/neat/graph/extractors/_classic_cdf/_sequences.py,sha256=o4yxkf81FGFrKkflvlyDYie05fTYsT_LcRFM63OTVCI,3406
66
- cognite/neat/graph/extractors/_classic_cdf/_timeseries.py,sha256=KTYmL8vhXijlmkN1UFQrGpaCllpRekr1y55SoLhlLbg,4559
67
- cognite/neat/graph/extractors/_dexpi.py,sha256=CYSLt0Fl7Y2RCqOfIAT0N8Cjs-Yu2lRLvB13axtAaWw,9384
63
+ cognite/neat/graph/extractors/_classic_cdf/_labels.py,sha256=4JxQHPDciMjbk7F6GxMa-HfhOgAv8LT3VO3mRfEgQ0E,2832
64
+ cognite/neat/graph/extractors/_classic_cdf/_relationships.py,sha256=jgIN__nztlhLwoIJw59s2-Blc9gxIm7YDha5qEoXBSg,5654
65
+ cognite/neat/graph/extractors/_classic_cdf/_sequences.py,sha256=5FuhwpgDiGG51C0bQacQ4LD6KkutUaU1cX2NSy_krhU,3652
66
+ cognite/neat/graph/extractors/_classic_cdf/_timeseries.py,sha256=Ui7WRAvot3KJFwpzqmEYvRs3cN0qh93ocJjYaNLfH30,4811
67
+ cognite/neat/graph/extractors/_dexpi.py,sha256=xIw3kSaQ17k_bAuecvrVRic70PUhFHtcyy-ReLt36Q4,9385
68
68
  cognite/neat/graph/extractors/_mock_graph_generator.py,sha256=1TjgbxDVwgZjivIqx1lLKwggn_zHqWLiYM26esgDAMs,14694
69
- cognite/neat/graph/extractors/_rdf_file.py,sha256=w4-XgPgNsmZOkNxjO1ZQCcopTntmmtxfDBkQxn1se6E,463
69
+ cognite/neat/graph/extractors/_rdf_file.py,sha256=ialMCLv9WH5k6v1YMfozfcmAYhz8OVo9jVhsKMyQkDA,763
70
70
  cognite/neat/graph/issues/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  cognite/neat/graph/issues/loader.py,sha256=v8YDsehkUT1QUG61JM9BDV_lqowMUnDmGmbay0aFzN4,3085
72
- cognite/neat/graph/loaders/__init__.py,sha256=hHC9sfFfbnGSVFTYeuNTIEu4tdLSJ2mWV07fereLelo,125
73
- cognite/neat/graph/loaders/_base.py,sha256=bdYC6CwsHVqnQa1QzOhL68qQhF1OtrsearqH6D-z3E4,4037
72
+ cognite/neat/graph/loaders/__init__.py,sha256=BteVkTklPVUB2W-bbzZug-cEUrx8ZFA-YcQPSxWVpTI,678
73
+ cognite/neat/graph/loaders/_base.py,sha256=t33I-Olw0xYz3Icf2RJZq9cs5_dEKeY87npxsA597Sw,3012
74
74
  cognite/neat/graph/loaders/_rdf2asset.py,sha256=aFby7BwIrW253LEJ4XqGeUuf4jG9VUe8Lg7OlUnXMlM,4493
75
- cognite/neat/graph/loaders/_rdf2dms.py,sha256=ZijbUsKOT01LBmTxsCgTw6lW65ysyxYe5aGOzD1l_r0,12991
75
+ cognite/neat/graph/loaders/_rdf2dms.py,sha256=xyr0SfIusfjYjlAXZcIGS1-IQ6LoVWeOB3_q6D304oo,13603
76
76
  cognite/neat/graph/models.py,sha256=AtLgZh2qyRP6NRetjQCy9qLMuTQB0CH52Zsev-qa2sk,149
77
77
  cognite/neat/graph/queries/__init__.py,sha256=BgDd-037kvtWwAoGAy8eORVNMiZ5-E9sIV0txIpeaN4,50
78
78
  cognite/neat/graph/queries/_base.py,sha256=20A7GDBdmc35VmHVz5n0YCGPcnBAmUX-bM2ImHPManc,3844
79
79
  cognite/neat/graph/queries/_construct.py,sha256=FxzSQqzCpo7lKVYerlLAY03oqCeFM5L6MozfBUblzr4,7341
80
80
  cognite/neat/graph/queries/_shared.py,sha256=EwW2RbPttt7-z7QTgfKWlthA2Nq5d3bYyyewFkCA7R4,5043
81
81
  cognite/neat/graph/stores/__init__.py,sha256=G-VG_YwfRt1kuPao07PDJyZ3w_0-eguzLUM13n-Z_RA,64
82
- cognite/neat/graph/stores/_base.py,sha256=7NnJUh7SQ1lDfJruOgKWc2IesT1DC81Yi6Xqp44RFwM,10140
82
+ cognite/neat/graph/stores/_base.py,sha256=z69uhzBhzl1JQJU9U-h9zAyySn25ZQTJRXam4BavnoY,10586
83
83
  cognite/neat/graph/stores/_oxrdflib.py,sha256=A5zeRm5_e8ui_ihGpgstRDg_N7qcLZ3QZBRGrOXSGI0,9569
84
- cognite/neat/graph/stores/_provenance.py,sha256=Hr9WBhFj-eoet4czL8XSBGYnu9Yn66YsTgH_G0n3QpY,3293
84
+ cognite/neat/graph/stores/_provenance.py,sha256=HIXa-p7yc2l3HFkQWMnGPhn-t_FROEG21thADGkgy0c,3590
85
85
  cognite/neat/graph/transformers/__init__.py,sha256=wXrNSyJNGnis3haaCKVPZ5y5kKSUsOUHnh-860ekatk,555
86
86
  cognite/neat/graph/transformers/_base.py,sha256=b37Ek-9njuM5pTR_3XhnxCMrg_ip_2BMwM7ZhKpAAlw,328
87
87
  cognite/neat/graph/transformers/_classic_cdf.py,sha256=ZHHJU-1-lXeufuZJSuDa2Zmu56PS9PcGeFYI91VZNI4,12214
@@ -188,28 +188,28 @@ cognite/neat/rules/analysis/_information_rules.py,sha256=fdSMyInsPJdgLHKwSkj2N9b
188
188
  cognite/neat/rules/examples/__init__.py,sha256=nxIwueAcHgZhkYriGxnDLQmIyiT8PByPHbScjYKDKe0,374
189
189
  cognite/neat/rules/examples/wind-energy.owl,sha256=NuomCA9FuuLF0JlSuG3OKqD4VBcHgSjDKFLV17G1zV8,65934
190
190
  cognite/neat/rules/exceptions.py,sha256=YLnsbXXJdDSr_szQoioEtOdqDV8PR7RdQjpMP2SWeCs,123868
191
- cognite/neat/rules/exporters/__init__.py,sha256=Gn3CjkVKHJF9Po1ZPH4wAJ-sRW9up7b2CpXm-eReV3Q,413
192
- cognite/neat/rules/exporters/_base.py,sha256=TkdpmliKjKVDITBAE4ySq_Zc8edFDQzHkbvHll4ODkg,1763
193
- cognite/neat/rules/exporters/_rules2dms.py,sha256=xnmq4FbMAvYVtJzDM-wwTRwVq_t3XWf8ffmhpE27BSk,14547
191
+ cognite/neat/rules/exporters/__init__.py,sha256=nRMUBUf7yr1QPjyITeX2rTLtLLawHv24hhRE39d2-e0,1109
192
+ cognite/neat/rules/exporters/_base.py,sha256=qZt236sNKTbiM41sgVEYcEtuK5v8Pt14LMLBNiZrNWs,1936
193
+ cognite/neat/rules/exporters/_rules2dms.py,sha256=xK9xXJ7lLQnzrRlBUJQVLlY4SC-vnnjGUXOzaWvOKmY,14553
194
194
  cognite/neat/rules/exporters/_rules2excel.py,sha256=HvUdXYHxfLMijYWdTnfqCsw3Izf8S-XDSve-2ZbqF8Y,14248
195
- cognite/neat/rules/exporters/_rules2ontology.py,sha256=Od53uLdcC2Q7UiF5PA2P0gw3O14eTD3MeJ1-trd64ZM,20388
195
+ cognite/neat/rules/exporters/_rules2ontology.py,sha256=bt8IuxaAYFRyZc1AlS-gYw9Jf6jBr5CYFOaJ3qPeoaA,20527
196
196
  cognite/neat/rules/exporters/_rules2yaml.py,sha256=GA8eUYRxUfIU6IMvlyGO5JidkOD5eUKSbH3qAiFiaCg,3026
197
197
  cognite/neat/rules/exporters/_validation.py,sha256=OlKIyf4nhSDehJwFHDQ8Zdf6HpNfW7dSe2s67eywHu4,4078
198
- cognite/neat/rules/importers/__init__.py,sha256=gR6_TAEa3iO5NCLKRztHg-FMiLdBnx47Z3iSzbwLfcE,481
199
- cognite/neat/rules/importers/_base.py,sha256=_hvxYnLiK8zf0MFes1VJ5TFisdZl-bOB6tFi3DQgYpc,4374
200
- cognite/neat/rules/importers/_dms2rules.py,sha256=5yJGYkM7lAMu-QfO0_r59WE4RGtMu2smMqLm16ohgLQ,18994
198
+ cognite/neat/rules/importers/__init__.py,sha256=Vxl2Iq1dMXUsI6Wb411xPI3rromdq50xZUci-S8faSw,1097
199
+ cognite/neat/rules/importers/_base.py,sha256=3LmDfR-f0nlLGcioWB8IbeZJ6uW5dvnzxJlqaMWc-u0,4516
200
+ cognite/neat/rules/importers/_dms2rules.py,sha256=Dqoh4qO5IVvjRpxLHZaqCgPC99_r4y7ncEo2WYMxwqU,19302
201
201
  cognite/neat/rules/importers/_dtdl2rules/__init__.py,sha256=CNR-sUihs2mnR1bPMKs3j3L4ds3vFTsrl6YycExZTfU,68
202
202
  cognite/neat/rules/importers/_dtdl2rules/_unit_lookup.py,sha256=wW4saKva61Q_i17guY0dc4OseJDQfqHy_QZBtm0OD6g,12134
203
203
  cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py,sha256=ysmWUxZ0npwrTB0uiH5jA0v37sfCwowGaYk17IyxPUU,12663
204
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py,sha256=QDyGt5YBaxzF4v_oCFSgKRSpwVdVruDU3-VW0DEiHbY,6718
204
+ cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py,sha256=Psj3C2jembY_Wu7WWJIFIwrMawvjISjeqfBnoRy_csw,6740
205
205
  cognite/neat/rules/importers/_dtdl2rules/spec.py,sha256=tim_MfN1J0F3Oeqk3BMgIA82d_MZvhRuRMsLK3B4PYc,11897
206
- cognite/neat/rules/importers/_inference2rules.py,sha256=vN3l6gfca19FHGzPb9fwolZaq4Z8KkeiR-iK1up8Kqk,11478
206
+ cognite/neat/rules/importers/_inference2rules.py,sha256=OilV3aF3LTjFHK2Yzs8KktTGG41iPB4hh3u1aAwhNbI,11675
207
207
  cognite/neat/rules/importers/_owl2rules/__init__.py,sha256=tdGcrgtozdQyST-pTlxIa4cLBNTLvtk1nNYR4vOdFSw,63
208
208
  cognite/neat/rules/importers/_owl2rules/_owl2classes.py,sha256=QpTxvrTGczIa48X8lgXGnMN1AWPhHK0DR6uNq175xak,7357
209
209
  cognite/neat/rules/importers/_owl2rules/_owl2metadata.py,sha256=nwnUaBNAAYMoBre2UmsnkJXUuaqGEpR3U3txDrH2w6g,7527
210
210
  cognite/neat/rules/importers/_owl2rules/_owl2properties.py,sha256=eKr-e-ZTTV54PJ9UXNVPTT_c9XxszNPraS4Y43AF7qQ,7297
211
211
  cognite/neat/rules/importers/_owl2rules/_owl2rules.py,sha256=41_wZFvt0A6TI55zlT04oQkvU7V73li4aGLgc4T4Lxo,6358
212
- cognite/neat/rules/importers/_spreadsheet2rules.py,sha256=32Mnu74cSKQ0XcnAshwL6XTS_TxqYBUm5u29U7sirCg,12684
212
+ cognite/neat/rules/importers/_spreadsheet2rules.py,sha256=vyYXNvP64dBGTWRWfrg7wtUcs0PdPiGLAHwE8itItAA,13072
213
213
  cognite/neat/rules/importers/_yaml2rules.py,sha256=F0uksSz1A3po5OlRM2152_w5j8D9oYTLB9NFTkSMlWI,4275
214
214
  cognite/neat/rules/issues/__init__.py,sha256=c12m0HAHHzF6oR8lKbULE3TxOPimTi9s1O9IIrtgh0g,549
215
215
  cognite/neat/rules/issues/base.py,sha256=x2YLCfmqtPlFLoURq3qHaprXCpFaQdf0iWkql-EMyps,2446
@@ -251,7 +251,8 @@ cognite/neat/rules/models/information/_serializer.py,sha256=yti9I_xJruxrib66YIBI
251
251
  cognite/neat/rules/models/information/_validation.py,sha256=Is2GzL2lZU3A5zPu3NjvlXfmIU2_Y10C5Nxi5Denz4g,7528
252
252
  cognite/neat/rules/models/wrapped_entities.py,sha256=ThhjnNNrpgz0HeORIQ8Q894trxP73P7T_TuZj6qH2CU,7157
253
253
  cognite/neat/utils/__init__.py,sha256=l5Nyqhqo25bcQXCOb_lk01cr-UXsG8cczz_y_I0u6bg,68
254
- cognite/neat/utils/auxiliary.py,sha256=E2-YtddzScvN7l7j0kNYIMlfqIUT9NWMqLpcJYPK4rY,309
254
+ cognite/neat/utils/auth.py,sha256=0UeZH4nx-0LizNsBrYJiquK6ZldNYk_2frLBZzcB5JM,11270
255
+ cognite/neat/utils/auxiliary.py,sha256=IOVbr6lPQulMJUyrrhfSsF6lIHch0Aw6KszMkBomprc,1248
255
256
  cognite/neat/utils/cdf.py,sha256=piRx-6GRz4cCfBZD5rU0OM6ixQ3cj5TMzI0yCYUveR8,2422
256
257
  cognite/neat/utils/cdf_classes.py,sha256=NEmz5UprBlqfqZnqJkRk5xjSpzazwHbhcWsMH_GNxP8,5831
257
258
  cognite/neat/utils/cdf_loaders/__init__.py,sha256=s2aPR5XLo6WZ0ybstAJlcGFYkA7CyHW1XO-NYpL0V6o,483
@@ -262,9 +263,9 @@ cognite/neat/utils/cdf_loaders/data_classes.py,sha256=0apspfwVlFltYOZfmk_PNknS3Z
262
263
  cognite/neat/utils/exceptions.py,sha256=-w4cAcvcoWLf-_ZwAl7QV_NysfqtQzIOd1Ti-mpxJgM,981
263
264
  cognite/neat/utils/spreadsheet.py,sha256=LI0c7dlW0zXHkHw0NvB-gg6Df6cDcE3FbiaHBYLXdzQ,2714
264
265
  cognite/neat/utils/text.py,sha256=4bg1_Q0lg7KsoxaDOvXrVyeY78BJN8i-27BlyDzUCls,3082
265
- cognite/neat/utils/upload.py,sha256=opAB8oDtpgcrugcjbUg0tjGqtFUnAS7zixtLiRYZ3TA,5084
266
+ cognite/neat/utils/upload.py,sha256=nZEuDu22A1kTbl-ctzAJ2vx1cjiQtqdDdpC_mRRMvUI,3597
266
267
  cognite/neat/utils/utils.py,sha256=1LEwR8gpHw_6pvEeLkW_cDU_lUun4qSsw_Rr3JsKwgA,14172
267
- cognite/neat/utils/xml.py,sha256=ppLT3lQKVp8wOP-m8-tFY8uB2P4R76l7R_-kUtsABng,992
268
+ cognite/neat/utils/xml_.py,sha256=ppLT3lQKVp8wOP-m8-tFY8uB2P4R76l7R_-kUtsABng,992
268
269
  cognite/neat/workflows/__init__.py,sha256=oiKub_U9f5cA0I1nKl5dFkR4BD8_6Be9eMzQ_50PwP0,396
269
270
  cognite/neat/workflows/_exceptions.py,sha256=ugI_X1XNpikAiL8zIggBjcx6q7WvOpRIgvxHrj2Rhr4,1348
270
271
  cognite/neat/workflows/base.py,sha256=2cSnxfc9GSoTluneTWLJbE9rImp0wJt8--LHkQHQfi4,26800
@@ -309,8 +310,8 @@ cognite/neat/workflows/steps_registry.py,sha256=fkTX14ZA7_gkUYfWIlx7A1XbCidvqR23
309
310
  cognite/neat/workflows/tasks.py,sha256=dqlJwKAb0jlkl7abbY8RRz3m7MT4SK8-7cntMWkOYjw,788
310
311
  cognite/neat/workflows/triggers.py,sha256=_BLNplzoz0iic367u1mhHMHiUrCwP-SLK6_CZzfODX0,7071
311
312
  cognite/neat/workflows/utils.py,sha256=gKdy3RLG7ctRhbCRwaDIWpL9Mi98zm56-d4jfHDqP1E,453
312
- cognite_neat-0.84.1.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
313
- cognite_neat-0.84.1.dist-info/METADATA,sha256=i54w9dUQS9DCWAsl2IR-P14CUA1uMLpjrdISbJXnL8o,9400
314
- cognite_neat-0.84.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
315
- cognite_neat-0.84.1.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
316
- cognite_neat-0.84.1.dist-info/RECORD,,
313
+ cognite_neat-0.85.0.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
314
+ cognite_neat-0.85.0.dist-info/METADATA,sha256=v0E853i0iw38J4bJSXaqj-K7ZLL9aBaTO4roAkCu-VE,9493
315
+ cognite_neat-0.85.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
316
+ cognite_neat-0.85.0.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
317
+ cognite_neat-0.85.0.dist-info/RECORD,,
File without changes