cognite-neat 0.103.0__py3-none-any.whl → 0.104.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 (64) hide show
  1. cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
  2. cognite/neat/_graph/transformers/_base.py +109 -1
  3. cognite/neat/_graph/transformers/_classic_cdf.py +4 -0
  4. cognite/neat/_graph/transformers/_prune_graph.py +103 -47
  5. cognite/neat/_graph/transformers/_rdfpath.py +41 -17
  6. cognite/neat/_graph/transformers/_value_type.py +119 -154
  7. cognite/neat/_issues/_base.py +35 -8
  8. cognite/neat/_issues/warnings/_resources.py +1 -1
  9. cognite/neat/_rules/_shared.py +18 -34
  10. cognite/neat/_rules/exporters/_base.py +28 -2
  11. cognite/neat/_rules/exporters/_rules2dms.py +4 -0
  12. cognite/neat/_rules/exporters/_rules2excel.py +11 -0
  13. cognite/neat/_rules/exporters/_rules2instance_template.py +4 -0
  14. cognite/neat/_rules/exporters/_rules2ontology.py +13 -1
  15. cognite/neat/_rules/exporters/_rules2yaml.py +4 -0
  16. cognite/neat/_rules/importers/_base.py +9 -0
  17. cognite/neat/_rules/importers/_dms2rules.py +17 -5
  18. cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +5 -2
  19. cognite/neat/_rules/importers/_rdf/_base.py +10 -8
  20. cognite/neat/_rules/importers/_rdf/_imf2rules.py +4 -0
  21. cognite/neat/_rules/importers/_rdf/_inference2rules.py +7 -0
  22. cognite/neat/_rules/importers/_rdf/_owl2rules.py +4 -0
  23. cognite/neat/_rules/importers/_spreadsheet2rules.py +17 -8
  24. cognite/neat/_rules/importers/_yaml2rules.py +21 -7
  25. cognite/neat/_rules/models/_base_input.py +1 -1
  26. cognite/neat/_rules/models/_base_rules.py +5 -0
  27. cognite/neat/_rules/models/dms/_rules.py +4 -0
  28. cognite/neat/_rules/models/dms/_rules_input.py +9 -0
  29. cognite/neat/_rules/models/dms/_validation.py +2 -0
  30. cognite/neat/_rules/models/entities/_single_value.py +25 -5
  31. cognite/neat/_rules/models/information/_rules.py +4 -0
  32. cognite/neat/_rules/models/information/_rules_input.py +9 -0
  33. cognite/neat/_rules/models/mapping/_classic2core.py +2 -5
  34. cognite/neat/_rules/transformers/__init__.py +5 -4
  35. cognite/neat/_rules/transformers/_base.py +41 -65
  36. cognite/neat/_rules/transformers/_converters.py +149 -62
  37. cognite/neat/_rules/transformers/_mapping.py +17 -12
  38. cognite/neat/_rules/transformers/_verification.py +50 -37
  39. cognite/neat/_session/_base.py +32 -121
  40. cognite/neat/_session/_inspect.py +3 -3
  41. cognite/neat/_session/_mapping.py +17 -105
  42. cognite/neat/_session/_prepare.py +36 -254
  43. cognite/neat/_session/_read.py +11 -130
  44. cognite/neat/_session/_set.py +6 -30
  45. cognite/neat/_session/_show.py +49 -30
  46. cognite/neat/_session/_state.py +49 -107
  47. cognite/neat/_session/_to.py +42 -31
  48. cognite/neat/_shared.py +23 -2
  49. cognite/neat/_store/_provenance.py +3 -82
  50. cognite/neat/_store/_rules_store.py +372 -10
  51. cognite/neat/_store/exceptions.py +23 -0
  52. cognite/neat/_utils/graph_transformations_report.py +36 -0
  53. cognite/neat/_utils/io_.py +11 -0
  54. cognite/neat/_utils/rdf_.py +8 -0
  55. cognite/neat/_utils/spreadsheet.py +5 -4
  56. cognite/neat/_version.py +1 -1
  57. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +7 -7
  58. cognite/neat/_workflows/steps/lib/current/rules_importer.py +24 -99
  59. {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/METADATA +1 -1
  60. {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/RECORD +63 -61
  61. cognite/neat/_rules/transformers/_pipelines.py +0 -70
  62. {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/LICENSE +0 -0
  63. {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/WHEEL +0 -0
  64. {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/entry_points.txt +0 -0
@@ -5,17 +5,18 @@ from urllib.parse import quote
5
5
 
6
6
  import rdflib
7
7
  from rdflib import RDF, XSD, Graph, Namespace, URIRef
8
+ from rdflib.query import ResultRow
8
9
 
9
10
  from cognite.neat._constants import UNKNOWN_TYPE
10
11
  from cognite.neat._graph.queries import Queries
11
- from cognite.neat._issues.warnings import NeatValueWarning, PropertyDataTypeConversionWarning
12
+ from cognite.neat._issues.warnings import PropertyDataTypeConversionWarning
12
13
  from cognite.neat._utils.auxiliary import string_to_ideal_type
13
- from cognite.neat._utils.collection_ import iterate_progress_bar
14
14
  from cognite.neat._utils.rdf_ import get_namespace, remove_namespace_from_uri
15
15
 
16
- from ._base import BaseTransformer
16
+ from ._base import BaseTransformer, BaseTransformerStandardised, RowTransformationOutput
17
17
 
18
18
 
19
+ # TODO: Standardise
19
20
  class SplitMultiValueProperty(BaseTransformer):
20
21
  description: str = (
21
22
  "SplitMultiValueProperty is a transformer that splits a "
@@ -75,34 +76,11 @@ class SplitMultiValueProperty(BaseTransformer):
75
76
  graph.add((s, new_property, o))
76
77
 
77
78
 
78
- class ConvertLiteral(BaseTransformer):
79
+ class ConvertLiteral(BaseTransformerStandardised):
79
80
  description: str = "ConvertLiteral is a transformer that improve data typing of a literal value."
80
81
  _use_only_once: bool = False
81
82
  _need_changes = frozenset({})
82
83
 
83
- _count_by_properties = """SELECT (COUNT(?value) AS ?valueCount)
84
- WHERE {{
85
- ?instance a <{subject_type}> .
86
- ?instance <{subject_predicate}> ?value
87
- FILTER(isLiteral(?value))
88
- }}"""
89
-
90
- _count_by_properties_uri = """SELECT (COUNT(?value) AS ?valueCount)
91
- WHERE {{
92
- ?instance a <{subject_type}> .
93
- ?instance <{subject_predicate}> ?value
94
- FILTER(isIRI(?value))
95
- }}"""
96
-
97
- _properties = """SELECT ?instance ?value
98
- WHERE {{
99
- ?instance a <{subject_type}> .
100
- ?instance <{subject_predicate}> ?value
101
-
102
- FILTER(isLiteral(?value))
103
-
104
- }}"""
105
-
106
84
  def __init__(
107
85
  self,
108
86
  subject_type: URIRef,
@@ -115,89 +93,55 @@ class ConvertLiteral(BaseTransformer):
115
93
  self._type_name = remove_namespace_from_uri(subject_type)
116
94
  self._property_name = remove_namespace_from_uri(subject_predicate)
117
95
 
118
- def transform(self, graph: Graph) -> None:
119
- count_connection_query = self._count_by_properties_uri.format(
120
- subject_type=self.subject_type, subject_predicate=self.subject_predicate
121
- )
122
- connection_count_res = list(graph.query(count_connection_query))
123
- connection_count = int(connection_count_res[0][0]) # type: ignore [index, arg-type]
124
-
125
- if connection_count > 0:
96
+ def _skip_count_query(self) -> str:
97
+ query = """SELECT (COUNT(?value) AS ?valueCount)
98
+ WHERE {{
99
+ ?instance a <{subject_type}> .
100
+ ?instance <{subject_predicate}> ?value
101
+ FILTER(isIRI(?value))
102
+ }}"""
103
+ return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
104
+
105
+ def _count_query(self) -> str:
106
+ query = """SELECT (COUNT(?value) AS ?valueCount)
107
+ WHERE {{
108
+ ?instance a <{subject_type}> .
109
+ ?instance <{subject_predicate}> ?value
110
+ FILTER(isLiteral(?value))
111
+ }}"""
112
+ return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
113
+
114
+ def _iterate_query(self) -> str:
115
+ query = """SELECT ?instance ?value
116
+ WHERE {{
117
+ ?instance a <{subject_type}> .
118
+ ?instance <{subject_predicate}> ?value
119
+ FILTER(isLiteral(?value))
120
+ }}"""
121
+ return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
122
+
123
+ def operation(self, query_result_row: ResultRow) -> RowTransformationOutput:
124
+ row_output = RowTransformationOutput()
125
+
126
+ instance, literal = query_result_row
127
+ value = cast(rdflib.Literal, literal).toPython()
128
+
129
+ try:
130
+ converted_value = self.conversion(value)
131
+ except Exception as e:
126
132
  warnings.warn(
127
- NeatValueWarning(
128
- f"Skipping {connection_count} of {self._type_name}.{self._property_name} "
129
- f"as these are connections and not data values."
130
- ),
133
+ PropertyDataTypeConversionWarning(str(instance), self._type_name, self._property_name, str(e)),
131
134
  stacklevel=2,
132
135
  )
136
+ row_output.add_triples.append((instance, self.subject_predicate, rdflib.Literal(converted_value))) # type: ignore[arg-type]
137
+ row_output.remove_triples.append((instance, self.subject_predicate, literal)) # type: ignore[arg-type]
138
+ row_output.instances_modified_count += 1
139
+
140
+ return row_output
133
141
 
134
- count_query = self._count_by_properties.format(
135
- subject_type=self.subject_type, subject_predicate=self.subject_predicate
136
- )
137
-
138
- property_count_res = list(graph.query(count_query))
139
- property_count = int(property_count_res[0][0]) # type: ignore [index, arg-type]
140
- iterate_query = self._properties.format(
141
- subject_type=self.subject_type, subject_predicate=self.subject_predicate
142
- )
143
-
144
- for instance, literal in iterate_progress_bar( # type: ignore[misc]
145
- graph.query(iterate_query),
146
- total=property_count,
147
- description=f"Converting {self._type_name}.{self._property_name}.",
148
- ):
149
- value = cast(rdflib.Literal, literal).toPython()
150
- try:
151
- converted_value = self.conversion(value)
152
- except Exception as e:
153
- warnings.warn(
154
- PropertyDataTypeConversionWarning(str(instance), self._type_name, self._property_name, str(e)),
155
- stacklevel=2,
156
- )
157
- continue
158
-
159
- graph.add((instance, self.subject_predicate, rdflib.Literal(converted_value)))
160
- graph.remove((instance, self.subject_predicate, literal))
161
-
162
-
163
- class LiteralToEntity(BaseTransformer):
164
- description = "Converts a literal value to new entity"
165
142
 
166
- _count_properties_of_type = """SELECT (COUNT(?property) AS ?propertyCount)
167
- WHERE {{
168
- ?instance a <{subject_type}> .
169
- ?instance <{subject_predicate}> ?property
170
- FILTER(isLiteral(?property))
171
- }}"""
172
- _count_connections_of_type = """SELECT (COUNT(?property) AS ?propertyCount)
173
- WHERE {{
174
- ?instance a <{subject_type}> .
175
- ?instance <{subject_predicate}> ?property
176
- FILTER(isIRI(?property))
177
- }}"""
178
-
179
- _properties_of_type = """SELECT ?instance ?property
180
- WHERE {{
181
- ?instance a <{subject_type}> .
182
- ?instance <{subject_predicate}> ?property
183
- FILTER(isLiteral(?property))
184
- }}"""
185
-
186
- _count_properties = """SELECT (COUNT(?property) AS ?propertyCount)
187
- WHERE {{
188
- ?instance <{subject_predicate}> ?property
189
- FILTER(isLiteral(?property))
190
- }}"""
191
- _count_connections = """SELECT (COUNT(?property) AS ?propertyCount)
192
- WHERE {{
193
- ?instance <{subject_predicate}> ?property
194
- FILTER(isIRI(?property))
195
- }}"""
196
- _properties = """SELECT ?instance ?property
197
- WHERE {{
198
- ?instance <{subject_predicate}> ?property
199
- FILTER(isLiteral(?property))
200
- }}"""
143
+ class LiteralToEntity(BaseTransformerStandardised):
144
+ description = "Converts a literal value to new entity"
201
145
 
202
146
  def __init__(
203
147
  self, subject_type: URIRef | None, subject_predicate: URIRef, entity_type: str, new_property: str | None = None
@@ -207,54 +151,75 @@ class LiteralToEntity(BaseTransformer):
207
151
  self.entity_type = entity_type
208
152
  self.new_property = new_property
209
153
 
210
- def transform(self, graph: Graph) -> None:
154
+ def _iterate_query(self) -> str:
211
155
  if self.subject_type is None:
212
- count_query = self._count_properties.format(subject_predicate=self.subject_predicate)
213
- iterate_query = self._properties.format(subject_predicate=self.subject_predicate)
214
- connection_count_query = self._count_connections.format(subject_predicate=self.subject_predicate)
156
+ query = """SELECT ?instance ?property
157
+ WHERE {{
158
+ ?instance <{subject_predicate}> ?property
159
+ FILTER(isLiteral(?property))
160
+ }}"""
161
+ return query.format(subject_predicate=self.subject_predicate)
215
162
  else:
216
- count_query = self._count_properties_of_type.format(
217
- subject_type=self.subject_type, subject_predicate=self.subject_predicate
218
- )
219
- iterate_query = self._properties_of_type.format(
220
- subject_type=self.subject_type, subject_predicate=self.subject_predicate
221
- )
222
- connection_count_query = self._count_connections_of_type.format(
223
- subject_type=self.subject_type, subject_predicate=self.subject_predicate
224
- )
225
-
226
- connection_count_res = list(graph.query(connection_count_query))
227
- connection_count = int(connection_count_res[0][0]) # type: ignore [index, arg-type]
228
- if connection_count > 0:
229
- warnings.warn(
230
- NeatValueWarning(
231
- f"Skipping {connection_count} of {remove_namespace_from_uri(self.subject_predicate)} "
232
- f"as these are connections and not data values."
233
- ),
234
- stacklevel=2,
235
- )
236
-
237
- property_count_res = list(graph.query(count_query))
238
- property_count = int(property_count_res[0][0]) # type: ignore [index, arg-type]
239
-
240
- instance: URIRef
241
- description = f"Creating {remove_namespace_from_uri(self.subject_predicate)}."
242
- if self.subject_type is not None:
243
- description = (
244
- f"Creating {remove_namespace_from_uri(self.subject_type)}."
245
- f"{remove_namespace_from_uri(self.subject_predicate)}."
246
- )
247
- for instance, literal in iterate_progress_bar( # type: ignore[misc, assignment]
248
- graph.query(iterate_query),
249
- total=property_count,
250
- description=description,
251
- ):
252
- value = cast(rdflib.Literal, literal).toPython()
253
- namespace = Namespace(get_namespace(instance))
254
- entity_type = namespace[self.entity_type]
255
- new_entity = namespace[f"{self.entity_type}_{quote(value)!s}"]
256
- graph.add((new_entity, RDF.type, entity_type))
257
- if self.new_property is not None:
258
- graph.add((new_entity, namespace[self.new_property], rdflib.Literal(value)))
259
- graph.add((instance, self.subject_predicate, new_entity))
260
- graph.remove((instance, self.subject_predicate, literal))
163
+ query = """SELECT ?instance ?property
164
+ WHERE {{
165
+ ?instance a <{subject_type}> .
166
+ ?instance <{subject_predicate}> ?property
167
+ FILTER(isLiteral(?property))
168
+ }}"""
169
+ return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
170
+
171
+ def _skip_count_query(self) -> str:
172
+ if self.subject_type is None:
173
+ query = """SELECT (COUNT(?property) AS ?propertyCount)
174
+ WHERE {{
175
+ ?instance <{subject_predicate}> ?property
176
+ FILTER(isIRI(?property))
177
+ }}"""
178
+ return query.format(subject_predicate=self.subject_predicate)
179
+ else:
180
+ query = """SELECT (COUNT(?property) AS ?propertyCount)
181
+ WHERE {{
182
+ ?instance a <{subject_type}> .
183
+ ?instance <{subject_predicate}> ?property
184
+ FILTER(isIRI(?property))
185
+ }}"""
186
+ return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
187
+
188
+ def _count_query(self) -> str:
189
+ if self.subject_type is None:
190
+ query = """SELECT (COUNT(?property) AS ?propertyCount)
191
+ WHERE {{
192
+ ?instance <{subject_predicate}> ?property
193
+ FILTER(isLiteral(?property))
194
+ }}"""
195
+ return query.format(subject_predicate=self.subject_predicate)
196
+ else:
197
+ query = """SELECT (COUNT(?property) AS ?propertyCount)
198
+ WHERE {{
199
+ ?instance a <{subject_type}> .
200
+ ?instance <{subject_predicate}> ?property
201
+ FILTER(isLiteral(?property))
202
+ }}"""
203
+
204
+ return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
205
+
206
+ def operation(self, query_result_row: ResultRow) -> RowTransformationOutput:
207
+ row_output = RowTransformationOutput()
208
+
209
+ instance, literal = query_result_row
210
+ value = cast(rdflib.Literal, literal).toPython()
211
+ namespace = Namespace(get_namespace(instance)) # type: ignore[arg-type]
212
+ entity_type = namespace[self.entity_type]
213
+ new_entity = namespace[f"{self.entity_type}_{quote(value)!s}"]
214
+ row_output.add_triples.append((new_entity, RDF.type, entity_type))
215
+ row_output.instances_added_count += 1 # we add one new entity
216
+
217
+ if self.new_property is not None:
218
+ row_output.add_triples.append((new_entity, namespace[self.new_property], rdflib.Literal(value))) # type: ignore[arg-type]
219
+ row_output.instances_modified_count += 1 # we modify the new entity
220
+
221
+ row_output.add_triples.append((instance, self.subject_predicate, new_entity)) # type: ignore[arg-type]
222
+ row_output.remove_triples.append((instance, self.subject_predicate, literal)) # type: ignore[arg-type]
223
+ row_output.instances_modified_count += 1 # we modify the old entity
224
+
225
+ return row_output
@@ -390,11 +390,16 @@ class NeatIssueList(list, Sequence[T_NeatIssue], ABC):
390
390
  """This is a generic list of NeatIssues."""
391
391
 
392
392
  def __init__(
393
- self, issues: Sequence[T_NeatIssue] | None = None, title: str | None = None, action: str | None = None
393
+ self,
394
+ issues: Sequence[T_NeatIssue] | None = None,
395
+ title: str | None = None,
396
+ action: str | None = None,
397
+ hint: str | None = None,
394
398
  ):
395
399
  super().__init__(issues or [])
396
400
  self.title = title
397
401
  self.action = action
402
+ self.hint = hint
398
403
 
399
404
  @property
400
405
  def errors(self) -> Self:
@@ -406,6 +411,11 @@ class NeatIssueList(list, Sequence[T_NeatIssue], ABC):
406
411
  """Return True if this list contains any errors."""
407
412
  return any(isinstance(issue, NeatError) for issue in self)
408
413
 
414
+ @property
415
+ def has_warnings(self) -> bool:
416
+ """Return True if this list contains any warnings."""
417
+ return any(isinstance(issue, NeatWarning) for issue in self)
418
+
409
419
  @property
410
420
  def warnings(self) -> Self:
411
421
  """Return all the warnings in this list."""
@@ -455,14 +465,31 @@ class IssueList(NeatIssueList[NeatIssue]):
455
465
  """This is a list of NeatIssues."""
456
466
 
457
467
  def _repr_html_(self) -> str | None:
458
- if not self:
459
- return "<p>'No issues found'</p>"
460
- df = self.to_pandas()
461
- agg_df = df["NeatIssue"].value_counts().to_frame()
462
- if len(agg_df) < 10:
463
- return agg_df._repr_html_() # type: ignore[operator]
468
+ if self.action and not self:
469
+ header = f"Success: {self.action}"
470
+ elif self.action and self.has_errors:
471
+ header = f"Failed: {self.action}"
472
+ elif self.action and self.has_warnings:
473
+ header = f"Succeeded with warnings: {self.action}"
474
+ elif not self:
475
+ header = "No issues found"
464
476
  else:
465
- return agg_df.head()._repr_html_() # type: ignore[operator]
477
+ header = ""
478
+
479
+ body = f"<p>{header}</p>"
480
+
481
+ if self:
482
+ df = self.to_pandas()
483
+ agg_df = df["NeatIssue"].value_counts().to_frame()
484
+ if len(agg_df) < 10:
485
+ table = agg_df._repr_html_() # type: ignore[operator]
486
+ else:
487
+ table = agg_df.head()._repr_html_() # type: ignore[operator]
488
+ body += f"<br />{table}"
489
+
490
+ if self.hint:
491
+ body += f"<br />Hint: {self.hint}"
492
+ return body
466
493
 
467
494
 
468
495
  T_Cls = TypeVar("T_Cls")
@@ -21,7 +21,7 @@ class ResourceRegexViolationWarning(ResourceNeatWarning):
21
21
 
22
22
  fix = (
23
23
  "Either export the data model and make the necessary changes manually"
24
- " or run prepare.cdf_compliant_external_ids."
24
+ " or run prepare.data_model.cdf_compliant_external_ids."
25
25
  )
26
26
 
27
27
  location: str
@@ -1,8 +1,6 @@
1
- from abc import ABC, abstractmethod
2
1
  from dataclasses import dataclass
3
2
  from typing import Any, Generic, TypeAlias, TypeVar
4
3
 
5
- from cognite.neat._issues import IssueList
6
4
  from cognite.neat._rules.models import (
7
5
  DMSRules,
8
6
  InformationRules,
@@ -11,46 +9,32 @@ from cognite.neat._rules.models.dms._rules_input import DMSInputRules
11
9
  from cognite.neat._rules.models.information._rules_input import InformationInputRules
12
10
 
13
11
  VerifiedRules: TypeAlias = InformationRules | DMSRules
14
- InputRules: TypeAlias = DMSInputRules | InformationInputRules
15
- Rules: TypeAlias = DMSInputRules | InformationInputRules | InformationRules | DMSRules
16
- T_Rules = TypeVar("T_Rules", bound=Rules)
12
+
17
13
  T_VerifiedRules = TypeVar("T_VerifiedRules", bound=VerifiedRules)
14
+ InputRules: TypeAlias = DMSInputRules | InformationInputRules
18
15
  T_InputRules = TypeVar("T_InputRules", bound=InputRules)
19
16
 
20
17
 
21
18
  @dataclass
22
- class OutRules(Generic[T_Rules], ABC):
23
- """This is a base class for all rule states."""
24
-
25
- @abstractmethod
26
- def get_rules(self) -> T_Rules | None:
27
- """Get the rules from the state."""
28
- raise NotImplementedError()
29
-
30
-
31
- @dataclass
32
- class JustRules(OutRules[T_Rules]):
33
- """This represents a rule that exists"""
19
+ class ReadRules(Generic[T_InputRules]):
20
+ """This represents a rules that has been read."""
34
21
 
35
- rules: T_Rules
36
-
37
- def get_rules(self) -> T_Rules:
38
- return self.rules
39
-
40
-
41
- @dataclass
42
- class MaybeRules(OutRules[T_Rules]):
43
- """This represents a rule that may or may not exist"""
22
+ rules: T_InputRules | None
23
+ read_context: dict[str, Any]
44
24
 
45
- rules: T_Rules | None
46
- issues: IssueList
25
+ @classmethod
26
+ def display_type_name(cls) -> str:
27
+ return "UnverifiedModel"
47
28
 
48
- def get_rules(self) -> T_Rules | None:
49
- return self.rules
29
+ @property
30
+ def display_name(self):
31
+ if self.rules is None:
32
+ return "FailedRead"
33
+ return self.rules.display_name
50
34
 
51
35
 
52
- @dataclass
53
- class ReadRules(MaybeRules[T_Rules]):
54
- """This represents a rule that does not exist"""
36
+ ReadInputRules: TypeAlias = ReadRules[DMSInputRules] | ReadRules[InformationInputRules]
37
+ T_ReadInputRules = TypeVar("T_ReadInputRules", bound=ReadInputRules)
55
38
 
56
- read_context: dict[str, Any]
39
+ Rules: TypeAlias = InformationRules | DMSRules | ReadRules[DMSInputRules] | ReadRules[InformationInputRules]
40
+ T_Rules = TypeVar("T_Rules", bound=Rules)
@@ -1,13 +1,19 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Iterable
3
+ from functools import lru_cache
3
4
  from pathlib import Path
4
- from typing import Generic, TypeVar
5
+ from types import UnionType
6
+ from typing import TYPE_CHECKING, Generic, TypeVar, Union, get_args, get_origin
5
7
 
6
8
  from cognite.neat._client import NeatClient
9
+ from cognite.neat._constants import DEFAULT_NAMESPACE
7
10
  from cognite.neat._rules._shared import T_VerifiedRules
8
11
  from cognite.neat._utils.auxiliary import class_html_doc
9
12
  from cognite.neat._utils.upload import UploadResult, UploadResultList
10
13
 
14
+ if TYPE_CHECKING:
15
+ from cognite.neat._store._provenance import Agent as ProvenanceAgent
16
+
11
17
  T_Export = TypeVar("T_Export")
12
18
 
13
19
 
@@ -27,8 +33,28 @@ class BaseExporter(ABC, Generic[T_VerifiedRules, T_Export]):
27
33
  def _repr_html_(cls) -> str:
28
34
  return class_html_doc(cls, include_factory_methods=False)
29
35
 
36
+ @property
37
+ def agent(self) -> "ProvenanceAgent":
38
+ """Provenance agent for the importer."""
39
+ from cognite.neat._store._provenance import Agent as ProvenanceAgent
40
+
41
+ return ProvenanceAgent(id_=DEFAULT_NAMESPACE[f"agent/{type(self).__name__}"])
42
+
43
+ @property
44
+ def description(self) -> str:
45
+ return "MISSING DESCRIPTION"
46
+
47
+ @classmethod
48
+ @lru_cache(maxsize=1)
49
+ def source_types(cls) -> tuple[type, ...]:
50
+ base_exporter = cls.__orig_bases__[0] # type: ignore[attr-defined]
51
+ source_type = get_args(base_exporter)[0]
52
+ if get_origin(source_type) in [Union, UnionType]:
53
+ return get_args(source_type)
54
+ return (source_type,)
55
+
30
56
 
31
- class CDFExporter(BaseExporter[T_VerifiedRules, T_Export]):
57
+ class CDFExporter(BaseExporter[T_VerifiedRules, T_Export], ABC):
32
58
  @abstractmethod
33
59
  def export_to_cdf_iterable(
34
60
  self, rules: T_VerifiedRules, client: NeatClient, dry_run: bool = False
@@ -109,6 +109,10 @@ class DMSExporter(CDFExporter[DMSRules, DMSSchema]):
109
109
  self._schema: DMSSchema | None = None
110
110
  self.remove_cdf_spaces = remove_cdf_spaces
111
111
 
112
+ @property
113
+ def description(self) -> str:
114
+ return "Export verified DMS Model to CDF."
115
+
112
116
  def export_to_file(self, rules: DMSRules, filepath: Path) -> None:
113
117
  """Export the rules to a file(s).
114
118
 
@@ -68,6 +68,7 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
68
68
  styling: Style = "default",
69
69
  new_model_id: tuple[str, str] | None = None,
70
70
  sheet_prefix: str | None = None,
71
+ reference_rules_with_prefix: tuple[VerifiedRules, str] | None = None,
71
72
  ):
72
73
  self.sheet_prefix = sheet_prefix or ""
73
74
  if styling not in self.style_options:
@@ -75,6 +76,11 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
75
76
  self.styling = styling
76
77
  self._styling_level = self.style_options.index(styling)
77
78
  self.new_model_id = new_model_id
79
+ self.reference_rules_with_prefix = reference_rules_with_prefix
80
+
81
+ @property
82
+ def description(self) -> str:
83
+ return "Export verified model to Excel."
78
84
 
79
85
  def export_to_file(self, rules: VerifiedRules, filepath: Path) -> None:
80
86
  """Exports transformation rules to excel file."""
@@ -94,6 +100,11 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
94
100
 
95
101
  self._write_metadata_sheet(workbook, dumped_user_rules["Metadata"], sheet_prefix=self.sheet_prefix)
96
102
  self._write_sheets(workbook, dumped_user_rules, rules, sheet_prefix=self.sheet_prefix)
103
+ if self.reference_rules_with_prefix:
104
+ reference_rules, prefix = self.reference_rules_with_prefix
105
+ dumped_reference_rules = reference_rules.dump(entities_exclude_defaults=False, by_alias=True)
106
+ self._write_sheets(workbook, dumped_reference_rules, reference_rules, sheet_prefix=prefix)
107
+ self._write_metadata_sheet(workbook, dumped_reference_rules["Metadata"], sheet_prefix=prefix)
97
108
 
98
109
  if isinstance(rules, InformationRules) and rules.prefixes:
99
110
  self._write_prefixes_sheet(workbook, rules.prefixes)
@@ -42,6 +42,10 @@ class InstanceTemplateExporter(BaseExporter[InformationRules, Workbook]):
42
42
  self.auto_identifier_type = auto_identifier_type
43
43
  self.add_drop_down_list = add_drop_down_list
44
44
 
45
+ @property
46
+ def description(self) -> str:
47
+ return "Export verified information instance template to Excel."
48
+
45
49
  def export(
46
50
  self,
47
51
  rules: InformationRules,
@@ -46,6 +46,10 @@ class OWLExporter(GraphExporter):
46
46
  def export(self, rules: InformationRules) -> Graph:
47
47
  return Ontology.from_rules(rules).as_owl()
48
48
 
49
+ @property
50
+ def description(self) -> str:
51
+ return "Export verified information model to OWL."
52
+
49
53
 
50
54
  class SHACLExporter(GraphExporter):
51
55
  """Exports rules to a SHACL graph."""
@@ -53,13 +57,21 @@ class SHACLExporter(GraphExporter):
53
57
  def export(self, rules: InformationRules) -> Graph:
54
58
  return Ontology.from_rules(rules).as_shacl()
55
59
 
60
+ @property
61
+ def description(self) -> str:
62
+ return "Export verified information model to SHACL."
63
+
56
64
 
57
65
  class SemanticDataModelExporter(GraphExporter):
58
- """Exports verified information rules to a semantic data model."""
66
+ """Exports verified information model to a semantic data model."""
59
67
 
60
68
  def export(self, rules: InformationRules) -> Graph:
61
69
  return Ontology.from_rules(rules).as_semantic_data_model()
62
70
 
71
+ @property
72
+ def description(self) -> str:
73
+ return "Export verified information model to a semantic data model."
74
+
63
75
 
64
76
  class OntologyModel(BaseModel):
65
77
  model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True, strict=False, extra="allow")
@@ -43,6 +43,10 @@ class YAMLExporter(BaseExporter[VerifiedRules, str]):
43
43
  self.files = files
44
44
  self.output = output
45
45
 
46
+ @property
47
+ def description(self) -> str:
48
+ return "Export verified model to YAML."
49
+
46
50
  def export_to_file(self, rules: VerifiedRules, filepath: Path) -> None:
47
51
  """Exports transformation rules to YAML/JSON file(s)."""
48
52
  if self.files == "single":
@@ -6,6 +6,7 @@ from datetime import datetime
6
6
  from typing import TYPE_CHECKING, Any, Generic, Literal
7
7
 
8
8
  from pydantic import ValidationError
9
+ from rdflib import URIRef
9
10
 
10
11
  from cognite.neat._constants import DEFAULT_NAMESPACE
11
12
  from cognite.neat._issues import IssueList, NeatError, NeatWarning
@@ -56,6 +57,14 @@ class BaseImporter(ABC, Generic[T_InputRules]):
56
57
 
57
58
  return ProvenanceAgent(id_=DEFAULT_NAMESPACE[f"agent/{type(self).__name__}"])
58
59
 
60
+ @property
61
+ def description(self) -> str:
62
+ return "MISSING DESCRIPTION"
63
+
64
+ @property
65
+ def source_uri(self) -> URIRef:
66
+ return DEFAULT_NAMESPACE["UNKNOWN"]
67
+
59
68
 
60
69
  class _FutureResult:
61
70
  def __init__(self) -> None: