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/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, run_when
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 += ':HIGH:!DH:!aNULL'
40
+ requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ":HIGH:!DH:!aNULL"
64
41
 
65
- logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
42
+ logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
66
43
 
67
44
 
68
45
  def debug_requests_on():
69
- '''Switches on logging of the requests module.'''
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
- '''Switches off logging of the requests module, might be some side-effects'''
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(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] = [])\
194
- -> Tuple[List, Graph, List]:
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['spec_path'].glob(
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 += [f"Could not extract spec from {file} due to exception of type "
226
- f"{type(e).__name__} when parsing file"]
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(file_graph,
231
- shacl_graph=shacl_graph,
232
- ont_graph=ont_graph,
233
- inference='none',
234
- abort_on_first=False,
235
- allow_infos=False,
236
- allow_warnings=False,
237
- meta_shacl=False,
238
- advanced=True,
239
- js=False,
240
- debug=False)
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(predicate=MUST.focus, object=Literal("true", datatype=XSD.boolean)):
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(file_graph, subject_uris, file,
260
- triple_stores, error_messages, invalid_specs, spec_graph)
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(file_graph: Graph, subject_uris: set, file: Path, triple_stores: List,
286
- error_messages: list, invalid_specs: list, spec_graph: Graph):
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 += [SpecSkipped(subject_uri, triple_store["type"], error_message, file.name, file)
314
- for triple_store in triple_stores]
315
-
316
-
317
- def get_specs(spec_uris: List[URIRef], spec_graph: Graph, triple_stores: List[dict],
318
- run_config: dict):
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
- skipped_results += [SpecSkipped(spec_uri, triple_store['type'], triple_store['error'],
327
- get_spec_file(spec_uri, spec_graph)) for spec_uri in
328
- spec_uris]
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 += [get_spec(spec_uri, spec_graph,
333
- run_config, triple_store)]
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 = 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"
338
- skipped_results += [SpecSkipped(spec_uri, triple_store['type'],
339
- str(e), str(file_name), Path(file_path))]
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 = "An exception of type {0} occurred when trying to parse the triple store configuration file. " \
343
- "Arguments:\n{1!r}"
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(spec_uri: URIRef, spec_graph: Graph, run_config: dict, mustrd_triple_store: dict = None) -> Specification:
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(parse_spec_component(subject=spec_uri,
378
- predicate=predicate,
379
- spec_graph=spec_graph,
380
- run_config=run_config,
381
- mustrd_triple_store=mustrd_triple_store))
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(spec_graph.value(
385
- subject=spec_uri, predicate=MUST.specSourceFile, default=Path("default.mustrd.ttl")))
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(spec_uri, mustrd_triple_store,
388
- components[0].value, components[1], components[2], spec_file_name, spec_file_path)
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 = "An exception of type {0} occurred. Arguments:\n{1!r}"
392
- message = template.format(type(e).__name__, e.args)
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(spec.spec_uri, spec.triple_store["type"], graph_compare)
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(spec.spec_uri, spec.triple_store["type"], graph_compare)
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['type'] == TRIPLESTORE.RdfLib:
445
- return SpecSkipped(spec_uri, triple_store['type'], "Unable to run Inherited State tests on Rdflib")
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 = run_when(spec_uri, triple_store, when)
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(triple_store_graph_path.stem +
486
- "_secrets" + triple_store_graph_path.suffix)
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='none'
575
+ inference="none",
504
576
  )
505
577
  if not conforms:
506
- raise ValueError(f"Triple store configuration not conform to the shapes. SHACL report: {results_text}",
507
- results_graph)
508
- for triple_store_config, rdf_type, triple_store_type in triple_store_graph.triples((None, RDF.type, None)):
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(triple_store: dict, triple_store_graph: Graph, triple_store_config: URIRef):
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(triple_store_graph.value(subject=triple_store_config,
535
- predicate=TRIPLESTORE.username))
536
- triple_store["password"] = str(triple_store_graph.value(subject=triple_store_config,
537
- predicate=TRIPLESTORE.password))
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(subject=triple_store_config,
541
- predicate=TRIPLESTORE.gqeURI)
542
- triple_store["input_graph"] = triple_store_graph.value(subject=triple_store_config,
543
- predicate=TRIPLESTORE.inputGraph)
544
- triple_store["output_graph"] = triple_store_graph.value(subject=triple_store_config,
545
- predicate=TRIPLESTORE.outputGraph)
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(triple_store: dict, triple_store_graph: Graph, triple_store_config: URIRef):
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(triple_store_graph.value(subject=triple_store_config,
560
- predicate=TRIPLESTORE.username))
561
- triple_store["password"] = str(triple_store_graph.value(subject=triple_store_config,
562
- predicate=TRIPLESTORE.password))
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(subject=triple_store_config,
567
- predicate=TRIPLESTORE.repository)
568
- triple_store["input_graph"] = triple_store_graph.value(subject=triple_store_config,
569
- predicate=TRIPLESTORE.inputGraph)
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(f"Cannot establish connection to {triple_store['type']}. "
581
- f"Missing required parameter(s): {', '.join(missing_params)}.")
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(triple_store_name: URIRef, credential: str, config_path: Literal) -> str:
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(f"Cannot establish connection defined in {triple_store_name}. "
589
- f"Missing required parameter: {credential}.")
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(objs=[frames, pandas.DataFrame(
626
- [values], columns=columns)], ignore_index=True)
627
- frames.fillna('', inplace=True)
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 = "Actual result is ordered, must:then must contain sh:order on every row."
650
- return SelectSpecFailure(spec.spec_uri, spec.triple_store["type"], None, message)
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(spec.spec_uri, spec.triple_store["type"], None, "Sparql result is not in JSON")
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(spec.spec_uri, spec.triple_store["type"], warning)
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(spec.spec_uri, spec.triple_store["type"], df_diff, message)
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 resultDf.shape == then.shape and (resultDf.columns == then.columns).all():
702
- df_diff = then.compare(
703
- resultDf, result_names=("expected", "actual"))
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(then.shape[0], round(
716
- then.shape[1] / 2), resultDf.shape[0], round(resultDf.shape[1] / 2))
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(0, 0, resultDf.shape[0], round(resultDf.shape[1] / 2))
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 f"Expected {expected_rows} row(s) and {expected_columns} column(s), " \
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 = (in_expected - in_actual)
760
- in_actual_not_in_expected = (in_actual - in_expected)
761
- return GraphComparison(in_expected_not_in_actual, in_actual_not_in_expected, in_both)
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 isinstance(res, TripleStoreConnectionError) or isinstance(res, SparqlExecutionError) or \
806
- isinstance(res, SparqlParseFailure):
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(df1: pandas.DataFrame,
815
- df2: pandas.DataFrame) -> pandas.DataFrame:
816
- df_all = df1.merge(df2.drop_duplicates(), how='left', indicator=True)
817
- actual_rows = df_all[df_all['_merge'] == 'left_only']
818
- actual_rows = actual_rows.drop('_merge', axis=1)
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
- modified_then[actual_columns.to_list(
837
- )] = modified_then[actual_columns.to_list()].fillna('')
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
- modified_df[expected_columns.to_list(
843
- )] = modified_df[expected_columns.to_list()].fillna('')
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(modified_df, result_names=("expected", "actual"), keep_shape=True,
856
- keep_equal=True)
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(actual_rows: pandas.DataFrame, expected_rows: pandas.DataFrame) -> pandas.DataFrame:
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 = {SpecPassed: Fore.GREEN,
893
- SpecPassedWithWarning: Fore.YELLOW, SpecSkipped: Fore.YELLOW}
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(status for inner_dict in status_dict.values()
901
- for status in inner_dict.values())
902
- triple_stores = list(set(status for inner_dict in status_dict.values()
903
- for status in inner_dict.keys()))
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 = [[spec_uri] + [
907
- f"""{colours.get(status_dict[spec_uri][triple_store], Fore.RED)}
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
- for triple_store in triple_stores] for spec_uri in set(status_dict.keys())]
910
-
911
- status_rows = [[f"{colours.get(status, Fore.RED)}{status.__name__}{Style.RESET_ALL}"] +
912
- [f"{colours.get(status, Fore.RED)}{status_counts[triple_store][status]}{Style.RESET_ALL}"
913
- for triple_store in triple_stores] for status in set(statuses)]
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(tabulate(table_rows, headers=[
917
- 'Spec Uris / triple stores'] + triple_stores, tablefmt="pretty"))
918
- log.info(tabulate(status_rows, headers=[
919
- 'Status / triple stores'] + triple_stores, tablefmt="pretty"))
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(filter(lambda status: status not in [SpecPassed, SpecPassedWithWarning, SpecSkipped], statuses)))
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(f"{overview_colour}===== {fail_count} failures, {skipped_count} skipped, {Fore.GREEN}{pass_count} passed, "
936
- f"{overview_colour}{warning_count} passed with warnings =====")
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 isinstance(res, TripleStoreConnectionError) or type(res, SparqlExecutionError) or \
967
- isinstance(res, SparqlParseFailure):
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