mustrd 0.3.0.0__py3-none-any.whl → 0.3.1a0__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/README.md +2 -0
- mustrd/logger_setup.py +2 -0
- mustrd/model/mustrdShapes.ttl +16 -6
- mustrd/model/ontology.ttl +1 -2
- mustrd/mustrd.py +442 -227
- mustrd/mustrdRdfLib.py +8 -1
- mustrd/namespace.py +10 -1
- mustrd/spec_component.py +224 -45
- mustrd/steprunner.py +62 -14
- mustrd-0.3.1a0.dist-info/METADATA +96 -0
- {mustrd-0.3.0.0.dist-info → mustrd-0.3.1a0.dist-info}/RECORD +14 -14
- mustrd-0.3.0.0.dist-info/METADATA +0 -96
- {mustrd-0.3.0.0.dist-info → mustrd-0.3.1a0.dist-info}/LICENSE +0 -0
- {mustrd-0.3.0.0.dist-info → mustrd-0.3.1a0.dist-info}/WHEEL +0 -0
- {mustrd-0.3.0.0.dist-info → mustrd-0.3.1a0.dist-info}/entry_points.txt +0 -0
mustrd/mustrd.py
CHANGED
@@ -1,27 +1,3 @@
|
|
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 os
|
26
2
|
from typing import Tuple, List, Union
|
27
3
|
|
@@ -53,20 +29,21 @@ from collections import defaultdict
|
|
53
29
|
from pyshacl import validate
|
54
30
|
import logging
|
55
31
|
from http.client import HTTPConnection
|
56
|
-
from .steprunner import upload_given,
|
32
|
+
from .steprunner import upload_given, run_when_impl
|
57
33
|
from multimethods import MultiMethod
|
58
34
|
import traceback
|
35
|
+
from functools import wraps
|
59
36
|
|
60
37
|
log = logging.getLogger(__name__)
|
61
38
|
|
62
39
|
requests.packages.urllib3.disable_warnings()
|
63
|
-
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS +=
|
40
|
+
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ":HIGH:!DH:!aNULL"
|
64
41
|
|
65
|
-
logging.basicConfig(format=
|
42
|
+
logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
|
66
43
|
|
67
44
|
|
68
45
|
def debug_requests_on():
|
69
|
-
|
46
|
+
"""Switches on logging of the requests module."""
|
70
47
|
HTTPConnection.debuglevel = 1
|
71
48
|
|
72
49
|
logging.basicConfig()
|
@@ -77,7 +54,7 @@ def debug_requests_on():
|
|
77
54
|
|
78
55
|
|
79
56
|
def debug_requests_off():
|
80
|
-
|
57
|
+
"""Switches off logging of the requests module, might be some side-effects"""
|
81
58
|
HTTPConnection.debuglevel = 0
|
82
59
|
|
83
60
|
root_logger = logging.getLogger()
|
@@ -185,13 +162,14 @@ class UpdateSparqlQuery(SparqlAction):
|
|
185
162
|
|
186
163
|
# https://github.com/Semantic-partners/mustrd/issues/19
|
187
164
|
# Validate the specs found in spec_path
|
188
|
-
def validate_specs(
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
165
|
+
def validate_specs(
|
166
|
+
run_config: dict,
|
167
|
+
triple_stores: List,
|
168
|
+
shacl_graph: Graph,
|
169
|
+
ont_graph: Graph,
|
170
|
+
file_name: str = "*",
|
171
|
+
selected_test_files: List[str] = [],
|
172
|
+
) -> Tuple[List, Graph, List]:
|
195
173
|
spec_graph = Graph()
|
196
174
|
subject_uris = set()
|
197
175
|
focus_uris = set()
|
@@ -199,10 +177,10 @@ def validate_specs(run_config: dict,
|
|
199
177
|
ttl_files = []
|
200
178
|
|
201
179
|
if not selected_test_files:
|
202
|
-
ttl_files = list(run_config[
|
203
|
-
f'**/{file_name}.mustrd.ttl'))
|
180
|
+
ttl_files = list(run_config["spec_path"].glob(f"**/{file_name}.mustrd.ttl"))
|
204
181
|
log.info(
|
205
|
-
f"Found {len(ttl_files)} {file_name}.mustrd.ttl files in {run_config['spec_path']}"
|
182
|
+
f"Found {len(ttl_files)} {file_name}.mustrd.ttl files in {run_config['spec_path']}"
|
183
|
+
)
|
206
184
|
else:
|
207
185
|
ttl_files = selected_test_files
|
208
186
|
|
@@ -221,23 +199,27 @@ def validate_specs(run_config: dict,
|
|
221
199
|
except BadSyntax as e:
|
222
200
|
template = "An exception of type {0} occurred when trying to parse a spec file. Arguments:\n{1!r}"
|
223
201
|
message = template.format(type(e).__name__, e.args)
|
224
|
-
log.error(message)
|
225
|
-
error_messages += [
|
226
|
-
|
202
|
+
log.error(message, exc_info=True)
|
203
|
+
error_messages += [
|
204
|
+
f"Could not extract spec from {file} due to exception of type "
|
205
|
+
f"{type(e).__name__} when parsing file"
|
206
|
+
]
|
227
207
|
continue
|
228
208
|
|
229
209
|
# run shacl validation
|
230
|
-
conforms, results_graph, results_text = validate(
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
210
|
+
conforms, results_graph, results_text = validate(
|
211
|
+
file_graph,
|
212
|
+
shacl_graph=shacl_graph,
|
213
|
+
ont_graph=ont_graph,
|
214
|
+
inference="none",
|
215
|
+
abort_on_first=False,
|
216
|
+
allow_infos=False,
|
217
|
+
allow_warnings=False,
|
218
|
+
meta_shacl=False,
|
219
|
+
advanced=True,
|
220
|
+
js=False,
|
221
|
+
debug=False,
|
222
|
+
)
|
241
223
|
if str(file.name).endswith("_duplicate"):
|
242
224
|
log.debug(f"Validation of {file.name} against SHACL shapes: {conforms}")
|
243
225
|
log.debug(f"{results_graph.serialize(format='turtle')}")
|
@@ -251,13 +233,22 @@ def validate_specs(run_config: dict,
|
|
251
233
|
|
252
234
|
# collect a list of uris of the tests in focus
|
253
235
|
# If focus is found, only the spec in the focus will be executed
|
254
|
-
for focus_uri in file_graph.subjects(
|
236
|
+
for focus_uri in file_graph.subjects(
|
237
|
+
predicate=MUST.focus, object=Literal("true", datatype=XSD.boolean)
|
238
|
+
):
|
255
239
|
if focus_uri in focus_uris:
|
256
240
|
focus_uri = URIRef(str(focus_uri) + "_DUPLICATE")
|
257
241
|
focus_uris.add(focus_uri)
|
258
242
|
|
259
|
-
add_spec_validation(
|
260
|
-
|
243
|
+
add_spec_validation(
|
244
|
+
file_graph,
|
245
|
+
subject_uris,
|
246
|
+
file,
|
247
|
+
triple_stores,
|
248
|
+
error_messages,
|
249
|
+
invalid_specs,
|
250
|
+
spec_graph,
|
251
|
+
)
|
261
252
|
|
262
253
|
valid_spec_uris = list(spec_graph.subjects(RDF.type, MUST.TestSpec))
|
263
254
|
|
@@ -282,8 +273,15 @@ def get_invalid_focus_spec(focus_uris: set, invalid_specs: list):
|
|
282
273
|
# Detect duplicate,
|
283
274
|
# If no error: associate the spec configuration and the file where this conf is stored
|
284
275
|
# If error, aggregate the messages and mark spec as skipped
|
285
|
-
def add_spec_validation(
|
286
|
-
|
276
|
+
def add_spec_validation(
|
277
|
+
file_graph: Graph,
|
278
|
+
subject_uris: set,
|
279
|
+
file: Path,
|
280
|
+
triple_stores: List,
|
281
|
+
error_messages: list,
|
282
|
+
invalid_specs: list,
|
283
|
+
spec_graph: Graph,
|
284
|
+
):
|
287
285
|
|
288
286
|
for subject_uri in file_graph.subjects(RDF.type, MUST.TestSpec):
|
289
287
|
# Always add file name and source file to the graph for error reporting
|
@@ -293,7 +291,8 @@ def add_spec_validation(file_graph: Graph, subject_uris: set, file: Path, triple
|
|
293
291
|
# If we already collected a URI, then we tag it as duplicate and it won't be executed
|
294
292
|
if subject_uri in subject_uris:
|
295
293
|
log.warning(
|
296
|
-
f"Duplicate subject URI found: {file.name} {subject_uri}. File will not be parsed."
|
294
|
+
f"Duplicate subject URI found: {file.name} {subject_uri}. File will not be parsed."
|
295
|
+
)
|
297
296
|
error_messages += [f"Duplicate subject URI found in {file.name}."]
|
298
297
|
subject_uri = URIRef(str(subject_uri) + "_DUPLICATE")
|
299
298
|
if len(error_messages) == 0:
|
@@ -301,46 +300,81 @@ def add_spec_validation(file_graph: Graph, subject_uris: set, file: Path, triple
|
|
301
300
|
this_spec_graph = Graph()
|
302
301
|
this_spec_graph.parse(file)
|
303
302
|
spec_uris_in_this_file = list(
|
304
|
-
this_spec_graph.subjects(RDF.type, MUST.TestSpec)
|
303
|
+
this_spec_graph.subjects(RDF.type, MUST.TestSpec)
|
304
|
+
)
|
305
305
|
for spec in spec_uris_in_this_file:
|
306
306
|
this_spec_graph.add([spec, MUST.specSourceFile, Literal(file)])
|
307
|
-
this_spec_graph.add(
|
308
|
-
[spec, MUST.specFileName, Literal(file.name)])
|
307
|
+
this_spec_graph.add([spec, MUST.specFileName, Literal(file.name)])
|
309
308
|
spec_graph += this_spec_graph
|
310
309
|
else:
|
311
310
|
error_messages.sort()
|
312
311
|
error_message = "\n".join(msg for msg in error_messages)
|
313
|
-
invalid_specs += [
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
312
|
+
invalid_specs += [
|
313
|
+
SpecSkipped(
|
314
|
+
subject_uri, triple_store["type"], error_message, file.name, file
|
315
|
+
)
|
316
|
+
for triple_store in triple_stores
|
317
|
+
]
|
318
|
+
|
319
|
+
|
320
|
+
def get_specs(
|
321
|
+
spec_uris: List[URIRef],
|
322
|
+
spec_graph: Graph,
|
323
|
+
triple_stores: List[dict],
|
324
|
+
run_config: dict,
|
325
|
+
):
|
319
326
|
specs = []
|
320
327
|
skipped_results = []
|
321
328
|
try:
|
322
329
|
for triple_store in triple_stores:
|
323
330
|
if "error" in triple_store:
|
324
331
|
log.error(
|
325
|
-
f"{triple_store['error']}. No specs run for this triple store."
|
326
|
-
|
327
|
-
|
328
|
-
|
332
|
+
f"{triple_store['error']}. No specs run for this triple store."
|
333
|
+
)
|
334
|
+
skipped_results += [
|
335
|
+
SpecSkipped(
|
336
|
+
spec_uri,
|
337
|
+
triple_store["type"],
|
338
|
+
triple_store["error"],
|
339
|
+
get_spec_file(spec_uri, spec_graph),
|
340
|
+
)
|
341
|
+
for spec_uri in spec_uris
|
342
|
+
]
|
329
343
|
else:
|
330
344
|
for spec_uri in spec_uris:
|
331
345
|
try:
|
332
|
-
specs += [
|
333
|
-
|
346
|
+
specs += [
|
347
|
+
get_spec(spec_uri, spec_graph, run_config, triple_store)
|
348
|
+
]
|
334
349
|
except (ValueError, FileNotFoundError, ConnectionError) as e:
|
335
350
|
# Try to get file name/path from the graph, but fallback to "unknown"
|
336
|
-
file_name =
|
337
|
-
|
338
|
-
|
339
|
-
|
351
|
+
file_name = (
|
352
|
+
spec_graph.value(
|
353
|
+
subject=spec_uri, predicate=MUST.specFileName
|
354
|
+
)
|
355
|
+
or "unknown"
|
356
|
+
)
|
357
|
+
file_path = (
|
358
|
+
spec_graph.value(
|
359
|
+
subject=spec_uri, predicate=MUST.specSourceFile
|
360
|
+
)
|
361
|
+
or "unknown"
|
362
|
+
)
|
363
|
+
skipped_results += [
|
364
|
+
SpecSkipped(
|
365
|
+
spec_uri,
|
366
|
+
triple_store["type"],
|
367
|
+
str(e),
|
368
|
+
str(file_name),
|
369
|
+
Path(file_path),
|
370
|
+
)
|
371
|
+
]
|
340
372
|
|
341
373
|
except (BadSyntax, FileNotFoundError) as e:
|
342
|
-
template =
|
343
|
-
|
374
|
+
template = (
|
375
|
+
"An exception of type {0} occurred when trying to parse the triple store configuration file. "
|
376
|
+
"Arguments:\n{1!r}"
|
377
|
+
)
|
344
378
|
message = template.format(type(e).__name__, e.args)
|
345
379
|
log.error(message)
|
346
380
|
log.error("No specifications will be run.")
|
@@ -368,28 +402,52 @@ def get_spec_file(spec_uri: URIRef, spec_graph: Graph):
|
|
368
402
|
return "default.mustrd.ttl"
|
369
403
|
|
370
404
|
|
371
|
-
def get_spec(
|
405
|
+
def get_spec(
|
406
|
+
spec_uri: URIRef,
|
407
|
+
spec_graph: Graph,
|
408
|
+
run_config: dict,
|
409
|
+
mustrd_triple_store: dict = None,
|
410
|
+
) -> Specification:
|
372
411
|
try:
|
373
412
|
if not mustrd_triple_store:
|
374
413
|
mustrd_triple_store = {"type": TRIPLESTORE.RdfLib}
|
375
414
|
components = []
|
376
415
|
for predicate in MUST.given, MUST.when, MUST.then:
|
377
|
-
components.append(
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
416
|
+
components.append(
|
417
|
+
parse_spec_component(
|
418
|
+
subject=spec_uri,
|
419
|
+
predicate=predicate,
|
420
|
+
spec_graph=spec_graph,
|
421
|
+
run_config=run_config,
|
422
|
+
mustrd_triple_store=mustrd_triple_store,
|
423
|
+
)
|
424
|
+
)
|
382
425
|
|
383
426
|
spec_file_name = get_spec_file(spec_uri, spec_graph)
|
384
|
-
spec_file_path = Path(
|
385
|
-
|
427
|
+
spec_file_path = Path(
|
428
|
+
spec_graph.value(
|
429
|
+
subject=spec_uri,
|
430
|
+
predicate=MUST.specSourceFile,
|
431
|
+
default=Path("default.mustrd.ttl"),
|
432
|
+
)
|
433
|
+
)
|
386
434
|
# https://github.com/Semantic-partners/mustrd/issues/92
|
387
|
-
return Specification(
|
388
|
-
|
435
|
+
return Specification(
|
436
|
+
spec_uri,
|
437
|
+
mustrd_triple_store,
|
438
|
+
components[0].value,
|
439
|
+
components[1],
|
440
|
+
components[2],
|
441
|
+
spec_file_name,
|
442
|
+
spec_file_path,
|
443
|
+
)
|
389
444
|
|
390
445
|
except (ValueError, FileNotFoundError) as e:
|
391
|
-
template =
|
392
|
-
|
446
|
+
template = (
|
447
|
+
"An exception of type {0} occurred. Arguments:\n{1!r}\nStacktrace:\n{2}"
|
448
|
+
)
|
449
|
+
stacktrace = traceback.format_exc()
|
450
|
+
message = template.format(type(e).__name__, e.args, stacktrace)
|
393
451
|
log.exception(message)
|
394
452
|
raise
|
395
453
|
except ConnectionError as e:
|
@@ -398,9 +456,10 @@ def get_spec(spec_uri: URIRef, spec_graph: Graph, run_config: dict, mustrd_tripl
|
|
398
456
|
|
399
457
|
|
400
458
|
def check_result(spec: Specification, result: Union[str, Graph]):
|
401
|
-
|
459
|
+
|
402
460
|
log.debug(
|
403
|
-
f"check_result {spec.spec_uri=}, {spec.triple_store=}, {result=} {type(spec.then)}"
|
461
|
+
f"check_result {spec.spec_uri=}, {spec.triple_store=}, {result=} {type(spec.then)}"
|
462
|
+
)
|
404
463
|
if isinstance(spec.then, TableThenSpec):
|
405
464
|
log.debug("table_comparison")
|
406
465
|
return table_comparison(result, spec)
|
@@ -417,10 +476,14 @@ def check_result(spec: Specification, result: Union[str, Graph]):
|
|
417
476
|
log.debug("not isomorphic")
|
418
477
|
if spec.when[0].queryType == MUST.ConstructSparql:
|
419
478
|
log.debug("ConstructSpecFailure")
|
420
|
-
return ConstructSpecFailure(
|
479
|
+
return ConstructSpecFailure(
|
480
|
+
spec.spec_uri, spec.triple_store["type"], graph_compare
|
481
|
+
)
|
421
482
|
else:
|
422
483
|
log.debug("UpdateSpecFailure")
|
423
|
-
return UpdateSpecFailure(
|
484
|
+
return UpdateSpecFailure(
|
485
|
+
spec.spec_uri, spec.triple_store["type"], graph_compare
|
486
|
+
)
|
424
487
|
|
425
488
|
|
426
489
|
def run_spec(spec: Specification) -> SpecResult:
|
@@ -431,29 +494,34 @@ def run_spec(spec: Specification) -> SpecResult:
|
|
431
494
|
log.warning(f"check_result called with non-Specification: {type(spec)}")
|
432
495
|
return spec
|
433
496
|
# 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=}")
|
497
|
+
|
498
|
+
log.debug(f"run_spec {spec=}")
|
437
499
|
log.debug(
|
438
|
-
f"run_when {spec_uri=}, {triple_store=}, {spec.given=}, {spec.when=}, {spec.then=}"
|
500
|
+
f"run_when {spec_uri=}, {triple_store=}, {spec.given=}, {spec.when=}, {spec.then=}"
|
501
|
+
)
|
439
502
|
if spec.given:
|
440
503
|
given_as_turtle = spec.given.serialize(format="turtle")
|
441
504
|
log.debug(f"{given_as_turtle}")
|
442
505
|
upload_given(triple_store, spec.given)
|
443
506
|
else:
|
444
|
-
if triple_store[
|
445
|
-
return SpecSkipped(
|
507
|
+
if triple_store["type"] == TRIPLESTORE.RdfLib:
|
508
|
+
return SpecSkipped(
|
509
|
+
spec_uri,
|
510
|
+
triple_store["type"],
|
511
|
+
"Unable to run Inherited State tests on Rdflib",
|
512
|
+
)
|
446
513
|
try:
|
447
514
|
for when in spec.when:
|
448
515
|
log.info(
|
449
|
-
f"Running {when.queryType} spec {spec_uri} on {triple_store['type']}"
|
516
|
+
f"Running {when.queryType} spec {spec_uri} on {triple_store['type']}"
|
517
|
+
)
|
450
518
|
try:
|
451
|
-
result =
|
519
|
+
result = run_when_impl(spec_uri, triple_store, when)
|
452
520
|
log.info(
|
453
|
-
f"run {when.queryType} spec {spec_uri} on {triple_store['type']} {result=}"
|
521
|
+
f"run {when.queryType} spec {spec_uri} on {triple_store['type']} {result=}"
|
522
|
+
)
|
454
523
|
except ParseException as e:
|
455
|
-
log.error(
|
456
|
-
f"parseException {e}")
|
524
|
+
log.error(f"parseException {e}")
|
457
525
|
return SparqlParseFailure(spec_uri, triple_store["type"], e)
|
458
526
|
except NotImplementedError as ex:
|
459
527
|
log.error(f"NotImplementedError {ex}")
|
@@ -462,15 +530,16 @@ def run_spec(spec: Specification) -> SpecResult:
|
|
462
530
|
return check_result(spec, result)
|
463
531
|
except (ConnectionError, TimeoutError, HTTPError, ConnectTimeout, OSError) as e:
|
464
532
|
# close_connection = False
|
533
|
+
stacktrace = traceback.format_exc()
|
465
534
|
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
|
466
535
|
message = template.format(type(e).__name__, e.args)
|
467
|
-
log.error(message)
|
536
|
+
log.error(message, exc_info=True)
|
468
537
|
return TripleStoreConnectionError(spec_uri, triple_store["type"], message)
|
469
538
|
except (TypeError, RequestException) as e:
|
470
|
-
log.error(f"{type(e)} {e}")
|
539
|
+
log.error(f"{type(e)} {e}", exc_info=True)
|
471
540
|
return SparqlExecutionError(spec_uri, triple_store["type"], e)
|
472
541
|
except Exception as e:
|
473
|
-
log.error(f"Unexpected error {e}")
|
542
|
+
log.error(f"Unexpected error {e}", exc_info=True)
|
474
543
|
return RuntimeError(spec_uri, triple_store["type"], f"{type(e).__name__}: {e}")
|
475
544
|
# https://github.com/Semantic-partners/mustrd/issues/78
|
476
545
|
# finally:
|
@@ -482,8 +551,9 @@ def get_triple_store_graph(triple_store_graph_path: Path, secrets: str):
|
|
482
551
|
if secrets:
|
483
552
|
return Graph().parse(triple_store_graph_path).parse(data=secrets)
|
484
553
|
else:
|
485
|
-
secret_path = triple_store_graph_path.parent / Path(
|
486
|
-
|
554
|
+
secret_path = triple_store_graph_path.parent / Path(
|
555
|
+
triple_store_graph_path.stem + "_secrets" + triple_store_graph_path.suffix
|
556
|
+
)
|
487
557
|
return Graph().parse(triple_store_graph_path).parse(secret_path)
|
488
558
|
|
489
559
|
|
@@ -491,32 +561,40 @@ def get_triple_store_graph(triple_store_graph_path: Path, secrets: str):
|
|
491
561
|
def get_triple_stores(triple_store_graph: Graph) -> list[dict]:
|
492
562
|
triple_stores = []
|
493
563
|
shacl_graph = Graph().parse(
|
494
|
-
Path(os.path.join(get_mustrd_root(), "model/triplestoreshapes.ttl"))
|
564
|
+
Path(os.path.join(get_mustrd_root(), "model/triplestoreshapes.ttl"))
|
565
|
+
)
|
495
566
|
ont_graph = Graph().parse(
|
496
|
-
Path(os.path.join(get_mustrd_root(), "model/triplestoreOntology.ttl"))
|
567
|
+
Path(os.path.join(get_mustrd_root(), "model/triplestoreOntology.ttl"))
|
568
|
+
)
|
497
569
|
# SHACL validation of triple store configuration
|
498
570
|
conforms, results_graph, results_text = validate(
|
499
571
|
data_graph=triple_store_graph,
|
500
572
|
shacl_graph=shacl_graph,
|
501
573
|
ont_graph=ont_graph,
|
502
574
|
advanced=True,
|
503
|
-
inference=
|
575
|
+
inference="none",
|
504
576
|
)
|
505
577
|
if not conforms:
|
506
|
-
raise ValueError(
|
507
|
-
|
508
|
-
|
578
|
+
raise ValueError(
|
579
|
+
f"Triple store configuration not conform to the shapes. SHACL report: {results_text}",
|
580
|
+
results_graph,
|
581
|
+
)
|
582
|
+
for triple_store_config, rdf_type, triple_store_type in triple_store_graph.triples(
|
583
|
+
(None, RDF.type, None)
|
584
|
+
):
|
509
585
|
triple_store = {}
|
510
586
|
triple_store["type"] = triple_store_type
|
511
587
|
triple_store["uri"] = triple_store_config
|
512
588
|
# Anzo graph via anzo
|
513
589
|
if triple_store_type == TRIPLESTORE.Anzo:
|
514
590
|
get_anzo_configuration(
|
515
|
-
triple_store, triple_store_graph, triple_store_config
|
591
|
+
triple_store, triple_store_graph, triple_store_config
|
592
|
+
)
|
516
593
|
# GraphDB
|
517
594
|
elif triple_store_type == TRIPLESTORE.GraphDb:
|
518
595
|
get_graphDB_configuration(
|
519
|
-
triple_store, triple_store_graph, triple_store_config
|
596
|
+
triple_store, triple_store_graph, triple_store_config
|
597
|
+
)
|
520
598
|
|
521
599
|
elif triple_store_type != TRIPLESTORE.RdfLib:
|
522
600
|
triple_store["error"] = f"Triple store not implemented: {triple_store_type}"
|
@@ -525,48 +603,74 @@ def get_triple_stores(triple_store_graph: Graph) -> list[dict]:
|
|
525
603
|
return triple_stores
|
526
604
|
|
527
605
|
|
528
|
-
def get_anzo_configuration(
|
606
|
+
def get_anzo_configuration(
|
607
|
+
triple_store: dict, triple_store_graph: Graph, triple_store_config: URIRef
|
608
|
+
):
|
529
609
|
triple_store["url"] = triple_store_graph.value(
|
530
|
-
subject=triple_store_config, predicate=TRIPLESTORE.url
|
610
|
+
subject=triple_store_config, predicate=TRIPLESTORE.url
|
611
|
+
)
|
531
612
|
triple_store["port"] = triple_store_graph.value(
|
532
|
-
subject=triple_store_config, predicate=TRIPLESTORE.port
|
613
|
+
subject=triple_store_config, predicate=TRIPLESTORE.port
|
614
|
+
)
|
533
615
|
try:
|
534
|
-
triple_store["username"] = str(
|
535
|
-
|
536
|
-
|
537
|
-
|
616
|
+
triple_store["username"] = str(
|
617
|
+
triple_store_graph.value(
|
618
|
+
subject=triple_store_config, predicate=TRIPLESTORE.username
|
619
|
+
)
|
620
|
+
)
|
621
|
+
triple_store["password"] = str(
|
622
|
+
triple_store_graph.value(
|
623
|
+
subject=triple_store_config, predicate=TRIPLESTORE.password
|
624
|
+
)
|
625
|
+
)
|
538
626
|
except (FileNotFoundError, ValueError) as e:
|
539
627
|
triple_store["error"] = e
|
540
|
-
triple_store["gqe_uri"] = triple_store_graph.value(
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
628
|
+
triple_store["gqe_uri"] = triple_store_graph.value(
|
629
|
+
subject=triple_store_config, predicate=TRIPLESTORE.gqeURI
|
630
|
+
)
|
631
|
+
triple_store["input_graph"] = triple_store_graph.value(
|
632
|
+
subject=triple_store_config, predicate=TRIPLESTORE.inputGraph
|
633
|
+
)
|
634
|
+
triple_store["output_graph"] = triple_store_graph.value(
|
635
|
+
subject=triple_store_config, predicate=TRIPLESTORE.outputGraph
|
636
|
+
)
|
546
637
|
try:
|
547
638
|
check_triple_store_params(
|
548
|
-
triple_store, ["url", "port", "username", "password", "input_graph"]
|
639
|
+
triple_store, ["url", "port", "username", "password", "input_graph"]
|
640
|
+
)
|
549
641
|
except ValueError as e:
|
550
642
|
triple_store["error"] = e
|
551
643
|
|
552
644
|
|
553
|
-
def get_graphDB_configuration(
|
645
|
+
def get_graphDB_configuration(
|
646
|
+
triple_store: dict, triple_store_graph: Graph, triple_store_config: URIRef
|
647
|
+
):
|
554
648
|
triple_store["url"] = triple_store_graph.value(
|
555
|
-
subject=triple_store_config, predicate=TRIPLESTORE.url
|
649
|
+
subject=triple_store_config, predicate=TRIPLESTORE.url
|
650
|
+
)
|
556
651
|
triple_store["port"] = triple_store_graph.value(
|
557
|
-
subject=triple_store_config, predicate=TRIPLESTORE.port
|
652
|
+
subject=triple_store_config, predicate=TRIPLESTORE.port
|
653
|
+
)
|
558
654
|
try:
|
559
|
-
triple_store["username"] = str(
|
560
|
-
|
561
|
-
|
562
|
-
|
655
|
+
triple_store["username"] = str(
|
656
|
+
triple_store_graph.value(
|
657
|
+
subject=triple_store_config, predicate=TRIPLESTORE.username
|
658
|
+
)
|
659
|
+
)
|
660
|
+
triple_store["password"] = str(
|
661
|
+
triple_store_graph.value(
|
662
|
+
subject=triple_store_config, predicate=TRIPLESTORE.password
|
663
|
+
)
|
664
|
+
)
|
563
665
|
except (FileNotFoundError, ValueError) as e:
|
564
666
|
log.error(f"Credential retrieval failed {e}")
|
565
667
|
triple_store["error"] = e
|
566
|
-
triple_store["repository"] = triple_store_graph.value(
|
567
|
-
|
568
|
-
|
569
|
-
|
668
|
+
triple_store["repository"] = triple_store_graph.value(
|
669
|
+
subject=triple_store_config, predicate=TRIPLESTORE.repository
|
670
|
+
)
|
671
|
+
triple_store["input_graph"] = triple_store_graph.value(
|
672
|
+
subject=triple_store_config, predicate=TRIPLESTORE.inputGraph
|
673
|
+
)
|
570
674
|
try:
|
571
675
|
check_triple_store_params(triple_store, ["url", "repository"])
|
572
676
|
except ValueError as e:
|
@@ -575,18 +679,26 @@ def get_graphDB_configuration(triple_store: dict, triple_store_graph: Graph, tri
|
|
575
679
|
|
576
680
|
def check_triple_store_params(triple_store: dict, required_params: List[str]):
|
577
681
|
missing_params = [
|
578
|
-
param for param in required_params if triple_store.get(param) is None
|
682
|
+
param for param in required_params if triple_store.get(param) is None
|
683
|
+
]
|
579
684
|
if missing_params:
|
580
|
-
raise ValueError(
|
581
|
-
|
685
|
+
raise ValueError(
|
686
|
+
f"Cannot establish connection to {triple_store['type']}. "
|
687
|
+
f"Missing required parameter(s): {', '.join(missing_params)}."
|
688
|
+
)
|
582
689
|
|
583
690
|
|
584
|
-
def get_credential_from_file(
|
691
|
+
def get_credential_from_file(
|
692
|
+
triple_store_name: URIRef, credential: str, config_path: Literal
|
693
|
+
) -> str:
|
585
694
|
log.info(
|
586
|
-
f"get_credential_from_file {triple_store_name}, {credential}, {config_path}"
|
695
|
+
f"get_credential_from_file {triple_store_name}, {credential}, {config_path}"
|
696
|
+
)
|
587
697
|
if not config_path:
|
588
|
-
raise ValueError(
|
589
|
-
|
698
|
+
raise ValueError(
|
699
|
+
f"Cannot establish connection defined in {triple_store_name}. "
|
700
|
+
f"Missing required parameter: {credential}."
|
701
|
+
)
|
590
702
|
path = Path(config_path)
|
591
703
|
log.info(f"get_credential_from_file {path}")
|
592
704
|
|
@@ -622,9 +734,11 @@ def json_results_to_panda_dataframe(result: str) -> pandas.DataFrame:
|
|
622
734
|
else:
|
623
735
|
values.append(str(XSD.anyURI))
|
624
736
|
|
625
|
-
frames = pandas.concat(
|
626
|
-
[values], columns=columns)],
|
627
|
-
|
737
|
+
frames = pandas.concat(
|
738
|
+
objs=[frames, pandas.DataFrame([values], columns=columns)],
|
739
|
+
ignore_index=True,
|
740
|
+
)
|
741
|
+
frames.fillna("", inplace=True)
|
628
742
|
|
629
743
|
if frames.size == 0:
|
630
744
|
frames = pandas.DataFrame()
|
@@ -635,7 +749,8 @@ def table_comparison(result: str, spec: Specification) -> SpecResult:
|
|
635
749
|
warning = None
|
636
750
|
order_list = ["order by ?", "order by desc", "order by asc"]
|
637
751
|
ordered_result = any(
|
638
|
-
pattern in spec.when[0].value.lower() for pattern in order_list
|
752
|
+
pattern in spec.when[0].value.lower() for pattern in order_list
|
753
|
+
)
|
639
754
|
|
640
755
|
# If sparql query doesn't contain order by clause, but order is define in then spec:
|
641
756
|
# Then ignore order in then spec and print a warning
|
@@ -646,27 +761,40 @@ def table_comparison(result: str, spec: Specification) -> SpecResult:
|
|
646
761
|
# If sparql query contains an order by clause and then spec is not order:
|
647
762
|
# Spec is inconsistent
|
648
763
|
if ordered_result and not spec.then.ordered:
|
649
|
-
message =
|
650
|
-
|
764
|
+
message = (
|
765
|
+
"Actual result is ordered, must:then must contain sh:order on every row."
|
766
|
+
)
|
767
|
+
return SelectSpecFailure(
|
768
|
+
spec.spec_uri, spec.triple_store["type"], None, message
|
769
|
+
)
|
651
770
|
|
652
771
|
# Convert results to dataframe
|
653
772
|
if is_json(result):
|
654
773
|
df = json_results_to_panda_dataframe(result)
|
655
774
|
else:
|
656
|
-
return SelectSpecFailure(
|
775
|
+
return SelectSpecFailure(
|
776
|
+
spec.spec_uri,
|
777
|
+
spec.triple_store["type"],
|
778
|
+
None,
|
779
|
+
"Sparql result is not in JSON",
|
780
|
+
)
|
657
781
|
|
658
782
|
# Compare result with expected
|
659
783
|
df_diff, message = compare_table_results(df, spec)
|
660
784
|
|
661
785
|
if df_diff.empty:
|
662
786
|
if warning:
|
663
|
-
return SpecPassedWithWarning(
|
787
|
+
return SpecPassedWithWarning(
|
788
|
+
spec.spec_uri, spec.triple_store["type"], warning
|
789
|
+
)
|
664
790
|
else:
|
665
791
|
return SpecPassed(spec.spec_uri, spec.triple_store["type"])
|
666
792
|
else:
|
667
793
|
log.error("\n" + df_diff.to_markdown())
|
668
794
|
log.error(message)
|
669
|
-
return SelectSpecFailure(
|
795
|
+
return SelectSpecFailure(
|
796
|
+
spec.spec_uri, spec.triple_store["type"], df_diff, message
|
797
|
+
)
|
670
798
|
|
671
799
|
|
672
800
|
def compare_table_results_dispatch(resultDf: DataFrame, spec: Specification):
|
@@ -674,7 +802,8 @@ def compare_table_results_dispatch(resultDf: DataFrame, spec: Specification):
|
|
674
802
|
|
675
803
|
|
676
804
|
compare_table_results = MultiMethod(
|
677
|
-
"compare_table_results", compare_table_results_dispatch
|
805
|
+
"compare_table_results", compare_table_results_dispatch
|
806
|
+
)
|
678
807
|
|
679
808
|
|
680
809
|
# Scenario 1: expected a result and got a result
|
@@ -686,7 +815,8 @@ def _compare_results(resultDf: DataFrame, spec: Specification):
|
|
686
815
|
sorted_then_cols = sorted(list(then))
|
687
816
|
order_list = ["order by ?", "order by desc", "order by asc"]
|
688
817
|
ordered_result = any(
|
689
|
-
pattern in spec.when[0].value.lower() for pattern in order_list
|
818
|
+
pattern in spec.when[0].value.lower() for pattern in order_list
|
819
|
+
)
|
690
820
|
|
691
821
|
if not ordered_result:
|
692
822
|
resultDf.sort_values(by=list(resultDf.columns)[::2], inplace=True)
|
@@ -698,9 +828,11 @@ def _compare_results(resultDf: DataFrame, spec: Specification):
|
|
698
828
|
if not ordered_result:
|
699
829
|
then.sort_values(by=columns[::2], inplace=True)
|
700
830
|
then.reset_index(drop=True, inplace=True)
|
701
|
-
if
|
702
|
-
|
703
|
-
|
831
|
+
if (
|
832
|
+
resultDf.shape == then.shape
|
833
|
+
and (resultDf.columns == then.columns).all()
|
834
|
+
):
|
835
|
+
df_diff = then.compare(resultDf, result_names=("expected", "actual"))
|
704
836
|
else:
|
705
837
|
df_diff = construct_df_diff(resultDf, then)
|
706
838
|
else:
|
@@ -712,8 +844,12 @@ def _compare_results(resultDf: DataFrame, spec: Specification):
|
|
712
844
|
resultDf = resultDf[sorted_columns]
|
713
845
|
df_diff = construct_df_diff(resultDf, then)
|
714
846
|
|
715
|
-
message = build_summary_message(
|
716
|
-
then.shape[
|
847
|
+
message = build_summary_message(
|
848
|
+
then.shape[0],
|
849
|
+
round(then.shape[1] / 2),
|
850
|
+
resultDf.shape[0],
|
851
|
+
round(resultDf.shape[1] / 2),
|
852
|
+
)
|
717
853
|
return df_diff, message
|
718
854
|
|
719
855
|
|
@@ -723,7 +859,9 @@ def _unexpected_results(resultDf: DataFrame, spec: Specification):
|
|
723
859
|
empty_then = create_empty_dataframe_with_columns(resultDf)
|
724
860
|
df_diff = empty_then.compare(resultDf, result_names=("expected", "actual"))
|
725
861
|
|
726
|
-
return df_diff, build_summary_message(
|
862
|
+
return df_diff, build_summary_message(
|
863
|
+
0, 0, resultDf.shape[0], round(resultDf.shape[1] / 2)
|
864
|
+
)
|
727
865
|
|
728
866
|
|
729
867
|
# Scenario 3: expected a result, but got an empty result
|
@@ -747,8 +885,10 @@ def _no_results(resultDf: DataFrame, spec: Specification):
|
|
747
885
|
|
748
886
|
|
749
887
|
def build_summary_message(expected_rows, expected_columns, got_rows, got_columns):
|
750
|
-
return
|
888
|
+
return (
|
889
|
+
f"Expected {expected_rows} row(s) and {expected_columns} column(s), "
|
751
890
|
f"got {got_rows} row(s) and {got_columns} column(s)"
|
891
|
+
)
|
752
892
|
|
753
893
|
|
754
894
|
def graph_comparison(expected_graph: Graph, actual_graph: Graph) -> GraphComparison:
|
@@ -756,9 +896,11 @@ def graph_comparison(expected_graph: Graph, actual_graph: Graph) -> GraphCompari
|
|
756
896
|
in_both = diff[0]
|
757
897
|
in_expected = diff[1]
|
758
898
|
in_actual = diff[2]
|
759
|
-
in_expected_not_in_actual =
|
760
|
-
in_actual_not_in_expected =
|
761
|
-
return GraphComparison(
|
899
|
+
in_expected_not_in_actual = in_expected - in_actual
|
900
|
+
in_actual_not_in_expected = in_actual - in_expected
|
901
|
+
return GraphComparison(
|
902
|
+
in_expected_not_in_actual, in_actual_not_in_expected, in_both
|
903
|
+
)
|
762
904
|
|
763
905
|
|
764
906
|
def get_then_update(spec_uri: URIRef, spec_graph: Graph) -> Graph:
|
@@ -786,11 +928,9 @@ def write_result_diff_to_log(res, info):
|
|
786
928
|
if isinstance(res, UpdateSpecFailure) or isinstance(res, ConstructSpecFailure):
|
787
929
|
info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
|
788
930
|
info(f"{Fore.BLUE} In Expected Not In Actual:")
|
789
|
-
info(
|
790
|
-
res.graph_comparison.in_expected_not_in_actual.serialize(format="ttl"))
|
931
|
+
info(res.graph_comparison.in_expected_not_in_actual.serialize(format="ttl"))
|
791
932
|
info(f"{Fore.RED} in_actual_not_in_expected")
|
792
|
-
info(
|
793
|
-
res.graph_comparison.in_actual_not_in_expected.serialize(format="ttl"))
|
933
|
+
info(res.graph_comparison.in_actual_not_in_expected.serialize(format="ttl"))
|
794
934
|
info(f"{Fore.GREEN} in_both")
|
795
935
|
info(res.graph_comparison.in_both.serialize(format="ttl"))
|
796
936
|
|
@@ -799,11 +939,13 @@ def write_result_diff_to_log(res, info):
|
|
799
939
|
info(res.message)
|
800
940
|
info(res.table_comparison.to_markdown())
|
801
941
|
if isinstance(res, SpecPassedWithWarning):
|
802
|
-
info(
|
803
|
-
f"{Fore.YELLOW}Passed with warning {res.spec_uri} {res.triple_store}")
|
942
|
+
info(f"{Fore.YELLOW}Passed with warning {res.spec_uri} {res.triple_store}")
|
804
943
|
info(res.warning)
|
805
|
-
if
|
806
|
-
|
944
|
+
if (
|
945
|
+
isinstance(res, TripleStoreConnectionError)
|
946
|
+
or isinstance(res, SparqlExecutionError)
|
947
|
+
or isinstance(res, SparqlParseFailure)
|
948
|
+
):
|
807
949
|
info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
|
808
950
|
info(res.exception)
|
809
951
|
if isinstance(res, SpecSkipped):
|
@@ -811,16 +953,16 @@ def write_result_diff_to_log(res, info):
|
|
811
953
|
info(res.message)
|
812
954
|
|
813
955
|
|
814
|
-
def calculate_row_difference(
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
actual_rows =
|
956
|
+
def calculate_row_difference(
|
957
|
+
df1: pandas.DataFrame, df2: pandas.DataFrame
|
958
|
+
) -> pandas.DataFrame:
|
959
|
+
df_all = df1.merge(df2.drop_duplicates(), how="left", indicator=True)
|
960
|
+
actual_rows = df_all[df_all["_merge"] == "left_only"]
|
961
|
+
actual_rows = actual_rows.drop("_merge", axis=1)
|
819
962
|
return actual_rows
|
820
963
|
|
821
964
|
|
822
|
-
def construct_df_diff(df: pandas.DataFrame,
|
823
|
-
then: pandas.DataFrame) -> pandas.DataFrame:
|
965
|
+
def construct_df_diff(df: pandas.DataFrame, then: pandas.DataFrame) -> pandas.DataFrame:
|
824
966
|
actual_rows = calculate_row_difference(df, then)
|
825
967
|
expected_rows = calculate_row_difference(then, df)
|
826
968
|
actual_columns = df.columns.difference(then.columns)
|
@@ -832,15 +974,19 @@ def construct_df_diff(df: pandas.DataFrame,
|
|
832
974
|
|
833
975
|
if actual_columns.size > 0:
|
834
976
|
modified_then = modified_then.reindex(
|
835
|
-
modified_then.columns.to_list() + actual_columns.to_list(), axis=1
|
836
|
-
|
837
|
-
|
977
|
+
modified_then.columns.to_list() + actual_columns.to_list(), axis=1
|
978
|
+
)
|
979
|
+
modified_then[actual_columns.to_list()] = modified_then[
|
980
|
+
actual_columns.to_list()
|
981
|
+
].fillna("")
|
838
982
|
|
839
983
|
if expected_columns.size > 0:
|
840
984
|
modified_df = modified_df.reindex(
|
841
|
-
modified_df.columns.to_list() + expected_columns.to_list(), axis=1
|
842
|
-
|
843
|
-
|
985
|
+
modified_df.columns.to_list() + expected_columns.to_list(), axis=1
|
986
|
+
)
|
987
|
+
modified_df[expected_columns.to_list()] = modified_df[
|
988
|
+
expected_columns.to_list()
|
989
|
+
].fillna("")
|
844
990
|
|
845
991
|
modified_df = modified_df.reindex(modified_then.columns, axis=1)
|
846
992
|
|
@@ -852,29 +998,37 @@ def construct_df_diff(df: pandas.DataFrame,
|
|
852
998
|
elif actual_rows.shape[0] > 0 or expected_rows.shape[0] > 0:
|
853
999
|
df_diff = generate_row_diff(actual_rows, expected_rows)
|
854
1000
|
elif actual_columns.size > 0 or expected_columns.size > 0:
|
855
|
-
df_diff = modified_then.compare(
|
856
|
-
|
1001
|
+
df_diff = modified_then.compare(
|
1002
|
+
modified_df,
|
1003
|
+
result_names=("expected", "actual"),
|
1004
|
+
keep_shape=True,
|
1005
|
+
keep_equal=True,
|
1006
|
+
)
|
857
1007
|
df_diff.fillna("", inplace=True)
|
858
1008
|
return df_diff
|
859
1009
|
|
860
1010
|
|
861
|
-
def generate_row_diff(
|
1011
|
+
def generate_row_diff(
|
1012
|
+
actual_rows: pandas.DataFrame, expected_rows: pandas.DataFrame
|
1013
|
+
) -> pandas.DataFrame:
|
862
1014
|
df_diff_actual_rows = pandas.DataFrame()
|
863
1015
|
df_diff_expected_rows = pandas.DataFrame()
|
864
1016
|
|
865
1017
|
if actual_rows.shape[0] > 0:
|
866
1018
|
empty_actual_copy = create_empty_dataframe_with_columns(actual_rows)
|
867
1019
|
df_diff_actual_rows = empty_actual_copy.compare(
|
868
|
-
actual_rows, result_names=("expected", "actual")
|
1020
|
+
actual_rows, result_names=("expected", "actual")
|
1021
|
+
)
|
869
1022
|
|
870
1023
|
if expected_rows.shape[0] > 0:
|
871
|
-
empty_expected_copy = create_empty_dataframe_with_columns(
|
872
|
-
expected_rows)
|
1024
|
+
empty_expected_copy = create_empty_dataframe_with_columns(expected_rows)
|
873
1025
|
df_diff_expected_rows = expected_rows.compare(
|
874
|
-
empty_expected_copy, result_names=("expected", "actual")
|
1026
|
+
empty_expected_copy, result_names=("expected", "actual")
|
1027
|
+
)
|
875
1028
|
|
876
1029
|
df_diff_rows = pandas.concat(
|
877
|
-
[df_diff_actual_rows, df_diff_expected_rows], ignore_index=True
|
1030
|
+
[df_diff_actual_rows, df_diff_expected_rows], ignore_index=True
|
1031
|
+
)
|
878
1032
|
return df_diff_rows
|
879
1033
|
|
880
1034
|
|
@@ -889,40 +1043,76 @@ def review_results(results: List[SpecResult], verbose: bool) -> None:
|
|
889
1043
|
# Init dictionaries
|
890
1044
|
status_dict = defaultdict(lambda: defaultdict(int))
|
891
1045
|
status_counts = defaultdict(lambda: defaultdict(int))
|
892
|
-
colours = {
|
893
|
-
|
1046
|
+
colours = {
|
1047
|
+
SpecPassed: Fore.GREEN,
|
1048
|
+
SpecPassedWithWarning: Fore.YELLOW,
|
1049
|
+
SpecSkipped: Fore.YELLOW,
|
1050
|
+
}
|
894
1051
|
# Populate dictionaries from results
|
895
1052
|
for result in results:
|
896
1053
|
status_counts[result.triple_store][type(result)] += 1
|
897
1054
|
status_dict[result.spec_uri][result.triple_store] = type(result)
|
898
1055
|
|
899
1056
|
# Get the list of statuses and list of unique triple stores
|
900
|
-
statuses = list(
|
901
|
-
|
902
|
-
|
903
|
-
|
1057
|
+
statuses = list(
|
1058
|
+
status for inner_dict in status_dict.values() for status in inner_dict.values()
|
1059
|
+
)
|
1060
|
+
triple_stores = list(
|
1061
|
+
set(
|
1062
|
+
status
|
1063
|
+
for inner_dict in status_dict.values()
|
1064
|
+
for status in inner_dict.keys()
|
1065
|
+
)
|
1066
|
+
)
|
904
1067
|
|
905
1068
|
# Convert dictionaries to list for tabulate
|
906
|
-
table_rows = [
|
907
|
-
|
1069
|
+
table_rows = [
|
1070
|
+
[spec_uri]
|
1071
|
+
+ [
|
1072
|
+
f"""{colours.get(status_dict[spec_uri][triple_store], Fore.RED)}
|
908
1073
|
{status_dict[spec_uri][triple_store].__name__}{Style.RESET_ALL}"""
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
1074
|
+
for triple_store in triple_stores
|
1075
|
+
]
|
1076
|
+
for spec_uri in set(status_dict.keys())
|
1077
|
+
]
|
1078
|
+
|
1079
|
+
status_rows = [
|
1080
|
+
[f"{colours.get(status, Fore.RED)}{status.__name__}{Style.RESET_ALL}"]
|
1081
|
+
+ [
|
1082
|
+
f"{colours.get(status, Fore.RED)}{status_counts[triple_store][status]}{Style.RESET_ALL}"
|
1083
|
+
for triple_store in triple_stores
|
1084
|
+
]
|
1085
|
+
for status in set(statuses)
|
1086
|
+
]
|
914
1087
|
|
915
1088
|
# Display tables with tabulate
|
916
|
-
log.info(
|
917
|
-
|
918
|
-
|
919
|
-
|
1089
|
+
log.info(
|
1090
|
+
tabulate(
|
1091
|
+
table_rows,
|
1092
|
+
headers=["Spec Uris / triple stores"] + triple_stores,
|
1093
|
+
tablefmt="pretty",
|
1094
|
+
)
|
1095
|
+
)
|
1096
|
+
log.info(
|
1097
|
+
tabulate(
|
1098
|
+
status_rows,
|
1099
|
+
headers=["Status / triple stores"] + triple_stores,
|
1100
|
+
tablefmt="pretty",
|
1101
|
+
)
|
1102
|
+
)
|
920
1103
|
|
921
1104
|
pass_count = statuses.count(SpecPassed)
|
922
1105
|
warning_count = statuses.count(SpecPassedWithWarning)
|
923
1106
|
skipped_count = statuses.count(SpecSkipped)
|
924
1107
|
fail_count = len(
|
925
|
-
list(
|
1108
|
+
list(
|
1109
|
+
filter(
|
1110
|
+
lambda status: status
|
1111
|
+
not in [SpecPassed, SpecPassedWithWarning, SpecSkipped],
|
1112
|
+
statuses,
|
1113
|
+
)
|
1114
|
+
)
|
1115
|
+
)
|
926
1116
|
|
927
1117
|
if fail_count:
|
928
1118
|
overview_colour = Fore.RED
|
@@ -932,8 +1122,10 @@ def review_results(results: List[SpecResult], verbose: bool) -> None:
|
|
932
1122
|
overview_colour = Fore.GREEN
|
933
1123
|
|
934
1124
|
logger_setup.flush()
|
935
|
-
log.info(
|
936
|
-
|
1125
|
+
log.info(
|
1126
|
+
f"{overview_colour}===== {fail_count} failures, {skipped_count} skipped, {Fore.GREEN}{pass_count} passed, "
|
1127
|
+
f"{overview_colour}{warning_count} passed with warnings ====="
|
1128
|
+
)
|
937
1129
|
|
938
1130
|
if verbose and (fail_count or warning_count or skipped_count):
|
939
1131
|
display_verbose(results)
|
@@ -945,11 +1137,13 @@ def display_verbose(results: List[SpecResult]):
|
|
945
1137
|
log.info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
|
946
1138
|
log.info(f"{Fore.BLUE} In Expected Not In Actual:")
|
947
1139
|
log.info(
|
948
|
-
res.graph_comparison.in_expected_not_in_actual.serialize(format="ttl")
|
1140
|
+
res.graph_comparison.in_expected_not_in_actual.serialize(format="ttl")
|
1141
|
+
)
|
949
1142
|
log.info()
|
950
1143
|
log.info(f"{Fore.RED} in_actual_not_in_expected")
|
951
1144
|
log.info(
|
952
|
-
res.graph_comparison.in_actual_not_in_expected.serialize(format="ttl")
|
1145
|
+
res.graph_comparison.in_actual_not_in_expected.serialize(format="ttl")
|
1146
|
+
)
|
953
1147
|
log.info(f"{Fore.GREEN} in_both")
|
954
1148
|
log.info(res.graph_comparison.in_both.serialize(format="ttl"))
|
955
1149
|
|
@@ -961,12 +1155,33 @@ def display_verbose(results: List[SpecResult]):
|
|
961
1155
|
log.info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
|
962
1156
|
if isinstance(res, SpecPassedWithWarning):
|
963
1157
|
log.info(
|
964
|
-
f"{Fore.YELLOW}Passed with warning {res.spec_uri} {res.triple_store}"
|
1158
|
+
f"{Fore.YELLOW}Passed with warning {res.spec_uri} {res.triple_store}"
|
1159
|
+
)
|
965
1160
|
log.info(res.warning)
|
966
|
-
if
|
967
|
-
|
1161
|
+
if (
|
1162
|
+
isinstance(res, TripleStoreConnectionError)
|
1163
|
+
or type(res, SparqlExecutionError)
|
1164
|
+
or isinstance(res, SparqlParseFailure)
|
1165
|
+
):
|
968
1166
|
log.info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
|
969
1167
|
log.info(res.exception)
|
970
1168
|
if isinstance(res, SpecSkipped):
|
971
1169
|
log.info(f"{Fore.YELLOW}Skipped {res.spec_uri} {res.triple_store}")
|
972
1170
|
log.info(res.message)
|
1171
|
+
|
1172
|
+
|
1173
|
+
# Preserve the original run_when_impl multimethod
|
1174
|
+
original_run_when_impl = run_when_impl
|
1175
|
+
|
1176
|
+
|
1177
|
+
# Wrapper function for logging inputs and outputs of run_when
|
1178
|
+
def run_when_with_logging(*args, **kwargs):
|
1179
|
+
log.debug(f"run_when called with args: {args}, kwargs: {kwargs}")
|
1180
|
+
result = original_run_when_impl(*args, **kwargs) # Call the original multimethod
|
1181
|
+
log.debug(f"run_when returned: {result}")
|
1182
|
+
return result
|
1183
|
+
|
1184
|
+
|
1185
|
+
# Replace the original run_when_impl with the wrapped version
|
1186
|
+
run_when_impl = run_when_with_logging
|
1187
|
+
run_when = run_when_impl
|