mustrd 0.2.6.3__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/__init__.py +0 -1
- mustrd/anzo_utils.py +2 -1
- mustrd/logger_setup.py +1 -0
- mustrd/model/mustrdShapes.ttl +11 -2
- mustrd/model/ontology.ttl +5 -0
- mustrd/mustrd.py +37 -17
- mustrd/mustrdAnzo.py +2 -1
- mustrd/mustrdTestPlugin.py +124 -65
- mustrd/spec_component.py +5 -13
- mustrd/steprunner.py +1 -3
- {mustrd-0.2.6.3.dist-info → mustrd-0.3.0.0.dist-info}/METADATA +2 -2
- {mustrd-0.2.6.3.dist-info → mustrd-0.3.0.0.dist-info}/RECORD +15 -15
- {mustrd-0.2.6.3.dist-info → mustrd-0.3.0.0.dist-info}/LICENSE +0 -0
- {mustrd-0.2.6.3.dist-info → mustrd-0.3.0.0.dist-info}/WHEEL +0 -0
- {mustrd-0.2.6.3.dist-info → mustrd-0.3.0.0.dist-info}/entry_points.txt +0 -0
mustrd/__init__.py
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
|
mustrd/anzo_utils.py
CHANGED
@@ -33,6 +33,7 @@ import logging
|
|
33
33
|
|
34
34
|
logger = logging.getLogger()
|
35
35
|
|
36
|
+
|
36
37
|
def query_azg(anzo_config: dict, query: str,
|
37
38
|
format: str = "json", is_update: bool = False,
|
38
39
|
data_layers: List[str] = None):
|
@@ -41,7 +42,7 @@ def query_azg(anzo_config: dict, query: str,
|
|
41
42
|
'format': format,
|
42
43
|
'datasourceURI': anzo_config['gqe_uri'],
|
43
44
|
'using-graph-uri' if is_update else 'default-graph-uri': data_layers,
|
44
|
-
'using-named-graph-uri' if is_update else'named-graph-uri': data_layers
|
45
|
+
'using-named-graph-uri' if is_update else 'named-graph-uri': data_layers
|
45
46
|
}
|
46
47
|
url = f"{anzo_config['url']}/sparql"
|
47
48
|
return send_anzo_query(anzo_config, url=url, params=params, query=query, is_update=is_update)
|
mustrd/logger_setup.py
CHANGED
mustrd/model/mustrdShapes.ttl
CHANGED
@@ -249,5 +249,14 @@ must:AnzoGraphmartQueryDrivenTemplatedStepSparqlSourceShape
|
|
249
249
|
sh:minCount 1 ;
|
250
250
|
sh:maxCount 1 ; ] .
|
251
251
|
|
252
|
-
|
253
|
-
|
252
|
+
must:SpadeEdnGroupSourceShape
|
253
|
+
a sh:NodeShape ;
|
254
|
+
sh:targetClass must:SpadeEdnGroupSource ;
|
255
|
+
sh:property [ sh:path must:file ;
|
256
|
+
sh:message "A SpadeEdnGroupSource must have a file property pointing to the spade.edn config." ;
|
257
|
+
sh:minCount 1 ;
|
258
|
+
sh:maxCount 1 ; ] ;
|
259
|
+
sh:property [ sh:path must:groupId ;
|
260
|
+
sh:message "A SpadeEdnGroupSource must have a groupId property referencing the group in the EDN file." ;
|
261
|
+
sh:minCount 1 ;
|
262
|
+
sh:maxCount 1 ; ] .
|
mustrd/model/ontology.ttl
CHANGED
@@ -461,6 +461,11 @@ sh:order rdf:type owl:DatatypeProperty ;
|
|
461
461
|
rdfs:isDefinedBy : ;
|
462
462
|
rdfs:label "AnzoGraphmartQueryDrivenTemplatedStepSparqlSource" .
|
463
463
|
|
464
|
+
### https://mustrd.com/model/SpadeEdnGroupSource
|
465
|
+
:SpadeEdnGroupSource rdf:type owl:Class ;
|
466
|
+
rdfs:subClassOf :SparqlSource ;
|
467
|
+
rdfs:comment "Allows reference to a spade.edn file, and a specific groupid (think Anzo layer), within that" ;
|
468
|
+
rdfs:label "SpadeEdnGroupSource" .
|
464
469
|
|
465
470
|
### https://mustrd.com/model/Then
|
466
471
|
:Then rdf:type owl:Class ;
|
mustrd/mustrd.py
CHANGED
@@ -55,7 +55,6 @@ 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 logging
|
59
58
|
import traceback
|
60
59
|
|
61
60
|
log = logging.getLogger(__name__)
|
@@ -239,7 +238,10 @@ def validate_specs(run_config: dict,
|
|
239
238
|
advanced=True,
|
240
239
|
js=False,
|
241
240
|
debug=False)
|
242
|
-
|
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}")
|
243
245
|
# Add error message if not conform to spec shapes
|
244
246
|
if not conforms:
|
245
247
|
for msg in results_graph.objects(predicate=SH.resultMessage):
|
@@ -284,6 +286,10 @@ def add_spec_validation(file_graph: Graph, subject_uris: set, file: Path, triple
|
|
284
286
|
error_messages: list, invalid_specs: list, spec_graph: Graph):
|
285
287
|
|
286
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
|
+
|
287
293
|
# If we already collected a URI, then we tag it as duplicate and it won't be executed
|
288
294
|
if subject_uri in subject_uris:
|
289
295
|
log.warning(
|
@@ -326,8 +332,11 @@ def get_specs(spec_uris: List[URIRef], spec_graph: Graph, triple_stores: List[di
|
|
326
332
|
specs += [get_spec(spec_uri, spec_graph,
|
327
333
|
run_config, triple_store)]
|
328
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"
|
329
338
|
skipped_results += [SpecSkipped(spec_uri, triple_store['type'],
|
330
|
-
e,
|
339
|
+
str(e), str(file_name), Path(file_path))]
|
331
340
|
|
332
341
|
except (BadSyntax, FileNotFoundError) as e:
|
333
342
|
template = "An exception of type {0} occurred when trying to parse the triple store configuration file. " \
|
@@ -349,7 +358,14 @@ def run_specs(specs) -> List[SpecResult]:
|
|
349
358
|
|
350
359
|
|
351
360
|
def get_spec_file(spec_uri: URIRef, spec_graph: Graph):
|
352
|
-
|
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"
|
353
369
|
|
354
370
|
|
355
371
|
def get_spec(spec_uri: URIRef, spec_graph: Graph, run_config: dict, mustrd_triple_store: dict = None) -> Specification:
|
@@ -382,10 +398,11 @@ def get_spec(spec_uri: URIRef, spec_graph: Graph, run_config: dict, mustrd_tripl
|
|
382
398
|
|
383
399
|
|
384
400
|
def check_result(spec: Specification, result: Union[str, Graph]):
|
401
|
+
|
385
402
|
log.debug(
|
386
403
|
f"check_result {spec.spec_uri=}, {spec.triple_store=}, {result=} {type(spec.then)}")
|
387
404
|
if isinstance(spec.then, TableThenSpec):
|
388
|
-
log.debug(
|
405
|
+
log.debug("table_comparison")
|
389
406
|
return table_comparison(result, spec)
|
390
407
|
else:
|
391
408
|
graph_compare = graph_comparison(spec.then.value, result)
|
@@ -397,20 +414,26 @@ def check_result(spec: Specification, result: Union[str, Graph]):
|
|
397
414
|
|
398
415
|
return ret
|
399
416
|
else:
|
400
|
-
log.debug(
|
417
|
+
log.debug("not isomorphic")
|
401
418
|
if spec.when[0].queryType == MUST.ConstructSparql:
|
402
|
-
log.debug(
|
403
|
-
|
419
|
+
log.debug("ConstructSpecFailure")
|
404
420
|
return ConstructSpecFailure(spec.spec_uri, spec.triple_store["type"], graph_compare)
|
405
421
|
else:
|
406
|
-
log.debug(
|
422
|
+
log.debug("UpdateSpecFailure")
|
407
423
|
return UpdateSpecFailure(spec.spec_uri, spec.triple_store["type"], graph_compare)
|
408
424
|
|
409
425
|
|
410
426
|
def run_spec(spec: Specification) -> SpecResult:
|
411
427
|
spec_uri = spec.spec_uri
|
412
428
|
triple_store = spec.triple_store
|
413
|
-
|
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=}")
|
414
437
|
log.debug(
|
415
438
|
f"run_when {spec_uri=}, {triple_store=}, {spec.given=}, {spec.when=}, {spec.then=}")
|
416
439
|
if spec.given:
|
@@ -434,7 +457,8 @@ def run_spec(spec: Specification) -> SpecResult:
|
|
434
457
|
return SparqlParseFailure(spec_uri, triple_store["type"], e)
|
435
458
|
except NotImplementedError as ex:
|
436
459
|
log.error(f"NotImplementedError {ex}")
|
437
|
-
|
460
|
+
raise ex
|
461
|
+
# return SpecSkipped(spec_uri, triple_store["type"], ex.args[0])
|
438
462
|
return check_result(spec, result)
|
439
463
|
except (ConnectionError, TimeoutError, HTTPError, ConnectTimeout, OSError) as e:
|
440
464
|
# close_connection = False
|
@@ -446,12 +470,8 @@ def run_spec(spec: Specification) -> SpecResult:
|
|
446
470
|
log.error(f"{type(e)} {e}")
|
447
471
|
return SparqlExecutionError(spec_uri, triple_store["type"], e)
|
448
472
|
except Exception as e:
|
449
|
-
|
450
|
-
|
451
|
-
raise
|
452
|
-
else:
|
453
|
-
log.error(f"Unknown error")
|
454
|
-
return RuntimeError(spec_uri, triple_store["type"], e)
|
473
|
+
log.error(f"Unexpected error {e}")
|
474
|
+
return RuntimeError(spec_uri, triple_store["type"], f"{type(e).__name__}: {e}")
|
455
475
|
# https://github.com/Semantic-partners/mustrd/issues/78
|
456
476
|
# finally:
|
457
477
|
# if type(mustrd_triple_store) == MustrdAnzo and close_connection:
|
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
|
|
mustrd/mustrdTestPlugin.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 logging
|
26
2
|
from dataclasses import dataclass
|
27
3
|
import pytest
|
@@ -93,6 +69,14 @@ def pytest_addoption(parser):
|
|
93
69
|
default=None,
|
94
70
|
help="Give the secrets by command line in order to be able to store secrets safely in CI tools",
|
95
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
|
+
)
|
96
80
|
return
|
97
81
|
|
98
82
|
|
@@ -217,27 +201,39 @@ class MustrdTestPlugin:
|
|
217
201
|
@pytest.hookimpl(tryfirst=True)
|
218
202
|
def pytest_collection(self, session):
|
219
203
|
logger.info("Starting test collection")
|
204
|
+
|
220
205
|
self.unit_tests = []
|
221
206
|
args = session.config.args
|
222
|
-
|
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
|
+
|
223
212
|
self.selected_tests = list(
|
224
213
|
map(
|
225
214
|
lambda arg: Path(arg.split("::")[0]),
|
226
|
-
|
227
|
-
# Remove it as it is not a test
|
228
|
-
filter(lambda arg: arg != os.getcwd() and "::" in arg, args),
|
215
|
+
mustrd_args
|
229
216
|
)
|
230
217
|
)
|
231
218
|
logger.info(f"selected_tests is: {self.selected_tests}")
|
232
219
|
|
233
|
-
self.path_filter = (
|
234
|
-
|
235
|
-
if len(args) == 1 and args[0] != os.getcwd() and not "::" in args[0]
|
236
|
-
else None
|
237
|
-
)
|
220
|
+
self.path_filter = session.config.getoption("pytest_path") or None
|
221
|
+
|
238
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
|
239
235
|
|
240
|
-
session.config.args
|
236
|
+
logger.info(f"Final session.config.args: {session.config.args}")
|
241
237
|
|
242
238
|
def get_file_name_from_arg(self, arg):
|
243
239
|
if arg and len(arg) > 0 and "[" in arg and ".mustrd.ttl " in arg:
|
@@ -247,6 +243,13 @@ class MustrdTestPlugin:
|
|
247
243
|
@pytest.hookimpl
|
248
244
|
def pytest_collect_file(self, parent, path):
|
249
245
|
logger.debug(f"Collecting file: {path}")
|
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
|
+
|
250
253
|
mustrd_file = MustrdFile.from_parent(parent, path=pathlib.Path(path), mustrd_plugin=self)
|
251
254
|
mustrd_file.mustrd_plugin = self
|
252
255
|
return mustrd_file
|
@@ -269,13 +272,24 @@ class MustrdTestPlugin:
|
|
269
272
|
file_name or "*",
|
270
273
|
selected_test_files=self.selected_tests,
|
271
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
|
+
|
272
286
|
|
273
287
|
specs, skipped_spec_results = get_specs(
|
274
288
|
valid_spec_uris, spec_graph, triple_stores, config
|
275
289
|
)
|
276
290
|
|
277
291
|
# Return normal specs + skipped results
|
278
|
-
return specs + skipped_spec_results +
|
292
|
+
return specs + skipped_spec_results + invalid_specs
|
279
293
|
|
280
294
|
# Function called to generate the name of the test
|
281
295
|
def get_test_name(self, spec):
|
@@ -383,39 +397,47 @@ class MustrdTestPlugin:
|
|
383
397
|
with open(self.md_path, "w") as file:
|
384
398
|
file.write(md)
|
385
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
|
386
407
|
|
387
408
|
class MustrdFile(pytest.File):
|
388
409
|
mustrd_plugin: MustrdTestPlugin
|
389
410
|
|
390
411
|
def __init__(self, *args, mustrd_plugin, **kwargs):
|
412
|
+
logger.debug(f"Creating MustrdFile with args: {args}, kwargs: {kwargs}")
|
391
413
|
self.mustrd_plugin = mustrd_plugin
|
392
414
|
super(pytest.File, self).__init__(*args, **kwargs)
|
393
415
|
|
394
416
|
def collect(self):
|
395
417
|
try:
|
396
|
-
logger.
|
397
|
-
|
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)
|
398
431
|
for test_config in test_configs:
|
399
|
-
# Skip if there is a path filter and it is not in the pytest path
|
400
432
|
if (
|
401
433
|
self.mustrd_plugin.path_filter is not None
|
402
|
-
and self.mustrd_plugin.path_filter
|
434
|
+
and not str(test_config.pytest_path).startswith(str(self.mustrd_plugin.path_filter))
|
403
435
|
):
|
436
|
+
logger.info(f"Skipping test config due to path filter: {test_config.pytest_path=} {self.mustrd_plugin.path_filter=}")
|
404
437
|
continue
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
if test_config.filter_on_tripleStore and not triple_stores:
|
410
|
-
specs = list(
|
411
|
-
map(
|
412
|
-
lambda triple_store: SpecSkipped(
|
413
|
-
MUST.TestSpec, triple_store, "No triplestore found"
|
414
|
-
),
|
415
|
-
test_config.filter_on_tripleStore,
|
416
|
-
)
|
417
|
-
)
|
418
|
-
else:
|
438
|
+
|
439
|
+
triple_stores = self.mustrd_plugin.get_triple_stores_from_file(test_config)
|
440
|
+
try:
|
419
441
|
specs = self.mustrd_plugin.generate_tests_for_config(
|
420
442
|
{
|
421
443
|
"spec_path": test_config.spec_path,
|
@@ -424,27 +446,63 @@ class MustrdFile(pytest.File):
|
|
424
446
|
triple_stores,
|
425
447
|
None,
|
426
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")
|
427
462
|
for spec in specs:
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
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
|
+
)
|
435
475
|
except Exception as e:
|
436
|
-
# Catch error here otherwise it will be lost
|
437
476
|
self.mustrd_plugin.collect_error = e
|
438
|
-
logger.error(f"Error during collection: {e}")
|
477
|
+
logger.error(f"Error during collection {self.path}: {type(e)} {e} {traceback.format_exc()}")
|
439
478
|
raise e
|
440
479
|
|
441
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
|
+
|
442
499
|
class MustrdItem(pytest.Item):
|
443
500
|
def __init__(self, name, parent, spec):
|
444
|
-
logging.
|
501
|
+
logging.debug(f"Creating item: {name}")
|
445
502
|
super().__init__(name, parent)
|
446
503
|
self.spec = spec
|
447
504
|
self.fspath = spec.spec_source_file
|
505
|
+
self.originalname = name
|
448
506
|
|
449
507
|
def runtest(self):
|
450
508
|
result = run_test_spec(self.spec)
|
@@ -460,8 +518,7 @@ class MustrdItem(pytest.Item):
|
|
460
518
|
f"{self.name} failed:\n"
|
461
519
|
f"Spec: {self.spec.spec_uri}\n"
|
462
520
|
f"File: {self.spec.spec_source_file}\n"
|
463
|
-
f"
|
464
|
-
f"Error: {excinfo.value}\n"
|
521
|
+
f"Error: \n{excinfo.value}\n"
|
465
522
|
f"Traceback:\n{tb_str}"
|
466
523
|
)
|
467
524
|
|
@@ -476,6 +533,8 @@ def run_test_spec(test_spec):
|
|
476
533
|
pytest.skip(f"Invalid configuration, error : {test_spec.message}")
|
477
534
|
result = run_spec(test_spec)
|
478
535
|
result_type = type(result)
|
536
|
+
if isinstance(test_spec, SpecInvalid):
|
537
|
+
raise ValueError(f"Invalid test specification: {test_spec.message} {test_spec}")
|
479
538
|
if result_type == SpecSkipped:
|
480
539
|
# FIXME: Better exception management
|
481
540
|
pytest.skip("Unsupported configuration")
|
mustrd/spec_component.py
CHANGED
@@ -35,8 +35,8 @@ from rdflib.term import Node
|
|
35
35
|
from rdflib.plugins.stores.memory import Memory
|
36
36
|
|
37
37
|
from . import logger_setup
|
38
|
-
from .mustrdAnzo import get_queries_for_layer, get_queries_from_templated_step
|
39
|
-
from .mustrdAnzo import get_query_from_querybuilder
|
38
|
+
from .mustrdAnzo import get_queries_for_layer, get_queries_from_templated_step
|
39
|
+
from .mustrdAnzo import get_query_from_querybuilder
|
40
40
|
from .namespace import MUST, TRIPLESTORE
|
41
41
|
from multimethods import MultiMethod, Default
|
42
42
|
from .utils import get_mustrd_root
|
@@ -119,8 +119,9 @@ def parse_spec_component(subject: URIRef,
|
|
119
119
|
data_source_type=data_source_type,
|
120
120
|
run_config=run_config,
|
121
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
|
122
|
+
|
123
|
+
# get_spec_component potentially talks to anzo for EVERY spec, massively slowing things down
|
124
|
+
# can we defer it to run time?
|
124
125
|
spec_component = get_spec_component(spec_component_details)
|
125
126
|
if isinstance(spec_component, list):
|
126
127
|
spec_components += spec_component
|
@@ -433,15 +434,7 @@ def _get_spec_component_AnzoGraphmartDataset(spec_component_details: SpecCompone
|
|
433
434
|
|
434
435
|
if spec_component_details.mustrd_triple_store["type"] == TRIPLESTORE.Anzo:
|
435
436
|
# Get GIVEN or THEN from anzo graphmart
|
436
|
-
graphmart = spec_component_details.spec_graph.value(subject=spec_component_details.spec_component_node,
|
437
|
-
predicate=MUST.graphmart)
|
438
|
-
layer = spec_component_details.spec_graph.value(subject=spec_component_details.spec_component_node,
|
439
|
-
predicate=MUST.layer)
|
440
437
|
spec_component.spec_component_details = spec_component_details
|
441
|
-
# return get_spec_component_from_graphmart(
|
442
|
-
# triple_store=spec_component_details.mustrd_triple_store,
|
443
|
-
# graphmart=graphmart,
|
444
|
-
# layer=layer)
|
445
438
|
else:
|
446
439
|
raise ValueError(f"You must define {TRIPLESTORE.Anzo} to use {MUST.AnzoGraphmartDataset}")
|
447
440
|
|
@@ -555,7 +548,6 @@ def _get_spec_component_default(spec_component_details: SpecComponentDetails) ->
|
|
555
548
|
|
556
549
|
|
557
550
|
def init_spec_component(predicate: URIRef, triple_store_type: URIRef = None) -> GivenSpec | WhenSpec | ThenSpec | TableThenSpec: # noqa
|
558
|
-
log.info(f"init_spec_component {predicate} {triple_store_type}")
|
559
551
|
if predicate == MUST.given:
|
560
552
|
spec_component = GivenSpec()
|
561
553
|
elif predicate == MUST.when:
|
mustrd/steprunner.py
CHANGED
@@ -24,7 +24,6 @@ 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
|
@@ -105,8 +104,7 @@ def _anzo_run_when_select(spec_uri: URIRef, triple_store: dict, when: AnzoWhenSp
|
|
105
104
|
|
106
105
|
@run_when.method((TRIPLESTORE.GraphDb, MUST.UpdateSparql))
|
107
106
|
def _graphdb_run_when_update(spec_uri: URIRef, triple_store: dict, when: WhenSpec):
|
108
|
-
|
109
|
-
return execute_update_graphdb(triple_store, query, when.bindings)
|
107
|
+
return execute_update_graphdb(triple_store, when.value, when.bindings)
|
110
108
|
|
111
109
|
|
112
110
|
@run_when.method((TRIPLESTORE.GraphDb, MUST.ConstructSparql))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: mustrd
|
3
|
-
Version: 0.
|
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
|
@@ -26,7 +26,7 @@ Requires-Dist: flake8 (==7.0.0)
|
|
26
26
|
Requires-Dist: multimethods-py (>=0.5.3,<0.6.0)
|
27
27
|
Requires-Dist: numpy (>=1.26.0,<1.27.0)
|
28
28
|
Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
|
29
|
-
Requires-Dist: pandas (>=
|
29
|
+
Requires-Dist: pandas (>=2.0,<3.0)
|
30
30
|
Requires-Dist: pyshacl (>=0.30.0,<0.31.0)
|
31
31
|
Requires-Dist: pytest (>=7.2.0,<8.0.0)
|
32
32
|
Requires-Dist: rdflib (>=7.1.3,<8.0.0)
|
@@ -1,32 +1,32 @@
|
|
1
1
|
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
|
-
mustrd/__init__.py,sha256=
|
5
|
-
mustrd/anzo_utils.py,sha256
|
6
|
-
mustrd/logger_setup.py,sha256=
|
4
|
+
mustrd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
mustrd/anzo_utils.py,sha256=CKNSF_oTFKc4v64EDXJzeirP1GxOIOCUKNfaJKk0oEc,4677
|
6
|
+
mustrd/logger_setup.py,sha256=AdHf1_rEYiWleR-8IVatsMu-ntYH3_uiDDhFchQ8BcM,1721
|
7
7
|
mustrd/model/catalog-v001.xml,sha256=IEtaw3FK4KGQWKancEe1HqzUQTrKdk89vNxnoLKGF4o,381
|
8
|
-
mustrd/model/mustrdShapes.ttl,sha256=
|
8
|
+
mustrd/model/mustrdShapes.ttl,sha256=3BjiOMLs0sS5Ky4_1YkzqYE7JpW-bsfw7FaFej6Ri8w,10056
|
9
9
|
mustrd/model/mustrdTestOntology.ttl,sha256=W7IRbPKrhpYFweZvc9QH8gdjBiuZMHKETIuHBepLnYw,2034
|
10
10
|
mustrd/model/mustrdTestShapes.ttl,sha256=5PUpU9AzPSeLpF_UzNBVnACLOhs3hfoQpiz-ybsrbj8,818
|
11
|
-
mustrd/model/ontology.ttl,sha256=
|
11
|
+
mustrd/model/ontology.ttl,sha256=089FOWtSN107xR04MFxtvPUJVFiT9TSzmKqqNOfm7Gk,17090
|
12
12
|
mustrd/model/test-resources/resources.ttl,sha256=1Dsp1nuNxauj9bxeX-HShQsiO-CVy5Irwm2y2x0cdjI,1498
|
13
13
|
mustrd/model/triplestoreOntology.ttl,sha256=9K5gj0hDOolRYjHc58UT4igex8cUnq9h7SUe4ToYbdw,5834
|
14
14
|
mustrd/model/triplestoreshapes.ttl,sha256=G1kdgASdPa8s5JVGXL4KM2ewp-F5Vmbdist0f77VTBc,1706
|
15
|
-
mustrd/mustrd.py,sha256
|
16
|
-
mustrd/mustrdAnzo.py,sha256=
|
15
|
+
mustrd/mustrd.py,sha256=-J_Od8Uv0WFWKIt6C2QBl_ZCQtubx3nBUjfTodJ3a4w,40289
|
16
|
+
mustrd/mustrdAnzo.py,sha256=0XhLkfagW_HManfZ3iGAf_K2_d-pLVTAOT-0pX8hhM4,8352
|
17
17
|
mustrd/mustrdGraphDb.py,sha256=Ro_fxDPFl64r-FAM18awhZydydEY1-IXO0zdKpvZD3U,5405
|
18
18
|
mustrd/mustrdRdfLib.py,sha256=CvrzEl1-lEPugYVe3y_Ip8JMzUxv6IeWauLOa_WA-XI,2073
|
19
|
-
mustrd/mustrdTestPlugin.py,sha256=
|
19
|
+
mustrd/mustrdTestPlugin.py,sha256=hbYo8i1j4uFuRekXGd4Mnjt9wkSgQrS5o809xtbcLgs,19664
|
20
20
|
mustrd/namespace.py,sha256=xGfH-nRP3t_I4rmoGJPqlSGbI0G5HUMlUeAl7l_yW-s,3622
|
21
21
|
mustrd/run.py,sha256=5xZUgKPMBQ-03cWROAnwtbOs2Nb0Vat6n8Fi6EyfS-k,4257
|
22
|
-
mustrd/spec_component.py,sha256=
|
23
|
-
mustrd/steprunner.py,sha256
|
22
|
+
mustrd/spec_component.py,sha256=5WQdvj0v9U-HEiOomoLcwwKfAt6kLDxPKomxyMkleI0,31382
|
23
|
+
mustrd/steprunner.py,sha256=73ykrrfZAPAQcr95ewcRfLO0gl_2xVGlEDYnI7FncXg,7951
|
24
24
|
mustrd/templates/md_ResultList_leaf_template.jinja,sha256=IzwZjliCx7-viipATDQK6MQg_5q1kLMKdeNSZg1sXXY,508
|
25
25
|
mustrd/templates/md_ResultList_template.jinja,sha256=_8joJ7vtw_qoqxv3HhUtBgRfhOeqmgfaRFwEo4MROvQ,203
|
26
26
|
mustrd/templates/md_stats_template.jinja,sha256=96W62cMWu9UGLNv65ZQ8RYLjkxKHhJy-FlUtXgud6XY,155
|
27
27
|
mustrd/utils.py,sha256=OGdLvw7GvjrFgTJo0J97Xwdh-_ZgSmapmOistrEchO0,1387
|
28
|
-
mustrd-0.
|
29
|
-
mustrd-0.
|
30
|
-
mustrd-0.
|
31
|
-
mustrd-0.
|
32
|
-
mustrd-0.
|
28
|
+
mustrd-0.3.0.0.dist-info/LICENSE,sha256=r8nmh5fUct9h2w8_RDl13EIscvmwCLoarPr1kg35MnA,1078
|
29
|
+
mustrd-0.3.0.0.dist-info/METADATA,sha256=Eu_S5x8JM7ZP8LWF6nh9qkhYpjhLCApa2__EGBlNtXk,4204
|
30
|
+
mustrd-0.3.0.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
31
|
+
mustrd-0.3.0.0.dist-info/entry_points.txt,sha256=v7V7sN0_L1aB4Ug_9io5axlQSeJ1C0tNrQWwdXdV58s,50
|
32
|
+
mustrd-0.3.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|