mustrd 0.2.6.1__py3-none-any.whl → 0.2.6.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mustrd/README.adoc +33 -0
- mustrd/__init__.py +1 -0
- mustrd/anzo_utils.py +123 -0
- mustrd/logger_setup.py +4 -0
- mustrd/model/triplestoreOntology.ttl +0 -8
- mustrd/model/triplestoreshapes.ttl +0 -3
- mustrd/mustrd.py +382 -208
- mustrd/mustrdAnzo.py +55 -130
- mustrd/mustrdGraphDb.py +3 -3
- mustrd/mustrdTestPlugin.py +278 -122
- mustrd/spec_component.py +16 -7
- mustrd/steprunner.py +16 -4
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.6.3.dist-info}/METADATA +6 -7
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.6.3.dist-info}/RECORD +17 -16
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.6.3.dist-info}/WHEEL +1 -1
- mustrd/test/test_mustrd.py +0 -5
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.6.3.dist-info}/LICENSE +0 -0
- {mustrd-0.2.6.1.dist-info → mustrd-0.2.6.3.dist-info}/entry_points.txt +0 -0
mustrd/mustrdTestPlugin.py
CHANGED
@@ -22,22 +22,38 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
SOFTWARE.
|
23
23
|
"""
|
24
24
|
|
25
|
+
import logging
|
25
26
|
from dataclasses import dataclass
|
26
27
|
import pytest
|
27
28
|
import os
|
28
|
-
from pathlib import Path
|
29
|
+
from pathlib import Path, PosixPath
|
29
30
|
from rdflib.namespace import Namespace
|
30
31
|
from rdflib import Graph, RDF
|
31
32
|
from pytest import Session
|
32
33
|
|
34
|
+
from mustrd import logger_setup
|
33
35
|
from mustrd.TestResult import ResultList, TestResult, get_result_list
|
34
36
|
from mustrd.utils import get_mustrd_root
|
35
|
-
from mustrd.mustrd import
|
36
|
-
|
37
|
+
from mustrd.mustrd import (
|
38
|
+
write_result_diff_to_log,
|
39
|
+
get_triple_store_graph,
|
40
|
+
get_triple_stores,
|
41
|
+
)
|
42
|
+
from mustrd.mustrd import (
|
43
|
+
Specification,
|
44
|
+
SpecSkipped,
|
45
|
+
validate_specs,
|
46
|
+
get_specs,
|
47
|
+
SpecPassed,
|
48
|
+
run_spec,
|
49
|
+
)
|
37
50
|
from mustrd.namespace import MUST, TRIPLESTORE, MUSTRDTEST
|
38
51
|
from typing import Union
|
39
52
|
from pyshacl import validate
|
40
53
|
|
54
|
+
import pathlib
|
55
|
+
import traceback
|
56
|
+
|
41
57
|
spnamespace = Namespace("https://semanticpartners.com/data/test/")
|
42
58
|
|
43
59
|
mustrd_root = get_mustrd_root()
|
@@ -82,55 +98,87 @@ def pytest_addoption(parser):
|
|
82
98
|
|
83
99
|
def pytest_configure(config) -> None:
|
84
100
|
# Read configuration file
|
85
|
-
if config.getoption("mustrd"):
|
86
|
-
|
87
|
-
|
88
|
-
|
101
|
+
if config.getoption("mustrd") and config.getoption("configpath"):
|
102
|
+
config.pluginmanager.register(
|
103
|
+
MustrdTestPlugin(
|
104
|
+
config.getoption("mdpath"),
|
105
|
+
Path(config.getoption("configpath")),
|
106
|
+
config.getoption("secrets"),
|
107
|
+
)
|
108
|
+
)
|
89
109
|
|
90
110
|
|
91
111
|
def parse_config(config_path):
|
92
112
|
test_configs = []
|
93
113
|
config_graph = Graph().parse(config_path)
|
94
|
-
shacl_graph = Graph().parse(
|
95
|
-
|
114
|
+
shacl_graph = Graph().parse(
|
115
|
+
Path(os.path.join(mustrd_root, "model/mustrdTestShapes.ttl"))
|
116
|
+
)
|
117
|
+
ont_graph = Graph().parse(
|
118
|
+
Path(os.path.join(mustrd_root, "model/mustrdTestOntology.ttl"))
|
119
|
+
)
|
96
120
|
conforms, results_graph, results_text = validate(
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
121
|
+
data_graph=config_graph,
|
122
|
+
shacl_graph=shacl_graph,
|
123
|
+
ont_graph=ont_graph,
|
124
|
+
advanced=True,
|
125
|
+
inference="none",
|
126
|
+
)
|
103
127
|
if not conforms:
|
104
|
-
raise ValueError(
|
105
|
-
|
128
|
+
raise ValueError(
|
129
|
+
f"Mustrd test configuration not conform to the shapes. SHACL report: {results_text}",
|
130
|
+
results_graph,
|
131
|
+
)
|
106
132
|
|
107
|
-
for test_config_subject in config_graph.subjects(
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
133
|
+
for test_config_subject in config_graph.subjects(
|
134
|
+
predicate=RDF.type, object=MUSTRDTEST.MustrdTest
|
135
|
+
):
|
136
|
+
spec_path = get_config_param(
|
137
|
+
config_graph, test_config_subject, MUSTRDTEST.hasSpecPath, str
|
138
|
+
)
|
139
|
+
data_path = get_config_param(
|
140
|
+
config_graph, test_config_subject, MUSTRDTEST.hasDataPath, str
|
141
|
+
)
|
142
|
+
triplestore_spec_path = get_config_param(
|
143
|
+
config_graph, test_config_subject, MUSTRDTEST.triplestoreSpecPath, str
|
144
|
+
)
|
145
|
+
pytest_path = get_config_param(
|
146
|
+
config_graph, test_config_subject, MUSTRDTEST.hasPytestPath, str
|
147
|
+
)
|
148
|
+
filter_on_tripleStore = tuple(
|
149
|
+
config_graph.objects(
|
150
|
+
subject=test_config_subject, predicate=MUSTRDTEST.filterOnTripleStore
|
151
|
+
)
|
152
|
+
)
|
114
153
|
|
115
154
|
# Root path is the mustrd test config path
|
116
155
|
root_path = Path(config_path).parent
|
117
156
|
spec_path = root_path / Path(spec_path) if spec_path else None
|
118
157
|
data_path = root_path / Path(data_path) if data_path else None
|
119
|
-
triplestore_spec_path =
|
158
|
+
triplestore_spec_path = (
|
159
|
+
root_path / Path(triplestore_spec_path) if triplestore_spec_path else None
|
160
|
+
)
|
120
161
|
|
121
|
-
test_configs.append(
|
122
|
-
|
123
|
-
|
124
|
-
|
162
|
+
test_configs.append(
|
163
|
+
TestConfig(
|
164
|
+
spec_path=spec_path,
|
165
|
+
data_path=data_path,
|
166
|
+
triplestore_spec_path=triplestore_spec_path,
|
167
|
+
pytest_path=pytest_path,
|
168
|
+
filter_on_tripleStore=filter_on_tripleStore,
|
169
|
+
)
|
170
|
+
)
|
125
171
|
return test_configs
|
126
172
|
|
127
173
|
|
128
174
|
def get_config_param(config_graph, config_subject, config_param, convert_function):
|
129
|
-
raw_value = config_graph.value(
|
175
|
+
raw_value = config_graph.value(
|
176
|
+
subject=config_subject, predicate=config_param, any=True
|
177
|
+
)
|
130
178
|
return convert_function(raw_value) if raw_value else None
|
131
179
|
|
132
180
|
|
133
|
-
@dataclass
|
181
|
+
@dataclass(frozen=True)
|
134
182
|
class TestConfig:
|
135
183
|
spec_path: Path
|
136
184
|
data_path: Path
|
@@ -139,105 +187,92 @@ class TestConfig:
|
|
139
187
|
filter_on_tripleStore: str = None
|
140
188
|
|
141
189
|
|
142
|
-
@dataclass
|
190
|
+
@dataclass(frozen=True)
|
143
191
|
class TestParamWrapper:
|
192
|
+
id: str
|
144
193
|
test_config: TestConfig
|
145
194
|
unit_test: Union[Specification, SpecSkipped]
|
146
195
|
|
147
196
|
|
197
|
+
# Configure logging
|
198
|
+
logger = logger_setup.setup_logger(__name__)
|
199
|
+
|
200
|
+
|
148
201
|
class MustrdTestPlugin:
|
149
202
|
md_path: str
|
150
|
-
|
203
|
+
test_config_file: Path
|
204
|
+
selected_tests: list
|
151
205
|
secrets: str
|
152
206
|
unit_tests: Union[Specification, SpecSkipped]
|
153
207
|
items: list
|
208
|
+
path_filter: str
|
209
|
+
collect_error: BaseException
|
154
210
|
|
155
|
-
def __init__(self, md_path,
|
211
|
+
def __init__(self, md_path, test_config_file, secrets):
|
156
212
|
self.md_path = md_path
|
157
|
-
self.
|
213
|
+
self.test_config_file = test_config_file
|
158
214
|
self.secrets = secrets
|
159
215
|
self.items = []
|
160
216
|
|
161
217
|
@pytest.hookimpl(tryfirst=True)
|
162
218
|
def pytest_collection(self, session):
|
219
|
+
logger.info("Starting test collection")
|
163
220
|
self.unit_tests = []
|
164
221
|
args = session.config.args
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
if one_test_config.filter_on_tripleStore and not triple_stores:
|
186
|
-
self.unit_tests.extend(list(map(
|
187
|
-
lambda triple_store:
|
188
|
-
TestParamWrapper(test_config=one_test_config,
|
189
|
-
unit_test=SpecSkipped(MUST.TestSpec, triple_store, "No triplestore found")),
|
190
|
-
one_test_config.filter_on_tripleStore)))
|
191
|
-
else:
|
192
|
-
specs = self.generate_tests_for_config({"spec_path": one_test_config.spec_path,
|
193
|
-
"data_path": one_test_config.data_path},
|
194
|
-
triple_stores, file_name)
|
195
|
-
self.unit_tests.extend(list(map(
|
196
|
-
lambda spec: TestParamWrapper(test_config=one_test_config, unit_test=spec), specs)))
|
222
|
+
logger.info("Used arguments: " + str(args))
|
223
|
+
self.selected_tests = list(
|
224
|
+
map(
|
225
|
+
lambda arg: Path(arg.split("::")[0]),
|
226
|
+
# By default the current directory is given as argument
|
227
|
+
# Remove it as it is not a test
|
228
|
+
filter(lambda arg: arg != os.getcwd() and "::" in arg, args),
|
229
|
+
)
|
230
|
+
)
|
231
|
+
logger.info(f"selected_tests is: {self.selected_tests}")
|
232
|
+
|
233
|
+
self.path_filter = (
|
234
|
+
args[0]
|
235
|
+
if len(args) == 1 and args[0] != os.getcwd() and not "::" in args[0]
|
236
|
+
else None
|
237
|
+
)
|
238
|
+
logger.info(f"path_filter is: {self.path_filter}")
|
239
|
+
|
240
|
+
session.config.args = [str(self.test_config_file.resolve())]
|
197
241
|
|
198
242
|
def get_file_name_from_arg(self, arg):
|
199
|
-
if arg and len(arg) > 0 and "[" in arg and ".mustrd.ttl
|
200
|
-
return arg[arg.index("[") + 1: arg.index(".mustrd.ttl
|
243
|
+
if arg and len(arg) > 0 and "[" in arg and ".mustrd.ttl " in arg:
|
244
|
+
return arg[arg.index("[") + 1 : arg.index(".mustrd.ttl ")]
|
201
245
|
return None
|
202
246
|
|
203
|
-
@pytest.hookimpl
|
204
|
-
def
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
for item in items:
|
210
|
-
virtual_path = MUSTRD_PYTEST_PATH + (item.callspec.params["unit_tests"].test_config.pytest_path or "default")
|
211
|
-
item.fspath = Path(virtual_path)
|
212
|
-
item._nodeid = virtual_path + "::" + item.name
|
213
|
-
self.items.append(item)
|
214
|
-
new_results.append(item)
|
215
|
-
return new_results
|
216
|
-
|
217
|
-
# Hook called at collection time: reads the configuration of the tests, and generate pytests from it
|
218
|
-
def pytest_generate_tests(self, metafunc):
|
219
|
-
if len(metafunc.fixturenames) > 0:
|
220
|
-
if metafunc.function.__name__ == "test_unit":
|
221
|
-
# Create the test in itself
|
222
|
-
if self.unit_tests:
|
223
|
-
metafunc.parametrize(metafunc.fixturenames[0], self.unit_tests,
|
224
|
-
ids=lambda test_param: (test_param.unit_test.spec_file_name or "") + "@" +
|
225
|
-
(test_param.test_config.pytest_path or ""))
|
226
|
-
else:
|
227
|
-
metafunc.parametrize(metafunc.fixturenames[0],
|
228
|
-
[SpecSkipped(MUST.TestSpec, None, "No triplestore found")],
|
229
|
-
ids=lambda x: "No configuration found for this test")
|
247
|
+
@pytest.hookimpl
|
248
|
+
def pytest_collect_file(self, parent, path):
|
249
|
+
logger.debug(f"Collecting file: {path}")
|
250
|
+
mustrd_file = MustrdFile.from_parent(parent, path=pathlib.Path(path), mustrd_plugin=self)
|
251
|
+
mustrd_file.mustrd_plugin = self
|
252
|
+
return mustrd_file
|
230
253
|
|
231
254
|
# Generate test for each triple store available
|
232
255
|
def generate_tests_for_config(self, config, triple_stores, file_name):
|
233
|
-
|
234
|
-
shacl_graph = Graph().parse(
|
256
|
+
logger.debug(f"generate_tests_for_config {config=} {self=} {dir(self)}")
|
257
|
+
shacl_graph = Graph().parse(
|
258
|
+
Path(os.path.join(mustrd_root, "model/mustrdShapes.ttl"))
|
259
|
+
)
|
235
260
|
ont_graph = Graph().parse(Path(os.path.join(mustrd_root, "model/ontology.ttl")))
|
236
|
-
|
237
|
-
|
261
|
+
logger.debug("Generating tests for config: " + str(config))
|
262
|
+
logger.debug(f"selected_tests {self.selected_tests}")
|
263
|
+
|
264
|
+
valid_spec_uris, spec_graph, invalid_spec_results = validate_specs(
|
265
|
+
config,
|
266
|
+
triple_stores,
|
267
|
+
shacl_graph,
|
268
|
+
ont_graph,
|
269
|
+
file_name or "*",
|
270
|
+
selected_test_files=self.selected_tests,
|
271
|
+
)
|
238
272
|
|
239
|
-
specs, skipped_spec_results =
|
240
|
-
|
273
|
+
specs, skipped_spec_results = get_specs(
|
274
|
+
valid_spec_uris, spec_graph, triple_stores, config
|
275
|
+
)
|
241
276
|
|
242
277
|
# Return normal specs + skipped results
|
243
278
|
return specs + skipped_spec_results + invalid_spec_results
|
@@ -248,28 +283,43 @@ class MustrdTestPlugin:
|
|
248
283
|
if isinstance(spec, SpecSkipped):
|
249
284
|
triple_store = spec.triple_store
|
250
285
|
else:
|
251
|
-
triple_store = spec.triple_store[
|
286
|
+
triple_store = spec.triple_store["type"]
|
287
|
+
|
252
288
|
triple_store_name = triple_store.replace("https://mustrd.com/model/", "")
|
253
289
|
test_name = spec.spec_uri.replace(spnamespace, "").replace("_", " ")
|
254
|
-
return triple_store_name + ": " + test_name
|
290
|
+
return spec.spec_file_name + " : " + triple_store_name + ": " + test_name
|
255
291
|
|
256
292
|
# Get triple store configuration or default
|
257
293
|
def get_triple_stores_from_file(self, test_config):
|
258
294
|
if test_config.triplestore_spec_path:
|
259
295
|
try:
|
260
|
-
triple_stores = get_triple_stores(
|
261
|
-
|
296
|
+
triple_stores = get_triple_stores(
|
297
|
+
get_triple_store_graph(
|
298
|
+
test_config.triplestore_spec_path, self.secrets
|
299
|
+
)
|
300
|
+
)
|
262
301
|
except Exception as e:
|
263
|
-
print(
|
264
|
-
|
265
|
-
|
302
|
+
print(
|
303
|
+
f"""Triplestore configuration parsing failed {test_config.triplestore_spec_path}.
|
304
|
+
Only rdflib will be executed""",
|
305
|
+
e,
|
306
|
+
)
|
307
|
+
triple_stores = [
|
308
|
+
{"type": TRIPLESTORE.RdfLib, "uri": TRIPLESTORE.RdfLib}
|
309
|
+
]
|
266
310
|
else:
|
267
311
|
print("No triple store configuration required: using embedded rdflib")
|
268
|
-
triple_stores = [{
|
312
|
+
triple_stores = [{"type": TRIPLESTORE.RdfLib, "uri": TRIPLESTORE.RdfLib}]
|
269
313
|
|
270
314
|
if test_config.filter_on_tripleStore:
|
271
|
-
triple_stores = list(
|
272
|
-
|
315
|
+
triple_stores = list(
|
316
|
+
filter(
|
317
|
+
lambda triple_store: (
|
318
|
+
triple_store["uri"] in test_config.filter_on_tripleStore
|
319
|
+
),
|
320
|
+
triple_stores,
|
321
|
+
)
|
322
|
+
)
|
273
323
|
return triple_stores
|
274
324
|
|
275
325
|
# Hook function. Initialize the list of result in session
|
@@ -284,7 +334,7 @@ class MustrdTestPlugin:
|
|
284
334
|
outcome = yield
|
285
335
|
result = outcome.get_result()
|
286
336
|
|
287
|
-
if result.when ==
|
337
|
+
if result.when == "call":
|
288
338
|
# Add the result of the test to the session
|
289
339
|
item.session.results[item] = result
|
290
340
|
|
@@ -300,7 +350,11 @@ class MustrdTestPlugin:
|
|
300
350
|
if test_conf.originalname != test_conf.name:
|
301
351
|
module_name = test_conf.parent.name
|
302
352
|
class_name = test_conf.originalname
|
303
|
-
test_name =
|
353
|
+
test_name = (
|
354
|
+
test_conf.name.replace(class_name, "")
|
355
|
+
.replace("[", "")
|
356
|
+
.replace("]", "")
|
357
|
+
)
|
304
358
|
is_mustrd = True
|
305
359
|
# Case normal unit tests
|
306
360
|
else:
|
@@ -309,26 +363,128 @@ class MustrdTestPlugin:
|
|
309
363
|
test_name = test_conf.originalname
|
310
364
|
is_mustrd = False
|
311
365
|
|
312
|
-
test_results.append(
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
366
|
+
test_results.append(
|
367
|
+
TestResult(
|
368
|
+
test_name, class_name, module_name, result.outcome, is_mustrd
|
369
|
+
)
|
370
|
+
)
|
371
|
+
|
372
|
+
result_list = ResultList(
|
373
|
+
None,
|
374
|
+
get_result_list(
|
375
|
+
test_results,
|
376
|
+
lambda result: result.type,
|
377
|
+
lambda result: is_mustrd and result.test_name.split("@")[1],
|
378
|
+
),
|
379
|
+
False,
|
380
|
+
)
|
318
381
|
|
319
382
|
md = result_list.render()
|
320
|
-
with open(self.md_path,
|
383
|
+
with open(self.md_path, "w") as file:
|
321
384
|
file.write(md)
|
322
385
|
|
323
386
|
|
387
|
+
class MustrdFile(pytest.File):
|
388
|
+
mustrd_plugin: MustrdTestPlugin
|
389
|
+
|
390
|
+
def __init__(self, *args, mustrd_plugin, **kwargs):
|
391
|
+
self.mustrd_plugin = mustrd_plugin
|
392
|
+
super(pytest.File, self).__init__(*args, **kwargs)
|
393
|
+
|
394
|
+
def collect(self):
|
395
|
+
try:
|
396
|
+
logger.debug(f"Collecting tests from file: {self.fspath}")
|
397
|
+
test_configs = parse_config(self.fspath)
|
398
|
+
for test_config in test_configs:
|
399
|
+
# Skip if there is a path filter and it is not in the pytest path
|
400
|
+
if (
|
401
|
+
self.mustrd_plugin.path_filter is not None
|
402
|
+
and self.mustrd_plugin.path_filter not in test_config.pytest_path
|
403
|
+
):
|
404
|
+
continue
|
405
|
+
triple_stores = self.mustrd_plugin.get_triple_stores_from_file(
|
406
|
+
test_config
|
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:
|
419
|
+
specs = self.mustrd_plugin.generate_tests_for_config(
|
420
|
+
{
|
421
|
+
"spec_path": test_config.spec_path,
|
422
|
+
"data_path": test_config.data_path,
|
423
|
+
},
|
424
|
+
triple_stores,
|
425
|
+
None,
|
426
|
+
)
|
427
|
+
for spec in specs:
|
428
|
+
item = MustrdItem.from_parent(
|
429
|
+
self,
|
430
|
+
name=test_config.pytest_path + "/" + spec.spec_file_name,
|
431
|
+
spec=spec,
|
432
|
+
)
|
433
|
+
self.mustrd_plugin.items.append(item)
|
434
|
+
yield item
|
435
|
+
except Exception as e:
|
436
|
+
# Catch error here otherwise it will be lost
|
437
|
+
self.mustrd_plugin.collect_error = e
|
438
|
+
logger.error(f"Error during collection: {e}")
|
439
|
+
raise e
|
440
|
+
|
441
|
+
|
442
|
+
class MustrdItem(pytest.Item):
|
443
|
+
def __init__(self, name, parent, spec):
|
444
|
+
logging.info(f"Creating item: {name}")
|
445
|
+
super().__init__(name, parent)
|
446
|
+
self.spec = spec
|
447
|
+
self.fspath = spec.spec_source_file
|
448
|
+
|
449
|
+
def runtest(self):
|
450
|
+
result = run_test_spec(self.spec)
|
451
|
+
if not result:
|
452
|
+
raise AssertionError(f"Test {self.name} failed")
|
453
|
+
|
454
|
+
def repr_failure(self, excinfo):
|
455
|
+
# excinfo.value is the exception instance
|
456
|
+
# You can add more context here
|
457
|
+
tb_lines = traceback.format_exception(excinfo.type, excinfo.value, excinfo.tb)
|
458
|
+
tb_str = "".join(tb_lines)
|
459
|
+
return (
|
460
|
+
f"{self.name} failed:\n"
|
461
|
+
f"Spec: {self.spec.spec_uri}\n"
|
462
|
+
f"File: {self.spec.spec_source_file}\n"
|
463
|
+
f"Dir: {dir(self.spec)}\n"
|
464
|
+
f"Error: {excinfo.value}\n"
|
465
|
+
f"Traceback:\n{tb_str}"
|
466
|
+
)
|
467
|
+
|
468
|
+
def reportinfo(self):
|
469
|
+
r = "", 0, f"mustrd test: {self.name}"
|
470
|
+
return r
|
471
|
+
|
472
|
+
|
324
473
|
# Function called in the test to actually run it
|
325
474
|
def run_test_spec(test_spec):
|
326
475
|
if isinstance(test_spec, SpecSkipped):
|
327
476
|
pytest.skip(f"Invalid configuration, error : {test_spec.message}")
|
328
477
|
result = run_spec(test_spec)
|
329
|
-
|
330
478
|
result_type = type(result)
|
331
479
|
if result_type == SpecSkipped:
|
332
480
|
# FIXME: Better exception management
|
333
481
|
pytest.skip("Unsupported configuration")
|
482
|
+
if result_type != SpecPassed:
|
483
|
+
write_result_diff_to_log(result, logger.info)
|
484
|
+
log_lines = []
|
485
|
+
def log_to_string(message):
|
486
|
+
log_lines.append(message)
|
487
|
+
write_result_diff_to_log(result, log_to_string)
|
488
|
+
raise AssertionError("Test failed: " + "\n".join(log_lines))
|
489
|
+
|
334
490
|
return result_type == SpecPassed
|
mustrd/spec_component.py
CHANGED
@@ -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,8 @@ 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, can we defer it to run time?
|
120
124
|
spec_component = get_spec_component(spec_component_details)
|
121
125
|
if isinstance(spec_component, list):
|
122
126
|
spec_components += spec_component
|
@@ -433,10 +437,11 @@ def _get_spec_component_AnzoGraphmartDataset(spec_component_details: SpecCompone
|
|
433
437
|
predicate=MUST.graphmart)
|
434
438
|
layer = spec_component_details.spec_graph.value(subject=spec_component_details.spec_component_node,
|
435
439
|
predicate=MUST.layer)
|
436
|
-
spec_component.
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
+
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)
|
440
445
|
else:
|
441
446
|
raise ValueError(f"You must define {TRIPLESTORE.Anzo} to use {MUST.AnzoGraphmartDataset}")
|
442
447
|
|
@@ -468,14 +473,16 @@ def _get_spec_component_AnzoQueryBuilderSparqlSource(spec_component_details: Spe
|
|
468
473
|
|
469
474
|
@get_spec_component.method((MUST.AnzoGraphmartStepSparqlSource, MUST.when))
|
470
475
|
def _get_spec_component_AnzoGraphmartStepSparqlSource(spec_component_details: SpecComponentDetails) -> SpecComponent:
|
471
|
-
spec_component =
|
476
|
+
spec_component = AnzoWhenSpec()
|
472
477
|
|
473
478
|
# Get WHEN specComponent from query builder
|
474
479
|
if spec_component_details.mustrd_triple_store["type"] == TRIPLESTORE.Anzo:
|
475
480
|
query_step_uri = spec_component_details.spec_graph.value(subject=spec_component_details.spec_component_node,
|
476
481
|
predicate=MUST.anzoQueryStep)
|
477
|
-
spec_component.
|
478
|
-
|
482
|
+
spec_component.spec_component_details = spec_component_details
|
483
|
+
spec_component.query_step_uri = query_step_uri
|
484
|
+
# spec_component.value = get_query_from_step(triple_store=spec_component_details.mustrd_triple_store,
|
485
|
+
# query_step_uri=query_step_uri)
|
479
486
|
# If anzo specific function is called but no anzo defined
|
480
487
|
else:
|
481
488
|
raise ValueError(f"You must define {TRIPLESTORE.Anzo} to use {MUST.AnzoGraphmartStepSparqlSource}")
|
@@ -529,6 +536,7 @@ def _get_spec_component_AnzoGraphmartLayerSparqlSource(spec_component_details: S
|
|
529
536
|
spec_component.value = query.get("query")
|
530
537
|
spec_component.paramQuery = query.get("param_query")
|
531
538
|
spec_component.queryTemplate = query.get("query_template")
|
539
|
+
spec_component.spec_component_details = spec_component_details
|
532
540
|
if spec_component.value:
|
533
541
|
spec_component.queryType = spec_component_details.spec_graph.value(
|
534
542
|
subject=spec_component_details.spec_component_node,
|
@@ -547,6 +555,7 @@ def _get_spec_component_default(spec_component_details: SpecComponentDetails) ->
|
|
547
555
|
|
548
556
|
|
549
557
|
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}")
|
550
559
|
if predicate == MUST.given:
|
551
560
|
spec_component = GivenSpec()
|
552
561
|
elif predicate == MUST.when:
|
mustrd/steprunner.py
CHANGED
@@ -31,7 +31,7 @@ from rdflib import Graph, URIRef
|
|
31
31
|
from .mustrdRdfLib import execute_select as execute_select_rdflib
|
32
32
|
from .mustrdRdfLib import execute_construct as execute_construct_rdflib
|
33
33
|
from .mustrdRdfLib import execute_update as execute_update_rdflib
|
34
|
-
from .mustrdAnzo import upload_given as upload_given_anzo
|
34
|
+
from .mustrdAnzo import get_query_from_step, upload_given as upload_given_anzo
|
35
35
|
from .mustrdAnzo import execute_update as execute_update_anzo
|
36
36
|
from .mustrdAnzo import execute_construct as execute_construct_anzo
|
37
37
|
from .mustrdAnzo import execute_select as execute_select_anzo
|
@@ -40,8 +40,9 @@ from .mustrdGraphDb import execute_update as execute_update_graphdb
|
|
40
40
|
from .mustrdGraphDb import execute_construct as execute_construct_graphdb
|
41
41
|
from .mustrdGraphDb import execute_select as execute_select_graphdb
|
42
42
|
from .spec_component import AnzoWhenSpec, WhenSpec
|
43
|
+
import logging
|
43
44
|
|
44
|
-
log =
|
45
|
+
log = logging.getLogger(__name__)
|
45
46
|
|
46
47
|
|
47
48
|
def dispatch_upload_given(triple_store: dict, given: Graph):
|
@@ -80,7 +81,16 @@ run_when = MultiMethod('run_when', dispatch_run_when)
|
|
80
81
|
|
81
82
|
@run_when.method((TRIPLESTORE.Anzo, MUST.UpdateSparql))
|
82
83
|
def _anzo_run_when_update(spec_uri: URIRef, triple_store: dict, when: AnzoWhenSpec):
|
83
|
-
|
84
|
+
log.debug(f"_anzo_run_when_update {spec_uri} {triple_store} {when} {type(when)}")
|
85
|
+
if when.value is None:
|
86
|
+
# fetch the query from the query step on anzo
|
87
|
+
query = get_query_from_step(triple_store=when.spec_component_details.mustrd_triple_store,
|
88
|
+
query_step_uri=when.query_step_uri)
|
89
|
+
else:
|
90
|
+
# we must already have the query
|
91
|
+
query = when.value
|
92
|
+
log.debug(f"_anzo_run_when_update.query {query}")
|
93
|
+
return execute_update_anzo(triple_store, query, when.bindings)
|
84
94
|
|
85
95
|
|
86
96
|
@run_when.method((TRIPLESTORE.Anzo, MUST.ConstructSparql))
|
@@ -95,7 +105,8 @@ def _anzo_run_when_select(spec_uri: URIRef, triple_store: dict, when: AnzoWhenSp
|
|
95
105
|
|
96
106
|
@run_when.method((TRIPLESTORE.GraphDb, MUST.UpdateSparql))
|
97
107
|
def _graphdb_run_when_update(spec_uri: URIRef, triple_store: dict, when: WhenSpec):
|
98
|
-
|
108
|
+
|
109
|
+
return execute_update_graphdb(triple_store, query, when.bindings)
|
99
110
|
|
100
111
|
|
101
112
|
@run_when.method((TRIPLESTORE.GraphDb, MUST.ConstructSparql))
|
@@ -152,6 +163,7 @@ def _multi_run_when_anzo_query_driven_update(spec_uri: URIRef, triple_store: dic
|
|
152
163
|
|
153
164
|
@run_when.method(Default)
|
154
165
|
def _multi_run_when_default(spec_uri: URIRef, triple_store: dict, when: WhenSpec):
|
166
|
+
log.error(f"run_when not implemented for {spec_uri} {triple_store} {when}")
|
155
167
|
if when.queryType == MUST.AskSparql:
|
156
168
|
log.warning(f"Skipping {spec_uri}, SPARQL ASK not implemented.")
|
157
169
|
msg = "SPARQL ASK not implemented."
|