mustrd 0.2.7a0__py3-none-any.whl → 0.3.0.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.
- mustrd/anzo_utils.py +8 -5
- mustrd/logger_setup.py +1 -0
- mustrd/model/mustrdShapes.ttl +11 -2
- mustrd/model/ontology.ttl +5 -0
- mustrd/mustrd.py +88 -30
- mustrd/mustrdAnzo.py +3 -2
- mustrd/mustrdTestPlugin.py +299 -128
- mustrd/spec_component.py +14 -13
- mustrd/steprunner.py +14 -4
- {mustrd-0.2.7a0.dist-info → mustrd-0.3.0.0.dist-info}/METADATA +1 -1
- {mustrd-0.2.7a0.dist-info → mustrd-0.3.0.0.dist-info}/RECORD +14 -14
- {mustrd-0.2.7a0.dist-info → mustrd-0.3.0.0.dist-info}/LICENSE +0 -0
- {mustrd-0.2.7a0.dist-info → mustrd-0.3.0.0.dist-info}/WHEEL +0 -0
- {mustrd-0.2.7a0.dist-info → mustrd-0.3.0.0.dist-info}/entry_points.txt +0 -0
mustrd/anzo_utils.py
CHANGED
@@ -31,16 +31,18 @@ from requests import Response, HTTPError, RequestException
|
|
31
31
|
from bs4 import BeautifulSoup
|
32
32
|
import logging
|
33
33
|
|
34
|
+
logger = logging.getLogger()
|
35
|
+
|
34
36
|
|
35
37
|
def query_azg(anzo_config: dict, query: str,
|
36
38
|
format: str = "json", is_update: bool = False,
|
37
39
|
data_layers: List[str] = None):
|
38
40
|
params = {
|
39
|
-
'skipCache':
|
41
|
+
'skipCache': 'true',
|
40
42
|
'format': format,
|
41
43
|
'datasourceURI': anzo_config['gqe_uri'],
|
42
|
-
'default-graph-uri': data_layers,
|
43
|
-
'named-graph-uri': data_layers
|
44
|
+
'using-graph-uri' if is_update else 'default-graph-uri': data_layers,
|
45
|
+
'using-named-graph-uri' if is_update else 'named-graph-uri': data_layers
|
44
46
|
}
|
45
47
|
url = f"{anzo_config['url']}/sparql"
|
46
48
|
return send_anzo_query(anzo_config, url=url, params=params, query=query, is_update=is_update)
|
@@ -52,7 +54,7 @@ def query_graphmart(anzo_config: dict,
|
|
52
54
|
format: str = "json",
|
53
55
|
data_layers: List[str] = None):
|
54
56
|
params = {
|
55
|
-
'skipCache':
|
57
|
+
'skipCache': 'true',
|
56
58
|
'format': format,
|
57
59
|
'default-graph-uri': data_layers,
|
58
60
|
'named-graph-uri': data_layers
|
@@ -87,7 +89,8 @@ def manage_anzo_response(response: Response) -> str:
|
|
87
89
|
|
88
90
|
def send_anzo_query(anzo_config, url, params, query, is_update=False):
|
89
91
|
headers = {"Content-Type": f"application/sparql-{'update' if is_update else 'query' }"}
|
90
|
-
|
92
|
+
logger.debug(f"send_anzo_query {url=} {query=} {is_update=}")
|
93
|
+
return manage_anzo_response(requests.post(url=url, params=params, data=query.encode('utf-8'),
|
91
94
|
auth=(anzo_config['username'], anzo_config['password']),
|
92
95
|
headers=headers, verify=False))
|
93
96
|
|
mustrd/logger_setup.py
CHANGED
mustrd/model/mustrdShapes.ttl
CHANGED
@@ -249,5 +249,14 @@ must:AnzoGraphmartQueryDrivenTemplatedStepSparqlSourceShape
|
|
249
249
|
sh:minCount 1 ;
|
250
250
|
sh:maxCount 1 ; ] .
|
251
251
|
|
252
|
-
|
253
|
-
|
252
|
+
must:SpadeEdnGroupSourceShape
|
253
|
+
a sh:NodeShape ;
|
254
|
+
sh:targetClass must:SpadeEdnGroupSource ;
|
255
|
+
sh:property [ sh:path must:file ;
|
256
|
+
sh:message "A SpadeEdnGroupSource must have a file property pointing to the spade.edn config." ;
|
257
|
+
sh:minCount 1 ;
|
258
|
+
sh:maxCount 1 ; ] ;
|
259
|
+
sh:property [ sh:path must:groupId ;
|
260
|
+
sh:message "A SpadeEdnGroupSource must have a groupId property referencing the group in the EDN file." ;
|
261
|
+
sh:minCount 1 ;
|
262
|
+
sh:maxCount 1 ; ] .
|
mustrd/model/ontology.ttl
CHANGED
@@ -461,6 +461,11 @@ sh:order rdf:type owl:DatatypeProperty ;
|
|
461
461
|
rdfs:isDefinedBy : ;
|
462
462
|
rdfs:label "AnzoGraphmartQueryDrivenTemplatedStepSparqlSource" .
|
463
463
|
|
464
|
+
### https://mustrd.com/model/SpadeEdnGroupSource
|
465
|
+
:SpadeEdnGroupSource rdf:type owl:Class ;
|
466
|
+
rdfs:subClassOf :SparqlSource ;
|
467
|
+
rdfs:comment "Allows reference to a spade.edn file, and a specific groupid (think Anzo layer), within that" ;
|
468
|
+
rdfs:label "SpadeEdnGroupSource" .
|
464
469
|
|
465
470
|
### https://mustrd.com/model/Then
|
466
471
|
:Then rdf:type owl:Class ;
|
mustrd/mustrd.py
CHANGED
@@ -55,8 +55,9 @@ import logging
|
|
55
55
|
from http.client import HTTPConnection
|
56
56
|
from .steprunner import upload_given, run_when
|
57
57
|
from multimethods import MultiMethod
|
58
|
+
import traceback
|
58
59
|
|
59
|
-
log =
|
60
|
+
log = logging.getLogger(__name__)
|
60
61
|
|
61
62
|
requests.packages.urllib3.disable_warnings()
|
62
63
|
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
|
@@ -184,20 +185,33 @@ class UpdateSparqlQuery(SparqlAction):
|
|
184
185
|
|
185
186
|
# https://github.com/Semantic-partners/mustrd/issues/19
|
186
187
|
# Validate the specs found in spec_path
|
187
|
-
def validate_specs(run_config: dict,
|
188
|
+
def validate_specs(run_config: dict,
|
189
|
+
triple_stores: List,
|
190
|
+
shacl_graph: Graph,
|
191
|
+
ont_graph: Graph,
|
192
|
+
file_name: str = "*",
|
193
|
+
selected_test_files: List[str] = [])\
|
188
194
|
-> Tuple[List, Graph, List]:
|
189
195
|
spec_graph = Graph()
|
190
196
|
subject_uris = set()
|
191
197
|
focus_uris = set()
|
192
198
|
invalid_specs = []
|
193
|
-
ttl_files =
|
194
|
-
|
199
|
+
ttl_files = []
|
200
|
+
|
201
|
+
if not selected_test_files:
|
202
|
+
ttl_files = list(run_config['spec_path'].glob(
|
203
|
+
f'**/{file_name}.mustrd.ttl'))
|
204
|
+
log.info(
|
205
|
+
f"Found {len(ttl_files)} {file_name}.mustrd.ttl files in {run_config['spec_path']}")
|
206
|
+
else:
|
207
|
+
ttl_files = selected_test_files
|
208
|
+
|
209
|
+
log.info(f"Using {ttl_files} for test source")
|
195
210
|
ttl_files.sort()
|
196
|
-
log.info(
|
197
|
-
f"Found {len(ttl_files)} {file_name}.mustrd.ttl files in {run_config['spec_path']}")
|
198
211
|
|
199
212
|
# For each spec file found in spec_path
|
200
213
|
for file in ttl_files:
|
214
|
+
# file = file.resolve()
|
201
215
|
error_messages = []
|
202
216
|
|
203
217
|
log.info(f"Parse: {file}")
|
@@ -224,7 +238,10 @@ def validate_specs(run_config: dict, triple_stores: List, shacl_graph: Graph, on
|
|
224
238
|
advanced=True,
|
225
239
|
js=False,
|
226
240
|
debug=False)
|
227
|
-
|
241
|
+
if str(file.name).endswith("_duplicate"):
|
242
|
+
log.debug(f"Validation of {file.name} against SHACL shapes: {conforms}")
|
243
|
+
log.debug(f"{results_graph.serialize(format='turtle')}")
|
244
|
+
# log.debug(f"SHACL validation results: {results_text}")
|
228
245
|
# Add error message if not conform to spec shapes
|
229
246
|
if not conforms:
|
230
247
|
for msg in results_graph.objects(predicate=SH.resultMessage):
|
@@ -269,6 +286,10 @@ def add_spec_validation(file_graph: Graph, subject_uris: set, file: Path, triple
|
|
269
286
|
error_messages: list, invalid_specs: list, spec_graph: Graph):
|
270
287
|
|
271
288
|
for subject_uri in file_graph.subjects(RDF.type, MUST.TestSpec):
|
289
|
+
# Always add file name and source file to the graph for error reporting
|
290
|
+
file_graph.add([subject_uri, MUST.specSourceFile, Literal(str(file))])
|
291
|
+
file_graph.add([subject_uri, MUST.specFileName, Literal(file.name)])
|
292
|
+
|
272
293
|
# If we already collected a URI, then we tag it as duplicate and it won't be executed
|
273
294
|
if subject_uri in subject_uris:
|
274
295
|
log.warning(
|
@@ -311,8 +332,11 @@ def get_specs(spec_uris: List[URIRef], spec_graph: Graph, triple_stores: List[di
|
|
311
332
|
specs += [get_spec(spec_uri, spec_graph,
|
312
333
|
run_config, triple_store)]
|
313
334
|
except (ValueError, FileNotFoundError, ConnectionError) as e:
|
335
|
+
# Try to get file name/path from the graph, but fallback to "unknown"
|
336
|
+
file_name = spec_graph.value(subject=spec_uri, predicate=MUST.specFileName) or "unknown"
|
337
|
+
file_path = spec_graph.value(subject=spec_uri, predicate=MUST.specSourceFile) or "unknown"
|
314
338
|
skipped_results += [SpecSkipped(spec_uri, triple_store['type'],
|
315
|
-
e,
|
339
|
+
str(e), str(file_name), Path(file_path))]
|
316
340
|
|
317
341
|
except (BadSyntax, FileNotFoundError) as e:
|
318
342
|
template = "An exception of type {0} occurred when trying to parse the triple store configuration file. " \
|
@@ -334,7 +358,14 @@ def run_specs(specs) -> List[SpecResult]:
|
|
334
358
|
|
335
359
|
|
336
360
|
def get_spec_file(spec_uri: URIRef, spec_graph: Graph):
|
337
|
-
|
361
|
+
file_name = spec_graph.value(subject=spec_uri, predicate=MUST.specFileName)
|
362
|
+
if file_name:
|
363
|
+
return str(file_name)
|
364
|
+
# fallback: try to get from MUST.specSourceFile
|
365
|
+
file_path = spec_graph.value(subject=spec_uri, predicate=MUST.specSourceFile)
|
366
|
+
if file_path:
|
367
|
+
return str(Path(file_path).name)
|
368
|
+
return "default.mustrd.ttl"
|
338
369
|
|
339
370
|
|
340
371
|
def get_spec(spec_uri: URIRef, spec_graph: Graph, run_config: dict, mustrd_triple_store: dict = None) -> Specification:
|
@@ -367,23 +398,42 @@ def get_spec(spec_uri: URIRef, spec_graph: Graph, run_config: dict, mustrd_tripl
|
|
367
398
|
|
368
399
|
|
369
400
|
def check_result(spec: Specification, result: Union[str, Graph]):
|
401
|
+
|
402
|
+
log.debug(
|
403
|
+
f"check_result {spec.spec_uri=}, {spec.triple_store=}, {result=} {type(spec.then)}")
|
370
404
|
if isinstance(spec.then, TableThenSpec):
|
405
|
+
log.debug("table_comparison")
|
371
406
|
return table_comparison(result, spec)
|
372
407
|
else:
|
373
408
|
graph_compare = graph_comparison(spec.then.value, result)
|
374
409
|
if isomorphic(result, spec.then.value):
|
375
|
-
|
410
|
+
log.debug(f"isomorphic {spec}")
|
411
|
+
log.debug(f"{spec.spec_uri}")
|
412
|
+
log.debug(f"{spec.triple_store}")
|
413
|
+
ret = SpecPassed(spec.spec_uri, spec.triple_store["type"])
|
414
|
+
|
415
|
+
return ret
|
376
416
|
else:
|
417
|
+
log.debug("not isomorphic")
|
377
418
|
if spec.when[0].queryType == MUST.ConstructSparql:
|
419
|
+
log.debug("ConstructSpecFailure")
|
378
420
|
return ConstructSpecFailure(spec.spec_uri, spec.triple_store["type"], graph_compare)
|
379
421
|
else:
|
422
|
+
log.debug("UpdateSpecFailure")
|
380
423
|
return UpdateSpecFailure(spec.spec_uri, spec.triple_store["type"], graph_compare)
|
381
424
|
|
382
425
|
|
383
426
|
def run_spec(spec: Specification) -> SpecResult:
|
384
427
|
spec_uri = spec.spec_uri
|
385
428
|
triple_store = spec.triple_store
|
386
|
-
|
429
|
+
|
430
|
+
if not isinstance(spec, Specification):
|
431
|
+
log.warning(f"check_result called with non-Specification: {type(spec)}")
|
432
|
+
return spec
|
433
|
+
# return SpecSkipped(getattr(spec, 'spec_uri', None), getattr(spec, 'triple_store', {}), "Spec is not a valid Specification instance")
|
434
|
+
|
435
|
+
log.debug(
|
436
|
+
f"run_spec {spec=}")
|
387
437
|
log.debug(
|
388
438
|
f"run_when {spec_uri=}, {triple_store=}, {spec.given=}, {spec.when=}, {spec.then=}")
|
389
439
|
if spec.given:
|
@@ -399,10 +449,16 @@ def run_spec(spec: Specification) -> SpecResult:
|
|
399
449
|
f"Running {when.queryType} spec {spec_uri} on {triple_store['type']}")
|
400
450
|
try:
|
401
451
|
result = run_when(spec_uri, triple_store, when)
|
452
|
+
log.info(
|
453
|
+
f"run {when.queryType} spec {spec_uri} on {triple_store['type']} {result=}")
|
402
454
|
except ParseException as e:
|
455
|
+
log.error(
|
456
|
+
f"parseException {e}")
|
403
457
|
return SparqlParseFailure(spec_uri, triple_store["type"], e)
|
404
458
|
except NotImplementedError as ex:
|
405
|
-
|
459
|
+
log.error(f"NotImplementedError {ex}")
|
460
|
+
raise ex
|
461
|
+
# return SpecSkipped(spec_uri, triple_store["type"], ex.args[0])
|
406
462
|
return check_result(spec, result)
|
407
463
|
except (ConnectionError, TimeoutError, HTTPError, ConnectTimeout, OSError) as e:
|
408
464
|
# close_connection = False
|
@@ -413,7 +469,9 @@ def run_spec(spec: Specification) -> SpecResult:
|
|
413
469
|
except (TypeError, RequestException) as e:
|
414
470
|
log.error(f"{type(e)} {e}")
|
415
471
|
return SparqlExecutionError(spec_uri, triple_store["type"], e)
|
416
|
-
|
472
|
+
except Exception as e:
|
473
|
+
log.error(f"Unexpected error {e}")
|
474
|
+
return RuntimeError(spec_uri, triple_store["type"], f"{type(e).__name__}: {e}")
|
417
475
|
# https://github.com/Semantic-partners/mustrd/issues/78
|
418
476
|
# finally:
|
419
477
|
# if type(mustrd_triple_store) == MustrdAnzo and close_connection:
|
@@ -724,33 +782,33 @@ def get_then_update(spec_uri: URIRef, spec_graph: Graph) -> Graph:
|
|
724
782
|
return expected_results
|
725
783
|
|
726
784
|
|
727
|
-
def write_result_diff_to_log(res):
|
785
|
+
def write_result_diff_to_log(res, info):
|
728
786
|
if isinstance(res, UpdateSpecFailure) or isinstance(res, ConstructSpecFailure):
|
729
|
-
|
730
|
-
|
731
|
-
|
787
|
+
info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
|
788
|
+
info(f"{Fore.BLUE} In Expected Not In Actual:")
|
789
|
+
info(
|
732
790
|
res.graph_comparison.in_expected_not_in_actual.serialize(format="ttl"))
|
733
|
-
|
734
|
-
|
791
|
+
info(f"{Fore.RED} in_actual_not_in_expected")
|
792
|
+
info(
|
735
793
|
res.graph_comparison.in_actual_not_in_expected.serialize(format="ttl"))
|
736
|
-
|
737
|
-
|
794
|
+
info(f"{Fore.GREEN} in_both")
|
795
|
+
info(res.graph_comparison.in_both.serialize(format="ttl"))
|
738
796
|
|
739
797
|
if isinstance(res, SelectSpecFailure):
|
740
|
-
|
741
|
-
|
742
|
-
|
798
|
+
info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
|
799
|
+
info(res.message)
|
800
|
+
info(res.table_comparison.to_markdown())
|
743
801
|
if isinstance(res, SpecPassedWithWarning):
|
744
|
-
|
802
|
+
info(
|
745
803
|
f"{Fore.YELLOW}Passed with warning {res.spec_uri} {res.triple_store}")
|
746
|
-
|
804
|
+
info(res.warning)
|
747
805
|
if isinstance(res, TripleStoreConnectionError) or isinstance(res, SparqlExecutionError) or \
|
748
806
|
isinstance(res, SparqlParseFailure):
|
749
|
-
|
750
|
-
|
807
|
+
info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
|
808
|
+
info(res.exception)
|
751
809
|
if isinstance(res, SpecSkipped):
|
752
|
-
|
753
|
-
|
810
|
+
info(f"{Fore.YELLOW}Skipped {res.spec_uri} {res.triple_store}")
|
811
|
+
info(res.message)
|
754
812
|
|
755
813
|
|
756
814
|
def calculate_row_difference(df1: pandas.DataFrame,
|
mustrd/mustrdAnzo.py
CHANGED
@@ -29,6 +29,7 @@ from mustrd.anzo_utils import query_azg, query_graphmart
|
|
29
29
|
from mustrd.anzo_utils import query_configuration, json_to_dictlist, ttl_to_graph
|
30
30
|
|
31
31
|
|
32
|
+
|
32
33
|
def execute_select(triple_store: dict, when: str, bindings: dict = None) -> str:
|
33
34
|
try:
|
34
35
|
if bindings:
|
@@ -39,7 +40,7 @@ def execute_select(triple_store: dict, when: str, bindings: dict = None) -> str
|
|
39
40
|
f"FROM <{triple_store['input_graph']}>\nFROM <{triple_store['output_graph']}>").replace(
|
40
41
|
"${targetGraph}", f"<{triple_store['output_graph']}>")
|
41
42
|
# TODO: manage results here
|
42
|
-
return query_azg(anzo_config=triple_store, query=when)
|
43
|
+
return query_azg(anzo_config=triple_store, query=when, data_layers=[triple_store['input_graph']])
|
43
44
|
except (ConnectionError, TimeoutError, HTTPError, ConnectTimeout):
|
44
45
|
raise
|
45
46
|
|
@@ -58,7 +59,7 @@ USING <{triple_store['output_graph']}>""").replace(
|
|
58
59
|
"${targetGraph}", f"<{output_graph}>")
|
59
60
|
|
60
61
|
response = query_azg(anzo_config=triple_store, query=substituted_query, is_update=True,
|
61
|
-
data_layers=input_graph, format="ttl")
|
62
|
+
data_layers=[input_graph, output_graph], format="ttl")
|
62
63
|
logging.debug(f'response {response}')
|
63
64
|
# TODO: deal with error responses
|
64
65
|
new_graph = ttl_to_graph(query_azg(anzo_config=triple_store, query="construct {?s ?p ?o} { ?s ?p ?o }",
|
mustrd/mustrdTestPlugin.py
CHANGED
@@ -1,32 +1,8 @@
|
|
1
|
-
"""
|
2
|
-
MIT License
|
3
|
-
|
4
|
-
Copyright (c) 2023 Semantic Partners Ltd
|
5
|
-
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
8
|
-
in the Software without restriction, including without limitation the rights
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
11
|
-
furnished to do so, subject to the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
14
|
-
copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
-
SOFTWARE.
|
23
|
-
"""
|
24
|
-
|
25
1
|
import logging
|
26
2
|
from dataclasses import dataclass
|
27
3
|
import pytest
|
28
4
|
import os
|
29
|
-
from pathlib import Path
|
5
|
+
from pathlib import Path, PosixPath
|
30
6
|
from rdflib.namespace import Namespace
|
31
7
|
from rdflib import Graph, RDF
|
32
8
|
from pytest import Session
|
@@ -34,12 +10,26 @@ from pytest import Session
|
|
34
10
|
from mustrd import logger_setup
|
35
11
|
from mustrd.TestResult import ResultList, TestResult, get_result_list
|
36
12
|
from mustrd.utils import get_mustrd_root
|
37
|
-
from mustrd.mustrd import
|
38
|
-
|
13
|
+
from mustrd.mustrd import (
|
14
|
+
write_result_diff_to_log,
|
15
|
+
get_triple_store_graph,
|
16
|
+
get_triple_stores,
|
17
|
+
)
|
18
|
+
from mustrd.mustrd import (
|
19
|
+
Specification,
|
20
|
+
SpecSkipped,
|
21
|
+
validate_specs,
|
22
|
+
get_specs,
|
23
|
+
SpecPassed,
|
24
|
+
run_spec,
|
25
|
+
)
|
39
26
|
from mustrd.namespace import MUST, TRIPLESTORE, MUSTRDTEST
|
40
27
|
from typing import Union
|
41
28
|
from pyshacl import validate
|
42
|
-
|
29
|
+
|
30
|
+
import pathlib
|
31
|
+
import traceback
|
32
|
+
|
43
33
|
spnamespace = Namespace("https://semanticpartners.com/data/test/")
|
44
34
|
|
45
35
|
mustrd_root = get_mustrd_root()
|
@@ -79,64 +69,96 @@ def pytest_addoption(parser):
|
|
79
69
|
default=None,
|
80
70
|
help="Give the secrets by command line in order to be able to store secrets safely in CI tools",
|
81
71
|
)
|
72
|
+
group.addoption(
|
73
|
+
"--pytest-path",
|
74
|
+
action="store",
|
75
|
+
dest="pytest_path",
|
76
|
+
metavar="PytestPath",
|
77
|
+
default=None,
|
78
|
+
help="Filter tests based on the pytest_path property in .mustrd.ttl files.",
|
79
|
+
)
|
82
80
|
return
|
83
81
|
|
84
82
|
|
85
83
|
def pytest_configure(config) -> None:
|
86
84
|
# Read configuration file
|
87
85
|
if config.getoption("mustrd") and config.getoption("configpath"):
|
88
|
-
config.pluginmanager.register(
|
89
|
-
|
86
|
+
config.pluginmanager.register(
|
87
|
+
MustrdTestPlugin(
|
88
|
+
config.getoption("mdpath"),
|
89
|
+
Path(config.getoption("configpath")),
|
90
|
+
config.getoption("secrets"),
|
91
|
+
)
|
92
|
+
)
|
90
93
|
|
91
94
|
|
92
95
|
def parse_config(config_path):
|
93
96
|
test_configs = []
|
94
|
-
print(f"{config_path=}")
|
95
97
|
config_graph = Graph().parse(config_path)
|
96
98
|
shacl_graph = Graph().parse(
|
97
|
-
Path(os.path.join(mustrd_root, "model/mustrdTestShapes.ttl"))
|
99
|
+
Path(os.path.join(mustrd_root, "model/mustrdTestShapes.ttl"))
|
100
|
+
)
|
98
101
|
ont_graph = Graph().parse(
|
99
|
-
Path(os.path.join(mustrd_root, "model/mustrdTestOntology.ttl"))
|
102
|
+
Path(os.path.join(mustrd_root, "model/mustrdTestOntology.ttl"))
|
103
|
+
)
|
100
104
|
conforms, results_graph, results_text = validate(
|
101
105
|
data_graph=config_graph,
|
102
106
|
shacl_graph=shacl_graph,
|
103
107
|
ont_graph=ont_graph,
|
104
108
|
advanced=True,
|
105
|
-
inference=
|
109
|
+
inference="none",
|
106
110
|
)
|
107
111
|
if not conforms:
|
108
|
-
raise ValueError(
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
+
raise ValueError(
|
113
|
+
f"Mustrd test configuration not conform to the shapes. SHACL report: {results_text}",
|
114
|
+
results_graph,
|
115
|
+
)
|
116
|
+
|
117
|
+
for test_config_subject in config_graph.subjects(
|
118
|
+
predicate=RDF.type, object=MUSTRDTEST.MustrdTest
|
119
|
+
):
|
112
120
|
spec_path = get_config_param(
|
113
|
-
config_graph, test_config_subject, MUSTRDTEST.hasSpecPath, str
|
121
|
+
config_graph, test_config_subject, MUSTRDTEST.hasSpecPath, str
|
122
|
+
)
|
114
123
|
data_path = get_config_param(
|
115
|
-
config_graph, test_config_subject, MUSTRDTEST.hasDataPath, str
|
124
|
+
config_graph, test_config_subject, MUSTRDTEST.hasDataPath, str
|
125
|
+
)
|
116
126
|
triplestore_spec_path = get_config_param(
|
117
|
-
config_graph, test_config_subject, MUSTRDTEST.triplestoreSpecPath, str
|
127
|
+
config_graph, test_config_subject, MUSTRDTEST.triplestoreSpecPath, str
|
128
|
+
)
|
118
129
|
pytest_path = get_config_param(
|
119
|
-
config_graph, test_config_subject, MUSTRDTEST.hasPytestPath, str
|
120
|
-
|
121
|
-
|
130
|
+
config_graph, test_config_subject, MUSTRDTEST.hasPytestPath, str
|
131
|
+
)
|
132
|
+
filter_on_tripleStore = tuple(
|
133
|
+
config_graph.objects(
|
134
|
+
subject=test_config_subject, predicate=MUSTRDTEST.filterOnTripleStore
|
135
|
+
)
|
136
|
+
)
|
122
137
|
|
123
138
|
# Root path is the mustrd test config path
|
124
139
|
root_path = Path(config_path).parent
|
125
140
|
spec_path = root_path / Path(spec_path) if spec_path else None
|
126
141
|
data_path = root_path / Path(data_path) if data_path else None
|
127
|
-
triplestore_spec_path =
|
128
|
-
Path(triplestore_spec_path) if triplestore_spec_path else None
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
142
|
+
triplestore_spec_path = (
|
143
|
+
root_path / Path(triplestore_spec_path) if triplestore_spec_path else None
|
144
|
+
)
|
145
|
+
|
146
|
+
test_configs.append(
|
147
|
+
TestConfig(
|
148
|
+
spec_path=spec_path,
|
149
|
+
data_path=data_path,
|
150
|
+
triplestore_spec_path=triplestore_spec_path,
|
151
|
+
pytest_path=pytest_path,
|
152
|
+
filter_on_tripleStore=filter_on_tripleStore,
|
153
|
+
)
|
154
|
+
)
|
134
155
|
return test_configs
|
135
156
|
|
136
157
|
|
137
158
|
def get_config_param(config_graph, config_subject, config_param, convert_function):
|
138
159
|
raw_value = config_graph.value(
|
139
|
-
subject=config_subject, predicate=config_param, any=True
|
160
|
+
subject=config_subject, predicate=config_param, any=True
|
161
|
+
)
|
140
162
|
return convert_function(raw_value) if raw_value else None
|
141
163
|
|
142
164
|
|
@@ -179,45 +201,95 @@ class MustrdTestPlugin:
|
|
179
201
|
@pytest.hookimpl(tryfirst=True)
|
180
202
|
def pytest_collection(self, session):
|
181
203
|
logger.info("Starting test collection")
|
204
|
+
|
182
205
|
self.unit_tests = []
|
183
206
|
args = session.config.args
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
207
|
+
|
208
|
+
# Split args into mustrd and regular pytest args
|
209
|
+
mustrd_args = [arg for arg in args if ".mustrd.ttl" in arg]
|
210
|
+
pytest_args = [arg for arg in args if arg != os.getcwd() and ".mustrd.ttl" not in arg]
|
211
|
+
|
212
|
+
self.selected_tests = list(
|
213
|
+
map(
|
214
|
+
lambda arg: Path(arg.split("::")[0]),
|
215
|
+
mustrd_args
|
216
|
+
)
|
217
|
+
)
|
218
|
+
logger.info(f"selected_tests is: {self.selected_tests}")
|
219
|
+
|
220
|
+
self.path_filter = session.config.getoption("pytest_path") or None
|
221
|
+
|
222
|
+
logger.info(f"path_filter is: {self.path_filter}")
|
223
|
+
logger.info(f"Args: {args}")
|
224
|
+
logger.info(f"Mustrd Args: {mustrd_args}")
|
225
|
+
logger.info(f"Pytest Args: {pytest_args}")
|
226
|
+
logger.info(f"Path Filter: {self.path_filter}")
|
227
|
+
|
228
|
+
# Only modify args if we have mustrd tests to run
|
229
|
+
if self.selected_tests:
|
230
|
+
# Keep original pytest args and add config file for mustrd
|
231
|
+
session.config.args = pytest_args + [str(self.test_config_file.resolve())]
|
232
|
+
else:
|
233
|
+
# Keep original args unchanged for regular pytest
|
234
|
+
session.config.args = args
|
192
235
|
|
193
|
-
session.config.args
|
236
|
+
logger.info(f"Final session.config.args: {session.config.args}")
|
194
237
|
|
195
238
|
def get_file_name_from_arg(self, arg):
|
196
239
|
if arg and len(arg) > 0 and "[" in arg and ".mustrd.ttl " in arg:
|
197
|
-
return arg[arg.index("[") + 1: arg.index(".mustrd.ttl ")]
|
240
|
+
return arg[arg.index("[") + 1 : arg.index(".mustrd.ttl ")]
|
198
241
|
return None
|
199
242
|
|
200
243
|
@pytest.hookimpl
|
201
244
|
def pytest_collect_file(self, parent, path):
|
202
245
|
logger.debug(f"Collecting file: {path}")
|
203
|
-
|
204
|
-
|
246
|
+
# Only collect .ttl files that are mustrd suite config files
|
247
|
+
if not str(path).endswith('.ttl'):
|
248
|
+
return None
|
249
|
+
if Path(path).resolve() != Path(self.test_config_file).resolve():
|
250
|
+
logger.debug(f"{self.test_config_file}: Skipping non-matching-config file: {path}")
|
251
|
+
return None
|
252
|
+
|
253
|
+
mustrd_file = MustrdFile.from_parent(parent, path=pathlib.Path(path), mustrd_plugin=self)
|
205
254
|
mustrd_file.mustrd_plugin = self
|
206
255
|
return mustrd_file
|
207
256
|
|
208
257
|
# Generate test for each triple store available
|
209
258
|
def generate_tests_for_config(self, config, triple_stores, file_name):
|
210
|
-
|
211
|
-
shacl_graph = Graph().parse(
|
259
|
+
logger.debug(f"generate_tests_for_config {config=} {self=} {dir(self)}")
|
260
|
+
shacl_graph = Graph().parse(
|
261
|
+
Path(os.path.join(mustrd_root, "model/mustrdShapes.ttl"))
|
262
|
+
)
|
212
263
|
ont_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/ontology.ttl")))
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
264
|
+
logger.debug("Generating tests for config: " + str(config))
|
265
|
+
logger.debug(f"selected_tests {self.selected_tests}")
|
266
|
+
|
267
|
+
valid_spec_uris, spec_graph, invalid_spec_results = validate_specs(
|
268
|
+
config,
|
269
|
+
triple_stores,
|
270
|
+
shacl_graph,
|
271
|
+
ont_graph,
|
272
|
+
file_name or "*",
|
273
|
+
selected_test_files=self.selected_tests,
|
274
|
+
)
|
275
|
+
# Convert invalid specs to SpecInvalid instead of SpecSkipped
|
276
|
+
invalid_specs = [
|
277
|
+
SpecInvalid(
|
278
|
+
spec.spec_uri,
|
279
|
+
spec.triple_store,
|
280
|
+
spec.message,
|
281
|
+
spec.spec_file_name,
|
282
|
+
spec.spec_source_file
|
283
|
+
) for spec in invalid_spec_results
|
284
|
+
]
|
285
|
+
|
286
|
+
|
287
|
+
specs, skipped_spec_results = get_specs(
|
288
|
+
valid_spec_uris, spec_graph, triple_stores, config
|
289
|
+
)
|
218
290
|
|
219
291
|
# Return normal specs + skipped results
|
220
|
-
return specs + skipped_spec_results +
|
292
|
+
return specs + skipped_spec_results + invalid_specs
|
221
293
|
|
222
294
|
# Function called to generate the name of the test
|
223
295
|
def get_test_name(self, spec):
|
@@ -225,9 +297,9 @@ class MustrdTestPlugin:
|
|
225
297
|
if isinstance(spec, SpecSkipped):
|
226
298
|
triple_store = spec.triple_store
|
227
299
|
else:
|
228
|
-
triple_store = spec.triple_store[
|
229
|
-
|
230
|
-
|
300
|
+
triple_store = spec.triple_store["type"]
|
301
|
+
|
302
|
+
triple_store_name = triple_store.replace("https://mustrd.com/model/", "")
|
231
303
|
test_name = spec.spec_uri.replace(spnamespace, "").replace("_", " ")
|
232
304
|
return spec.spec_file_name + " : " + triple_store_name + ": " + test_name
|
233
305
|
|
@@ -235,21 +307,33 @@ class MustrdTestPlugin:
|
|
235
307
|
def get_triple_stores_from_file(self, test_config):
|
236
308
|
if test_config.triplestore_spec_path:
|
237
309
|
try:
|
238
|
-
triple_stores = get_triple_stores(
|
239
|
-
|
310
|
+
triple_stores = get_triple_stores(
|
311
|
+
get_triple_store_graph(
|
312
|
+
test_config.triplestore_spec_path, self.secrets
|
313
|
+
)
|
314
|
+
)
|
240
315
|
except Exception as e:
|
241
|
-
print(
|
242
|
-
|
316
|
+
print(
|
317
|
+
f"""Triplestore configuration parsing failed {test_config.triplestore_spec_path}.
|
318
|
+
Only rdflib will be executed""",
|
319
|
+
e,
|
320
|
+
)
|
243
321
|
triple_stores = [
|
244
|
-
{
|
322
|
+
{"type": TRIPLESTORE.RdfLib, "uri": TRIPLESTORE.RdfLib}
|
323
|
+
]
|
245
324
|
else:
|
246
325
|
print("No triple store configuration required: using embedded rdflib")
|
247
|
-
triple_stores = [
|
248
|
-
{'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
|
326
|
+
triple_stores = [{"type": TRIPLESTORE.RdfLib, "uri": TRIPLESTORE.RdfLib}]
|
249
327
|
|
250
328
|
if test_config.filter_on_tripleStore:
|
251
|
-
triple_stores = list(
|
252
|
-
|
329
|
+
triple_stores = list(
|
330
|
+
filter(
|
331
|
+
lambda triple_store: (
|
332
|
+
triple_store["uri"] in test_config.filter_on_tripleStore
|
333
|
+
),
|
334
|
+
triple_stores,
|
335
|
+
)
|
336
|
+
)
|
253
337
|
return triple_stores
|
254
338
|
|
255
339
|
# Hook function. Initialize the list of result in session
|
@@ -264,7 +348,7 @@ class MustrdTestPlugin:
|
|
264
348
|
outcome = yield
|
265
349
|
result = outcome.get_result()
|
266
350
|
|
267
|
-
if result.when ==
|
351
|
+
if result.when == "call":
|
268
352
|
# Add the result of the test to the session
|
269
353
|
item.session.results[item] = result
|
270
354
|
|
@@ -280,8 +364,11 @@ class MustrdTestPlugin:
|
|
280
364
|
if test_conf.originalname != test_conf.name:
|
281
365
|
module_name = test_conf.parent.name
|
282
366
|
class_name = test_conf.originalname
|
283
|
-
test_name =
|
284
|
-
|
367
|
+
test_name = (
|
368
|
+
test_conf.name.replace(class_name, "")
|
369
|
+
.replace("[", "")
|
370
|
+
.replace("]", "")
|
371
|
+
)
|
285
372
|
is_mustrd = True
|
286
373
|
# Case normal unit tests
|
287
374
|
else:
|
@@ -290,64 +377,132 @@ class MustrdTestPlugin:
|
|
290
377
|
test_name = test_conf.originalname
|
291
378
|
is_mustrd = False
|
292
379
|
|
293
|
-
test_results.append(
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
380
|
+
test_results.append(
|
381
|
+
TestResult(
|
382
|
+
test_name, class_name, module_name, result.outcome, is_mustrd
|
383
|
+
)
|
384
|
+
)
|
385
|
+
|
386
|
+
result_list = ResultList(
|
387
|
+
None,
|
388
|
+
get_result_list(
|
389
|
+
test_results,
|
390
|
+
lambda result: result.type,
|
391
|
+
lambda result: is_mustrd and result.test_name.split("@")[1],
|
392
|
+
),
|
393
|
+
False,
|
394
|
+
)
|
300
395
|
|
301
396
|
md = result_list.render()
|
302
|
-
with open(self.md_path,
|
397
|
+
with open(self.md_path, "w") as file:
|
303
398
|
file.write(md)
|
304
399
|
|
400
|
+
@dataclass(frozen=True)
|
401
|
+
class SpecInvalid:
|
402
|
+
spec_uri: str
|
403
|
+
triple_store: str
|
404
|
+
message: str
|
405
|
+
spec_file_name: str = None
|
406
|
+
spec_source_file: Path = None
|
305
407
|
|
306
408
|
class MustrdFile(pytest.File):
|
307
409
|
mustrd_plugin: MustrdTestPlugin
|
308
410
|
|
411
|
+
def __init__(self, *args, mustrd_plugin, **kwargs):
|
412
|
+
logger.debug(f"Creating MustrdFile with args: {args}, kwargs: {kwargs}")
|
413
|
+
self.mustrd_plugin = mustrd_plugin
|
414
|
+
super(pytest.File, self).__init__(*args, **kwargs)
|
415
|
+
|
309
416
|
def collect(self):
|
310
417
|
try:
|
311
|
-
logger.
|
312
|
-
|
418
|
+
logger.info(f"{self.mustrd_plugin.test_config_file}: Collecting tests from file: {self.path=}")
|
419
|
+
# Only process the specific mustrd config file we were given
|
420
|
+
|
421
|
+
# if not str(self.fspath).endswith(".ttl"):
|
422
|
+
# return []
|
423
|
+
# Only process the specific mustrd config file we were given
|
424
|
+
# if str(self.fspath) != str(self.mustrd_plugin.test_config_file):
|
425
|
+
# logger.info(f"Skipping non-config file: {self.fspath}")
|
426
|
+
# return []
|
427
|
+
|
428
|
+
test_configs = parse_config(self.path)
|
429
|
+
from collections import defaultdict
|
430
|
+
pytest_path_grouped = defaultdict(list)
|
313
431
|
for test_config in test_configs:
|
314
|
-
|
315
|
-
|
432
|
+
if (
|
433
|
+
self.mustrd_plugin.path_filter is not None
|
434
|
+
and not str(test_config.pytest_path).startswith(str(self.mustrd_plugin.path_filter))
|
435
|
+
):
|
436
|
+
logger.info(f"Skipping test config due to path filter: {test_config.pytest_path=} {self.mustrd_plugin.path_filter=}")
|
316
437
|
continue
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
438
|
+
|
439
|
+
triple_stores = self.mustrd_plugin.get_triple_stores_from_file(test_config)
|
440
|
+
try:
|
441
|
+
specs = self.mustrd_plugin.generate_tests_for_config(
|
442
|
+
{
|
443
|
+
"spec_path": test_config.spec_path,
|
444
|
+
"data_path": test_config.data_path,
|
445
|
+
},
|
446
|
+
triple_stores,
|
447
|
+
None,
|
448
|
+
)
|
449
|
+
except Exception as e:
|
450
|
+
logger.error(f"Error generating tests: {e}\n{traceback.format_exc()}")
|
451
|
+
specs = [
|
452
|
+
SpecInvalid(
|
453
|
+
MUST.TestSpec,
|
454
|
+
triple_store["uri"] if isinstance(triple_store, dict) else triple_store,
|
455
|
+
f"Test generation failed: {str(e)}",
|
456
|
+
spec_file_name=str(test_config.spec_path.name) if test_config.spec_path else "unknown.mustrd.ttl",
|
457
|
+
spec_source_file=self.path if test_config.spec_path else Path("unknown.mustrd.ttl"),
|
458
|
+
)
|
459
|
+
for triple_store in (triple_stores or test_config.filter_on_tripleStore)
|
460
|
+
]
|
461
|
+
pytest_path = getattr(test_config, "pytest_path", "unknown")
|
330
462
|
for spec in specs:
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
463
|
+
pytest_path_grouped[pytest_path].append(spec)
|
464
|
+
|
465
|
+
for pytest_path, specs_for_path in pytest_path_grouped.items():
|
466
|
+
logger.info(f"pytest_path group: {pytest_path} ({len(specs_for_path)} specs)")
|
467
|
+
|
468
|
+
yield MustrdPytestPathCollector.from_parent(
|
469
|
+
self,
|
470
|
+
name=str(pytest_path),
|
471
|
+
pytest_path=pytest_path,
|
472
|
+
specs=specs_for_path,
|
473
|
+
mustrd_plugin=self.mustrd_plugin,
|
474
|
+
)
|
475
|
+
except Exception as e:
|
340
476
|
self.mustrd_plugin.collect_error = e
|
341
|
-
logger.error(f"Error during collection: {e}")
|
477
|
+
logger.error(f"Error during collection {self.path}: {type(e)} {e} {traceback.format_exc()}")
|
342
478
|
raise e
|
343
479
|
|
344
480
|
|
481
|
+
class MustrdPytestPathCollector(pytest.Class):
|
482
|
+
def __init__(self, name, parent, pytest_path, specs, mustrd_plugin):
|
483
|
+
super().__init__(name, parent)
|
484
|
+
self.pytest_path = pytest_path
|
485
|
+
self.specs = specs
|
486
|
+
self.mustrd_plugin = mustrd_plugin
|
487
|
+
|
488
|
+
def collect(self):
|
489
|
+
for spec in self.specs:
|
490
|
+
item = MustrdItem.from_parent(
|
491
|
+
self,
|
492
|
+
name=spec.spec_file_name,
|
493
|
+
spec=spec,
|
494
|
+
)
|
495
|
+
self.mustrd_plugin.items.append(item)
|
496
|
+
yield item
|
497
|
+
|
498
|
+
|
345
499
|
class MustrdItem(pytest.Item):
|
346
500
|
def __init__(self, name, parent, spec):
|
347
|
-
logging.
|
501
|
+
logging.debug(f"Creating item: {name}")
|
348
502
|
super().__init__(name, parent)
|
349
503
|
self.spec = spec
|
350
504
|
self.fspath = spec.spec_source_file
|
505
|
+
self.originalname = name
|
351
506
|
|
352
507
|
def runtest(self):
|
353
508
|
result = run_test_spec(self.spec)
|
@@ -355,11 +510,20 @@ class MustrdItem(pytest.Item):
|
|
355
510
|
raise AssertionError(f"Test {self.name} failed")
|
356
511
|
|
357
512
|
def repr_failure(self, excinfo):
|
358
|
-
|
359
|
-
|
513
|
+
# excinfo.value is the exception instance
|
514
|
+
# You can add more context here
|
515
|
+
tb_lines = traceback.format_exception(excinfo.type, excinfo.value, excinfo.tb)
|
516
|
+
tb_str = "".join(tb_lines)
|
517
|
+
return (
|
518
|
+
f"{self.name} failed:\n"
|
519
|
+
f"Spec: {self.spec.spec_uri}\n"
|
520
|
+
f"File: {self.spec.spec_source_file}\n"
|
521
|
+
f"Error: \n{excinfo.value}\n"
|
522
|
+
f"Traceback:\n{tb_str}"
|
523
|
+
)
|
524
|
+
|
360
525
|
def reportinfo(self):
|
361
526
|
r = "", 0, f"mustrd test: {self.name}"
|
362
|
-
logger.debug(f"Reporting info for {self.name} {r=}")
|
363
527
|
return r
|
364
528
|
|
365
529
|
|
@@ -368,11 +532,18 @@ def run_test_spec(test_spec):
|
|
368
532
|
if isinstance(test_spec, SpecSkipped):
|
369
533
|
pytest.skip(f"Invalid configuration, error : {test_spec.message}")
|
370
534
|
result = run_spec(test_spec)
|
371
|
-
|
372
535
|
result_type = type(result)
|
536
|
+
if isinstance(test_spec, SpecInvalid):
|
537
|
+
raise ValueError(f"Invalid test specification: {test_spec.message} {test_spec}")
|
373
538
|
if result_type == SpecSkipped:
|
374
539
|
# FIXME: Better exception management
|
375
540
|
pytest.skip("Unsupported configuration")
|
376
541
|
if result_type != SpecPassed:
|
377
|
-
write_result_diff_to_log(result)
|
542
|
+
write_result_diff_to_log(result, logger.info)
|
543
|
+
log_lines = []
|
544
|
+
def log_to_string(message):
|
545
|
+
log_lines.append(message)
|
546
|
+
write_result_diff_to_log(result, log_to_string)
|
547
|
+
raise AssertionError("Test failed: " + "\n".join(log_lines))
|
548
|
+
|
378
549
|
return result_type == SpecPassed
|
mustrd/spec_component.py
CHANGED
@@ -35,8 +35,8 @@ from rdflib.term import Node
|
|
35
35
|
from rdflib.plugins.stores.memory import Memory
|
36
36
|
|
37
37
|
from . import logger_setup
|
38
|
-
from .mustrdAnzo import get_queries_for_layer, get_queries_from_templated_step
|
39
|
-
from .mustrdAnzo import get_query_from_querybuilder
|
38
|
+
from .mustrdAnzo import get_queries_for_layer, get_queries_from_templated_step
|
39
|
+
from .mustrdAnzo import get_query_from_querybuilder
|
40
40
|
from .namespace import MUST, TRIPLESTORE
|
41
41
|
from multimethods import MultiMethod, Default
|
42
42
|
from .utils import get_mustrd_root
|
@@ -65,6 +65,7 @@ class WhenSpec(SpecComponent):
|
|
65
65
|
class AnzoWhenSpec(WhenSpec):
|
66
66
|
paramQuery: str = None
|
67
67
|
queryTemplate: str = None
|
68
|
+
spec_component_details: any = None
|
68
69
|
|
69
70
|
|
70
71
|
@dataclass
|
@@ -108,6 +109,7 @@ def parse_spec_component(subject: URIRef,
|
|
108
109
|
for spec_component_node in spec_component_nodes:
|
109
110
|
data_source_types = get_data_source_types(subject, predicate, spec_graph, spec_component_node)
|
110
111
|
for data_source_type in data_source_types:
|
112
|
+
log.debug(f"parse_spec_component {spec_component_node} {data_source_type} {mustrd_triple_store=}")
|
111
113
|
spec_component_details = SpecComponentDetails(
|
112
114
|
subject=subject,
|
113
115
|
predicate=predicate,
|
@@ -117,6 +119,9 @@ def parse_spec_component(subject: URIRef,
|
|
117
119
|
data_source_type=data_source_type,
|
118
120
|
run_config=run_config,
|
119
121
|
root_paths=get_components_roots(spec_graph, subject, run_config))
|
122
|
+
|
123
|
+
# get_spec_component potentially talks to anzo for EVERY spec, massively slowing things down
|
124
|
+
# can we defer it to run time?
|
120
125
|
spec_component = get_spec_component(spec_component_details)
|
121
126
|
if isinstance(spec_component, list):
|
122
127
|
spec_components += spec_component
|
@@ -429,14 +434,7 @@ def _get_spec_component_AnzoGraphmartDataset(spec_component_details: SpecCompone
|
|
429
434
|
|
430
435
|
if spec_component_details.mustrd_triple_store["type"] == TRIPLESTORE.Anzo:
|
431
436
|
# Get GIVEN or THEN from anzo graphmart
|
432
|
-
|
433
|
-
predicate=MUST.graphmart)
|
434
|
-
layer = spec_component_details.spec_graph.value(subject=spec_component_details.spec_component_node,
|
435
|
-
predicate=MUST.layer)
|
436
|
-
spec_component.value = get_spec_component_from_graphmart(
|
437
|
-
triple_store=spec_component_details.mustrd_triple_store,
|
438
|
-
graphmart=graphmart,
|
439
|
-
layer=layer)
|
437
|
+
spec_component.spec_component_details = spec_component_details
|
440
438
|
else:
|
441
439
|
raise ValueError(f"You must define {TRIPLESTORE.Anzo} to use {MUST.AnzoGraphmartDataset}")
|
442
440
|
|
@@ -468,14 +466,16 @@ def _get_spec_component_AnzoQueryBuilderSparqlSource(spec_component_details: Spe
|
|
468
466
|
|
469
467
|
@get_spec_component.method((MUST.AnzoGraphmartStepSparqlSource, MUST.when))
|
470
468
|
def _get_spec_component_AnzoGraphmartStepSparqlSource(spec_component_details: SpecComponentDetails) -> SpecComponent:
|
471
|
-
spec_component =
|
469
|
+
spec_component = AnzoWhenSpec()
|
472
470
|
|
473
471
|
# Get WHEN specComponent from query builder
|
474
472
|
if spec_component_details.mustrd_triple_store["type"] == TRIPLESTORE.Anzo:
|
475
473
|
query_step_uri = spec_component_details.spec_graph.value(subject=spec_component_details.spec_component_node,
|
476
474
|
predicate=MUST.anzoQueryStep)
|
477
|
-
spec_component.
|
478
|
-
|
475
|
+
spec_component.spec_component_details = spec_component_details
|
476
|
+
spec_component.query_step_uri = query_step_uri
|
477
|
+
# spec_component.value = get_query_from_step(triple_store=spec_component_details.mustrd_triple_store,
|
478
|
+
# query_step_uri=query_step_uri)
|
479
479
|
# If anzo specific function is called but no anzo defined
|
480
480
|
else:
|
481
481
|
raise ValueError(f"You must define {TRIPLESTORE.Anzo} to use {MUST.AnzoGraphmartStepSparqlSource}")
|
@@ -529,6 +529,7 @@ def _get_spec_component_AnzoGraphmartLayerSparqlSource(spec_component_details: S
|
|
529
529
|
spec_component.value = query.get("query")
|
530
530
|
spec_component.paramQuery = query.get("param_query")
|
531
531
|
spec_component.queryTemplate = query.get("query_template")
|
532
|
+
spec_component.spec_component_details = spec_component_details
|
532
533
|
if spec_component.value:
|
533
534
|
spec_component.queryType = spec_component_details.spec_graph.value(
|
534
535
|
subject=spec_component_details.spec_component_node,
|
mustrd/steprunner.py
CHANGED
@@ -24,14 +24,13 @@ SOFTWARE.
|
|
24
24
|
|
25
25
|
import json
|
26
26
|
|
27
|
-
from . import logger_setup
|
28
27
|
from multimethods import MultiMethod, Default
|
29
28
|
from .namespace import MUST, TRIPLESTORE
|
30
29
|
from rdflib import Graph, URIRef
|
31
30
|
from .mustrdRdfLib import execute_select as execute_select_rdflib
|
32
31
|
from .mustrdRdfLib import execute_construct as execute_construct_rdflib
|
33
32
|
from .mustrdRdfLib import execute_update as execute_update_rdflib
|
34
|
-
from .mustrdAnzo import upload_given as upload_given_anzo
|
33
|
+
from .mustrdAnzo import get_query_from_step, upload_given as upload_given_anzo
|
35
34
|
from .mustrdAnzo import execute_update as execute_update_anzo
|
36
35
|
from .mustrdAnzo import execute_construct as execute_construct_anzo
|
37
36
|
from .mustrdAnzo import execute_select as execute_select_anzo
|
@@ -40,8 +39,9 @@ from .mustrdGraphDb import execute_update as execute_update_graphdb
|
|
40
39
|
from .mustrdGraphDb import execute_construct as execute_construct_graphdb
|
41
40
|
from .mustrdGraphDb import execute_select as execute_select_graphdb
|
42
41
|
from .spec_component import AnzoWhenSpec, WhenSpec
|
42
|
+
import logging
|
43
43
|
|
44
|
-
log =
|
44
|
+
log = logging.getLogger(__name__)
|
45
45
|
|
46
46
|
|
47
47
|
def dispatch_upload_given(triple_store: dict, given: Graph):
|
@@ -80,7 +80,16 @@ run_when = MultiMethod('run_when', dispatch_run_when)
|
|
80
80
|
|
81
81
|
@run_when.method((TRIPLESTORE.Anzo, MUST.UpdateSparql))
|
82
82
|
def _anzo_run_when_update(spec_uri: URIRef, triple_store: dict, when: AnzoWhenSpec):
|
83
|
-
|
83
|
+
log.debug(f"_anzo_run_when_update {spec_uri} {triple_store} {when} {type(when)}")
|
84
|
+
if when.value is None:
|
85
|
+
# fetch the query from the query step on anzo
|
86
|
+
query = get_query_from_step(triple_store=when.spec_component_details.mustrd_triple_store,
|
87
|
+
query_step_uri=when.query_step_uri)
|
88
|
+
else:
|
89
|
+
# we must already have the query
|
90
|
+
query = when.value
|
91
|
+
log.debug(f"_anzo_run_when_update.query {query}")
|
92
|
+
return execute_update_anzo(triple_store, query, when.bindings)
|
84
93
|
|
85
94
|
|
86
95
|
@run_when.method((TRIPLESTORE.Anzo, MUST.ConstructSparql))
|
@@ -152,6 +161,7 @@ def _multi_run_when_anzo_query_driven_update(spec_uri: URIRef, triple_store: dic
|
|
152
161
|
|
153
162
|
@run_when.method(Default)
|
154
163
|
def _multi_run_when_default(spec_uri: URIRef, triple_store: dict, when: WhenSpec):
|
164
|
+
log.error(f"run_when not implemented for {spec_uri} {triple_store} {when}")
|
155
165
|
if when.queryType == MUST.AskSparql:
|
156
166
|
log.warning(f"Skipping {spec_uri}, SPARQL ASK not implemented.")
|
157
167
|
msg = "SPARQL ASK not implemented."
|
@@ -2,31 +2,31 @@ mustrd/README.adoc,sha256=E5KuShPEl2U6NmRzEAwZtAhBoyJsOvjGS1CM2UsbSQ4,1394
|
|
2
2
|
mustrd/README.md,sha256=CR08uZ4eNELHBdDRXAVKkRZ4hgQB9OXA7tFRy4ER-LI,1364
|
3
3
|
mustrd/TestResult.py,sha256=K4yth-rYrK6Pl7SFiTAZkUStBIzHlgq0oxSSpq5F34M,4849
|
4
4
|
mustrd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
mustrd/anzo_utils.py,sha256=
|
6
|
-
mustrd/logger_setup.py,sha256=
|
5
|
+
mustrd/anzo_utils.py,sha256=CKNSF_oTFKc4v64EDXJzeirP1GxOIOCUKNfaJKk0oEc,4677
|
6
|
+
mustrd/logger_setup.py,sha256=AdHf1_rEYiWleR-8IVatsMu-ntYH3_uiDDhFchQ8BcM,1721
|
7
7
|
mustrd/model/catalog-v001.xml,sha256=IEtaw3FK4KGQWKancEe1HqzUQTrKdk89vNxnoLKGF4o,381
|
8
|
-
mustrd/model/mustrdShapes.ttl,sha256=
|
8
|
+
mustrd/model/mustrdShapes.ttl,sha256=3BjiOMLs0sS5Ky4_1YkzqYE7JpW-bsfw7FaFej6Ri8w,10056
|
9
9
|
mustrd/model/mustrdTestOntology.ttl,sha256=W7IRbPKrhpYFweZvc9QH8gdjBiuZMHKETIuHBepLnYw,2034
|
10
10
|
mustrd/model/mustrdTestShapes.ttl,sha256=5PUpU9AzPSeLpF_UzNBVnACLOhs3hfoQpiz-ybsrbj8,818
|
11
|
-
mustrd/model/ontology.ttl,sha256=
|
11
|
+
mustrd/model/ontology.ttl,sha256=089FOWtSN107xR04MFxtvPUJVFiT9TSzmKqqNOfm7Gk,17090
|
12
12
|
mustrd/model/test-resources/resources.ttl,sha256=1Dsp1nuNxauj9bxeX-HShQsiO-CVy5Irwm2y2x0cdjI,1498
|
13
13
|
mustrd/model/triplestoreOntology.ttl,sha256=9K5gj0hDOolRYjHc58UT4igex8cUnq9h7SUe4ToYbdw,5834
|
14
14
|
mustrd/model/triplestoreshapes.ttl,sha256=G1kdgASdPa8s5JVGXL4KM2ewp-F5Vmbdist0f77VTBc,1706
|
15
|
-
mustrd/mustrd.py,sha256
|
16
|
-
mustrd/mustrdAnzo.py,sha256=
|
15
|
+
mustrd/mustrd.py,sha256=-J_Od8Uv0WFWKIt6C2QBl_ZCQtubx3nBUjfTodJ3a4w,40289
|
16
|
+
mustrd/mustrdAnzo.py,sha256=0XhLkfagW_HManfZ3iGAf_K2_d-pLVTAOT-0pX8hhM4,8352
|
17
17
|
mustrd/mustrdGraphDb.py,sha256=Ro_fxDPFl64r-FAM18awhZydydEY1-IXO0zdKpvZD3U,5405
|
18
18
|
mustrd/mustrdRdfLib.py,sha256=CvrzEl1-lEPugYVe3y_Ip8JMzUxv6IeWauLOa_WA-XI,2073
|
19
|
-
mustrd/mustrdTestPlugin.py,sha256=
|
19
|
+
mustrd/mustrdTestPlugin.py,sha256=hbYo8i1j4uFuRekXGd4Mnjt9wkSgQrS5o809xtbcLgs,19664
|
20
20
|
mustrd/namespace.py,sha256=xGfH-nRP3t_I4rmoGJPqlSGbI0G5HUMlUeAl7l_yW-s,3622
|
21
21
|
mustrd/run.py,sha256=5xZUgKPMBQ-03cWROAnwtbOs2Nb0Vat6n8Fi6EyfS-k,4257
|
22
|
-
mustrd/spec_component.py,sha256=
|
23
|
-
mustrd/steprunner.py,sha256=
|
22
|
+
mustrd/spec_component.py,sha256=5WQdvj0v9U-HEiOomoLcwwKfAt6kLDxPKomxyMkleI0,31382
|
23
|
+
mustrd/steprunner.py,sha256=73ykrrfZAPAQcr95ewcRfLO0gl_2xVGlEDYnI7FncXg,7951
|
24
24
|
mustrd/templates/md_ResultList_leaf_template.jinja,sha256=IzwZjliCx7-viipATDQK6MQg_5q1kLMKdeNSZg1sXXY,508
|
25
25
|
mustrd/templates/md_ResultList_template.jinja,sha256=_8joJ7vtw_qoqxv3HhUtBgRfhOeqmgfaRFwEo4MROvQ,203
|
26
26
|
mustrd/templates/md_stats_template.jinja,sha256=96W62cMWu9UGLNv65ZQ8RYLjkxKHhJy-FlUtXgud6XY,155
|
27
27
|
mustrd/utils.py,sha256=OGdLvw7GvjrFgTJo0J97Xwdh-_ZgSmapmOistrEchO0,1387
|
28
|
-
mustrd-0.
|
29
|
-
mustrd-0.
|
30
|
-
mustrd-0.
|
31
|
-
mustrd-0.
|
32
|
-
mustrd-0.
|
28
|
+
mustrd-0.3.0.0.dist-info/LICENSE,sha256=r8nmh5fUct9h2w8_RDl13EIscvmwCLoarPr1kg35MnA,1078
|
29
|
+
mustrd-0.3.0.0.dist-info/METADATA,sha256=Eu_S5x8JM7ZP8LWF6nh9qkhYpjhLCApa2__EGBlNtXk,4204
|
30
|
+
mustrd-0.3.0.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
31
|
+
mustrd-0.3.0.0.dist-info/entry_points.txt,sha256=v7V7sN0_L1aB4Ug_9io5axlQSeJ1C0tNrQWwdXdV58s,50
|
32
|
+
mustrd-0.3.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|