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 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': True,
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': True,
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
- return manage_anzo_response(requests.post(url=url, params=params, data=query,
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
@@ -35,6 +35,7 @@ def setup_logger(name: str) -> logging.Logger:
35
35
  log = logging.getLogger(name)
36
36
  log.setLevel(LOG_LEVEL)
37
37
 
38
+
38
39
  stderr_handler = logging.StreamHandler(sys.stderr)
39
40
  stderr_handler.setLevel(logging.ERROR)
40
41
  log.addHandler(stderr_handler)
@@ -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 = logger_setup.setup_logger(__name__)
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, triple_stores: List, shacl_graph: Graph, ont_graph: Graph, file_name: str = "*")\
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 = list(run_config['spec_path'].glob(
194
- f'**/{file_name}.mustrd.ttl'))
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, get_spec_file(spec_uri, spec_graph))]
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
- return str(spec_graph.value(subject=spec_uri, predicate=MUST.specFileName, default="default.mustrd.ttl"))
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
- return SpecPassed(spec.spec_uri, spec.triple_store["type"])
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
- # close_connection = True
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
- return SpecSkipped(spec_uri, triple_store["type"], ex.args[0])
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
- log.info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
730
- log.info(f"{Fore.BLUE} In Expected Not In Actual:")
731
- log.info(
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
- log.info(f"{Fore.RED} in_actual_not_in_expected")
734
- log.info(
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
- log.info(f"{Fore.GREEN} in_both")
737
- log.info(res.graph_comparison.in_both.serialize(format="ttl"))
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
- log.info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
741
- log.info(res.message)
742
- log.info(res.table_comparison.to_markdown())
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
- log.info(
802
+ info(
745
803
  f"{Fore.YELLOW}Passed with warning {res.spec_uri} {res.triple_store}")
746
- log.info(res.warning)
804
+ info(res.warning)
747
805
  if isinstance(res, TripleStoreConnectionError) or isinstance(res, SparqlExecutionError) or \
748
806
  isinstance(res, SparqlParseFailure):
749
- log.info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
750
- log.info(res.exception)
807
+ info(f"{Fore.RED}Failed {res.spec_uri} {res.triple_store}")
808
+ info(res.exception)
751
809
  if isinstance(res, SpecSkipped):
752
- log.info(f"{Fore.YELLOW}Skipped {res.spec_uri} {res.triple_store}")
753
- log.info(res.message)
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 }",
@@ -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 write_result_diff_to_log, get_triple_store_graph, get_triple_stores
38
- from mustrd.mustrd import Specification, SpecSkipped, validate_specs, get_specs, SpecPassed, run_spec
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
- import logging
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(MustrdTestPlugin(config.getoption("mdpath"),
89
- Path(config.getoption("configpath")), config.getoption("secrets")))
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='none'
109
+ inference="none",
106
110
  )
107
111
  if not conforms:
108
- raise ValueError(f"Mustrd test configuration not conform to the shapes. SHACL report: {results_text}",
109
- results_graph)
110
-
111
- for test_config_subject in config_graph.subjects(predicate=RDF.type, object=MUSTRDTEST.MustrdTest):
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
- filter_on_tripleStore = tuple(config_graph.objects(subject=test_config_subject,
121
- predicate=MUSTRDTEST.filterOnTripleStore))
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 = root_path / \
128
- Path(triplestore_spec_path) if triplestore_spec_path else None
129
-
130
- test_configs.append(TestConfig(spec_path=spec_path, data_path=data_path,
131
- triplestore_spec_path=triplestore_spec_path,
132
- pytest_path=pytest_path,
133
- filter_on_tripleStore=filter_on_tripleStore))
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
- logger.info("Used arguments: " + str(args))
185
- self.selected_tests = list(map(lambda arg: Path(arg.split("::")[0]).resolve(),
186
- # By default the current directory is given as argument
187
- # Remove it as it is not a test
188
- filter(lambda arg: arg != os.getcwd() and "::" in arg, args)))
189
-
190
- self.path_filter = args[0] if len(
191
- args) == 1 and args[0] != os.getcwd() and not "::" in args[0] else None
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 = [str(self.test_config_file.resolve())]
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
- mustrd_file = MustrdFile.from_parent(
204
- parent, fspath=path, mustrd_plugin=self)
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(Path(os.path.join(mustrd_root, "model/mustrdShapes.ttl")))
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
- valid_spec_uris, spec_graph, invalid_spec_results = validate_specs(config, triple_stores,
214
- shacl_graph, ont_graph, file_name or "*")
215
-
216
- specs, skipped_spec_results = \
217
- get_specs(valid_spec_uris, spec_graph, triple_stores, config)
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 + invalid_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['type']
229
- triple_store_name = triple_store.replace(
230
- "https://mustrd.com/model/", "")
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(get_triple_store_graph(test_config.triplestore_spec_path,
239
- self.secrets))
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(f"""Triplestore configuration parsing failed {test_config.triplestore_spec_path}.
242
- Only rdflib will be executed""", e)
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
- {'type': TRIPLESTORE.RdfLib, 'uri': TRIPLESTORE.RdfLib}]
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(filter(lambda triple_store: (triple_store["uri"] in test_config.filter_on_tripleStore),
252
- triple_stores))
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 == 'call':
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 = test_conf.name.replace(
284
- class_name, "").replace("[", "").replace("]", "")
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(TestResult(
294
- test_name, class_name, module_name, result.outcome, is_mustrd))
295
-
296
- result_list = ResultList(None, get_result_list(test_results,
297
- lambda result: result.type,
298
- lambda result: is_mustrd and result.test_name.split("@")[1]),
299
- False)
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, 'w') as file:
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.debug(f"Collecting tests from file: {self.fspath}")
312
- test_configs = parse_config(self.fspath)
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
- # Skip if there is a path filter and it is not in the pytest path
315
- if self.mustrd_plugin.path_filter is not None and self.mustrd_plugin.path_filter not in test_config.pytest_path:
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
- triple_stores = self.mustrd_plugin.get_triple_stores_from_file(
318
- test_config)
319
-
320
- if test_config.filter_on_tripleStore and not triple_stores:
321
- specs = list(map(
322
- lambda triple_store:
323
- SpecSkipped(MUST.TestSpec, triple_store,
324
- "No triplestore found"),
325
- test_config.filter_on_tripleStore))
326
- else:
327
- specs = self.mustrd_plugin.generate_tests_for_config({"spec_path": test_config.spec_path,
328
- "data_path": test_config.data_path},
329
- triple_stores, None)
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
- # Check if the current test is in the selected tests in arguments
332
- if spec.spec_source_file.resolve() in self.mustrd_plugin.selected_tests \
333
- or self.mustrd_plugin.selected_tests == []:
334
- item = MustrdItem.from_parent(
335
- self, name=test_config.pytest_path + "/" + spec.spec_file_name, spec=spec)
336
- self.mustrd_plugin.items.append(item)
337
- yield item
338
- except BaseException as e:
339
- # Catch error here otherwise it will be lost
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.info(f"Creating item: {name}")
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
- return f"{self.name} failed: {excinfo.value}"
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, get_spec_component_from_graphmart
39
- from .mustrdAnzo import get_query_from_querybuilder, get_query_from_step
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
- graphmart = spec_component_details.spec_graph.value(subject=spec_component_details.spec_component_node,
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 = init_spec_component(spec_component_details.predicate)
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.value = get_query_from_step(triple_store=spec_component_details.mustrd_triple_store,
478
- query_step_uri=query_step_uri)
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 = logger_setup.setup_logger(__name__)
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
- return execute_update_anzo(triple_store, when.value, when.bindings)
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."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mustrd
3
- Version: 0.2.7a0
3
+ Version: 0.3.0.0
4
4
  Summary: A Spec By Example framework for RDF and SPARQL, Inspired by Cucumber.
5
5
  License: MIT
6
6
  Author: John Placek
@@ -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=4DXtdZo504Llzmc2fyhe13rLfJXk1mDUnUNDHA8N5YI,4483
6
- mustrd/logger_setup.py,sha256=6Z3E5_h8vZFn4PZUyJVFdkgwFjDWthWEQVJsI9wQVVU,1720
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=6-W8onQBWOwAvsIYLRGOk2FePSp4SPFfWd7dSuNC5ho,9456
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=AK27qyT8GW_SwJDwTFvtfxndBPEfYT6Y1jiW26Hyjg0,16809
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=8xPjdHyGA692zyThCILm8VYuxkSF1ATEf35zXSmLVRU,37750
16
- mustrd/mustrdAnzo.py,sha256=Fg1mCkuGiVywSt5HJ0KCVJUFuTxNMrsPtt45y_fqSII,8292
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=Xy7I7yJNwAatzrU13FkBs0ateWQ96w3MDte8gt8Ykto,15548
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=7BmMjJMy0ACbVBAEZpe5ZprnDZYs6h5f7-fI38l3jAk,31483
23
- mustrd/steprunner.py,sha256=YKrMSYhethWCAQEI63hK6jXh3ljyoM87w7eHyoRd8rc,7389
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.2.7a0.dist-info/LICENSE,sha256=r8nmh5fUct9h2w8_RDl13EIscvmwCLoarPr1kg35MnA,1078
29
- mustrd-0.2.7a0.dist-info/METADATA,sha256=G7prJE0-fM5tfkmtvCXWdJz6LEo6FxvrZQ5t4EnY9zk,4204
30
- mustrd-0.2.7a0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
31
- mustrd-0.2.7a0.dist-info/entry_points.txt,sha256=v7V7sN0_L1aB4Ug_9io5axlQSeJ1C0tNrQWwdXdV58s,50
32
- mustrd-0.2.7a0.dist-info/RECORD,,
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,,