cognite-neat 0.87.6__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 (125) 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/stores/_base.py +5 -0
  7. cognite/neat/rules/importers/_inference2rules.py +31 -35
  8. cognite/neat/workflows/steps/data_contracts.py +17 -43
  9. cognite/neat/workflows/steps/lib/current/graph_extractor.py +28 -24
  10. cognite/neat/workflows/steps/lib/current/graph_loader.py +4 -21
  11. cognite/neat/workflows/steps/lib/current/graph_store.py +18 -134
  12. cognite/neat/workflows/steps_registry.py +5 -7
  13. {cognite_neat-0.87.6.dist-info → cognite_neat-0.88.0.dist-info}/METADATA +1 -1
  14. {cognite_neat-0.87.6.dist-info → cognite_neat-0.88.0.dist-info}/RECORD +17 -125
  15. cognite/neat/app/api/routers/core.py +0 -91
  16. cognite/neat/app/api/routers/data_exploration.py +0 -336
  17. cognite/neat/app/api/routers/rules.py +0 -203
  18. cognite/neat/legacy/__init__.py +0 -0
  19. cognite/neat/legacy/graph/__init__.py +0 -3
  20. cognite/neat/legacy/graph/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -20182
  21. cognite/neat/legacy/graph/examples/Knowledge-Graph-Nordic44.xml +0 -20163
  22. cognite/neat/legacy/graph/examples/__init__.py +0 -10
  23. cognite/neat/legacy/graph/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
  24. cognite/neat/legacy/graph/exceptions.py +0 -90
  25. cognite/neat/legacy/graph/extractors/__init__.py +0 -6
  26. cognite/neat/legacy/graph/extractors/_base.py +0 -14
  27. cognite/neat/legacy/graph/extractors/_dexpi.py +0 -44
  28. cognite/neat/legacy/graph/extractors/_graph_capturing_sheet.py +0 -403
  29. cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +0 -361
  30. cognite/neat/legacy/graph/loaders/__init__.py +0 -23
  31. cognite/neat/legacy/graph/loaders/_asset_loader.py +0 -511
  32. cognite/neat/legacy/graph/loaders/_base.py +0 -67
  33. cognite/neat/legacy/graph/loaders/_exceptions.py +0 -85
  34. cognite/neat/legacy/graph/loaders/core/__init__.py +0 -0
  35. cognite/neat/legacy/graph/loaders/core/labels.py +0 -58
  36. cognite/neat/legacy/graph/loaders/core/models.py +0 -136
  37. cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +0 -1046
  38. cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +0 -559
  39. cognite/neat/legacy/graph/loaders/rdf_to_dms.py +0 -309
  40. cognite/neat/legacy/graph/loaders/validator.py +0 -87
  41. cognite/neat/legacy/graph/models.py +0 -6
  42. cognite/neat/legacy/graph/stores/__init__.py +0 -13
  43. cognite/neat/legacy/graph/stores/_base.py +0 -400
  44. cognite/neat/legacy/graph/stores/_graphdb_store.py +0 -52
  45. cognite/neat/legacy/graph/stores/_memory_store.py +0 -43
  46. cognite/neat/legacy/graph/stores/_oxigraph_store.py +0 -151
  47. cognite/neat/legacy/graph/stores/_oxrdflib.py +0 -247
  48. cognite/neat/legacy/graph/stores/_rdf_to_graph.py +0 -42
  49. cognite/neat/legacy/graph/transformations/__init__.py +0 -0
  50. cognite/neat/legacy/graph/transformations/entity_matcher.py +0 -101
  51. cognite/neat/legacy/graph/transformations/query_generator/__init__.py +0 -3
  52. cognite/neat/legacy/graph/transformations/query_generator/sparql.py +0 -575
  53. cognite/neat/legacy/graph/transformations/transformer.py +0 -322
  54. cognite/neat/legacy/rules/__init__.py +0 -0
  55. cognite/neat/legacy/rules/analysis.py +0 -231
  56. cognite/neat/legacy/rules/examples/Rules-Nordic44-to-graphql.xlsx +0 -0
  57. cognite/neat/legacy/rules/examples/Rules-Nordic44.xlsx +0 -0
  58. cognite/neat/legacy/rules/examples/__init__.py +0 -18
  59. cognite/neat/legacy/rules/examples/power-grid-containers.yaml +0 -124
  60. cognite/neat/legacy/rules/examples/power-grid-example.xlsx +0 -0
  61. cognite/neat/legacy/rules/examples/power-grid-model.yaml +0 -224
  62. cognite/neat/legacy/rules/examples/rules-template.xlsx +0 -0
  63. cognite/neat/legacy/rules/examples/sheet2cdf-transformation-rules.xlsx +0 -0
  64. cognite/neat/legacy/rules/examples/skos-rules.xlsx +0 -0
  65. cognite/neat/legacy/rules/examples/source-to-solution-mapping-rules.xlsx +0 -0
  66. cognite/neat/legacy/rules/examples/wind-energy.owl +0 -1511
  67. cognite/neat/legacy/rules/exceptions.py +0 -2972
  68. cognite/neat/legacy/rules/exporters/__init__.py +0 -20
  69. cognite/neat/legacy/rules/exporters/_base.py +0 -45
  70. cognite/neat/legacy/rules/exporters/_core/__init__.py +0 -5
  71. cognite/neat/legacy/rules/exporters/_core/rules2labels.py +0 -24
  72. cognite/neat/legacy/rules/exporters/_rules2dms.py +0 -885
  73. cognite/neat/legacy/rules/exporters/_rules2excel.py +0 -213
  74. cognite/neat/legacy/rules/exporters/_rules2graphql.py +0 -183
  75. cognite/neat/legacy/rules/exporters/_rules2ontology.py +0 -524
  76. cognite/neat/legacy/rules/exporters/_rules2pydantic_models.py +0 -748
  77. cognite/neat/legacy/rules/exporters/_rules2rules.py +0 -105
  78. cognite/neat/legacy/rules/exporters/_rules2triples.py +0 -38
  79. cognite/neat/legacy/rules/exporters/_validation.py +0 -146
  80. cognite/neat/legacy/rules/importers/__init__.py +0 -22
  81. cognite/neat/legacy/rules/importers/_base.py +0 -66
  82. cognite/neat/legacy/rules/importers/_dict2rules.py +0 -158
  83. cognite/neat/legacy/rules/importers/_dms2rules.py +0 -194
  84. cognite/neat/legacy/rules/importers/_graph2rules.py +0 -308
  85. cognite/neat/legacy/rules/importers/_json2rules.py +0 -39
  86. cognite/neat/legacy/rules/importers/_owl2rules/__init__.py +0 -3
  87. cognite/neat/legacy/rules/importers/_owl2rules/_owl2classes.py +0 -239
  88. cognite/neat/legacy/rules/importers/_owl2rules/_owl2metadata.py +0 -260
  89. cognite/neat/legacy/rules/importers/_owl2rules/_owl2properties.py +0 -217
  90. cognite/neat/legacy/rules/importers/_owl2rules/_owl2rules.py +0 -290
  91. cognite/neat/legacy/rules/importers/_spreadsheet2rules.py +0 -45
  92. cognite/neat/legacy/rules/importers/_xsd2rules.py +0 -20
  93. cognite/neat/legacy/rules/importers/_yaml2rules.py +0 -39
  94. cognite/neat/legacy/rules/models/__init__.py +0 -5
  95. cognite/neat/legacy/rules/models/_base.py +0 -151
  96. cognite/neat/legacy/rules/models/raw_rules.py +0 -316
  97. cognite/neat/legacy/rules/models/rdfpath.py +0 -237
  98. cognite/neat/legacy/rules/models/rules.py +0 -1289
  99. cognite/neat/legacy/rules/models/tables.py +0 -9
  100. cognite/neat/legacy/rules/models/value_types.py +0 -118
  101. cognite/neat/legacy/workflows/examples/Export_DMS/workflow.yaml +0 -89
  102. cognite/neat/legacy/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +0 -152
  103. cognite/neat/legacy/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +0 -139
  104. cognite/neat/legacy/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +0 -270
  105. cognite/neat/legacy/workflows/examples/Import_DMS/workflow.yaml +0 -65
  106. cognite/neat/legacy/workflows/examples/Ontology_to_Data_Model/workflow.yaml +0 -116
  107. cognite/neat/legacy/workflows/examples/Validate_Rules/workflow.yaml +0 -67
  108. cognite/neat/legacy/workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
  109. cognite/neat/legacy/workflows/examples/Visualize_Data_Model_Using_Mock_Graph/workflow.yaml +0 -95
  110. cognite/neat/legacy/workflows/examples/Visualize_Semantic_Data_Model/workflow.yaml +0 -111
  111. cognite/neat/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +0 -270
  112. cognite/neat/workflows/migration/__init__.py +0 -0
  113. cognite/neat/workflows/migration/steps.py +0 -91
  114. cognite/neat/workflows/migration/wf_manifests.py +0 -33
  115. cognite/neat/workflows/steps/lib/legacy/__init__.py +0 -7
  116. cognite/neat/workflows/steps/lib/legacy/graph_contextualization.py +0 -82
  117. cognite/neat/workflows/steps/lib/legacy/graph_extractor.py +0 -746
  118. cognite/neat/workflows/steps/lib/legacy/graph_loader.py +0 -606
  119. cognite/neat/workflows/steps/lib/legacy/graph_store.py +0 -307
  120. cognite/neat/workflows/steps/lib/legacy/graph_transformer.py +0 -58
  121. cognite/neat/workflows/steps/lib/legacy/rules_exporter.py +0 -511
  122. cognite/neat/workflows/steps/lib/legacy/rules_importer.py +0 -612
  123. {cognite_neat-0.87.6.dist-info → cognite_neat-0.88.0.dist-info}/LICENSE +0 -0
  124. {cognite_neat-0.87.6.dist-info → cognite_neat-0.88.0.dist-info}/WHEEL +0 -0
  125. {cognite_neat-0.87.6.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