psdi-data-conversion 0.0.39__py3-none-any.whl → 0.1.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.
- psdi_data_conversion/app.py +57 -21
- psdi_data_conversion/constants.py +1 -0
- psdi_data_conversion/converter.py +140 -12
- psdi_data_conversion/database.py +17 -9
- psdi_data_conversion/main.py +29 -22
- psdi_data_conversion/static/content/index-versions/psdi-common-footer.html +1 -1
- psdi_data_conversion/static/content/index-versions/psdi-common-header.html +1 -1
- psdi_data_conversion/static/content/psdi-common-footer.html +1 -1
- psdi_data_conversion/static/content/psdi-common-header.html +1 -1
- psdi_data_conversion/templates/index.htm +8 -9
- psdi_data_conversion/testing/conversion_test_specs.py +12 -4
- psdi_data_conversion/utils.py +21 -0
- {psdi_data_conversion-0.0.39.dist-info → psdi_data_conversion-0.1.0.dist-info}/METADATA +2 -2
- {psdi_data_conversion-0.0.39.dist-info → psdi_data_conversion-0.1.0.dist-info}/RECORD +17 -16
- {psdi_data_conversion-0.0.39.dist-info → psdi_data_conversion-0.1.0.dist-info}/WHEEL +0 -0
- {psdi_data_conversion-0.0.39.dist-info → psdi_data_conversion-0.1.0.dist-info}/entry_points.txt +0 -0
- {psdi_data_conversion-0.0.39.dist-info → psdi_data_conversion-0.1.0.dist-info}/licenses/LICENSE +0 -0
psdi_data_conversion/app.py
CHANGED
@@ -6,6 +6,7 @@ This script acts as a server for the PSDI Data Conversion Service website.
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
import json
|
9
|
+
from multiprocessing import Lock
|
9
10
|
import os
|
10
11
|
import sys
|
11
12
|
from argparse import ArgumentParser
|
@@ -19,6 +20,7 @@ from typing import Any
|
|
19
20
|
|
20
21
|
import werkzeug.serving
|
21
22
|
from flask import Flask, Response, abort, cli, render_template, request
|
23
|
+
from werkzeug.utils import secure_filename
|
22
24
|
|
23
25
|
import psdi_data_conversion
|
24
26
|
from psdi_data_conversion import constants as const
|
@@ -31,24 +33,29 @@ from psdi_data_conversion.main import print_wrap
|
|
31
33
|
# Env var for the SHA of the latest commit
|
32
34
|
SHA_EV = "SHA"
|
33
35
|
|
34
|
-
# Env var for whether this is running in service mode or locally
|
35
|
-
SERVICE_MODE_EV = "SERVICE_MODE"
|
36
|
-
|
37
36
|
# Env var for whether this is a production release or development
|
38
37
|
PRODUCTION_EV = "PRODUCTION_MODE"
|
39
38
|
|
39
|
+
# Env var for whether this is a production release or development
|
40
|
+
DEBUG_EV = "DEBUG_MODE"
|
41
|
+
|
40
42
|
# Key for the label given to the file uploaded in the web interface
|
41
43
|
FILE_TO_UPLOAD_KEY = 'fileToUpload'
|
42
44
|
|
45
|
+
# A lock to prevent multiple threads from logging at the same time
|
46
|
+
logLock = Lock()
|
47
|
+
|
43
48
|
# Create a token by hashing the current date and time.
|
44
49
|
dt = str(datetime.now())
|
45
50
|
token = md5(dt.encode('utf8')).hexdigest()
|
46
51
|
|
47
|
-
# Get the service and production modes from their envvars
|
48
|
-
service_mode_ev = os.environ.get(SERVICE_MODE_EV)
|
52
|
+
# Get the debug, service and production modes from their envvars
|
53
|
+
service_mode_ev = os.environ.get(const.SERVICE_MODE_EV)
|
49
54
|
service_mode = (service_mode_ev is not None) and (service_mode_ev.lower() == "true")
|
50
55
|
production_mode_ev = os.environ.get(PRODUCTION_EV)
|
51
56
|
production_mode = (production_mode_ev is not None) and (production_mode_ev.lower() == "true")
|
57
|
+
debug_mode_ev = os.environ.get(DEBUG_EV)
|
58
|
+
debug_mode = (debug_mode_ev is not None) and (debug_mode_ev.lower() == "true")
|
52
59
|
|
53
60
|
# Get the logging mode and level from their envvars
|
54
61
|
ev_log_mode = os.environ.get(const.LOG_MODE_EV)
|
@@ -86,11 +93,11 @@ if ev_max_file_size_ob is not None:
|
|
86
93
|
else:
|
87
94
|
max_file_size_ob = const.DEFAULT_MAX_FILE_SIZE_OB
|
88
95
|
|
89
|
-
# Since we're using the development server as the user GUI, we monkey-patch Flask to disable the warnings that would
|
90
|
-
# otherwise appear for this so they don't confuse the user
|
91
|
-
|
92
96
|
|
93
97
|
def suppress_warning(func: Callable[..., Any]) -> Callable[..., Any]:
|
98
|
+
"""Since we're using the development server as the user GUI, we monkey-patch Flask to disable the warnings that
|
99
|
+
would otherwise appear for this so they don't confuse the user
|
100
|
+
"""
|
94
101
|
@wraps(func)
|
95
102
|
def wrapper(*args, **kwargs) -> Any:
|
96
103
|
if args and isinstance(args[0], str) and args[0].startswith('WARNING: This is a development server.'):
|
@@ -105,6 +112,24 @@ cli.show_server_banner = lambda *_: None
|
|
105
112
|
app = Flask(__name__)
|
106
113
|
|
107
114
|
|
115
|
+
def limit_upload_size():
|
116
|
+
"""Impose a limit on the maximum file that can be uploaded before Flask will raise an error"""
|
117
|
+
|
118
|
+
# Determine the largest possible file size that can be uploaded, keeping in mind that 0 indicates unlimited
|
119
|
+
larger_max_file_size = max_file_size
|
120
|
+
if (max_file_size > 0) and (max_file_size_ob > max_file_size):
|
121
|
+
larger_max_file_size = max_file_size_ob
|
122
|
+
|
123
|
+
if larger_max_file_size > 0:
|
124
|
+
app.config['MAX_CONTENT_LENGTH'] = larger_max_file_size
|
125
|
+
else:
|
126
|
+
app.config['MAX_CONTENT_LENGTH'] = None
|
127
|
+
|
128
|
+
|
129
|
+
# Set the upload limit based on env vars to start with
|
130
|
+
limit_upload_size()
|
131
|
+
|
132
|
+
|
108
133
|
def get_last_sha() -> str:
|
109
134
|
"""Get the SHA of the last commit
|
110
135
|
"""
|
@@ -134,14 +159,13 @@ def get_last_sha() -> str:
|
|
134
159
|
def website():
|
135
160
|
"""Return the web page along with the token
|
136
161
|
"""
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
return render_template("index.htm", data=data)
|
162
|
+
return render_template("index.htm",
|
163
|
+
token=token,
|
164
|
+
max_file_size=max_file_size,
|
165
|
+
max_file_size_ob=max_file_size_ob,
|
166
|
+
service_mode=service_mode,
|
167
|
+
production_mode=production_mode,
|
168
|
+
sha=get_last_sha())
|
145
169
|
|
146
170
|
|
147
171
|
@app.route('/convert/', methods=['POST'])
|
@@ -153,9 +177,8 @@ def convert():
|
|
153
177
|
# Make sure the upload directory exists
|
154
178
|
os.makedirs(const.DEFAULT_UPLOAD_DIR, exist_ok=True)
|
155
179
|
|
156
|
-
# Save the file in the upload directory
|
157
180
|
file = request.files[FILE_TO_UPLOAD_KEY]
|
158
|
-
filename =
|
181
|
+
filename = secure_filename(file.filename)
|
159
182
|
|
160
183
|
qualified_filename = os.path.join(const.DEFAULT_UPLOAD_DIR, filename)
|
161
184
|
file.save(qualified_filename)
|
@@ -248,7 +271,10 @@ def feedback():
|
|
248
271
|
if key in report:
|
249
272
|
entry[key] = str(report[key])
|
250
273
|
|
251
|
-
|
274
|
+
# Write data in JSON format and send to stdout
|
275
|
+
logLock.acquire()
|
276
|
+
sys.stdout.write(f"{json.dumps(entry) + '\n'}")
|
277
|
+
logLock.release()
|
252
278
|
|
253
279
|
return Response(status=201)
|
254
280
|
|
@@ -318,7 +344,7 @@ def start_app():
|
|
318
344
|
"""
|
319
345
|
|
320
346
|
os.chdir(os.path.join(psdi_data_conversion.__path__[0], ".."))
|
321
|
-
app.run()
|
347
|
+
app.run(debug=debug_mode)
|
322
348
|
|
323
349
|
|
324
350
|
def main():
|
@@ -343,7 +369,11 @@ def main():
|
|
343
369
|
help="If set, will run as if deploying a service rather than the local GUI")
|
344
370
|
|
345
371
|
parser.add_argument("--dev-mode", action="store_true",
|
346
|
-
help="If set, will expose development elements")
|
372
|
+
help="If set, will expose development elements, such as the SHA of the latest commit")
|
373
|
+
|
374
|
+
parser.add_argument("--debug", action="store_true",
|
375
|
+
help="If set, will run the Flask server in debug mode, which will cause it to automatically "
|
376
|
+
"reload if code changes and show an interactive debugger in the case of errors")
|
347
377
|
|
348
378
|
parser.add_argument("--log-mode", type=str, default=const.LOG_FULL,
|
349
379
|
help="How logs should be stored. Allowed values are: \n"
|
@@ -371,6 +401,9 @@ def main():
|
|
371
401
|
global service_mode
|
372
402
|
service_mode = args.service_mode
|
373
403
|
|
404
|
+
global debug_mode
|
405
|
+
debug_mode = args.debug
|
406
|
+
|
374
407
|
global production_mode
|
375
408
|
production_mode = not args.dev_mode
|
376
409
|
|
@@ -380,6 +413,9 @@ def main():
|
|
380
413
|
global log_level
|
381
414
|
log_level = args.log_level
|
382
415
|
|
416
|
+
# Set the upload limit based on provided arguments now
|
417
|
+
limit_upload_size()
|
418
|
+
|
383
419
|
print_wrap("Starting the PSDI Data Conversion GUI. This GUI is run as a webpage, which you can open by "
|
384
420
|
"right-clicking the link below to open it in your default browser, or by copy-and-pasting it into your "
|
385
421
|
"browser of choice.")
|
@@ -5,11 +5,16 @@ Created 2024-12-10 by Bryan Gillis.
|
|
5
5
|
Class and functions to perform file conversion
|
6
6
|
"""
|
7
7
|
|
8
|
+
from dataclasses import dataclass, field
|
9
|
+
import json
|
8
10
|
import glob
|
9
11
|
import importlib
|
10
12
|
import os
|
11
13
|
import sys
|
12
14
|
import traceback
|
15
|
+
from typing import Any, Callable, NamedTuple
|
16
|
+
from multiprocessing import Lock
|
17
|
+
from psdi_data_conversion import log_utility
|
13
18
|
from collections.abc import Callable
|
14
19
|
from dataclasses import dataclass, field
|
15
20
|
from tempfile import TemporaryDirectory
|
@@ -20,6 +25,10 @@ from psdi_data_conversion.converters import base
|
|
20
25
|
from psdi_data_conversion.converters.openbabel import CONVERTER_OB
|
21
26
|
from psdi_data_conversion.file_io import (is_archive, is_supported_archive, pack_zip_or_tar, split_archive_ext,
|
22
27
|
unpack_zip_or_tar)
|
28
|
+
from psdi_data_conversion.utils import regularize_name
|
29
|
+
|
30
|
+
# A lock to prevent multiple threads from logging at the same time
|
31
|
+
logLock = Lock()
|
23
32
|
|
24
33
|
# Find all modules for specific converters
|
25
34
|
l_converter_modules = glob.glob(os.path.dirname(base.__file__) + "/*.py")
|
@@ -49,7 +58,8 @@ try:
|
|
49
58
|
|
50
59
|
converter_class = module.converter
|
51
60
|
|
52
|
-
|
61
|
+
# To make querying case/space-insensitive, we store all names in lowercase with spaces stripped
|
62
|
+
name = converter_class.name.lower().replace(" ", "")
|
53
63
|
|
54
64
|
return NameAndClass(name, converter_class)
|
55
65
|
|
@@ -91,6 +101,66 @@ except Exception:
|
|
91
101
|
D_CONVERTER_ARGS = {}
|
92
102
|
|
93
103
|
|
104
|
+
def get_supported_converter_class(name: str):
|
105
|
+
"""Get the appropriate converter class matching the provided name from the dict of supported converters
|
106
|
+
|
107
|
+
Parameters
|
108
|
+
----------
|
109
|
+
name : str
|
110
|
+
Converter name (case- and space-insensitive)
|
111
|
+
|
112
|
+
Returns
|
113
|
+
-------
|
114
|
+
type[base.FileConverter]
|
115
|
+
"""
|
116
|
+
return D_SUPPORTED_CONVERTERS[regularize_name(name)]
|
117
|
+
|
118
|
+
|
119
|
+
def get_registered_converter_class(name: str):
|
120
|
+
"""Get the appropriate converter class matching the provided name from the dict of supported converters
|
121
|
+
|
122
|
+
Parameters
|
123
|
+
----------
|
124
|
+
name : str
|
125
|
+
Converter name (case- and space-insensitive)
|
126
|
+
|
127
|
+
Returns
|
128
|
+
-------
|
129
|
+
type[base.FileConverter]
|
130
|
+
"""
|
131
|
+
return D_REGISTERED_CONVERTERS[regularize_name(name)]
|
132
|
+
|
133
|
+
|
134
|
+
def converter_is_supported(name: str):
|
135
|
+
"""Checks if a converter is supported in principle by this project
|
136
|
+
|
137
|
+
Parameters
|
138
|
+
----------
|
139
|
+
name : str
|
140
|
+
Converter name (case- and space-insensitive)
|
141
|
+
|
142
|
+
Returns
|
143
|
+
-------
|
144
|
+
bool
|
145
|
+
"""
|
146
|
+
return regularize_name(name) in L_SUPPORTED_CONVERTERS
|
147
|
+
|
148
|
+
|
149
|
+
def converter_is_registered(name: str):
|
150
|
+
"""Checks if a converter is registered (usable)
|
151
|
+
|
152
|
+
Parameters
|
153
|
+
----------
|
154
|
+
name : str
|
155
|
+
Converter name (case- and space-insensitive)
|
156
|
+
|
157
|
+
Returns
|
158
|
+
-------
|
159
|
+
bool
|
160
|
+
"""
|
161
|
+
return regularize_name(name) in L_REGISTERED_CONVERTERS
|
162
|
+
|
163
|
+
|
94
164
|
def get_converter(*args, name=const.CONVERTER_DEFAULT, **converter_kwargs) -> base.FileConverter:
|
95
165
|
"""Get a FileConverter of the proper subclass for the requested converter type
|
96
166
|
|
@@ -129,7 +199,7 @@ def get_converter(*args, name=const.CONVERTER_DEFAULT, **converter_kwargs) -> ba
|
|
129
199
|
If provided, all logging will go to a single file or stream. Otherwise, logs will be split up among multiple
|
130
200
|
files for server-style logging.
|
131
201
|
log_mode : str
|
132
|
-
How logs should be
|
202
|
+
How logs should be stored. Allowed values are:
|
133
203
|
- 'full' - Multi-file logging, only recommended when running as a public web app
|
134
204
|
- 'simple' - Logs saved to one file
|
135
205
|
- 'stdout' - Output logs and errors only to stdout
|
@@ -155,10 +225,11 @@ def get_converter(*args, name=const.CONVERTER_DEFAULT, **converter_kwargs) -> ba
|
|
155
225
|
FileConverterInputException
|
156
226
|
If the converter isn't recognized or there's some other issue with the input
|
157
227
|
"""
|
228
|
+
name = regularize_name(name)
|
158
229
|
if name not in L_REGISTERED_CONVERTERS:
|
159
230
|
raise base.FileConverterInputException(const.ERR_CONVERTER_NOT_RECOGNISED.format(name) +
|
160
231
|
f"{L_REGISTERED_CONVERTERS}")
|
161
|
-
converter_class =
|
232
|
+
converter_class = get_registered_converter_class(name)
|
162
233
|
|
163
234
|
return converter_class(*args, **converter_kwargs)
|
164
235
|
|
@@ -302,7 +373,7 @@ def run_converter(filename: str,
|
|
302
373
|
If provided, all logging will go to a single file or stream. Otherwise, logs will be split up among multiple
|
303
374
|
files for server-style logging.
|
304
375
|
log_mode : str
|
305
|
-
How logs should be
|
376
|
+
How logs should be stored. Allowed values are:
|
306
377
|
- 'full' - Multi-file logging, only recommended when running as a public web app
|
307
378
|
- 'simple' - Logs saved to one file
|
308
379
|
- 'stdout' - Output logs and errors only to stdout
|
@@ -355,14 +426,14 @@ def run_converter(filename: str,
|
|
355
426
|
if from_format is not None:
|
356
427
|
check_from_format(filename, from_format, strict=strict)
|
357
428
|
l_run_output.append(get_converter(filename,
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
429
|
+
to_format,
|
430
|
+
*args,
|
431
|
+
from_format=from_format,
|
432
|
+
download_dir=download_dir,
|
433
|
+
max_file_size=max_file_size,
|
434
|
+
log_file=log_file,
|
435
|
+
log_mode=log_mode,
|
436
|
+
**converter_kwargs).run())
|
366
437
|
|
367
438
|
elif not is_supported_archive(filename):
|
368
439
|
raise base.FileConverterInputException(f"{filename} is an unsupported archive type. Supported types are: "
|
@@ -473,4 +544,61 @@ def run_converter(filename: str,
|
|
473
544
|
exception_class = base.FileConverterAbortException
|
474
545
|
raise exception_class(status_code, msg)
|
475
546
|
|
547
|
+
# Log conversion information if in service mode
|
548
|
+
service_mode_ev = os.environ.get(const.SERVICE_MODE_EV)
|
549
|
+
service_mode = (service_mode_ev is not None) and (service_mode_ev.lower() == "true")
|
550
|
+
if service_mode:
|
551
|
+
try:
|
552
|
+
l_index = filename.rfind('/') + 1
|
553
|
+
r_index = len(filename)
|
554
|
+
in_filename = filename[l_index:r_index]
|
555
|
+
|
556
|
+
l_index = run_output.output_filename.rfind('/') + 1
|
557
|
+
r_index = len(run_output.output_filename)
|
558
|
+
|
559
|
+
input_size = set_size_units(run_output.in_size)
|
560
|
+
output_size = set_size_units(run_output.out_size)
|
561
|
+
|
562
|
+
if status_code:
|
563
|
+
outcome = "failed"
|
564
|
+
fail_reason = l_error_lines
|
565
|
+
else:
|
566
|
+
outcome = "succeeded"
|
567
|
+
fail_reason = ""
|
568
|
+
|
569
|
+
entry = {
|
570
|
+
"datetime": log_utility.get_date_time(),
|
571
|
+
"input_format": converter_kwargs['data']['from_full'],
|
572
|
+
"output_format": converter_kwargs['data']['to_full'],
|
573
|
+
"input_filename": in_filename,
|
574
|
+
"output_filename": run_output.output_filename[l_index:r_index],
|
575
|
+
"input_size": input_size,
|
576
|
+
"output_size": output_size }
|
577
|
+
|
578
|
+
for key in [ "converter", "coordinates", "coordOption", "from_flags",
|
579
|
+
"to_flags", "from_arg_flags", "to_arg_flags" ]:
|
580
|
+
if key in converter_kwargs['data'] and converter_kwargs['data'][key] != "" and not \
|
581
|
+
((key == "coordinates" or key == "coordOption") and converter_kwargs['data']['coordinates'] == "neither") :
|
582
|
+
entry[key] = converter_kwargs['data'][key]
|
583
|
+
|
584
|
+
entry["outcome"] = outcome
|
585
|
+
|
586
|
+
if fail_reason != "":
|
587
|
+
entry["fail_reason"] = fail_reason
|
588
|
+
|
589
|
+
logLock.acquire()
|
590
|
+
sys.__stdout__.write(f"{json.dumps(entry) + '\n'}")
|
591
|
+
logLock.release()
|
592
|
+
except Exception:
|
593
|
+
sys.__stdout__.write({"datetime": log_utility.get_date_time(),
|
594
|
+
"logging_error": "An error occurred during logging of conversion information."})
|
595
|
+
|
476
596
|
return run_output
|
597
|
+
|
598
|
+
def set_size_units(size):
|
599
|
+
if size >= 1024:
|
600
|
+
return str('%.3f' % (size / 1024)) + ' kB'
|
601
|
+
elif size >= const.MEGABYTE:
|
602
|
+
return str(size / const.MEGABYTE) + ' MB'
|
603
|
+
else:
|
604
|
+
return str(size) + ' B'
|
psdi_data_conversion/database.py
CHANGED
@@ -15,8 +15,9 @@ from logging import getLogger
|
|
15
15
|
from typing import Any, Literal, overload
|
16
16
|
|
17
17
|
from psdi_data_conversion import constants as const
|
18
|
-
from psdi_data_conversion.converter import
|
18
|
+
from psdi_data_conversion.converter import D_SUPPORTED_CONVERTERS, get_registered_converter_class
|
19
19
|
from psdi_data_conversion.converters.base import FileConverterException
|
20
|
+
from psdi_data_conversion.utils import regularize_name
|
20
21
|
|
21
22
|
# Keys for top-level and general items in the database
|
22
23
|
DB_FORMATS_KEY = "formats"
|
@@ -117,14 +118,14 @@ class ConverterInfo:
|
|
117
118
|
Parameters
|
118
119
|
----------
|
119
120
|
name : str
|
120
|
-
The name of the converter
|
121
|
+
The regularized name of the converter
|
121
122
|
parent : DataConversionDatabase
|
122
123
|
The database which this belongs to
|
123
124
|
d_data : dict[str, Any]
|
124
125
|
The loaded database dict
|
125
126
|
"""
|
126
127
|
|
127
|
-
self.name = name
|
128
|
+
self.name = regularize_name(name)
|
128
129
|
self.parent = parent
|
129
130
|
|
130
131
|
# Get info about the converter from the database
|
@@ -134,7 +135,7 @@ class ConverterInfo:
|
|
134
135
|
|
135
136
|
# Get necessary info about the converter from the class
|
136
137
|
try:
|
137
|
-
self._key_prefix =
|
138
|
+
self._key_prefix = get_registered_converter_class(name).database_key_prefix
|
138
139
|
except KeyError:
|
139
140
|
# We'll get a KeyError for converters in the database that don't yet have their own class, which we can
|
140
141
|
# safely ignore
|
@@ -529,6 +530,10 @@ class ConversionQualityInfo:
|
|
529
530
|
input and output file formats and a note on the implications
|
530
531
|
"""
|
531
532
|
|
533
|
+
def __post_init__(self):
|
534
|
+
"""Regularize the converter name"""
|
535
|
+
self.converter_name = regularize_name(self.converter_name)
|
536
|
+
|
532
537
|
|
533
538
|
class ConversionsTable:
|
534
539
|
"""Class providing information on available file format conversions.
|
@@ -623,7 +628,7 @@ class ConversionsTable:
|
|
623
628
|
|
624
629
|
# Check if this converter deals with ambiguous formats, so we know if we need to be strict about getting format
|
625
630
|
# info
|
626
|
-
if
|
631
|
+
if get_registered_converter_class(converter_name).supports_ambiguous_extensions:
|
627
632
|
which_format = None
|
628
633
|
else:
|
629
634
|
which_format = 0
|
@@ -805,7 +810,7 @@ class DataConversionDatabase:
|
|
805
810
|
if self._d_converter_info is None:
|
806
811
|
self._d_converter_info: dict[str, ConverterInfo] = {}
|
807
812
|
for d_single_converter_info in self.converters:
|
808
|
-
name: str = d_single_converter_info[DB_NAME_KEY]
|
813
|
+
name: str = regularize_name(d_single_converter_info[DB_NAME_KEY])
|
809
814
|
if name in self._d_converter_info:
|
810
815
|
logger.warning(f"Converter '{name}' appears more than once in the database. Only the first instance"
|
811
816
|
" will be used.")
|
@@ -1095,7 +1100,7 @@ def get_converter_info(name: str) -> ConverterInfo:
|
|
1095
1100
|
ConverterInfo
|
1096
1101
|
"""
|
1097
1102
|
|
1098
|
-
return get_database().d_converter_info[name]
|
1103
|
+
return get_database().d_converter_info[regularize_name(name)]
|
1099
1104
|
|
1100
1105
|
|
1101
1106
|
@overload
|
@@ -1155,7 +1160,7 @@ def get_conversion_quality(converter_name: str,
|
|
1155
1160
|
`ConversionQualityInfo` object with info on the conversion
|
1156
1161
|
"""
|
1157
1162
|
|
1158
|
-
return get_database().conversions_table.get_conversion_quality(converter_name=converter_name,
|
1163
|
+
return get_database().conversions_table.get_conversion_quality(converter_name=regularize_name(converter_name),
|
1159
1164
|
in_format=in_format,
|
1160
1165
|
out_format=out_format)
|
1161
1166
|
|
@@ -1210,6 +1215,9 @@ def disambiguate_formats(converter_name: str,
|
|
1210
1215
|
If more than one format combination is possible for this conversion, or no conversion is possible
|
1211
1216
|
"""
|
1212
1217
|
|
1218
|
+
# Regularize the converter name so we don't worry about case/spacing mismatches
|
1219
|
+
converter_name = regularize_name(converter_name)
|
1220
|
+
|
1213
1221
|
# Get all possible conversions, and see if we only have one for this converter
|
1214
1222
|
l_possible_conversions = [x for x in get_possible_conversions(in_format, out_format)
|
1215
1223
|
if x[0] == converter_name]
|
@@ -1243,7 +1251,7 @@ def get_possible_formats(converter_name: str) -> tuple[list[FormatInfo], list[Fo
|
|
1243
1251
|
tuple[list[FormatInfo], list[FormatInfo]]
|
1244
1252
|
A tuple of a list of the supported input formats and a list of the supported output formats
|
1245
1253
|
"""
|
1246
|
-
return get_database().conversions_table.get_possible_formats(converter_name=converter_name)
|
1254
|
+
return get_database().conversions_table.get_possible_formats(converter_name=regularize_name(converter_name))
|
1247
1255
|
|
1248
1256
|
|
1249
1257
|
def _find_arg(tl_args: tuple[list[FlagInfo], list[OptionInfo]],
|
psdi_data_conversion/main.py
CHANGED
@@ -16,8 +16,10 @@ from itertools import product
|
|
16
16
|
|
17
17
|
from psdi_data_conversion import constants as const
|
18
18
|
from psdi_data_conversion.constants import CL_SCRIPT_NAME, CONVERTER_DEFAULT, TERM_WIDTH
|
19
|
-
from psdi_data_conversion.converter import (D_CONVERTER_ARGS,
|
20
|
-
|
19
|
+
from psdi_data_conversion.converter import (D_CONVERTER_ARGS, L_REGISTERED_CONVERTERS, L_SUPPORTED_CONVERTERS,
|
20
|
+
converter_is_registered, converter_is_supported,
|
21
|
+
get_registered_converter_class, get_supported_converter_class,
|
22
|
+
run_converter)
|
21
23
|
from psdi_data_conversion.converters.base import (FileConverterAbortException, FileConverterException,
|
22
24
|
FileConverterInputException)
|
23
25
|
from psdi_data_conversion.database import (FormatInfo, get_conversion_quality, get_converter_info, get_format_info,
|
@@ -25,6 +27,7 @@ from psdi_data_conversion.database import (FormatInfo, get_conversion_quality, g
|
|
25
27
|
get_possible_formats)
|
26
28
|
from psdi_data_conversion.file_io import split_archive_ext
|
27
29
|
from psdi_data_conversion.log_utility import get_log_level_from_str
|
30
|
+
from psdi_data_conversion.utils import regularize_name
|
28
31
|
|
29
32
|
|
30
33
|
def print_wrap(s: str, newline=False, err=False, **kwargs):
|
@@ -58,9 +61,9 @@ class ConvertArgs:
|
|
58
61
|
self._output_dir: str | None = args.out
|
59
62
|
converter_name = getattr(args, "with")
|
60
63
|
if isinstance(converter_name, str):
|
61
|
-
self.name = converter_name
|
64
|
+
self.name = regularize_name(converter_name)
|
62
65
|
elif converter_name:
|
63
|
-
self.name
|
66
|
+
self.name = regularize_name(" ".join(converter_name))
|
64
67
|
else:
|
65
68
|
self.name = None
|
66
69
|
self.delete_input = args.delete_input
|
@@ -109,14 +112,14 @@ class ConvertArgs:
|
|
109
112
|
|
110
113
|
# Get the converter name from the arguments if it wasn't provided by -w/--with
|
111
114
|
if not self.name:
|
112
|
-
self.name = " ".join(self.l_args)
|
115
|
+
self.name = regularize_name(" ".join(self.l_args))
|
113
116
|
|
114
117
|
# For this operation, any other arguments can be ignored
|
115
118
|
return
|
116
119
|
|
117
120
|
# If not listing and a converter name wasn't supplied, use the default converter
|
118
121
|
if not self.name:
|
119
|
-
self.name = CONVERTER_DEFAULT
|
122
|
+
self.name = regularize_name(CONVERTER_DEFAULT)
|
120
123
|
|
121
124
|
# Quiet mode is equivalent to logging mode == LOGGING_NONE, so normalize them if either is set
|
122
125
|
if self.quiet:
|
@@ -147,13 +150,14 @@ class ConvertArgs:
|
|
147
150
|
os.makedirs(self._output_dir, exist_ok=True)
|
148
151
|
|
149
152
|
# Check the converter is recognized
|
150
|
-
if self.name
|
153
|
+
if not converter_is_supported(self.name):
|
151
154
|
msg = textwrap.fill(f"ERROR: Converter '{self.name}' not recognised", width=TERM_WIDTH)
|
152
155
|
msg += f"\n\n{get_supported_converters()}"
|
153
156
|
raise FileConverterInputException(msg, help=True, msg_preformatted=True)
|
154
|
-
elif self.name
|
155
|
-
|
156
|
-
|
157
|
+
elif not converter_is_registered(self.name):
|
158
|
+
converter_name = get_supported_converter_class(self.name).name
|
159
|
+
msg = textwrap.fill(f"ERROR: Converter '{converter_name}' is not registered. It may be possible to "
|
160
|
+
"register it by installing an appropriate binary for your platform.", width=TERM_WIDTH)
|
157
161
|
msg += f"\n\n{get_supported_converters()}"
|
158
162
|
raise FileConverterInputException(msg, help=True, msg_preformatted=True)
|
159
163
|
|
@@ -349,9 +353,10 @@ def detail_converter_use(args: ConvertArgs):
|
|
349
353
|
"""
|
350
354
|
|
351
355
|
converter_info = get_converter_info(args.name)
|
352
|
-
converter_class =
|
356
|
+
converter_class = get_supported_converter_class(args.name)
|
357
|
+
converter_name = converter_class.name
|
353
358
|
|
354
|
-
print_wrap(f"{
|
359
|
+
print_wrap(f"{converter_name}: {converter_info.description} ({converter_info.url})", break_long_words=False,
|
355
360
|
break_on_hyphens=False, newline=True)
|
356
361
|
|
357
362
|
if converter_class.info:
|
@@ -362,10 +367,10 @@ def detail_converter_use(args: ConvertArgs):
|
|
362
367
|
if args.from_format is not None and args.to_format is not None:
|
363
368
|
qual = get_conversion_quality(args.name, args.from_format, args.to_format)
|
364
369
|
if qual is None:
|
365
|
-
print_wrap(f"Conversion from '{args.from_format}' to '{args.to_format}' with {
|
370
|
+
print_wrap(f"Conversion from '{args.from_format}' to '{args.to_format}' with {converter_name} is not "
|
366
371
|
"supported.", newline=True)
|
367
372
|
else:
|
368
|
-
print_wrap(f"Conversion from '{args.from_format}' to '{args.to_format}' with {
|
373
|
+
print_wrap(f"Conversion from '{args.from_format}' to '{args.to_format}' with {converter_name} is "
|
369
374
|
f"possible with {qual.qual_str} conversion quality", newline=True)
|
370
375
|
# If there are any potential issues with the conversion, print them out
|
371
376
|
if qual.details:
|
@@ -385,7 +390,7 @@ def detail_converter_use(args: ConvertArgs):
|
|
385
390
|
optional_not: str = ""
|
386
391
|
else:
|
387
392
|
optional_not: str = "not "
|
388
|
-
print_wrap(f"Conversion {to_or_from} {format_name} is {optional_not}supported by {
|
393
|
+
print_wrap(f"Conversion {to_or_from} {format_name} is {optional_not}supported by {converter_name}.\n")
|
389
394
|
|
390
395
|
# List all possible formats, and which can be used for input and which for output
|
391
396
|
s_all_formats: set[FormatInfo] = set(l_input_formats)
|
@@ -393,7 +398,7 @@ def detail_converter_use(args: ConvertArgs):
|
|
393
398
|
l_all_formats: list[FormatInfo] = list(s_all_formats)
|
394
399
|
l_all_formats.sort(key=lambda x: x.disambiguated_name.lower())
|
395
400
|
|
396
|
-
print_wrap(f"File formats supported by {
|
401
|
+
print_wrap(f"File formats supported by {converter_name}:", newline=True)
|
397
402
|
max_format_length = max([len(x.disambiguated_name) for x in l_all_formats])
|
398
403
|
print(" "*(max_format_length+4) + " INPUT OUTPUT")
|
399
404
|
print(" "*(max_format_length+4) + " ----- ------")
|
@@ -472,13 +477,13 @@ def detail_converter_use(args: ConvertArgs):
|
|
472
477
|
# Now at the end, bring up input/output-format-specific flags and options
|
473
478
|
if mention_input_format and mention_output_format:
|
474
479
|
print_wrap("For details on input/output flags and options allowed for specific formats, call:\n"
|
475
|
-
f"{CL_SCRIPT_NAME} -l {
|
480
|
+
f"{CL_SCRIPT_NAME} -l {converter_name} -f <input_format> -t <output_format>")
|
476
481
|
elif mention_input_format:
|
477
482
|
print_wrap("For details on input flags and options allowed for a specific format, call:\n"
|
478
|
-
f"{CL_SCRIPT_NAME} -l {
|
483
|
+
f"{CL_SCRIPT_NAME} -l {converter_name} -f <input_format> [-t <output_format>]")
|
479
484
|
elif mention_output_format:
|
480
485
|
print_wrap("For details on output flags and options allowed for a specific format, call:\n"
|
481
|
-
f"{CL_SCRIPT_NAME} -l {
|
486
|
+
f"{CL_SCRIPT_NAME} -l {converter_name} -t <output_format> [-f <input_format>]")
|
482
487
|
|
483
488
|
|
484
489
|
def list_supported_formats(err=False):
|
@@ -600,9 +605,11 @@ def detail_formats_and_possible_converters(from_format: str, to_format: str):
|
|
600
605
|
l_conversions_matching_formats = [x for x in l_possible_conversions
|
601
606
|
if x[1] == possible_from_format and x[2] == possible_to_format]
|
602
607
|
|
603
|
-
l_possible_registered_converters = [x[0]
|
608
|
+
l_possible_registered_converters = [get_registered_converter_class(x[0]).name
|
609
|
+
for x in l_conversions_matching_formats
|
604
610
|
if x[0] in L_REGISTERED_CONVERTERS]
|
605
|
-
l_possible_unregistered_converters = [x[0]
|
611
|
+
l_possible_unregistered_converters = [get_supported_converter_class(x[0]).name
|
612
|
+
for x in l_conversions_matching_formats
|
606
613
|
if x[0] in L_SUPPORTED_CONVERTERS and x[0] not in L_REGISTERED_CONVERTERS]
|
607
614
|
|
608
615
|
print()
|
@@ -640,7 +647,7 @@ def get_supported_converters():
|
|
640
647
|
l_converters: list[str] = []
|
641
648
|
any_not_registered = False
|
642
649
|
for converter_name in L_SUPPORTED_CONVERTERS:
|
643
|
-
converter_text = converter_name
|
650
|
+
converter_text = get_supported_converter_class(converter_name).name
|
644
651
|
if converter_name not in L_REGISTERED_CONVERTERS:
|
645
652
|
converter_text += f" {MSG_NOT_REGISTERED}"
|
646
653
|
any_not_registered = True
|
@@ -48,7 +48,7 @@
|
|
48
48
|
<hr>
|
49
49
|
</div>
|
50
50
|
<ul class="footer__col footer__items clean-list">
|
51
|
-
<li><a href="https://
|
51
|
+
<li><a href="https://psdi.ac.uk/">PSDI Home</a></li>
|
52
52
|
<li><a href="mailto:support@psdi.ac.uk">Contact Us</a></li>
|
53
53
|
<li><a href="https://www.psdi.ac.uk/privacy/">Privacy</a></li>
|
54
54
|
<li><a href="https://www.psdi.ac.uk/terms-and-conditions/">Terms and Conditions</a></li>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
<div class="max-width-box navbar">
|
5
5
|
<div class="header-left">
|
6
6
|
<div class="navbar__brand">
|
7
|
-
<a class="navbar__logo" href="https://
|
7
|
+
<a class="navbar__logo" href="https://psdi.ac.uk/">
|
8
8
|
<img src="static/img/psdi-logo-darktext.png" alt="PSDI logo"
|
9
9
|
class="lm-only">
|
10
10
|
<img src="static/img/psdi-logo-lighttext.png" alt="PSDI logo"
|
@@ -48,7 +48,7 @@
|
|
48
48
|
<hr>
|
49
49
|
</div>
|
50
50
|
<ul class="footer__col footer__items clean-list">
|
51
|
-
<li><a href="https://
|
51
|
+
<li><a href="https://psdi.ac.uk/">PSDI Home</a></li>
|
52
52
|
<li><a href="mailto:support@psdi.ac.uk">Contact Us</a></li>
|
53
53
|
<li><a href="https://www.psdi.ac.uk/privacy/">Privacy</a></li>
|
54
54
|
<li><a href="https://www.psdi.ac.uk/terms-and-conditions/">Terms and Conditions</a></li>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
<div class="max-width-box navbar">
|
5
5
|
<div class="header-left">
|
6
6
|
<div class="navbar__brand">
|
7
|
-
<a class="navbar__logo" href="https://
|
7
|
+
<a class="navbar__logo" href="https://psdi.ac.uk/">
|
8
8
|
<img src="../img/psdi-logo-darktext.png" alt="PSDI logo"
|
9
9
|
class="lm-only">
|
10
10
|
<img src="../img/psdi-logo-lighttext.png" alt="PSDI logo"
|
@@ -20,13 +20,12 @@
|
|
20
20
|
crossorigin="anonymous"></script>
|
21
21
|
<script src="https://cdn.jsdelivr.net/jquery.dirtyforms/2.0.0/jquery.dirtyforms.min.js"></script>
|
22
22
|
|
23
|
-
{% for item in data %}
|
24
23
|
<script>
|
25
|
-
const token = "{{
|
26
|
-
const max_file_size = "{{
|
27
|
-
const max_file_size_ob = "{{
|
28
|
-
const service_mode = "{{
|
29
|
-
const production_mode = "{{
|
24
|
+
const token = "{{token}}";
|
25
|
+
const max_file_size = "{{max_file_size}}";
|
26
|
+
const max_file_size_ob = "{{max_file_size_ob}}";
|
27
|
+
const service_mode = "{{service_mode}}";
|
28
|
+
const production_mode = "{{production_mode}}";
|
30
29
|
document.documentElement.setAttribute("service-mode", service_mode);
|
31
30
|
document.documentElement.setAttribute("production-mode", production_mode);
|
32
31
|
</script>
|
@@ -121,11 +120,11 @@
|
|
121
120
|
<div class="medGap"></div>
|
122
121
|
</div>
|
123
122
|
</form>
|
124
|
-
|
123
|
+
{% if sha %}
|
125
124
|
<div class="secondary prod-only">
|
126
|
-
<div class="max-width-box">SHA: {{
|
125
|
+
<div class="max-width-box">SHA: {{ sha }}</div>
|
127
126
|
</div>
|
128
|
-
{%
|
127
|
+
{% endif %}
|
129
128
|
<script src="{{url_for('static', filename='/javascript/format.js')}}" type="module" language="JavaScript"></script>
|
130
129
|
|
131
130
|
<footer class="footer" id="psdi-footer"></footer>
|
@@ -30,7 +30,9 @@ l_all_test_specs.append(Spec(name="Standard Single Test",
|
|
30
30
|
CheckLogContentsSuccess(),
|
31
31
|
MatchOutputFile("standard_test.inchi")),
|
32
32
|
))
|
33
|
+
"""A quick single test, functioning mostly as a smoke test for things going right in the simplest case"""
|
33
34
|
|
35
|
+
simple_success_callback = MCB(CheckFileStatus(), CheckLogContentsSuccess())
|
34
36
|
l_all_test_specs.append(Spec(name="Standard Multiple Tests",
|
35
37
|
filename=["1NE6.mmcif",
|
36
38
|
"hemoglobin.pdb", "aceticacid.mol", "nacl.cif",
|
@@ -52,8 +54,7 @@ l_all_test_specs.append(Spec(name="Standard Multiple Tests",
|
|
52
54
|
CONVERTER_ATO, CONVERTER_ATO, CONVERTER_ATO,
|
53
55
|
CONVERTER_C2X, CONVERTER_C2X, CONVERTER_C2X,
|
54
56
|
CONVERTER_OB],
|
55
|
-
callback=
|
56
|
-
CheckLogContentsSuccess()),
|
57
|
+
callback=simple_success_callback,
|
57
58
|
))
|
58
59
|
"""A basic set of test conversions which we expect to succeed without issue, running conversions with each of the
|
59
60
|
Open Babel, Atomsk, and c2x converters"""
|
@@ -61,14 +62,21 @@ Open Babel, Atomsk, and c2x converters"""
|
|
61
62
|
l_all_test_specs.append(Spec(name="c2x Formats Tests",
|
62
63
|
to_format=["res", "abi", "POSCAR", "cml"],
|
63
64
|
converter_name=CONVERTER_C2X,
|
64
|
-
callback=
|
65
|
-
CheckLogContentsSuccess()),
|
65
|
+
callback=simple_success_callback,
|
66
66
|
compatible_with_gui=False,
|
67
67
|
))
|
68
68
|
"""Test converting with c2x to a few different formats which require special input. This test isn't run in the GUI
|
69
69
|
solely to save on resources, since there are unlikely to be an GUI-specific issues raised by this test that aren't
|
70
70
|
caught in others."""
|
71
71
|
|
72
|
+
l_all_test_specs.append(Spec(name="Converter Name Sensitivity Tests",
|
73
|
+
converter_name=["open babel", "oPeNbaBEL", "C2X", "atomsk"],
|
74
|
+
to_format="xyz-0",
|
75
|
+
callback=simple_success_callback,
|
76
|
+
compatible_with_gui=False,
|
77
|
+
))
|
78
|
+
"""Tests that converters can be specified case- and space-insensitively in the library and CLI"""
|
79
|
+
|
72
80
|
archive_callback = MCB(CheckFileStatus(),
|
73
81
|
CheckArchiveContents(l_filename_bases=["caffeine-no-flags",
|
74
82
|
"caffeine-ia",
|
@@ -0,0 +1,21 @@
|
|
1
|
+
"""
|
2
|
+
# utils.py
|
3
|
+
|
4
|
+
Miscellaneous utility functions used by this project
|
5
|
+
"""
|
6
|
+
|
7
|
+
|
8
|
+
def regularize_name(name: str):
|
9
|
+
"""Regularizes a name for comparisons, making it lowercase and stripping spaces
|
10
|
+
|
11
|
+
Parameters
|
12
|
+
----------
|
13
|
+
name : str
|
14
|
+
The name, e.g. "Open Babel"
|
15
|
+
|
16
|
+
Returns
|
17
|
+
-------
|
18
|
+
str
|
19
|
+
The regularized name, e.g. "openbabel"
|
20
|
+
"""
|
21
|
+
return name.lower().replace(" ", "")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: psdi_data_conversion
|
3
|
-
Version: 0.0
|
3
|
+
Version: 0.1.0
|
4
4
|
Summary: Chemistry file format conversion service, provided by PSDI
|
5
5
|
Project-URL: Homepage, https://data-conversion.psdi.ac.uk/
|
6
6
|
Project-URL: Documentation, https://psdi-uk.github.io/psdi-data-conversion/
|
@@ -243,7 +243,7 @@ Description-Content-Type: text/markdown
|
|
243
243
|
|
244
244
|
# PSDI Data Conversion
|
245
245
|
|
246
|
-
|
246
|
+
Release date: 2024-04-29
|
247
247
|
|
248
248
|
This is the repository for the PSDI PF2 Chemistry File Format Conversion project. The goal of this project is to provide utilities to assist in converting files between the many different file formats used in chemistry, providing information on what converters are available for a given conversion and the expected quality of it, and providing multiple interfaces to perform these conversions. These interfaces are:
|
249
249
|
|
@@ -1,13 +1,14 @@
|
|
1
1
|
psdi_data_conversion/__init__.py,sha256=urMsTqsTHTch1q4rMT9dgGnrvdPFMP9B8r-6Kr8H5sE,404
|
2
|
-
psdi_data_conversion/app.py,sha256=
|
3
|
-
psdi_data_conversion/constants.py,sha256=
|
4
|
-
psdi_data_conversion/converter.py,sha256=
|
5
|
-
psdi_data_conversion/database.py,sha256=
|
2
|
+
psdi_data_conversion/app.py,sha256=DCY5TLLYdCj-r9GH-SKXvwtWm9AcQJ0SJbCsefl3K94,15738
|
3
|
+
psdi_data_conversion/constants.py,sha256=Hq2OVbcSkcv6T87-YJlo1PVlr9ILlB4H3E9JYjzvCF4,7423
|
4
|
+
psdi_data_conversion/converter.py,sha256=Y77mqH2OKxf2YelphWDl82AKoRa-APle-l3e8wG_WZA,27315
|
5
|
+
psdi_data_conversion/database.py,sha256=XzKvtT-cFmKLWLo5OBB5CV_rGHuqEEuENNyZnbS39qE,56419
|
6
6
|
psdi_data_conversion/dist.py,sha256=LOcKEP7H7JA9teX1m-5awuBi69gmdhtUit7yxtCTOZ8,2293
|
7
7
|
psdi_data_conversion/file_io.py,sha256=LvdPmnYL_7Xlcr-7LJjUbbky4gKiqTTvPRzdbtvQaJo,8794
|
8
8
|
psdi_data_conversion/log_utility.py,sha256=CHAq-JvBnTKaE0SHK5hM5j2dTbfSli4iUc3hsf6dBhc,8789
|
9
|
-
psdi_data_conversion/main.py,sha256=
|
9
|
+
psdi_data_conversion/main.py,sha256=9Gu3CxbUfMuDxyNBx_-kQwWB4eOTWRHGmxCsVnpdbvs,42421
|
10
10
|
psdi_data_conversion/security.py,sha256=wjdrMre29TpkF2NqrsXJ5sschSAnDzqLYTLUcNR21Qw,902
|
11
|
+
psdi_data_conversion/utils.py,sha256=iTjNfrD4n_hU9h20ldYrX2Bmp5KhCBIeMSUMLtPZ_8k,402
|
11
12
|
psdi_data_conversion/bin/LICENSE_ATOMSK,sha256=-Ay6SFTAf9x-OaRAiOgMNoutfUMLHx5jQQA1HqZ6p7I,34886
|
12
13
|
psdi_data_conversion/bin/LICENSE_C2X,sha256=-Ay6SFTAf9x-OaRAiOgMNoutfUMLHx5jQQA1HqZ6p7I,34886
|
13
14
|
psdi_data_conversion/bin/linux/atomsk,sha256=GDsG1MlEvmk_XPspadzEzuil6N775iewDvNZS6rWJWk,34104032
|
@@ -29,12 +30,12 @@ psdi_data_conversion/static/content/documentation.htm,sha256=1GiEjlDCP0kJ3CKkx3l
|
|
29
30
|
psdi_data_conversion/static/content/download.htm,sha256=DQKWEuq_Bjv2TyV6DnPXnHrSOBvGYRHOU-m6YOwfsh4,4727
|
30
31
|
psdi_data_conversion/static/content/feedback.htm,sha256=fZrhn4Egs9g6ygqPzV6ZG9ndYK02VdALNVNXRsZ716k,1788
|
31
32
|
psdi_data_conversion/static/content/header-links.html,sha256=7B2bHa7dLfSZ4hPMvVJ3kR0niVAOO3FtHJo0K6m1oP4,693
|
32
|
-
psdi_data_conversion/static/content/psdi-common-footer.html,sha256=
|
33
|
-
psdi_data_conversion/static/content/psdi-common-header.html,sha256=
|
33
|
+
psdi_data_conversion/static/content/psdi-common-footer.html,sha256=3_KqTtwtkvCvi52LcRrN7oVfMfFdHqrHc39gMrYzGxg,4070
|
34
|
+
psdi_data_conversion/static/content/psdi-common-header.html,sha256=2upXPOZ-EM6Bv7ltPJnynPBjYz4Z27sNl4LuUaVdVjM,1815
|
34
35
|
psdi_data_conversion/static/content/report.htm,sha256=CRwlF7a7QvAjHsajuehWOtLxbo-JAjqrS_Q-I4BWxzs,4549
|
35
36
|
psdi_data_conversion/static/content/index-versions/header-links.html,sha256=WN9oZL7hLtH_yv5PvX3Ky7_YGrNvQL_RQ5eU8DB50Rg,764
|
36
|
-
psdi_data_conversion/static/content/index-versions/psdi-common-footer.html,sha256=
|
37
|
-
psdi_data_conversion/static/content/index-versions/psdi-common-header.html,sha256=
|
37
|
+
psdi_data_conversion/static/content/index-versions/psdi-common-footer.html,sha256=APBMI9c5-4fNV-CCYEs9ySgAahc7u1w-0e3EjM1MiD8,4126
|
38
|
+
psdi_data_conversion/static/content/index-versions/psdi-common-header.html,sha256=w6Q_uu0W4MdioVoh6w0Cy4m2ACr7xAnampx1TfPvT9M,1897
|
38
39
|
psdi_data_conversion/static/data/data.json,sha256=1nljosxtwbLRfIIIa6-GHJnhvzhB759oEGvVQ18QP_4,3647095
|
39
40
|
psdi_data_conversion/static/img/colormode-toggle-dm.svg,sha256=Q85ODwU67chZ77lyT9gITtnmqzJEycFmz35dJuqaPXE,502
|
40
41
|
psdi_data_conversion/static/img/colormode-toggle-lm.svg,sha256=sIKXsNmLIXU4fSuuqrN0r-J4Hd3NIqoiXNT3mdq5-Fo,1155
|
@@ -73,15 +74,15 @@ psdi_data_conversion/static/javascript/psdi-common.js,sha256=I0QqGQ7l_rA4KEfenQT
|
|
73
74
|
psdi_data_conversion/static/javascript/report.js,sha256=BHH5UOhXJtB6J_xk_y6woquNKt5W9hCrQapxKtGG1eA,12470
|
74
75
|
psdi_data_conversion/static/styles/format.css,sha256=PaQkUVxQfXI9nbJ-7YsN1tNIcLXfwXk8wJC-zht8nRA,3269
|
75
76
|
psdi_data_conversion/static/styles/psdi-common.css,sha256=09VY-lldoZCrohuqPKnd9fvDget5g9ybi6uh13pYeY0,17249
|
76
|
-
psdi_data_conversion/templates/index.htm,sha256=
|
77
|
+
psdi_data_conversion/templates/index.htm,sha256=oZEJNc1aenHExnUZMv6XJfn7nibtYevGGpOaeRbIZ2Y,6784
|
77
78
|
psdi_data_conversion/testing/__init__.py,sha256=Xku7drtLTYLLPsd403eC0LIEa_iohVifyeyAITy2w7U,135
|
78
79
|
psdi_data_conversion/testing/constants.py,sha256=BtIafruSobZ9cFY0VW5Bu209eiftnN8b3ObouZBrFQU,521
|
79
80
|
psdi_data_conversion/testing/conversion_callbacks.py,sha256=ATR-_BsYCUN8KyOyUjfdWCELzySxLN5jOI0JyrQnmHQ,18858
|
80
|
-
psdi_data_conversion/testing/conversion_test_specs.py,sha256=
|
81
|
+
psdi_data_conversion/testing/conversion_test_specs.py,sha256=8W97tI6dVbHE9BEW76dsKDlfsm5oTlrlntG--b0h8HU,26106
|
81
82
|
psdi_data_conversion/testing/gui.py,sha256=ul7ixYANIzmOG2ZNOZmQO6wsHmGHdiBGAlw-KuoN0j8,19085
|
82
83
|
psdi_data_conversion/testing/utils.py,sha256=YrFxjyiIx1seph0j7jCUgAVm6HvXY9QJjx0MvNJRbfw,26134
|
83
|
-
psdi_data_conversion-0.0.
|
84
|
-
psdi_data_conversion-0.0.
|
85
|
-
psdi_data_conversion-0.0.
|
86
|
-
psdi_data_conversion-0.0.
|
87
|
-
psdi_data_conversion-0.0.
|
84
|
+
psdi_data_conversion-0.1.0.dist-info/METADATA,sha256=mD4CvjwfelhGA25tGlYa2XUqQyzwycNCod3r7SD052Q,48108
|
85
|
+
psdi_data_conversion-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
86
|
+
psdi_data_conversion-0.1.0.dist-info/entry_points.txt,sha256=xL7XTzaPRr2E67WhOD1M1Q-76hB8ausQlnNiHzuZQPA,123
|
87
|
+
psdi_data_conversion-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
88
|
+
psdi_data_conversion-0.1.0.dist-info/RECORD,,
|
File without changes
|
{psdi_data_conversion-0.0.39.dist-info → psdi_data_conversion-0.1.0.dist-info}/entry_points.txt
RENAMED
File without changes
|
{psdi_data_conversion-0.0.39.dist-info → psdi_data_conversion-0.1.0.dist-info}/licenses/LICENSE
RENAMED
File without changes
|