psdi-data-conversion 0.0.36__py3-none-any.whl → 0.0.38__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.
- psdi_data_conversion/app.py +122 -16
- psdi_data_conversion/constants.py +6 -5
- psdi_data_conversion/converter.py +13 -6
- psdi_data_conversion/converters/base.py +58 -55
- psdi_data_conversion/converters/c2x.py +1 -0
- psdi_data_conversion/converters/openbabel.py +10 -10
- psdi_data_conversion/database.py +335 -113
- psdi_data_conversion/main.py +151 -69
- psdi_data_conversion/static/content/download.htm +22 -9
- psdi_data_conversion/static/javascript/data.js +18 -4
- psdi_data_conversion/static/javascript/format.js +22 -9
- psdi_data_conversion/static/styles/psdi-common.css +5 -4
- psdi_data_conversion/templates/index.htm +59 -45
- psdi_data_conversion/testing/constants.py +9 -3
- psdi_data_conversion/testing/conversion_callbacks.py +9 -7
- psdi_data_conversion/testing/conversion_test_specs.py +128 -27
- psdi_data_conversion/testing/gui.py +428 -0
- psdi_data_conversion/testing/utils.py +121 -60
- {psdi_data_conversion-0.0.36.dist-info → psdi_data_conversion-0.0.38.dist-info}/METADATA +160 -60
- {psdi_data_conversion-0.0.36.dist-info → psdi_data_conversion-0.0.38.dist-info}/RECORD +23 -22
- {psdi_data_conversion-0.0.36.dist-info → psdi_data_conversion-0.0.38.dist-info}/WHEEL +1 -1
- {psdi_data_conversion-0.0.36.dist-info → psdi_data_conversion-0.0.38.dist-info}/entry_points.txt +1 -0
- {psdi_data_conversion-0.0.36.dist-info → psdi_data_conversion-0.0.38.dist-info}/licenses/LICENSE +0 -0
@@ -19,19 +19,56 @@ from unittest.mock import patch
|
|
19
19
|
import py
|
20
20
|
import pytest
|
21
21
|
|
22
|
+
import psdi_data_conversion
|
22
23
|
from psdi_data_conversion.constants import CONVERTER_DEFAULT, GLOBAL_LOG_FILENAME, LOG_NONE, OUTPUT_LOG_EXT
|
23
24
|
from psdi_data_conversion.converter import run_converter
|
24
25
|
from psdi_data_conversion.converters.openbabel import COORD_GEN_KEY, COORD_GEN_QUAL_KEY
|
26
|
+
from psdi_data_conversion.database import get_format_info
|
25
27
|
from psdi_data_conversion.dist import LINUX_LABEL, get_dist
|
26
28
|
from psdi_data_conversion.file_io import is_archive, split_archive_ext
|
27
29
|
from psdi_data_conversion.main import main as data_convert_main
|
28
|
-
from psdi_data_conversion.testing.constants import
|
30
|
+
from psdi_data_conversion.testing.constants import (INPUT_TEST_DATA_LOC_IN_PROJECT, OUTPUT_TEST_DATA_LOC_IN_PROJECT,
|
31
|
+
TEST_DATA_LOC_IN_PROJECT)
|
32
|
+
|
33
|
+
|
34
|
+
def get_path_in_project(filename):
|
35
|
+
"""Get the realpath to a file contained within the project, given its project-relative path"""
|
36
|
+
|
37
|
+
old_cwd = os.getcwd()
|
38
|
+
|
39
|
+
try:
|
40
|
+
os.chdir(os.path.join(psdi_data_conversion.__path__[0], ".."))
|
41
|
+
realpath = os.path.realpath(filename)
|
42
|
+
|
43
|
+
finally:
|
44
|
+
# Change back to the previous directory
|
45
|
+
os.chdir(old_cwd)
|
46
|
+
|
47
|
+
return realpath
|
48
|
+
|
49
|
+
|
50
|
+
def get_test_data_loc():
|
51
|
+
"""Get the realpath of the base directory containing all data for tests"""
|
52
|
+
return get_path_in_project(TEST_DATA_LOC_IN_PROJECT)
|
53
|
+
|
54
|
+
|
55
|
+
def get_input_test_data_loc():
|
56
|
+
"""Get the realpath of the base directory containing input data for tests"""
|
57
|
+
return get_path_in_project(INPUT_TEST_DATA_LOC_IN_PROJECT)
|
58
|
+
|
59
|
+
|
60
|
+
def get_output_test_data_loc():
|
61
|
+
"""Get the realpath of the base directory containing expected output data for tests"""
|
62
|
+
return get_path_in_project(OUTPUT_TEST_DATA_LOC_IN_PROJECT)
|
29
63
|
|
30
64
|
|
31
65
|
@dataclass
|
32
66
|
class ConversionTestInfo:
|
33
67
|
"""Information about a tested conversion."""
|
34
68
|
|
69
|
+
run_type: str
|
70
|
+
"""One of "library", "cla", or "gui", describing which type of test run was performed"""
|
71
|
+
|
35
72
|
test_spec: SingleConversionTestSpec
|
36
73
|
"""The specification of the test conversion which was run to produce this"""
|
37
74
|
|
@@ -50,20 +87,23 @@ class ConversionTestInfo:
|
|
50
87
|
captured_stderr: str | None = None
|
51
88
|
"""Any output to stderr while the test was run"""
|
52
89
|
|
90
|
+
exc_info: pytest.ExceptionInfo | None = None
|
91
|
+
"""If the test conversion raised an exception, that exception's info, otherwise None"""
|
92
|
+
|
53
93
|
@property
|
54
94
|
def qualified_in_filename(self):
|
55
95
|
"""Get the fully-qualified name of the input file"""
|
56
|
-
return os.path.join(self.input_dir, self.test_spec.filename)
|
96
|
+
return os.path.realpath(os.path.join(self.input_dir, self.test_spec.filename))
|
57
97
|
|
58
98
|
@property
|
59
99
|
def qualified_out_filename(self):
|
60
100
|
"""Get the fully-qualified name of the output file"""
|
61
|
-
return os.path.join(self.output_dir, self.test_spec.out_filename)
|
101
|
+
return os.path.realpath(os.path.join(self.output_dir, self.test_spec.out_filename))
|
62
102
|
|
63
103
|
@property
|
64
104
|
def qualified_log_filename(self):
|
65
105
|
"""Get the fully-qualified name of the log file"""
|
66
|
-
return os.path.join(self.output_dir, self.test_spec.log_filename)
|
106
|
+
return os.path.realpath(os.path.join(self.output_dir, self.test_spec.log_filename))
|
67
107
|
|
68
108
|
@property
|
69
109
|
def qualified_global_log_filename(self):
|
@@ -71,28 +111,6 @@ class ConversionTestInfo:
|
|
71
111
|
return self.test_spec.global_log_filename
|
72
112
|
|
73
113
|
|
74
|
-
@dataclass
|
75
|
-
class LibraryConversionTestInfo(ConversionTestInfo):
|
76
|
-
"""Information about a tested conversion, specifically for when it was tested through a call to the library"""
|
77
|
-
|
78
|
-
exc_info: pytest.ExceptionInfo | None = None
|
79
|
-
"""If the test conversion raised an exception, that exception's info, otherwise None"""
|
80
|
-
|
81
|
-
|
82
|
-
@dataclass
|
83
|
-
class CLAConversionTestInfo(ConversionTestInfo):
|
84
|
-
"""Information about a tested conversion, specifically for when it was tested through a the command-line
|
85
|
-
application (CLA)
|
86
|
-
"""
|
87
|
-
|
88
|
-
|
89
|
-
@dataclass
|
90
|
-
class GUIConversionTestInfo(ConversionTestInfo):
|
91
|
-
"""Information about a tested conversion, specifically for when it was tested through the GUI (the local version of
|
92
|
-
the web app)
|
93
|
-
"""
|
94
|
-
|
95
|
-
|
96
114
|
@dataclass
|
97
115
|
class ConversionTestSpec:
|
98
116
|
"""Class providing a specification for a test file conversion.
|
@@ -108,9 +126,12 @@ class ConversionTestSpec:
|
|
108
126
|
filename: str | Iterable[str] = "nacl.cif"
|
109
127
|
"""The name of the input file, relative to the input test data location, or a list thereof"""
|
110
128
|
|
111
|
-
to_format: str | Iterable[str] = "pdb"
|
129
|
+
to_format: str | int | Iterable[str | int] = "pdb"
|
112
130
|
"""The format to test converting the input file to, or a list thereof"""
|
113
131
|
|
132
|
+
from_format: str | int | Iterable[str | int] | None = None
|
133
|
+
"""The format of the input file, when it needs to be explicitly specified"""
|
134
|
+
|
114
135
|
converter_name: str | Iterable[str] = CONVERTER_DEFAULT
|
115
136
|
"""The name of the converter to be used for the test, or a list thereof"""
|
116
137
|
|
@@ -121,6 +142,11 @@ class ConversionTestSpec:
|
|
121
142
|
expect_success: bool | Iterable[bool] = True
|
122
143
|
"""Whether or not to expect the test to succeed"""
|
123
144
|
|
145
|
+
skip: bool | Iterable[bool] = False
|
146
|
+
"""If set to true, this test will be skipped and not run. Can also be set individually for certain tests within an
|
147
|
+
array. This should typically only be used when debugging to skip working tests to more easily focus on non-working
|
148
|
+
tests"""
|
149
|
+
|
124
150
|
callback: (Callable[[ConversionTestInfo], str] |
|
125
151
|
Iterable[Callable[[ConversionTestInfo], str]] | None) = None
|
126
152
|
"""Function to be called after the conversion is performed to check in detail whether results are as expected. It
|
@@ -187,6 +213,9 @@ class ConversionTestSpec:
|
|
187
213
|
if attr_name in l_single_val_attrs:
|
188
214
|
setattr(self, attr_name, [getattr(self, attr_name)]*self._len)
|
189
215
|
|
216
|
+
# Check if all tests should be skipped
|
217
|
+
self.skip_all = all(self.skip)
|
218
|
+
|
190
219
|
def __len__(self):
|
191
220
|
"""Get the length from the member - valid only after `__post_init__` has been called"""
|
192
221
|
return self._len
|
@@ -208,9 +237,12 @@ class SingleConversionTestSpec:
|
|
208
237
|
filename: str
|
209
238
|
"""The name of the input file, relative to the input test data location"""
|
210
239
|
|
211
|
-
to_format: str
|
240
|
+
to_format: str | int
|
212
241
|
"""The format to test converting the input file to"""
|
213
242
|
|
243
|
+
from_format: str | int | None = None
|
244
|
+
"""The format of the input file, when it needs to be explicitly specified"""
|
245
|
+
|
214
246
|
converter_name: str | Iterable[str] = CONVERTER_DEFAULT
|
215
247
|
"""The name of the converter to be used for the test"""
|
216
248
|
|
@@ -221,6 +253,9 @@ class SingleConversionTestSpec:
|
|
221
253
|
expect_success: bool = True
|
222
254
|
"""Whether or not to expect the test to succeed"""
|
223
255
|
|
256
|
+
skip: bool = False
|
257
|
+
"""If set to True, this test will be skipped, always returning success"""
|
258
|
+
|
224
259
|
callback: (Callable[[ConversionTestInfo], str] | None) = None
|
225
260
|
"""Function to be called after the conversion is performed to check in detail whether results are as expected. It
|
226
261
|
should take as its only argument a `ConversionTestInfo` and return a string. The string should be empty if the check
|
@@ -229,11 +264,12 @@ class SingleConversionTestSpec:
|
|
229
264
|
@property
|
230
265
|
def out_filename(self) -> str:
|
231
266
|
"""The unqualified name of the output file which should have been created by the conversion."""
|
267
|
+
to_format_name = get_format_info(self.to_format, which=0).name
|
232
268
|
if not is_archive(self.filename):
|
233
|
-
return f"{os.path.splitext(self.filename)[0]}.{
|
269
|
+
return f"{os.path.splitext(self.filename)[0]}.{to_format_name}"
|
234
270
|
else:
|
235
271
|
filename_base, ext = split_archive_ext(os.path.basename(self.filename))
|
236
|
-
return f"{filename_base}-{
|
272
|
+
return f"{filename_base}-{to_format_name}{ext}"
|
237
273
|
|
238
274
|
@property
|
239
275
|
def log_filename(self) -> str:
|
@@ -258,9 +294,14 @@ def run_test_conversion_with_library(test_spec: ConversionTestSpec):
|
|
258
294
|
with TemporaryDirectory("_input") as input_dir, TemporaryDirectory("_output") as output_dir:
|
259
295
|
# Iterate over the test spec to run each individual test it defines
|
260
296
|
for single_test_spec in test_spec:
|
297
|
+
if single_test_spec.skip:
|
298
|
+
print(f"Skipping single test spec {single_test_spec}")
|
299
|
+
continue
|
300
|
+
print(f"Running single test spec: {single_test_spec}")
|
261
301
|
_run_single_test_conversion_with_library(test_spec=single_test_spec,
|
262
302
|
input_dir=input_dir,
|
263
303
|
output_dir=output_dir)
|
304
|
+
print(f"Success for test spec: {single_test_spec}")
|
264
305
|
|
265
306
|
|
266
307
|
def _run_single_test_conversion_with_library(test_spec: SingleConversionTestSpec,
|
@@ -279,9 +320,9 @@ def _run_single_test_conversion_with_library(test_spec: SingleConversionTestSpec
|
|
279
320
|
"""
|
280
321
|
|
281
322
|
# Symlink the input file to the input directory
|
282
|
-
qualified_in_filename = os.path.join(input_dir, test_spec.filename)
|
323
|
+
qualified_in_filename = os.path.realpath(os.path.join(input_dir, test_spec.filename))
|
283
324
|
try:
|
284
|
-
os.symlink(os.path.join(
|
325
|
+
os.symlink(os.path.join(get_input_test_data_loc(), test_spec.filename),
|
285
326
|
qualified_in_filename)
|
286
327
|
except FileExistsError:
|
287
328
|
pass
|
@@ -294,6 +335,7 @@ def _run_single_test_conversion_with_library(test_spec: SingleConversionTestSpec
|
|
294
335
|
if test_spec.expect_success:
|
295
336
|
run_converter(filename=qualified_in_filename,
|
296
337
|
to_format=test_spec.to_format,
|
338
|
+
from_format=test_spec.from_format,
|
297
339
|
name=test_spec.converter_name,
|
298
340
|
upload_dir=input_dir,
|
299
341
|
download_dir=output_dir,
|
@@ -303,6 +345,7 @@ def _run_single_test_conversion_with_library(test_spec: SingleConversionTestSpec
|
|
303
345
|
with pytest.raises(Exception) as exc_info:
|
304
346
|
run_converter(filename=qualified_in_filename,
|
305
347
|
to_format=test_spec.to_format,
|
348
|
+
from_format=test_spec.from_format,
|
306
349
|
name=test_spec.converter_name,
|
307
350
|
upload_dir=input_dir,
|
308
351
|
download_dir=output_dir,
|
@@ -316,15 +359,17 @@ def _run_single_test_conversion_with_library(test_spec: SingleConversionTestSpec
|
|
316
359
|
|
317
360
|
# Compile output info for the test and call the callback function if one is provided
|
318
361
|
if test_spec.callback:
|
319
|
-
test_info =
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
362
|
+
test_info = ConversionTestInfo(run_type="library",
|
363
|
+
test_spec=test_spec,
|
364
|
+
input_dir=input_dir,
|
365
|
+
output_dir=output_dir,
|
366
|
+
success=success,
|
367
|
+
captured_stdout=stdout,
|
368
|
+
captured_stderr=stderr,
|
369
|
+
exc_info=exc_info)
|
326
370
|
callback_msg = test_spec.callback(test_info)
|
327
|
-
|
371
|
+
if callback_msg:
|
372
|
+
pytest.fail(callback_msg)
|
328
373
|
|
329
374
|
|
330
375
|
def run_test_conversion_with_cla(test_spec: ConversionTestSpec):
|
@@ -339,9 +384,14 @@ def run_test_conversion_with_cla(test_spec: ConversionTestSpec):
|
|
339
384
|
with TemporaryDirectory("_input") as input_dir, TemporaryDirectory("_output") as output_dir:
|
340
385
|
# Iterate over the test spec to run each individual test it defines
|
341
386
|
for single_test_spec in test_spec:
|
387
|
+
if single_test_spec.skip:
|
388
|
+
print(f"Skipping single test spec {single_test_spec}")
|
389
|
+
continue
|
390
|
+
print(f"Running single test spec: {single_test_spec}")
|
342
391
|
_run_single_test_conversion_with_cla(test_spec=single_test_spec,
|
343
392
|
input_dir=input_dir,
|
344
393
|
output_dir=output_dir)
|
394
|
+
print(f"Success for test spec: {single_test_spec}")
|
345
395
|
|
346
396
|
|
347
397
|
def _run_single_test_conversion_with_cla(test_spec: SingleConversionTestSpec,
|
@@ -360,9 +410,9 @@ def _run_single_test_conversion_with_cla(test_spec: SingleConversionTestSpec,
|
|
360
410
|
"""
|
361
411
|
|
362
412
|
# Symlink the input file to the input directory
|
363
|
-
qualified_in_filename = os.path.join(input_dir, test_spec.filename)
|
413
|
+
qualified_in_filename = os.path.realpath(os.path.join(input_dir, test_spec.filename))
|
364
414
|
try:
|
365
|
-
os.symlink(os.path.join(
|
415
|
+
os.symlink(os.path.join(get_input_test_data_loc(), test_spec.filename),
|
366
416
|
qualified_in_filename)
|
367
417
|
except FileExistsError:
|
368
418
|
pass
|
@@ -374,6 +424,7 @@ def _run_single_test_conversion_with_cla(test_spec: SingleConversionTestSpec,
|
|
374
424
|
if test_spec.expect_success:
|
375
425
|
run_converter_through_cla(filename=qualified_in_filename,
|
376
426
|
to_format=test_spec.to_format,
|
427
|
+
from_format=test_spec.from_format,
|
377
428
|
name=test_spec.converter_name,
|
378
429
|
input_dir=input_dir,
|
379
430
|
output_dir=output_dir,
|
@@ -384,6 +435,7 @@ def _run_single_test_conversion_with_cla(test_spec: SingleConversionTestSpec,
|
|
384
435
|
with pytest.raises(SystemExit) as exc_info:
|
385
436
|
run_converter_through_cla(filename=qualified_in_filename,
|
386
437
|
to_format=test_spec.to_format,
|
438
|
+
from_format=test_spec.from_format,
|
387
439
|
name=test_spec.converter_name,
|
388
440
|
input_dir=input_dir,
|
389
441
|
output_dir=output_dir,
|
@@ -392,7 +444,7 @@ def _run_single_test_conversion_with_cla(test_spec: SingleConversionTestSpec,
|
|
392
444
|
# Get the success from whether or not the exit code is 0
|
393
445
|
success = not exc_info.value.code
|
394
446
|
|
395
|
-
qualified_out_filename = os.path.join(output_dir, test_spec.out_filename)
|
447
|
+
qualified_out_filename = os.path.realpath(os.path.join(output_dir, test_spec.out_filename))
|
396
448
|
|
397
449
|
# Determine success based on whether or not the output file exists with non-zero size
|
398
450
|
if not os.path.isfile(qualified_out_filename) or os.path.getsize(qualified_out_filename) == 0:
|
@@ -405,14 +457,16 @@ def _run_single_test_conversion_with_cla(test_spec: SingleConversionTestSpec,
|
|
405
457
|
|
406
458
|
# Compile output info for the test and call the callback function if one is provided
|
407
459
|
if test_spec.callback:
|
408
|
-
test_info =
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
460
|
+
test_info = ConversionTestInfo(run_type="cla",
|
461
|
+
test_spec=test_spec,
|
462
|
+
input_dir=input_dir,
|
463
|
+
output_dir=output_dir,
|
464
|
+
success=success,
|
465
|
+
captured_stdout=stdout,
|
466
|
+
captured_stderr=stderr)
|
414
467
|
callback_msg = test_spec.callback(test_info)
|
415
|
-
|
468
|
+
if callback_msg:
|
469
|
+
pytest.fail(callback_msg)
|
416
470
|
|
417
471
|
|
418
472
|
def run_converter_through_cla(filename: str,
|
@@ -421,6 +475,7 @@ def run_converter_through_cla(filename: str,
|
|
421
475
|
input_dir: str,
|
422
476
|
output_dir: str,
|
423
477
|
log_file: str,
|
478
|
+
from_format: str | None = None,
|
424
479
|
**conversion_kwargs):
|
425
480
|
"""Runs a test conversion through the command-line interface
|
426
481
|
|
@@ -441,17 +496,23 @@ def run_converter_through_cla(filename: str,
|
|
441
496
|
The directory which contains the output file
|
442
497
|
log_file : str
|
443
498
|
The desired name of the log file
|
499
|
+
conversion_kwargs : Any
|
500
|
+
Additional arguments describing the conversion
|
501
|
+
from_format : str | None
|
502
|
+
The format of the input file, when it needs to be explicitly specified, otherwise None
|
444
503
|
"""
|
445
504
|
|
446
505
|
# Start the argument string with the arguments we will always include
|
447
506
|
arg_string = f"{filename} -i {input_dir} -t {to_format} -o {output_dir} -w {name} --log-file {log_file}"
|
448
507
|
|
449
|
-
# For each argument in the conversion kwargs, convert it to the appropriate argument to be provided
|
450
|
-
# argument string
|
508
|
+
# For from_format and each argument in the conversion kwargs, convert it to the appropriate argument to be provided
|
509
|
+
# to the argument string
|
510
|
+
|
511
|
+
if from_format:
|
512
|
+
arg_string += f" -f {from_format}"
|
513
|
+
|
451
514
|
for key, val in conversion_kwargs.items():
|
452
|
-
if key == "
|
453
|
-
arg_string += f" -f {val}"
|
454
|
-
elif key == "log_mode":
|
515
|
+
if key == "log_mode":
|
455
516
|
if val == LOG_NONE:
|
456
517
|
arg_string += " -q"
|
457
518
|
else:
|
@@ -464,8 +525,8 @@ def run_converter_through_cla(filename: str,
|
|
464
525
|
arg_string += " --strict"
|
465
526
|
elif key == "max_file_size":
|
466
527
|
if val != 0:
|
467
|
-
|
468
|
-
|
528
|
+
pytest.fail("Test specification imposes a maximum file size, which isn't compatible with the "
|
529
|
+
"command-line application.")
|
469
530
|
elif key == "data":
|
470
531
|
for subkey, subval in val.items():
|
471
532
|
if subkey == "from_flags":
|
@@ -484,10 +545,10 @@ def run_converter_through_cla(filename: str,
|
|
484
545
|
# Handled alongside COORD_GEN_KEY above
|
485
546
|
pass
|
486
547
|
else:
|
487
|
-
|
488
|
-
|
548
|
+
pytest.fail(f"The key 'data[\"{subkey}\"]' was passed to `conversion_kwargs` but could not be "
|
549
|
+
"interpreted")
|
489
550
|
else:
|
490
|
-
|
551
|
+
pytest.fail(f"The key '{key}' was passed to `conversion_kwargs` but could not be interpreted")
|
491
552
|
|
492
553
|
run_with_arg_string(arg_string)
|
493
554
|
|