cognite-neat 0.87.4__py3-none-any.whl → 0.88.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 (132) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/app/api/data_classes/rest.py +0 -19
  3. cognite/neat/app/api/explorer.py +6 -4
  4. cognite/neat/app/api/routers/crud.py +11 -21
  5. cognite/neat/app/api/routers/workflows.py +24 -94
  6. cognite/neat/graph/extractors/_classic_cdf/_assets.py +8 -2
  7. cognite/neat/graph/extractors/_mock_graph_generator.py +2 -2
  8. cognite/neat/graph/loaders/_base.py +17 -12
  9. cognite/neat/graph/loaders/_rdf2asset.py +223 -58
  10. cognite/neat/graph/loaders/_rdf2dms.py +1 -1
  11. cognite/neat/graph/stores/_base.py +5 -0
  12. cognite/neat/rules/analysis/_asset.py +31 -1
  13. cognite/neat/rules/importers/_inference2rules.py +31 -35
  14. cognite/neat/rules/models/information/_rules.py +1 -1
  15. cognite/neat/workflows/steps/data_contracts.py +17 -43
  16. cognite/neat/workflows/steps/lib/current/graph_extractor.py +28 -24
  17. cognite/neat/workflows/steps/lib/current/graph_loader.py +4 -21
  18. cognite/neat/workflows/steps/lib/current/graph_store.py +18 -134
  19. cognite/neat/workflows/steps_registry.py +5 -7
  20. {cognite_neat-0.87.4.dist-info → cognite_neat-0.88.0.dist-info}/METADATA +1 -1
  21. {cognite_neat-0.87.4.dist-info → cognite_neat-0.88.0.dist-info}/RECORD +24 -132
  22. cognite/neat/app/api/routers/core.py +0 -91
  23. cognite/neat/app/api/routers/data_exploration.py +0 -336
  24. cognite/neat/app/api/routers/rules.py +0 -203
  25. cognite/neat/legacy/__init__.py +0 -0
  26. cognite/neat/legacy/graph/__init__.py +0 -3
  27. cognite/neat/legacy/graph/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -20182
  28. cognite/neat/legacy/graph/examples/Knowledge-Graph-Nordic44.xml +0 -20163
  29. cognite/neat/legacy/graph/examples/__init__.py +0 -10
  30. cognite/neat/legacy/graph/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
  31. cognite/neat/legacy/graph/exceptions.py +0 -90
  32. cognite/neat/legacy/graph/extractors/__init__.py +0 -6
  33. cognite/neat/legacy/graph/extractors/_base.py +0 -14
  34. cognite/neat/legacy/graph/extractors/_dexpi.py +0 -44
  35. cognite/neat/legacy/graph/extractors/_graph_capturing_sheet.py +0 -403
  36. cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +0 -361
  37. cognite/neat/legacy/graph/loaders/__init__.py +0 -23
  38. cognite/neat/legacy/graph/loaders/_asset_loader.py +0 -511
  39. cognite/neat/legacy/graph/loaders/_base.py +0 -67
  40. cognite/neat/legacy/graph/loaders/_exceptions.py +0 -85
  41. cognite/neat/legacy/graph/loaders/core/__init__.py +0 -0
  42. cognite/neat/legacy/graph/loaders/core/labels.py +0 -58
  43. cognite/neat/legacy/graph/loaders/core/models.py +0 -136
  44. cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +0 -1046
  45. cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +0 -559
  46. cognite/neat/legacy/graph/loaders/rdf_to_dms.py +0 -309
  47. cognite/neat/legacy/graph/loaders/validator.py +0 -87
  48. cognite/neat/legacy/graph/models.py +0 -6
  49. cognite/neat/legacy/graph/stores/__init__.py +0 -13
  50. cognite/neat/legacy/graph/stores/_base.py +0 -400
  51. cognite/neat/legacy/graph/stores/_graphdb_store.py +0 -52
  52. cognite/neat/legacy/graph/stores/_memory_store.py +0 -43
  53. cognite/neat/legacy/graph/stores/_oxigraph_store.py +0 -151
  54. cognite/neat/legacy/graph/stores/_oxrdflib.py +0 -247
  55. cognite/neat/legacy/graph/stores/_rdf_to_graph.py +0 -42
  56. cognite/neat/legacy/graph/transformations/__init__.py +0 -0
  57. cognite/neat/legacy/graph/transformations/entity_matcher.py +0 -101
  58. cognite/neat/legacy/graph/transformations/query_generator/__init__.py +0 -3
  59. cognite/neat/legacy/graph/transformations/query_generator/sparql.py +0 -575
  60. cognite/neat/legacy/graph/transformations/transformer.py +0 -322
  61. cognite/neat/legacy/rules/__init__.py +0 -0
  62. cognite/neat/legacy/rules/analysis.py +0 -231
  63. cognite/neat/legacy/rules/examples/Rules-Nordic44-to-graphql.xlsx +0 -0
  64. cognite/neat/legacy/rules/examples/Rules-Nordic44.xlsx +0 -0
  65. cognite/neat/legacy/rules/examples/__init__.py +0 -18
  66. cognite/neat/legacy/rules/examples/power-grid-containers.yaml +0 -124
  67. cognite/neat/legacy/rules/examples/power-grid-example.xlsx +0 -0
  68. cognite/neat/legacy/rules/examples/power-grid-model.yaml +0 -224
  69. cognite/neat/legacy/rules/examples/rules-template.xlsx +0 -0
  70. cognite/neat/legacy/rules/examples/sheet2cdf-transformation-rules.xlsx +0 -0
  71. cognite/neat/legacy/rules/examples/skos-rules.xlsx +0 -0
  72. cognite/neat/legacy/rules/examples/source-to-solution-mapping-rules.xlsx +0 -0
  73. cognite/neat/legacy/rules/examples/wind-energy.owl +0 -1511
  74. cognite/neat/legacy/rules/exceptions.py +0 -2972
  75. cognite/neat/legacy/rules/exporters/__init__.py +0 -20
  76. cognite/neat/legacy/rules/exporters/_base.py +0 -45
  77. cognite/neat/legacy/rules/exporters/_core/__init__.py +0 -5
  78. cognite/neat/legacy/rules/exporters/_core/rules2labels.py +0 -24
  79. cognite/neat/legacy/rules/exporters/_rules2dms.py +0 -885
  80. cognite/neat/legacy/rules/exporters/_rules2excel.py +0 -213
  81. cognite/neat/legacy/rules/exporters/_rules2graphql.py +0 -183
  82. cognite/neat/legacy/rules/exporters/_rules2ontology.py +0 -524
  83. cognite/neat/legacy/rules/exporters/_rules2pydantic_models.py +0 -748
  84. cognite/neat/legacy/rules/exporters/_rules2rules.py +0 -105
  85. cognite/neat/legacy/rules/exporters/_rules2triples.py +0 -38
  86. cognite/neat/legacy/rules/exporters/_validation.py +0 -146
  87. cognite/neat/legacy/rules/importers/__init__.py +0 -22
  88. cognite/neat/legacy/rules/importers/_base.py +0 -66
  89. cognite/neat/legacy/rules/importers/_dict2rules.py +0 -158
  90. cognite/neat/legacy/rules/importers/_dms2rules.py +0 -194
  91. cognite/neat/legacy/rules/importers/_graph2rules.py +0 -308
  92. cognite/neat/legacy/rules/importers/_json2rules.py +0 -39
  93. cognite/neat/legacy/rules/importers/_owl2rules/__init__.py +0 -3
  94. cognite/neat/legacy/rules/importers/_owl2rules/_owl2classes.py +0 -239
  95. cognite/neat/legacy/rules/importers/_owl2rules/_owl2metadata.py +0 -260
  96. cognite/neat/legacy/rules/importers/_owl2rules/_owl2properties.py +0 -217
  97. cognite/neat/legacy/rules/importers/_owl2rules/_owl2rules.py +0 -290
  98. cognite/neat/legacy/rules/importers/_spreadsheet2rules.py +0 -45
  99. cognite/neat/legacy/rules/importers/_xsd2rules.py +0 -20
  100. cognite/neat/legacy/rules/importers/_yaml2rules.py +0 -39
  101. cognite/neat/legacy/rules/models/__init__.py +0 -5
  102. cognite/neat/legacy/rules/models/_base.py +0 -151
  103. cognite/neat/legacy/rules/models/raw_rules.py +0 -316
  104. cognite/neat/legacy/rules/models/rdfpath.py +0 -237
  105. cognite/neat/legacy/rules/models/rules.py +0 -1289
  106. cognite/neat/legacy/rules/models/tables.py +0 -9
  107. cognite/neat/legacy/rules/models/value_types.py +0 -118
  108. cognite/neat/legacy/workflows/examples/Export_DMS/workflow.yaml +0 -89
  109. cognite/neat/legacy/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +0 -152
  110. cognite/neat/legacy/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +0 -139
  111. cognite/neat/legacy/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +0 -270
  112. cognite/neat/legacy/workflows/examples/Import_DMS/workflow.yaml +0 -65
  113. cognite/neat/legacy/workflows/examples/Ontology_to_Data_Model/workflow.yaml +0 -116
  114. cognite/neat/legacy/workflows/examples/Validate_Rules/workflow.yaml +0 -67
  115. cognite/neat/legacy/workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
  116. cognite/neat/legacy/workflows/examples/Visualize_Data_Model_Using_Mock_Graph/workflow.yaml +0 -95
  117. cognite/neat/legacy/workflows/examples/Visualize_Semantic_Data_Model/workflow.yaml +0 -111
  118. cognite/neat/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +0 -270
  119. cognite/neat/workflows/migration/__init__.py +0 -0
  120. cognite/neat/workflows/migration/steps.py +0 -91
  121. cognite/neat/workflows/migration/wf_manifests.py +0 -33
  122. cognite/neat/workflows/steps/lib/legacy/__init__.py +0 -7
  123. cognite/neat/workflows/steps/lib/legacy/graph_contextualization.py +0 -82
  124. cognite/neat/workflows/steps/lib/legacy/graph_extractor.py +0 -746
  125. cognite/neat/workflows/steps/lib/legacy/graph_loader.py +0 -606
  126. cognite/neat/workflows/steps/lib/legacy/graph_store.py +0 -307
  127. cognite/neat/workflows/steps/lib/legacy/graph_transformer.py +0 -58
  128. cognite/neat/workflows/steps/lib/legacy/rules_exporter.py +0 -511
  129. cognite/neat/workflows/steps/lib/legacy/rules_importer.py +0 -612
  130. {cognite_neat-0.87.4.dist-info → cognite_neat-0.88.0.dist-info}/LICENSE +0 -0
  131. {cognite_neat-0.87.4.dist-info → cognite_neat-0.88.0.dist-info}/WHEEL +0 -0
  132. {cognite_neat-0.87.4.dist-info → cognite_neat-0.88.0.dist-info}/entry_points.txt +0 -0
@@ -1,575 +0,0 @@
1
- import re
2
- from collections import Counter
3
- from collections.abc import Iterable
4
- from typing import cast
5
-
6
- from rdflib import Graph, Namespace
7
- from rdflib.term import URIRef
8
-
9
- from cognite.neat.constants import get_default_prefixes
10
- from cognite.neat.legacy.rules.analysis import get_classes_with_properties
11
- from cognite.neat.legacy.rules.models._base import Triple
12
- from cognite.neat.legacy.rules.models.rdfpath import (
13
- AllProperties,
14
- AllReferences,
15
- Hop,
16
- SingleProperty,
17
- Step,
18
- TransformationRuleType,
19
- Traversal,
20
- parse_rule,
21
- parse_traversal,
22
- )
23
- from cognite.neat.legacy.rules.models.rules import Rules
24
- from cognite.neat.utils.rdf_ import remove_namespace_from_uri
25
-
26
-
27
- def _generate_prefix_header(prefixes: dict[str, Namespace] | None = None) -> str:
28
- """Generate prefix header which is added to SPARQL query and allows for shorten query statements
29
-
30
- Parameters
31
- ----------
32
- prefixes : dict
33
- Dict containing prefix - namespace pairs, default PREFIXES
34
-
35
- Returns
36
- -------
37
- str
38
- Prefix header
39
- """
40
- prefixes = prefixes if prefixes else get_default_prefixes()
41
- return "".join(f"PREFIX {key}:<{value}>\n" for key, value in prefixes.items())
42
-
43
-
44
- def _get_predicate_id(
45
- graph: Graph,
46
- subject_type_id: str,
47
- object_type_id: str,
48
- prefixes: dict[str, Namespace] | None = None,
49
- ) -> URIRef:
50
- """Returns predicate (aka property) URI (i.e., ID) that connects subject and object
51
-
52
- Parameters
53
- ----------
54
- graph : Graph
55
- Data model graph or data model instance (aka knowledge graph)
56
- subject_type_id : str
57
- ID of subject type (aka subject class)
58
- object_type_id : str
59
- ID of object type (aka object class)
60
- prefixes : dict, optional
61
- Dict containing prefix - namespace pairs, default PREFIXES
62
-
63
- Returns
64
- -------
65
- URIRef
66
- ID of predicate (aka property) connecting subject and object
67
- """
68
- prefixes = prefixes if prefixes else get_default_prefixes()
69
- query = """
70
-
71
- SELECT ?predicateTypeID
72
- WHERE {
73
- ?subjectInstanceID a subjectTypeID .
74
- ?objectInstanceID a objectTypeID .
75
- ?subjectInstanceID ?predicateTypeID ?objectInstanceID .
76
- } LIMIT 1"""
77
-
78
- query = query.replace("insertPrefixes", _generate_prefix_header(prefixes))
79
- final_query = query.replace("subjectTypeID", subject_type_id).replace("objectTypeID", object_type_id)
80
- res = list(cast(tuple, graph.query(final_query)))
81
-
82
- if len(res) != 1:
83
- raise ValueError("Subject and Object must have exactly 1 relation!")
84
-
85
- return res[0][0]
86
-
87
-
88
- def _get_direct_mapping_triples(subject, predicate) -> list[Triple]:
89
- return [Triple(subject=subject, predicate=predicate, object="?object")]
90
-
91
-
92
- def _get_all_references_mapping_triples(object) -> list[Triple]:
93
- return [Triple(subject="?subject", predicate="a", object=object)]
94
-
95
-
96
- def _get_entire_object_mapping(subject) -> list[Triple]:
97
- return [Triple(subject=subject, predicate="*")]
98
-
99
-
100
- def _get_hop_triples(graph, path: Hop, prefixes) -> list[Triple]:
101
- triples = [Triple(subject="?subject", predicate="a", object=path.class_.id)]
102
- previous_step = Step(class_=path.class_, direction="origin")
103
-
104
- # add triples for all steps until destination
105
- for current_step in path.traversal:
106
- sub_entity, obj_entity = (
107
- (current_step, previous_step) if current_step.direction == "source" else (previous_step, current_step)
108
- )
109
-
110
- predicate = _get_predicate_id(graph, sub_entity.class_.id, obj_entity.class_.id, prefixes)
111
-
112
- # if this is first step after origin
113
- if previous_step.class_.id == path.class_.id:
114
- if current_step.direction == "source":
115
- sub, obj = f"?{sub_entity.class_.suffix}ID", "?subject"
116
- else:
117
- sub, obj = "?subject", f"?{obj_entity.class_.suffix}ID"
118
-
119
- # Any other step after hoping from origin to first step
120
- else:
121
- sub = f"?{sub_entity.class_.suffix}ID"
122
- obj = f"?{obj_entity.class_.suffix}ID"
123
-
124
- triples.append(Triple(subject=sub, predicate=predicate, object=obj))
125
- previous_step = current_step
126
-
127
- if previous_step.property:
128
- triples.extend(
129
- [
130
- Triple(
131
- subject=f"?{previous_step.class_.suffix}ID",
132
- predicate="a",
133
- object=previous_step.class_.id,
134
- ),
135
- Triple(
136
- subject=f"?{previous_step.class_.suffix}ID",
137
- predicate=previous_step.property.id,
138
- object="?object",
139
- ),
140
- Triple(
141
- subject="?predicate",
142
- predicate="a",
143
- object=previous_step.property.id,
144
- ),
145
- ]
146
- )
147
- else:
148
- if previous_step.direction == "source":
149
- triples[-1].subject = "?object"
150
- else:
151
- triples[-1].object = "?object"
152
- triples.append(Triple(subject="?object", predicate="a", object=previous_step.class_.id))
153
-
154
- return triples
155
-
156
-
157
- def _get_path_triples(graph: Graph, traversal: Traversal, prefixes: dict[str, Namespace] | None = None) -> list[Triple]:
158
- """Creates triples subject-predicate-object from declarative graph traversal path
159
-
160
- Parameters
161
- ----------
162
- graph : Graph
163
- Data model graph or data model instance (aka knowledge graph)
164
- traversal : Traversal
165
- Parsed traversal path in declarative form
166
- prefixes : dict, optional
167
- Dict containing prefix - namespace pairs, default PREFIXES
168
-
169
- Returns
170
- -------
171
- list
172
- List of triples to be consumed by SPARQL query builder
173
- """
174
- match traversal:
175
- case SingleProperty():
176
- return _get_direct_mapping_triples(traversal.class_.id, traversal.property.id)
177
- case AllProperties():
178
- return _get_entire_object_mapping(traversal.class_.id)
179
- case AllReferences():
180
- return _get_all_references_mapping_triples(traversal.class_.id)
181
- case Hop():
182
- return _get_hop_triples(graph, traversal, prefixes)
183
- case _:
184
- raise ValueError("Incorrectly set traversal path!")
185
-
186
-
187
- BASIC_SPARQL_QUERY_TEMPLATE = """insertPrefixes
188
-
189
- SELECT DISTINCT ?subject ?predicate ?object
190
- WHERE {
191
- query_insertions
192
- }"""
193
-
194
-
195
- REFERENCES_ONLY_SPARQL_QUERY_TEMPLATE = """insertPrefixes
196
-
197
- SELECT DISTINCT ?subject ?predicate ?object {
198
- {SELECT DISTINCT ?object ?predicate {
199
- query_insertions
200
- BIND(dct:identifier AS ?predicate)}}
201
-
202
- BIND(?object AS ?subject)
203
-
204
- }"""
205
-
206
-
207
- # This template does not work with in-memory graph, so it has been replaced with the one above
208
- REFERENCES_ONLY_SPARQL_QUERY_TEMPLATE_OLD = """insertPrefixes
209
-
210
- SELECT DISTINCT ?subject ?predicate ?object
211
- WHERE {
212
- query_insertions
213
- {
214
- BIND(?object AS ?subject)
215
- BIND(dct:identifier AS ?predicate)
216
- }
217
- }"""
218
-
219
- SINGLE_PROPERTY_SPARQL_QUERY_TEMPLATE = """insertPrefixes
220
-
221
- SELECT DISTINCT ?subject ?predicate ?object
222
- WHERE {
223
- query_insertions
224
- {
225
- BIND(property_insertion AS ?predicate)
226
- }
227
- }"""
228
-
229
-
230
- def _generate_all_properties_query_statement(subject: str, query_template: str = BASIC_SPARQL_QUERY_TEMPLATE) -> str:
231
- query_insertions = "\n".join([f"\t\t?subject a {subject} .", "\t\t?subject ?predicate ?object ."])
232
-
233
- return query_template.replace("query_insertions", query_insertions)
234
-
235
-
236
- def _generate_all_references_query_statement(
237
- object: str, query_template: str = REFERENCES_ONLY_SPARQL_QUERY_TEMPLATE
238
- ) -> str:
239
- query_insertions = "\n".join([f"\t\t?object a {object} ."])
240
-
241
- return query_template.replace("query_insertions", query_insertions)
242
-
243
-
244
- def _generate_single_property_query_statement(
245
- subject: str,
246
- predicate: str,
247
- query_template: str = SINGLE_PROPERTY_SPARQL_QUERY_TEMPLATE,
248
- ) -> str:
249
- query_insertions = "\n".join([f"\t\t?subject a {subject} .", f"\t\t?subject {predicate} ?object ."])
250
-
251
- return query_template.replace("query_insertions", query_insertions).replace("property_insertion", predicate)
252
-
253
-
254
- def _generate_hop_query_statement(triples: list[Triple], query_template: str = BASIC_SPARQL_QUERY_TEMPLATE) -> str:
255
- terminal_triplet = triples[-1]
256
-
257
- query_insertions = "".join(
258
- f" {triple.subject} {triple.predicate} {triple.object} .\n" for triple in triples[:-1]
259
- )
260
-
261
- # Creating terminal query statement based on whether we are query for specific
262
- # property of an object (first option) or object ID
263
- if terminal_triplet.subject == "?predicate":
264
- query_insertions += f" BIND({terminal_triplet.object} AS ?predicate)\n"
265
- else:
266
- query_insertions += (
267
- f" {terminal_triplet.subject} {terminal_triplet.predicate} {terminal_triplet.object} .\n"
268
- )
269
- query_insertions += " BIND(dct:relation AS ?predicate)\n"
270
-
271
- return query_template.replace("query_insertions", query_insertions)
272
-
273
-
274
- def build_sparql_query(
275
- graph: Graph,
276
- traversal_path: str | Traversal,
277
- prefixes: dict[str, Namespace] | None = None,
278
- insert_prefixes: bool = False,
279
- ) -> str:
280
- """Builds SPARQL query based on declarative traversal path
281
-
282
- Parameters
283
- ----------
284
- graph : Graph
285
- Data model graph or data model instance (aka knowledge graph)
286
- traversal_path : str
287
- String representing graph traversal path in declarative form
288
- prefixes : dict, optional
289
- Dict containing prefix - namespace pairs, default PREFIXES
290
-
291
- Returns
292
- -------
293
- str
294
- SPARQL query
295
- """
296
- prefixes = prefixes if prefixes else get_default_prefixes()
297
- traversal = parse_traversal(traversal_path) if isinstance(traversal_path, str) else traversal_path
298
- triples = _get_path_triples(graph, traversal, prefixes)
299
-
300
- if isinstance(traversal, AllProperties):
301
- query = _generate_all_properties_query_statement(cast(str, triples[0].subject))
302
- elif isinstance(traversal, AllReferences) and isinstance(triples[0].object, str):
303
- query = _generate_all_references_query_statement(triples[0].object)
304
- elif isinstance(traversal, SingleProperty):
305
- query = _generate_single_property_query_statement(
306
- cast(str, triples[0].subject), cast(str, triples[0].predicate)
307
- )
308
- elif isinstance(traversal, Hop):
309
- query = _generate_hop_query_statement(triples)
310
- else:
311
- raise ValueError("Not Supported!")
312
-
313
- # Replacing long URIs with short form using their prefixes
314
- for prefix, URI in prefixes.items():
315
- query = query.replace(URI, f"{prefix}:")
316
-
317
- return query.replace(
318
- "insertPrefixes\n\n",
319
- _generate_prefix_header(prefixes) if insert_prefixes else "",
320
- )
321
-
322
-
323
- def compress_uri(uri: URIRef, prefixes: dict) -> str:
324
- """Compresses URI to prefix:entity_id
325
-
326
- Parameters
327
- ----------
328
- uri : URIRef
329
- URI of entity
330
- prefixes : dict
331
- Dictionary of prefixes
332
-
333
- Returns
334
- -------
335
- str
336
- Compressed URI or original URI if no prefix is found
337
- """
338
- return next(
339
- (
340
- f"{prefix}:{uri.replace(namespace, '')}"
341
- for prefix, namespace in prefixes.items()
342
- if uri.startswith(namespace)
343
- ),
344
- uri,
345
- )
346
-
347
-
348
- def _hop2property_path(graph: Graph, hop: Hop, prefixes: dict[str, Namespace]) -> str:
349
- """Converts hop to property path string
350
-
351
- Parameters
352
- ----------
353
- graph : Graph
354
- Graph containing instances of classes
355
- hop : Hop
356
- Hop to convert
357
- prefixes : dict[str, Namespace]
358
- Dictionary of prefixes to use for compression and predicate quering
359
-
360
- Returns
361
- -------
362
- str
363
- Property path string for hop traversal (e.g. ^rdf:type/rdfs:subClassOf)
364
- """
365
-
366
- # setting previous step to origin, as we are starting from there
367
- previous_step = Step(class_=hop.class_, direction="origin")
368
-
369
- # add triples for all steps until destination
370
- property_path = ""
371
- for current_step in hop.traversal:
372
- sub_entity, obj_entity = (
373
- (current_step, previous_step) if current_step.direction == "source" else (previous_step, current_step)
374
- )
375
-
376
- predicate_raw = _get_predicate_id(graph, sub_entity.class_.id, obj_entity.class_.id, prefixes)
377
-
378
- predicate = compress_uri(predicate_raw, prefixes)
379
-
380
- predicate = f"^{predicate}" if current_step.direction == "source" else predicate
381
- property_path += f"{predicate}/"
382
-
383
- previous_step = current_step
384
-
385
- if previous_step.property:
386
- return property_path + previous_step.property.id
387
- else:
388
- # removing "/" at the end of property path if there is no property at the end
389
- return property_path[:-1]
390
-
391
-
392
- def build_construct_query(
393
- graph: Graph,
394
- class_: str,
395
- transformation_rules: Rules,
396
- properties_optional: bool = True,
397
- class_instances: list[URIRef] | None = None,
398
- ) -> str:
399
- """Builds CONSTRUCT query for given class and rules and optionally filters by class instances
400
-
401
- Parameters
402
- ----------
403
- graph : Graph
404
- Graph containing instances of classes
405
- class_ : str
406
- ID of class for which class_instance we want to query
407
- transformation_rules : TransformationRules
408
- Transformation rules to use for query generation
409
- properties_optional : bool, optional
410
- Whether to make all properties optional, default True
411
- class_instances : list[URIRef], optional
412
- List of class instances to filter by, default None (no filter return all instances)
413
-
414
- Returns
415
- -------
416
- str
417
- CONSTRUCT query
418
-
419
- Notes
420
- -----
421
- Construct query is far less unforgiving than SELECT query, in sense that it will not return
422
- anything if one of the properties that define "shape" of the class instance is missing.
423
- This is the reason why there is option to make all properties optional, so that query will
424
- return all instances that have at least one property defined.
425
-
426
- """
427
-
428
- query_template = "CONSTRUCT {graph_template\n}\n\nWHERE {graph_pattern\ninsert_filter}"
429
- query_template = _add_filter(class_instances, query_template)
430
-
431
- templates, patterns = _to_construct_triples(graph, class_, transformation_rules, properties_optional)
432
-
433
- graph_template = "\n ".join(_triples2sparql_statement(templates))
434
- graph_pattern = "\n ".join(_triples2sparql_statement(patterns))
435
-
436
- return query_template.replace("graph_template", graph_template).replace("graph_pattern", graph_pattern)
437
-
438
-
439
- def _add_filter(class_instances, query_template):
440
- if class_instances:
441
- class_instances_formatted = [f"<{instance}>" for instance in class_instances]
442
- query_template = query_template.replace(
443
- "insert_filter",
444
- f"\n\nFILTER (?subject IN ({', '.join(class_instances_formatted)}))",
445
- )
446
- else:
447
- query_template = query_template.replace("insert_filter", "")
448
- return query_template
449
-
450
-
451
- def _triples2sparql_statement(triples: list[Triple]):
452
- return [
453
- (
454
- f"OPTIONAL {{ {triple.subject} {triple.predicate} {triple.object} . }}"
455
- if triple.optional
456
- else f"{triple.subject} {triple.predicate} {triple.object} ."
457
- )
458
- for triple in triples
459
- ]
460
-
461
-
462
- def _to_construct_triples(
463
- graph: Graph,
464
- class_: str,
465
- transformation_rules: Rules,
466
- properties_optional: bool = True,
467
- ) -> tuple[list[Triple], list[Triple]]:
468
- """Converts class definition to CONSTRUCT triples which are used to generate CONSTRUCT query
469
-
470
- Parameters
471
- ----------
472
- graph : Graph
473
- Graph containing instances of classes
474
- class_ : str
475
- ID of class for which class_instance we want to query
476
- transformation_rules : TransformationRules
477
- Transformation rules to use for query generation
478
-
479
- Returns
480
- -------
481
- tuple[list[Triple],list[Triple]]
482
- Tuple of triples that define graph template and graph pattern parts of CONSTRUCT query
483
- """
484
- # TODO: Add handling of UNIONs in rules
485
-
486
- templates = []
487
- patterns = []
488
-
489
- class_ids = []
490
- for property_ in get_classes_with_properties(transformation_rules)[class_]:
491
- if property_.rule_type != TransformationRuleType.rdfpath or property_.skip_rule:
492
- continue
493
- if not isinstance(property_.rule, str):
494
- raise ValueError("Rule must be string!")
495
- traversal = parse_rule(property_.rule, property_.rule_type).traversal
496
-
497
- if isinstance(traversal, Traversal):
498
- class_ids.append(traversal.class_.id)
499
-
500
- graph_template_triple = Triple(
501
- subject="?subject",
502
- predicate=f"{transformation_rules.metadata.prefix}:{property_.property_id}",
503
- object=f'?{re.sub(r"[^_a-zA-Z0-9/_]", "_", str(property_.property_id).lower())}',
504
- optional=False,
505
- )
506
- templates.append(graph_template_triple)
507
-
508
- # AllReferences should not be "optional" since we are creating their values
509
- # by binding them to certain property
510
- if isinstance(traversal, AllReferences):
511
- graph_pattern_triple = Triple(
512
- subject="BIND(?subject",
513
- predicate="AS",
514
- object=f"{graph_template_triple.object})",
515
- optional=False,
516
- )
517
-
518
- elif isinstance(traversal, SingleProperty):
519
- graph_pattern_triple = Triple(
520
- subject=graph_template_triple.subject,
521
- predicate=traversal.property.id,
522
- object=graph_template_triple.object,
523
- optional=True if properties_optional else not property_.is_mandatory,
524
- )
525
-
526
- elif isinstance(traversal, Hop):
527
- graph_pattern_triple = Triple(
528
- subject="?subject",
529
- predicate=_hop2property_path(graph, traversal, transformation_rules.prefixes),
530
- object=graph_template_triple.object,
531
- optional=True if properties_optional else not property_.is_mandatory,
532
- )
533
- else:
534
- continue
535
-
536
- patterns.append(graph_pattern_triple)
537
-
538
- # add first triple for graph pattern stating type of object
539
- patterns.insert(
540
- 0,
541
- Triple(
542
- subject="?subject",
543
- predicate="a",
544
- object=_most_occurring_element(class_ids),
545
- optional=False,
546
- ),
547
- )
548
-
549
- return templates, patterns
550
-
551
-
552
- def _most_occurring_element(list_of_elements: list):
553
- counts = Counter(list_of_elements)
554
- return counts.most_common(1)[0][0]
555
-
556
-
557
- def triples2dictionary(triples: Iterable[tuple[URIRef, URIRef, str | URIRef]]) -> dict[URIRef, dict[str, list[str]]]:
558
- """Converts list of triples to dictionary"""
559
- dictionary: dict[URIRef, dict[str, list[str]]] = {}
560
- for triple in triples:
561
- id_: str
562
- property_: str
563
- value: str
564
- uri: URIRef
565
- id_, property_, value = remove_namespace_from_uri(*triple) # type: ignore[misc]
566
- uri = triple[0]
567
-
568
- if uri not in dictionary:
569
- dictionary[uri] = {"external_id": [id_]}
570
-
571
- if property_ not in dictionary[uri]:
572
- dictionary[uri][property_] = [value]
573
- else:
574
- dictionary[uri][property_].append(value)
575
- return dictionary