cognite-neat 0.88.0__py3-none-any.whl → 0.88.2__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 (99) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/app/api/routers/configuration.py +1 -1
  3. cognite/neat/app/ui/neat-app/build/asset-manifest.json +7 -7
  4. cognite/neat/app/ui/neat-app/build/index.html +1 -1
  5. cognite/neat/app/ui/neat-app/build/static/css/{main.38a62222.css → main.72e3d92e.css} +2 -2
  6. cognite/neat/app/ui/neat-app/build/static/css/main.72e3d92e.css.map +1 -0
  7. cognite/neat/app/ui/neat-app/build/static/js/main.5a52cf09.js +3 -0
  8. cognite/neat/app/ui/neat-app/build/static/js/{main.ec7f72e2.js.LICENSE.txt → main.5a52cf09.js.LICENSE.txt} +0 -9
  9. cognite/neat/app/ui/neat-app/build/static/js/main.5a52cf09.js.map +1 -0
  10. cognite/neat/config.py +44 -27
  11. cognite/neat/exceptions.py +8 -2
  12. cognite/neat/graph/extractors/_classic_cdf/_assets.py +21 -73
  13. cognite/neat/graph/extractors/_classic_cdf/_base.py +102 -0
  14. cognite/neat/graph/extractors/_classic_cdf/_events.py +46 -42
  15. cognite/neat/graph/extractors/_classic_cdf/_files.py +41 -45
  16. cognite/neat/graph/extractors/_classic_cdf/_labels.py +75 -52
  17. cognite/neat/graph/extractors/_classic_cdf/_relationships.py +49 -27
  18. cognite/neat/graph/extractors/_classic_cdf/_sequences.py +47 -50
  19. cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +47 -49
  20. cognite/neat/graph/loaders/_base.py +4 -4
  21. cognite/neat/graph/loaders/_rdf2asset.py +12 -14
  22. cognite/neat/graph/loaders/_rdf2dms.py +14 -10
  23. cognite/neat/graph/queries/_base.py +22 -29
  24. cognite/neat/graph/queries/_shared.py +1 -1
  25. cognite/neat/graph/stores/_base.py +19 -11
  26. cognite/neat/graph/transformers/_rdfpath.py +3 -2
  27. cognite/neat/issues/__init__.py +16 -0
  28. cognite/neat/{issues.py → issues/_base.py} +78 -2
  29. cognite/neat/issues/errors/external.py +21 -0
  30. cognite/neat/issues/errors/properties.py +75 -0
  31. cognite/neat/issues/errors/resources.py +123 -0
  32. cognite/neat/issues/errors/schema.py +0 -0
  33. cognite/neat/{rules/issues → issues}/formatters.py +9 -9
  34. cognite/neat/issues/neat_warnings/__init__.py +2 -0
  35. cognite/neat/issues/neat_warnings/identifier.py +27 -0
  36. cognite/neat/issues/neat_warnings/models.py +22 -0
  37. cognite/neat/issues/neat_warnings/properties.py +77 -0
  38. cognite/neat/issues/neat_warnings/resources.py +125 -0
  39. cognite/neat/rules/exporters/_rules2dms.py +3 -2
  40. cognite/neat/rules/exporters/_rules2ontology.py +28 -20
  41. cognite/neat/rules/exporters/_validation.py +15 -21
  42. cognite/neat/rules/importers/__init__.py +7 -3
  43. cognite/neat/rules/importers/_base.py +3 -3
  44. cognite/neat/rules/importers/_dms2rules.py +39 -18
  45. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +44 -53
  46. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +6 -5
  47. cognite/neat/rules/importers/_rdf/__init__.py +0 -0
  48. cognite/neat/rules/importers/_rdf/_imf2rules/__init__.py +3 -0
  49. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +82 -0
  50. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +34 -0
  51. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +123 -0
  52. cognite/neat/rules/importers/{_owl2rules/_owl2rules.py → _rdf/_imf2rules/_imf2rules.py} +15 -11
  53. cognite/neat/rules/importers/{_inference2rules.py → _rdf/_inference2rules.py} +1 -1
  54. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +57 -0
  55. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2metadata.py +68 -0
  56. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +59 -0
  57. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +76 -0
  58. cognite/neat/rules/importers/_rdf/_shared.py +586 -0
  59. cognite/neat/rules/importers/_spreadsheet2rules.py +31 -28
  60. cognite/neat/rules/importers/_yaml2rules.py +2 -1
  61. cognite/neat/rules/issues/__init__.py +1 -5
  62. cognite/neat/rules/issues/base.py +2 -21
  63. cognite/neat/rules/issues/dms.py +20 -134
  64. cognite/neat/rules/issues/ontology.py +298 -0
  65. cognite/neat/rules/issues/spreadsheet.py +51 -3
  66. cognite/neat/rules/issues/tables.py +72 -0
  67. cognite/neat/rules/models/_rdfpath.py +4 -4
  68. cognite/neat/rules/models/_types/_field.py +14 -21
  69. cognite/neat/rules/models/asset/_validation.py +1 -1
  70. cognite/neat/rules/models/dms/_schema.py +53 -30
  71. cognite/neat/rules/models/dms/_validation.py +2 -2
  72. cognite/neat/rules/models/entities.py +3 -0
  73. cognite/neat/rules/models/information/_rules.py +5 -4
  74. cognite/neat/rules/models/information/_validation.py +1 -1
  75. cognite/neat/utils/rdf_.py +17 -9
  76. cognite/neat/utils/regex_patterns.py +52 -0
  77. cognite/neat/workflows/steps/lib/current/rules_importer.py +73 -1
  78. cognite/neat/workflows/steps/lib/current/rules_validator.py +19 -7
  79. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/METADATA +2 -6
  80. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/RECORD +85 -72
  81. cognite/neat/app/ui/neat-app/build/static/css/main.38a62222.css.map +0 -1
  82. cognite/neat/app/ui/neat-app/build/static/js/main.ec7f72e2.js +0 -3
  83. cognite/neat/app/ui/neat-app/build/static/js/main.ec7f72e2.js.map +0 -1
  84. cognite/neat/graph/issues/loader.py +0 -104
  85. cognite/neat/graph/stores/_oxrdflib.py +0 -247
  86. cognite/neat/rules/exceptions.py +0 -2972
  87. cognite/neat/rules/importers/_owl2rules/_owl2classes.py +0 -215
  88. cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +0 -213
  89. cognite/neat/rules/importers/_owl2rules/_owl2properties.py +0 -203
  90. cognite/neat/rules/issues/importing.py +0 -408
  91. cognite/neat/rules/models/_types/_base.py +0 -16
  92. cognite/neat/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +0 -152
  93. cognite/neat/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +0 -139
  94. cognite/neat/workflows/examples/Ontology_to_Data_Model/workflow.yaml +0 -116
  95. /cognite/neat/{graph/issues → issues/errors}/__init__.py +0 -0
  96. /cognite/neat/rules/importers/{_owl2rules → _rdf/_owl2rules}/__init__.py +0 -0
  97. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/LICENSE +0 -0
  98. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/WHEEL +0 -0
  99. {cognite_neat-0.88.0.dist-info → cognite_neat-0.88.2.dist-info}/entry_points.txt +0 -0
@@ -1,40 +1,37 @@
1
- import json
2
- from collections.abc import Iterable
1
+ from collections.abc import Callable, Set
3
2
  from datetime import datetime, timezone
4
3
  from pathlib import Path
5
- from typing import cast
6
4
  from urllib.parse import quote
7
5
 
8
6
  from cognite.client import CogniteClient
9
7
  from cognite.client.data_classes import FileMetadata, FileMetadataList
10
- from pydantic import AnyHttpUrl, ValidationError
11
- from rdflib import RDF, Literal, Namespace, URIRef
8
+ from rdflib import RDF, Literal, Namespace
12
9
 
13
- from cognite.neat.constants import DEFAULT_NAMESPACE
14
- from cognite.neat.graph.extractors._base import BaseExtractor
15
10
  from cognite.neat.graph.models import Triple
16
- from cognite.neat.utils.auxiliary import string_to_ideal_type
17
11
 
12
+ from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFExtractor
18
13
 
19
- class FilesExtractor(BaseExtractor):
14
+
15
+ class FilesExtractor(ClassicCDFExtractor[FileMetadata]):
20
16
  """Extract data from Cognite Data Fusions files metadata into Neat.
21
17
 
22
18
  Args:
23
- files_metadata (Iterable[FileMetadata]): An iterable of files metadata.
19
+ items (Iterable[FileMetadata]): An iterable of items.
24
20
  namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
21
+ to_type (Callable[[FileMetadata], str | None], optional): A function to convert an item to a type.
22
+ Defaults to None. If None or if the function returns None, the asset will be set to the default type.
23
+ total (int, optional): The total number of items to load. If passed, you will get a progress bar if rich
24
+ is installed. Defaults to None.
25
+ limit (int, optional): The maximal number of items to load. Defaults to None. This is typically used for
26
+ testing setup of the extractor. For example, if you are extracting 100 000 assets, you might want to
27
+ limit the extraction to 1000 assets to test the setup.
25
28
  unpack_metadata (bool, optional): Whether to unpack metadata. Defaults to False, which yields the metadata as
26
29
  a JSON string.
30
+ skip_metadata_values (set[str] | frozenset[str] | None, optional): If you are unpacking metadata, then
31
+ values in this set will be skipped.
27
32
  """
28
33
 
29
- def __init__(
30
- self,
31
- files_metadata: Iterable[FileMetadata],
32
- namespace: Namespace | None = None,
33
- unpack_metadata: bool = True,
34
- ):
35
- self.namespace = namespace or DEFAULT_NAMESPACE
36
- self.files_metadata = files_metadata
37
- self.unpack_metadata = unpack_metadata
34
+ _default_rdf_type = "File"
38
35
 
39
36
  @classmethod
40
37
  def from_dataset(
@@ -42,15 +39,18 @@ class FilesExtractor(BaseExtractor):
42
39
  client: CogniteClient,
43
40
  data_set_external_id: str,
44
41
  namespace: Namespace | None = None,
42
+ to_type: Callable[[FileMetadata], str | None] | None = None,
43
+ limit: int | None = None,
45
44
  unpack_metadata: bool = True,
45
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
46
46
  ):
47
47
  return cls(
48
- cast(
49
- Iterable[FileMetadata],
50
- client.files(data_set_external_ids=data_set_external_id),
51
- ),
52
- namespace,
53
- unpack_metadata,
48
+ client.files(data_set_external_ids=data_set_external_id),
49
+ namespace=namespace,
50
+ to_type=to_type,
51
+ limit=limit,
52
+ unpack_metadata=unpack_metadata,
53
+ skip_metadata_values=skip_metadata_values,
54
54
  )
55
55
 
56
56
  @classmethod
@@ -58,24 +58,29 @@ class FilesExtractor(BaseExtractor):
58
58
  cls,
59
59
  file_path: str,
60
60
  namespace: Namespace | None = None,
61
+ to_type: Callable[[FileMetadata], str | None] | None = None,
62
+ limit: int | None = None,
61
63
  unpack_metadata: bool = True,
64
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
62
65
  ):
66
+ file_metadata = FileMetadataList.load(Path(file_path).read_text())
63
67
  return cls(
64
- FileMetadataList.load(Path(file_path).read_text()),
65
- namespace,
66
- unpack_metadata,
68
+ file_metadata,
69
+ namespace=namespace,
70
+ to_type=to_type,
71
+ limit=limit,
72
+ total=len(file_metadata),
73
+ unpack_metadata=unpack_metadata,
74
+ skip_metadata_values=skip_metadata_values,
67
75
  )
68
76
 
69
- def extract(self) -> Iterable[Triple]:
70
- """Extract files metadata as triples."""
71
- for event in self.files_metadata:
72
- yield from self._file2triples(event)
73
-
74
- def _file2triples(self, file: FileMetadata) -> list[Triple]:
77
+ def _item2triples(self, file: FileMetadata) -> list[Triple]:
75
78
  id_ = self.namespace[f"File_{file.id}"]
76
79
 
80
+ type_ = self._get_rdf_type(file)
81
+
77
82
  # Set rdf type
78
- triples: list[Triple] = [(id_, RDF.type, self.namespace.File)]
83
+ triples: list[Triple] = [(id_, RDF.type, self.namespace[type_])]
79
84
 
80
85
  # Create attributes
81
86
 
@@ -95,16 +100,7 @@ class FilesExtractor(BaseExtractor):
95
100
  triples.append((id_, self.namespace.source, Literal(file.source)))
96
101
 
97
102
  if file.metadata:
98
- if self.unpack_metadata:
99
- for key, value in file.metadata.items():
100
- if value:
101
- type_aware_value = string_to_ideal_type(value)
102
- try:
103
- triples.append((id_, self.namespace[key], URIRef(str(AnyHttpUrl(type_aware_value))))) # type: ignore
104
- except ValidationError:
105
- triples.append((id_, self.namespace[key], Literal(type_aware_value)))
106
- else:
107
- triples.append((id_, self.namespace.metadata, Literal(json.dumps(file.metadata))))
103
+ triples.extend(self._metadata_to_triples(id_, file.metadata))
108
104
 
109
105
  if file.source_created_time:
110
106
  triples.append(
@@ -1,33 +1,37 @@
1
- from collections.abc import Iterable
1
+ from collections.abc import Callable, Set
2
2
  from datetime import datetime, timezone
3
3
  from pathlib import Path
4
- from typing import cast
5
4
 
6
5
  from cognite.client import CogniteClient
7
6
  from cognite.client.data_classes import LabelDefinition, LabelDefinitionList
8
7
  from rdflib import RDF, Literal, Namespace
9
8
 
10
- from cognite.neat.constants import DEFAULT_NAMESPACE
11
- from cognite.neat.graph.extractors._base import BaseExtractor
12
9
  from cognite.neat.graph.models import Triple
13
10
  from cognite.neat.utils.auxiliary import create_sha256_hash
14
11
 
12
+ from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFExtractor
15
13
 
16
- class LabelsExtractor(BaseExtractor):
14
+
15
+ class LabelsExtractor(ClassicCDFExtractor[LabelDefinition]):
17
16
  """Extract data from Cognite Data Fusions Labels into Neat.
18
17
 
19
18
  Args:
20
- labels (Iterable[LabelDefinition]): An iterable of labels.
19
+ items (Iterable[LabelDefinition]): An iterable of items.
21
20
  namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
21
+ to_type (Callable[[LabelDefinition], str | None], optional): A function to convert an item to a type.
22
+ Defaults to None. If None or if the function returns None, the asset will be set to the default type.
23
+ total (int, optional): The total number of items to load. If passed, you will get a progress bar if rich
24
+ is installed. Defaults to None.
25
+ limit (int, optional): The maximal number of items to load. Defaults to None. This is typically used for
26
+ testing setup of the extractor. For example, if you are extracting 100 000 assets, you might want to
27
+ limit the extraction to 1000 assets to test the setup.
28
+ unpack_metadata (bool, optional): Whether to unpack metadata. Defaults to False, which yields the metadata as
29
+ a JSON string.
30
+ skip_metadata_values (set[str] | frozenset[str] | None, optional): If you are unpacking metadata, then
31
+ values in this set will be skipped.
22
32
  """
23
33
 
24
- def __init__(
25
- self,
26
- labels: Iterable[LabelDefinition],
27
- namespace: Namespace | None = None,
28
- ):
29
- self.namespace = namespace or DEFAULT_NAMESPACE
30
- self.labels = labels
34
+ _default_rdf_type = "Label"
31
35
 
32
36
  @classmethod
33
37
  def from_dataset(
@@ -35,57 +39,76 @@ class LabelsExtractor(BaseExtractor):
35
39
  client: CogniteClient,
36
40
  data_set_external_id: str,
37
41
  namespace: Namespace | None = None,
42
+ to_type: Callable[[LabelDefinition], str | None] | None = None,
43
+ limit: int | None = None,
44
+ unpack_metadata: bool = True,
45
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
38
46
  ):
39
47
  return cls(
40
- cast(
41
- Iterable[LabelDefinition],
42
- client.labels(data_set_external_ids=data_set_external_id),
43
- ),
44
- namespace,
48
+ client.labels(data_set_external_ids=data_set_external_id),
49
+ namespace=namespace,
50
+ to_type=to_type,
51
+ limit=limit,
52
+ unpack_metadata=unpack_metadata,
53
+ skip_metadata_values=skip_metadata_values,
45
54
  )
46
55
 
47
56
  @classmethod
48
- def from_file(cls, file_path: str, namespace: Namespace | None = None):
49
- return cls(LabelDefinitionList.load(Path(file_path).read_text()), namespace)
57
+ def from_file(
58
+ cls,
59
+ file_path: str,
60
+ namespace: Namespace | None = None,
61
+ to_type: Callable[[LabelDefinition], str | None] | None = None,
62
+ limit: int | None = None,
63
+ unpack_metadata: bool = True,
64
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
65
+ ):
66
+ labels = LabelDefinitionList.load(Path(file_path).read_text())
67
+ return cls(
68
+ labels,
69
+ total=len(labels),
70
+ namespace=namespace,
71
+ to_type=to_type,
72
+ limit=limit,
73
+ unpack_metadata=unpack_metadata,
74
+ skip_metadata_values=skip_metadata_values,
75
+ )
50
76
 
51
- def extract(self) -> Iterable[Triple]:
52
- """Extract labels as triples."""
53
- for label in self.labels:
54
- yield from self._labels2triples(label)
77
+ def _item2triples(self, label: LabelDefinition) -> list[Triple]:
78
+ if not label.external_id:
79
+ return []
55
80
 
56
- def _labels2triples(self, label: LabelDefinition) -> list[Triple]:
57
- if label.external_id:
58
- id_ = self.namespace[f"Label_{create_sha256_hash(label.external_id)}"]
81
+ id_ = self.namespace[f"Label_{create_sha256_hash(label.external_id)}"]
59
82
 
60
- # Set rdf type
61
- triples: list[Triple] = [(id_, RDF.type, self.namespace.Label)]
83
+ type_ = self._get_rdf_type(label)
84
+ # Set rdf type
85
+ triples: list[Triple] = [(id_, RDF.type, self.namespace[type_])]
62
86
 
63
- # Create attributes
64
- triples.append((id_, self.namespace.external_id, Literal(label.external_id)))
87
+ # Create attributes
88
+ triples.append((id_, self.namespace.external_id, Literal(label.external_id)))
65
89
 
66
- if label.name:
67
- triples.append((id_, self.namespace.name, Literal(label.name)))
90
+ if label.name:
91
+ triples.append((id_, self.namespace.name, Literal(label.name)))
68
92
 
69
- if label.description:
70
- triples.append((id_, self.namespace.description, Literal(label.description)))
93
+ if label.description:
94
+ triples.append((id_, self.namespace.description, Literal(label.description)))
71
95
 
72
- if label.created_time:
73
- triples.append(
74
- (
75
- id_,
76
- self.namespace.created_time,
77
- Literal(datetime.fromtimestamp(label.created_time / 1000, timezone.utc)),
78
- )
96
+ if label.created_time:
97
+ triples.append(
98
+ (
99
+ id_,
100
+ self.namespace.created_time,
101
+ Literal(datetime.fromtimestamp(label.created_time / 1000, timezone.utc)),
79
102
  )
80
-
81
- if label.data_set_id:
82
- triples.append(
83
- (
84
- id_,
85
- self.namespace.data_set_id,
86
- self.namespace[f"Dataset_{label.data_set_id}"],
87
- )
103
+ )
104
+
105
+ if label.data_set_id:
106
+ triples.append(
107
+ (
108
+ id_,
109
+ self.namespace.data_set_id,
110
+ self.namespace[f"Dataset_{label.data_set_id}"],
88
111
  )
112
+ )
89
113
 
90
- return triples
91
- return []
114
+ return triples
@@ -1,34 +1,38 @@
1
- from collections.abc import Iterable
1
+ from collections.abc import Callable, Set
2
2
  from datetime import datetime, timezone
3
3
  from pathlib import Path
4
- from typing import cast
5
4
  from urllib.parse import quote
6
5
 
7
6
  from cognite.client import CogniteClient
8
7
  from cognite.client.data_classes import Relationship, RelationshipList
9
8
  from rdflib import RDF, Literal, Namespace
10
9
 
11
- from cognite.neat.constants import DEFAULT_NAMESPACE
12
- from cognite.neat.graph.extractors._base import BaseExtractor
13
10
  from cognite.neat.graph.models import Triple
14
11
  from cognite.neat.utils.auxiliary import create_sha256_hash
15
12
 
13
+ from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFExtractor
16
14
 
17
- class RelationshipsExtractor(BaseExtractor):
15
+
16
+ class RelationshipsExtractor(ClassicCDFExtractor[Relationship]):
18
17
  """Extract data from Cognite Data Fusions Relationships into Neat.
19
18
 
20
19
  Args:
21
- relationships (Iterable[Asset]): An iterable of relationships.
20
+ items (Iterable[Relationship]): An iterable of items.
22
21
  namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
22
+ to_type (Callable[[Relationship], str | None], optional): A function to convert an item to a type.
23
+ Defaults to None. If None or if the function returns None, the asset will be set to the default type.
24
+ total (int, optional): The total number of items to load. If passed, you will get a progress bar if rich
25
+ is installed. Defaults to None.
26
+ limit (int, optional): The maximal number of items to load. Defaults to None. This is typically used for
27
+ testing setup of the extractor. For example, if you are extracting 100 000 assets, you might want to
28
+ limit the extraction to 1000 assets to test the setup.
29
+ unpack_metadata (bool, optional): Whether to unpack metadata. Defaults to False, which yields the metadata as
30
+ a JSON string.
31
+ skip_metadata_values (set[str] | frozenset[str] | None, optional): If you are unpacking metadata, then
32
+ values in this set will be skipped.
23
33
  """
24
34
 
25
- def __init__(
26
- self,
27
- relationships: Iterable[Relationship],
28
- namespace: Namespace | None = None,
29
- ):
30
- self.namespace = namespace or DEFAULT_NAMESPACE
31
- self.relationships = relationships
35
+ _default_rdf_type = "Relationship"
32
36
 
33
37
  @classmethod
34
38
  def from_dataset(
@@ -36,33 +40,51 @@ class RelationshipsExtractor(BaseExtractor):
36
40
  client: CogniteClient,
37
41
  data_set_external_id: str,
38
42
  namespace: Namespace | None = None,
43
+ to_type: Callable[[Relationship], str | None] | None = None,
44
+ limit: int | None = None,
45
+ unpack_metadata: bool = True,
46
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
39
47
  ):
40
48
  return cls(
41
- cast(
42
- Iterable[Relationship],
43
- client.relationships(data_set_external_ids=data_set_external_id),
44
- ),
45
- namespace,
49
+ client.relationships(data_set_external_ids=data_set_external_id),
50
+ namespace=namespace,
51
+ to_type=to_type,
52
+ limit=limit,
53
+ unpack_metadata=unpack_metadata,
54
+ skip_metadata_values=skip_metadata_values,
46
55
  )
47
56
 
48
57
  @classmethod
49
- def from_file(cls, file_path: str, namespace: Namespace | None = None):
50
- return cls(RelationshipList.load(Path(file_path).read_text()), namespace)
51
-
52
- def extract(self) -> Iterable[Triple]:
53
- """Extracts an asset with the given asset_id."""
54
- for relationship in self.relationships:
55
- yield from self._relationship2triples(relationship)
58
+ def from_file(
59
+ cls,
60
+ file_path: str,
61
+ namespace: Namespace | None = None,
62
+ to_type: Callable[[Relationship], str | None] | None = None,
63
+ limit: int | None = None,
64
+ unpack_metadata: bool = True,
65
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
66
+ ):
67
+ relationships = RelationshipList.load(Path(file_path).read_text())
68
+ return cls(
69
+ relationships,
70
+ namespace=namespace,
71
+ total=len(relationships),
72
+ to_type=to_type,
73
+ limit=limit,
74
+ unpack_metadata=unpack_metadata,
75
+ skip_metadata_values=skip_metadata_values,
76
+ )
56
77
 
57
- def _relationship2triples(self, relationship: Relationship) -> list[Triple]:
78
+ def _item2triples(self, relationship: Relationship) -> list[Triple]:
58
79
  """Converts an asset to triples."""
59
80
 
60
81
  if relationship.external_id and relationship.source_external_id and relationship.target_external_id:
61
82
  # relationships do not have an internal id, so we generate one
62
83
  id_ = self.namespace[f"Relationship_{create_sha256_hash(relationship.external_id)}"]
63
84
 
85
+ type_ = self._get_rdf_type(relationship)
64
86
  # Set rdf type
65
- triples: list[Triple] = [(id_, RDF.type, self.namespace["Relationship"])]
87
+ triples: list[Triple] = [(id_, RDF.type, self.namespace[type_])]
66
88
 
67
89
  # Set source and target types
68
90
  if source_type := relationship.source_type:
@@ -1,39 +1,36 @@
1
- import json
2
- from collections.abc import Iterable
1
+ from collections.abc import Callable, Set
3
2
  from datetime import datetime, timezone
4
3
  from pathlib import Path
5
- from typing import cast
6
4
 
7
5
  from cognite.client import CogniteClient
8
- from cognite.client.data_classes import Sequence, SequenceList
9
- from pydantic import AnyHttpUrl, ValidationError
10
- from rdflib import RDF, Literal, Namespace, URIRef
6
+ from cognite.client.data_classes import Sequence, SequenceFilter, SequenceList
7
+ from rdflib import RDF, Literal, Namespace
11
8
 
12
- from cognite.neat.constants import DEFAULT_NAMESPACE
13
- from cognite.neat.graph.extractors._base import BaseExtractor
14
9
  from cognite.neat.graph.models import Triple
15
- from cognite.neat.utils.auxiliary import string_to_ideal_type
16
10
 
11
+ from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFExtractor
17
12
 
18
- class SequencesExtractor(BaseExtractor):
13
+
14
+ class SequencesExtractor(ClassicCDFExtractor[Sequence]):
19
15
  """Extract data from Cognite Data Fusions Sequences into Neat.
20
16
 
21
17
  Args:
22
- sequence (Iterable[Sequence]): An iterable of sequences.
18
+ items (Iterable[Sequence]): An iterable of items.
23
19
  namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
20
+ to_type (Callable[[Sequence], str | None], optional): A function to convert an item to a type.
21
+ Defaults to None. If None or if the function returns None, the asset will be set to the default type.
22
+ total (int, optional): The total number of items to load. If passed, you will get a progress bar if rich
23
+ is installed. Defaults to None.
24
+ limit (int, optional): The maximal number of items to load. Defaults to None. This is typically used for
25
+ testing setup of the extractor. For example, if you are extracting 100 000 assets, you might want to
26
+ limit the extraction to 1000 assets to test the setup.
24
27
  unpack_metadata (bool, optional): Whether to unpack metadata. Defaults to False, which yields the metadata as
25
28
  a JSON string.
29
+ skip_metadata_values (set[str] | frozenset[str] | None, optional): If you are unpacking metadata, then
30
+ values in this set will be skipped.
26
31
  """
27
32
 
28
- def __init__(
29
- self,
30
- sequence: Iterable[Sequence],
31
- namespace: Namespace | None = None,
32
- unpack_metadata: bool = True,
33
- ):
34
- self.namespace = namespace or DEFAULT_NAMESPACE
35
- self.sequence = sequence
36
- self.unpack_metadata = unpack_metadata
33
+ _default_rdf_type = "Sequence"
37
34
 
38
35
  @classmethod
39
36
  def from_dataset(
@@ -41,15 +38,22 @@ class SequencesExtractor(BaseExtractor):
41
38
  client: CogniteClient,
42
39
  data_set_external_id: str,
43
40
  namespace: Namespace | None = None,
41
+ to_type: Callable[[Sequence], str | None] | None = None,
42
+ limit: int | None = None,
44
43
  unpack_metadata: bool = True,
44
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
45
45
  ):
46
+ total = client.sequences.aggregate_count(
47
+ filter=SequenceFilter(data_set_ids=[{"externalId": data_set_external_id}])
48
+ )
46
49
  return cls(
47
- cast(
48
- Iterable[Sequence],
49
- client.sequences(data_set_external_ids=data_set_external_id),
50
- ),
51
- namespace,
52
- unpack_metadata,
50
+ client.sequences(data_set_external_ids=data_set_external_id),
51
+ total=total,
52
+ namespace=namespace,
53
+ to_type=to_type,
54
+ limit=limit,
55
+ unpack_metadata=unpack_metadata,
56
+ skip_metadata_values=skip_metadata_values,
53
57
  )
54
58
 
55
59
  @classmethod
@@ -57,20 +61,28 @@ class SequencesExtractor(BaseExtractor):
57
61
  cls,
58
62
  file_path: str,
59
63
  namespace: Namespace | None = None,
64
+ to_type: Callable[[Sequence], str | None] | None = None,
65
+ limit: int | None = None,
60
66
  unpack_metadata: bool = True,
67
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
61
68
  ):
62
- return cls(SequenceList.load(Path(file_path).read_text()), namespace, unpack_metadata)
63
-
64
- def extract(self) -> Iterable[Triple]:
65
- """Extract sequences as triples."""
66
- for sequence in self.sequence:
67
- yield from self._sequence2triples(sequence)
69
+ sequences = SequenceList.load(Path(file_path).read_text())
70
+ return cls(
71
+ sequences,
72
+ total=len(sequences),
73
+ namespace=namespace,
74
+ to_type=to_type,
75
+ limit=limit,
76
+ unpack_metadata=unpack_metadata,
77
+ skip_metadata_values=skip_metadata_values,
78
+ )
68
79
 
69
- def _sequence2triples(self, sequence: Sequence) -> list[Triple]:
80
+ def _item2triples(self, sequence: Sequence) -> list[Triple]:
70
81
  id_ = self.namespace[f"Sequence_{sequence.id}"]
71
82
 
83
+ type_ = self._get_rdf_type(sequence)
72
84
  # Set rdf type
73
- triples: list[Triple] = [(id_, RDF.type, self.namespace.Sequence)]
85
+ triples: list[Triple] = [(id_, RDF.type, self.namespace[type_])]
74
86
 
75
87
  # Create attributes
76
88
 
@@ -81,22 +93,7 @@ class SequencesExtractor(BaseExtractor):
81
93
  triples.append((id_, self.namespace.name, Literal(sequence.name)))
82
94
 
83
95
  if sequence.metadata:
84
- if self.unpack_metadata:
85
- for key, value in sequence.metadata.items():
86
- if value:
87
- type_aware_value = string_to_ideal_type(value)
88
- try:
89
- triples.append((id_, self.namespace[key], URIRef(str(AnyHttpUrl(type_aware_value))))) # type: ignore
90
- except ValidationError:
91
- triples.append((id_, self.namespace[key], Literal(type_aware_value)))
92
- else:
93
- triples.append(
94
- (
95
- id_,
96
- self.namespace.metadata,
97
- Literal(json.dumps(sequence.metadata)),
98
- )
99
- )
96
+ triples.extend(self._metadata_to_triples(id_, sequence.metadata))
100
97
 
101
98
  if sequence.description:
102
99
  triples.append((id_, self.namespace.description, Literal(sequence.description)))