psdi-data-conversion 0.0.23__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/__init__.py +11 -0
- psdi_data_conversion/app.py +242 -0
- psdi_data_conversion/bin/linux/atomsk +0 -0
- psdi_data_conversion/bin/linux/c2x +0 -0
- psdi_data_conversion/bin/mac/atomsk +0 -0
- psdi_data_conversion/bin/mac/c2x +0 -0
- psdi_data_conversion/constants.py +185 -0
- psdi_data_conversion/converter.py +459 -0
- psdi_data_conversion/converters/__init__.py +6 -0
- psdi_data_conversion/converters/atomsk.py +32 -0
- psdi_data_conversion/converters/base.py +702 -0
- psdi_data_conversion/converters/c2x.py +32 -0
- psdi_data_conversion/converters/openbabel.py +239 -0
- psdi_data_conversion/database.py +1064 -0
- psdi_data_conversion/dist.py +87 -0
- psdi_data_conversion/file_io.py +216 -0
- psdi_data_conversion/log_utility.py +241 -0
- psdi_data_conversion/main.py +776 -0
- psdi_data_conversion/scripts/atomsk.sh +32 -0
- psdi_data_conversion/scripts/c2x.sh +26 -0
- psdi_data_conversion/security.py +38 -0
- psdi_data_conversion/static/content/accessibility.htm +254 -0
- psdi_data_conversion/static/content/convert.htm +121 -0
- psdi_data_conversion/static/content/convertato.htm +65 -0
- psdi_data_conversion/static/content/convertc2x.htm +65 -0
- psdi_data_conversion/static/content/documentation.htm +94 -0
- psdi_data_conversion/static/content/feedback.htm +53 -0
- psdi_data_conversion/static/content/header-links.html +8 -0
- psdi_data_conversion/static/content/index-versions/header-links.html +8 -0
- psdi_data_conversion/static/content/index-versions/psdi-common-footer.html +99 -0
- psdi_data_conversion/static/content/index-versions/psdi-common-header.html +28 -0
- psdi_data_conversion/static/content/psdi-common-footer.html +99 -0
- psdi_data_conversion/static/content/psdi-common-header.html +28 -0
- psdi_data_conversion/static/content/report.htm +103 -0
- psdi_data_conversion/static/data/data.json +143940 -0
- psdi_data_conversion/static/img/colormode-toggle-dm.svg +3 -0
- psdi_data_conversion/static/img/colormode-toggle-lm.svg +3 -0
- psdi_data_conversion/static/img/psdi-icon-dark.svg +136 -0
- psdi_data_conversion/static/img/psdi-icon-light.svg +208 -0
- psdi_data_conversion/static/img/psdi-logo-darktext.png +0 -0
- psdi_data_conversion/static/img/psdi-logo-lighttext.png +0 -0
- psdi_data_conversion/static/img/social-logo-bluesky-black.svg +4 -0
- psdi_data_conversion/static/img/social-logo-bluesky-white.svg +4 -0
- psdi_data_conversion/static/img/social-logo-instagram-black.svg +1 -0
- psdi_data_conversion/static/img/social-logo-instagram-white.svg +1 -0
- psdi_data_conversion/static/img/social-logo-linkedin-black.png +0 -0
- psdi_data_conversion/static/img/social-logo-linkedin-white.png +0 -0
- psdi_data_conversion/static/img/social-logo-mastodon-black.svg +4 -0
- psdi_data_conversion/static/img/social-logo-mastodon-white.svg +4 -0
- psdi_data_conversion/static/img/social-logo-x-black.svg +3 -0
- psdi_data_conversion/static/img/social-logo-x-white.svg +3 -0
- psdi_data_conversion/static/img/social-logo-youtube-black.png +0 -0
- psdi_data_conversion/static/img/social-logo-youtube-white.png +0 -0
- psdi_data_conversion/static/img/ukri-epsr-logo-darktext.png +0 -0
- psdi_data_conversion/static/img/ukri-epsr-logo-lighttext.png +0 -0
- psdi_data_conversion/static/img/ukri-logo-darktext.png +0 -0
- psdi_data_conversion/static/img/ukri-logo-lighttext.png +0 -0
- psdi_data_conversion/static/javascript/accessibility.js +196 -0
- psdi_data_conversion/static/javascript/common.js +42 -0
- psdi_data_conversion/static/javascript/convert.js +296 -0
- psdi_data_conversion/static/javascript/convert_common.js +252 -0
- psdi_data_conversion/static/javascript/convertato.js +107 -0
- psdi_data_conversion/static/javascript/convertc2x.js +107 -0
- psdi_data_conversion/static/javascript/data.js +176 -0
- psdi_data_conversion/static/javascript/format.js +611 -0
- psdi_data_conversion/static/javascript/load_accessibility.js +89 -0
- psdi_data_conversion/static/javascript/psdi-common.js +177 -0
- psdi_data_conversion/static/javascript/report.js +381 -0
- psdi_data_conversion/static/styles/format.css +147 -0
- psdi_data_conversion/static/styles/psdi-common.css +705 -0
- psdi_data_conversion/templates/index.htm +114 -0
- psdi_data_conversion/testing/__init__.py +5 -0
- psdi_data_conversion/testing/constants.py +12 -0
- psdi_data_conversion/testing/conversion_callbacks.py +394 -0
- psdi_data_conversion/testing/conversion_test_specs.py +208 -0
- psdi_data_conversion/testing/utils.py +522 -0
- psdi_data_conversion-0.0.23.dist-info/METADATA +663 -0
- psdi_data_conversion-0.0.23.dist-info/RECORD +81 -0
- psdi_data_conversion-0.0.23.dist-info/WHEEL +4 -0
- psdi_data_conversion-0.0.23.dist-info/entry_points.txt +2 -0
- psdi_data_conversion-0.0.23.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
"""
|
2
|
+
# PSDI Data Conversion package
|
3
|
+
|
4
|
+
This is the primary package for the PSDI Data Conversion python package, including all library code and executable
|
5
|
+
scripts used for it.
|
6
|
+
|
7
|
+
The README for this project can be found either packaged alongside it or online at
|
8
|
+
https://github.com/PSDI-UK/psdi-data-conversion/blob/main/README.md
|
9
|
+
|
10
|
+
The documentation for this package is still WIP as we prepare for release.
|
11
|
+
"""
|
@@ -0,0 +1,242 @@
|
|
1
|
+
"""app.py
|
2
|
+
|
3
|
+
Version 1.0, 8th November 2024
|
4
|
+
|
5
|
+
This script acts as a server for the PSDI Data Conversion Service website.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import hashlib
|
9
|
+
import os
|
10
|
+
import json
|
11
|
+
from datetime import datetime
|
12
|
+
from subprocess import run
|
13
|
+
import sys
|
14
|
+
import traceback
|
15
|
+
from flask import Flask, request, render_template, abort, Response
|
16
|
+
|
17
|
+
from psdi_data_conversion import log_utility
|
18
|
+
from psdi_data_conversion import constants as const
|
19
|
+
from psdi_data_conversion.converter import run_converter
|
20
|
+
from psdi_data_conversion.file_io import split_archive_ext
|
21
|
+
|
22
|
+
# Env var for the SHA of the latest commit
|
23
|
+
SHA_EV = "SHA"
|
24
|
+
|
25
|
+
# Env var for whether this is running in service mode or locally
|
26
|
+
SERVICE_MODE_EV = "SERVICE_MODE"
|
27
|
+
|
28
|
+
# Env var for whether this is a production release or development
|
29
|
+
PRODUCTION_EV = "PRODUCTION_MODE"
|
30
|
+
|
31
|
+
# Key for the label given to the file uploaded in the web interface
|
32
|
+
FILE_TO_UPLOAD_KEY = 'fileToUpload'
|
33
|
+
|
34
|
+
# Create a token by hashing the current date and time.
|
35
|
+
dt = str(datetime.now())
|
36
|
+
token = hashlib.md5(dt.encode('utf8')).hexdigest()
|
37
|
+
|
38
|
+
# Get the service and production modes from their envvars
|
39
|
+
service_mode_ev = os.environ.get(SERVICE_MODE_EV)
|
40
|
+
service_mode = (service_mode_ev is not None) and (service_mode_ev.lower() == "true")
|
41
|
+
production_mode_ev = os.environ.get(PRODUCTION_EV)
|
42
|
+
production_mode = (production_mode_ev is not None) and (production_mode_ev.lower() == "true")
|
43
|
+
|
44
|
+
# Get the logging mode and level from their envvars
|
45
|
+
ev_log_mode = os.environ.get(const.LOG_MODE_EV)
|
46
|
+
if ev_log_mode is None:
|
47
|
+
log_mode = const.LOG_MODE_DEFAULT
|
48
|
+
else:
|
49
|
+
ev_log_mode = ev_log_mode.lower()
|
50
|
+
if ev_log_mode not in const.L_ALLOWED_LOG_MODES:
|
51
|
+
print(f"ERROR: Unrecognised logging option: {ev_log_mode}. Allowed options are: {const.L_ALLOWED_LOG_MODES}",
|
52
|
+
file=sys.stderr)
|
53
|
+
exit(1)
|
54
|
+
log_mode = ev_log_mode
|
55
|
+
|
56
|
+
ev_log_level = os.environ.get(const.LOG_LEVEL_EV)
|
57
|
+
if ev_log_level is None:
|
58
|
+
log_level = None
|
59
|
+
else:
|
60
|
+
try:
|
61
|
+
log_level = log_utility.get_log_level_from_str(ev_log_level)
|
62
|
+
except ValueError as e:
|
63
|
+
print(f"ERROR: {str(e)}")
|
64
|
+
exit(1)
|
65
|
+
|
66
|
+
app = Flask(__name__)
|
67
|
+
|
68
|
+
|
69
|
+
def get_last_sha() -> str:
|
70
|
+
"""Get the SHA of the last commit
|
71
|
+
"""
|
72
|
+
|
73
|
+
# First check if the SHA is provided through an environmental variable
|
74
|
+
ev_sha = os.environ.get(SHA_EV)
|
75
|
+
if ev_sha:
|
76
|
+
return ev_sha
|
77
|
+
|
78
|
+
try:
|
79
|
+
# This bash command calls `git log` to get info on the last commit, uses `head` to trim it to one line, then
|
80
|
+
# uses `gawk` to get just the second word of this line, which is the SHA of this commit
|
81
|
+
cmd = "git log -n 1 | head -n 1 | gawk '{print($2)}'"
|
82
|
+
|
83
|
+
out_bytes = run(cmd, shell=True, capture_output=True).stdout
|
84
|
+
out_str = str(out_bytes.decode()).strip()
|
85
|
+
|
86
|
+
except Exception:
|
87
|
+
print("ERROR: Could not determine SHA of most recent commit. Error was:\n" + traceback.format_exc(),
|
88
|
+
file=sys.stderr)
|
89
|
+
out_str = "N/A"
|
90
|
+
|
91
|
+
return out_str
|
92
|
+
|
93
|
+
|
94
|
+
@app.route('/')
|
95
|
+
def website():
|
96
|
+
"""Return the web page along with the token
|
97
|
+
"""
|
98
|
+
# Get the maximum allowed size from the envvar for it
|
99
|
+
ev_max_file_size = os.environ.get(const.MAX_FILESIZE_EV)
|
100
|
+
if ev_max_file_size is not None:
|
101
|
+
max_file_size = float(ev_max_file_size)*const.MEGABYTE
|
102
|
+
else:
|
103
|
+
max_file_size = const.DEFAULT_MAX_FILE_SIZE
|
104
|
+
|
105
|
+
data = [{'token': token,
|
106
|
+
'max_file_size': max_file_size,
|
107
|
+
'service_mode': service_mode,
|
108
|
+
'production_mode': production_mode,
|
109
|
+
'sha': get_last_sha()}]
|
110
|
+
return render_template("index.htm", data=data)
|
111
|
+
|
112
|
+
|
113
|
+
@app.route('/convert/', methods=['POST'])
|
114
|
+
def convert():
|
115
|
+
"""Convert file to a different format and save to folder 'downloads'. Delete original file. Note that downloading is
|
116
|
+
achieved in format.js
|
117
|
+
"""
|
118
|
+
|
119
|
+
# Make sure the upload directory exists
|
120
|
+
os.makedirs(const.DEFAULT_UPLOAD_DIR, exist_ok=True)
|
121
|
+
|
122
|
+
# Save the file in the upload directory
|
123
|
+
file = request.files[FILE_TO_UPLOAD_KEY]
|
124
|
+
filename = filename = file.filename
|
125
|
+
|
126
|
+
qualified_filename = os.path.join(const.DEFAULT_UPLOAD_DIR, filename)
|
127
|
+
file.save(qualified_filename)
|
128
|
+
qualified_output_log = os.path.join(const.DEFAULT_DOWNLOAD_DIR,
|
129
|
+
split_archive_ext(filename)[0] + const.OUTPUT_LOG_EXT)
|
130
|
+
|
131
|
+
if (not service_mode) or (request.form['token'] == token and token != ''):
|
132
|
+
try:
|
133
|
+
conversion_output = run_converter(name=request.form['converter'],
|
134
|
+
filename=qualified_filename,
|
135
|
+
data=request.form,
|
136
|
+
to_format=request.form['to'],
|
137
|
+
from_format=request.form['from'],
|
138
|
+
strict=request.form['check_ext'],
|
139
|
+
log_mode=log_mode,
|
140
|
+
log_level=log_level,
|
141
|
+
delete_input=True,
|
142
|
+
abort_callback=abort)
|
143
|
+
except Exception as e:
|
144
|
+
|
145
|
+
# Check for anticipated exceptions, and write a simpler message for them
|
146
|
+
for err_message in (const.ERR_CONVERSION_FAILED, const.ERR_CONVERTER_NOT_RECOGNISED,
|
147
|
+
const.ERR_EMPTY_ARCHIVE, const.ERR_WRONG_EXTENSIONS):
|
148
|
+
if log_utility.string_with_placeholders_matches(err_message, str(e)):
|
149
|
+
with open(qualified_output_log, "w") as fo:
|
150
|
+
fo.write(str(e))
|
151
|
+
abort(const.STATUS_CODE_GENERAL)
|
152
|
+
|
153
|
+
# Failsafe exception message
|
154
|
+
msg = "The following unexpected exception was raised by the converter:\n" + traceback.format_exc()+"\n"
|
155
|
+
with open(qualified_output_log, "w") as fo:
|
156
|
+
fo.write(msg)
|
157
|
+
abort(const.STATUS_CODE_GENERAL)
|
158
|
+
|
159
|
+
return repr(conversion_output)
|
160
|
+
else:
|
161
|
+
# return http status code 405
|
162
|
+
abort(405)
|
163
|
+
|
164
|
+
|
165
|
+
@app.route('/feedback/', methods=['POST'])
|
166
|
+
def feedback():
|
167
|
+
"""Take feedback data from the web app and log it
|
168
|
+
"""
|
169
|
+
|
170
|
+
try:
|
171
|
+
|
172
|
+
entry = {
|
173
|
+
"datetime": log_utility.get_date_time(),
|
174
|
+
}
|
175
|
+
|
176
|
+
report = json.loads(request.form['data'])
|
177
|
+
|
178
|
+
for key in ["type", "missing", "reason", "from", "to"]:
|
179
|
+
if key in report:
|
180
|
+
entry[key] = str(report[key])
|
181
|
+
|
182
|
+
log_utility.append_to_log_file("feedback", entry)
|
183
|
+
|
184
|
+
return Response(status=201)
|
185
|
+
|
186
|
+
except Exception:
|
187
|
+
|
188
|
+
return Response(status=400)
|
189
|
+
|
190
|
+
|
191
|
+
@app.route('/delete/', methods=['POST'])
|
192
|
+
def delete():
|
193
|
+
"""Delete files in folder 'downloads'
|
194
|
+
"""
|
195
|
+
|
196
|
+
realbase = os.path.realpath(const.DEFAULT_DOWNLOAD_DIR)
|
197
|
+
|
198
|
+
realfilename = os.path.realpath(os.path.join(const.DEFAULT_DOWNLOAD_DIR, request.form['filename']))
|
199
|
+
reallogname = os.path.realpath(os.path.join(const.DEFAULT_DOWNLOAD_DIR, request.form['logname']))
|
200
|
+
|
201
|
+
if realfilename.startswith(realbase + os.sep) and reallogname.startswith(realbase + os.sep):
|
202
|
+
|
203
|
+
os.remove(realfilename)
|
204
|
+
os.remove(reallogname)
|
205
|
+
|
206
|
+
return 'okay'
|
207
|
+
|
208
|
+
else:
|
209
|
+
|
210
|
+
return Response(status=400)
|
211
|
+
|
212
|
+
|
213
|
+
@app.route('/del/', methods=['POST'])
|
214
|
+
def delete_file():
|
215
|
+
"""Delete file (cURL)
|
216
|
+
"""
|
217
|
+
os.remove(request.form['filepath'])
|
218
|
+
return 'Server-side file ' + request.form['filepath'] + ' deleted\n'
|
219
|
+
|
220
|
+
|
221
|
+
@app.route('/data/', methods=['GET'])
|
222
|
+
def data():
|
223
|
+
"""Check that the incoming token matches the one sent to the user (should mostly prevent spambots). Write date- and
|
224
|
+
time-stamped user input to server-side file 'user_responses'.
|
225
|
+
|
226
|
+
$$$$$$$$$$ Retained in case direct logging is required in the future. $$$$$$$$$$
|
227
|
+
|
228
|
+
Returns
|
229
|
+
-------
|
230
|
+
str
|
231
|
+
Output status - 'okay' if exited successfuly
|
232
|
+
"""
|
233
|
+
if service_mode and request.args['token'] == token and token != '':
|
234
|
+
message = '[' + log_utility.get_date_time() + '] ' + request.args['data'] + '\n'
|
235
|
+
|
236
|
+
with open("user_responses", "a") as f:
|
237
|
+
f.write(message)
|
238
|
+
|
239
|
+
return 'okay'
|
240
|
+
else:
|
241
|
+
# return http status code 405
|
242
|
+
abort(405)
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,185 @@
|
|
1
|
+
"""@file psdi_data_conversion/constants.py
|
2
|
+
|
3
|
+
Created 2025-01-23 by Bryan Gillis.
|
4
|
+
|
5
|
+
Miscellaneous constant values used within this project.
|
6
|
+
|
7
|
+
These values are stored as constants rather than hardcoded literals for various reasons, including:
|
8
|
+
- Better assurance of consistency that the same value is used every time
|
9
|
+
- If a value needs to be changed, this only needs to be done at one location
|
10
|
+
- Compatibility with IDE features - a constant can be checked for validity by an IDE, while e.g. a string key for a dict
|
11
|
+
can't, allowing more errors to be caught and fixed by the IDE rather than at runtime
|
12
|
+
- The use of a constant may improve readability - e.g. `MEGABYTE = 1024*1024; max_file_size = 1*MEGABYTE` is more
|
13
|
+
readable than `max_file_size = 1*1024*1024`, and so doesn't require a comment like the latter would
|
14
|
+
|
15
|
+
There are some known drawbacks to this approach which need to be considered though:
|
16
|
+
- Constants may obscure readability - it may be quite relevant to the reader exactly what a constant represents, which
|
17
|
+
is obscured until they (at minimum) mouse over it
|
18
|
+
- More code is necessary to use a constant than a literal (at minimum it needs an extra line to define it, and when
|
19
|
+
stored here, it also needs a line to import it or this module)
|
20
|
+
|
21
|
+
With these drawbacks in mind, we make the following recommendations for constant use:
|
22
|
+
- Messages for the user (print/logging messages, exceptions) should by default not be stored as constants. They should
|
23
|
+
be made constants if it's necessary to reference their exact text elsewhere (either in the executable code or unit
|
24
|
+
tests). In this case, the name of the constant should be descriptive, even if this means a rather long name
|
25
|
+
- If a value is only used in one file and only likely to ever be used in that file, it can be defined as a constant
|
26
|
+
there (or if used only two or three times in quick succession, left as a literal)
|
27
|
+
- Of course, deviations from this should be made when necessary, such as to avoid circular imports
|
28
|
+
"""
|
29
|
+
|
30
|
+
import logging
|
31
|
+
import shutil
|
32
|
+
|
33
|
+
# Interface
|
34
|
+
# ---------
|
35
|
+
|
36
|
+
# The name of the command-line script
|
37
|
+
CL_SCRIPT_NAME = "psdi-data-convert"
|
38
|
+
|
39
|
+
# Environmental variables
|
40
|
+
LOG_MODE_EV = "LOG_MODE"
|
41
|
+
LOG_LEVEL_EV = "LOG_LEVEL"
|
42
|
+
MAX_FILESIZE_EV = "MAX_FILESIZE"
|
43
|
+
|
44
|
+
# Files and Folders
|
45
|
+
# -----------------
|
46
|
+
|
47
|
+
# Maximum output file size in bytes
|
48
|
+
MEGABYTE = 1024*1024
|
49
|
+
DEFAULT_MAX_FILE_SIZE = 0*MEGABYTE
|
50
|
+
|
51
|
+
DEFAULT_UPLOAD_DIR = './psdi_data_conversion/static/uploads'
|
52
|
+
DEFAULT_DOWNLOAD_DIR = './psdi_data_conversion/static/downloads'
|
53
|
+
|
54
|
+
# Filename of the database, relative to the base of the python package
|
55
|
+
DATABASE_FILENAME = "static/data/data.json"
|
56
|
+
|
57
|
+
# Archive extensions and formats ('format' here meaning the value expected by shutil's archive functions)
|
58
|
+
|
59
|
+
ZIP_EXTENSION = ".zip"
|
60
|
+
ZIP_FORMAT = "zip"
|
61
|
+
|
62
|
+
D_ZIP_FORMATS = {ZIP_EXTENSION: ZIP_FORMAT}
|
63
|
+
|
64
|
+
TAR_EXTENSION = ".tar"
|
65
|
+
TAR_FORMAT = "tar"
|
66
|
+
GZTAR_EXTENSION = ".tar.gz"
|
67
|
+
GZTAR_FORMAT = "gztar"
|
68
|
+
BZTAR_EXTENSION = ".tar.bz"
|
69
|
+
BZTAR_FORMAT = "bztar"
|
70
|
+
XZTAR_EXTENSION = ".tar.xz"
|
71
|
+
XZTAR_FORMAT = "xztar"
|
72
|
+
|
73
|
+
D_TAR_FORMATS = {TAR_EXTENSION: TAR_FORMAT,
|
74
|
+
GZTAR_EXTENSION: GZTAR_FORMAT,
|
75
|
+
BZTAR_EXTENSION: BZTAR_FORMAT,
|
76
|
+
XZTAR_EXTENSION: BZTAR_FORMAT}
|
77
|
+
|
78
|
+
# A list of specifically the extensions that are combinations of multiple different extensions
|
79
|
+
L_COMPOUND_EXTENSIONS = [GZTAR_EXTENSION, BZTAR_EXTENSION, XZTAR_EXTENSION]
|
80
|
+
|
81
|
+
# Formats which are supported by shutil's built-in archive utility
|
82
|
+
D_SUPPORTED_ARCHIVE_FORMATS = {**D_ZIP_FORMATS, **D_TAR_FORMATS}
|
83
|
+
|
84
|
+
L_UNSUPPORTED_ARCHIVE_EXTENSIONS = [".rar", ".7z"]
|
85
|
+
|
86
|
+
L_ALL_ARCHIVE_EXTENSIONS = [*D_SUPPORTED_ARCHIVE_FORMATS.keys(), *L_UNSUPPORTED_ARCHIVE_EXTENSIONS]
|
87
|
+
|
88
|
+
|
89
|
+
# Logging and Formatting
|
90
|
+
# ----------------------
|
91
|
+
|
92
|
+
# Number of character spaces allocated for flags/options
|
93
|
+
|
94
|
+
# Get the terminal width so we can prettily print help text
|
95
|
+
TERM_WIDTH, _ = shutil.get_terminal_size((80, 20))
|
96
|
+
|
97
|
+
# Log formatting
|
98
|
+
LOG_FORMAT = r'[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s'
|
99
|
+
TIMESTAMP_FORMAT = r"%Y-%m-%d %H:%M:%S"
|
100
|
+
|
101
|
+
# Regex to match date/time format
|
102
|
+
DATE_RE_RAW = r"\d{4}-[0-1]\d-[0-3]\d"
|
103
|
+
TIME_RE_RAW = r"[0-2]\d:[0-5]\d:[0-5]\d"
|
104
|
+
DATETIME_RE_RAW = f"{DATE_RE_RAW} {TIME_RE_RAW}"
|
105
|
+
|
106
|
+
# Log mode info and settings
|
107
|
+
LOG_FULL = "full"
|
108
|
+
LOG_FULL_FORCE = "full-force"
|
109
|
+
LOG_SIMPLE = "simple"
|
110
|
+
LOG_STDOUT = "stdout"
|
111
|
+
LOG_NONE = "none"
|
112
|
+
|
113
|
+
LOG_MODE_DEFAULT = LOG_SIMPLE
|
114
|
+
|
115
|
+
L_ALLOWED_LOG_MODES = (LOG_FULL, LOG_FULL_FORCE, LOG_SIMPLE, LOG_STDOUT, LOG_NONE)
|
116
|
+
|
117
|
+
LOG_EXT = ".log"
|
118
|
+
OUTPUT_LOG_EXT = f"{LOG_EXT}.txt"
|
119
|
+
|
120
|
+
# Settings for global logger
|
121
|
+
GLOBAL_LOG_FILENAME = "./error_log.txt"
|
122
|
+
GLOBAL_LOGGER_LEVEL = logging.ERROR
|
123
|
+
|
124
|
+
# Settings for local logger
|
125
|
+
LOCAL_LOGGER_NAME = "data-conversion"
|
126
|
+
DEFAULT_LOCAL_LOGGER_LEVEL = logging.INFO
|
127
|
+
DEFAULT_LISTING_LOG_FILE = "data-convert-list" + LOG_EXT
|
128
|
+
|
129
|
+
# Converters and Related
|
130
|
+
# ----------------------
|
131
|
+
|
132
|
+
# Converter names are determined based on the modules present in the 'converters' package by the 'converter' module
|
133
|
+
# This module contains constant dicts and lists of registered converters
|
134
|
+
|
135
|
+
# Default converter - this must match the name of one of the registered converters
|
136
|
+
CONVERTER_DEFAULT = 'Open Babel'
|
137
|
+
|
138
|
+
# File format properties which are used to judge conversion quality - KEY is the key for it in the database, and LABEL
|
139
|
+
# is how we want to print it out for the user
|
140
|
+
QUAL_COMP_KEY = "composition"
|
141
|
+
QUAL_COMP_LABEL = "Atomic composition is"
|
142
|
+
QUAL_CONN_KEY = "connections"
|
143
|
+
QUAL_CONN_LABEL = "Atomic connections are"
|
144
|
+
QUAL_2D_KEY = "two_dim"
|
145
|
+
QUAL_2D_LABEL = "2D atomic coordinates are"
|
146
|
+
QUAL_3D_KEY = "three_dim"
|
147
|
+
QUAL_3D_LABEL = "2D atomic coordinates are"
|
148
|
+
|
149
|
+
D_QUAL_LABELS = {QUAL_COMP_KEY: QUAL_COMP_LABEL,
|
150
|
+
QUAL_CONN_KEY: QUAL_CONN_LABEL,
|
151
|
+
QUAL_2D_KEY: QUAL_2D_LABEL,
|
152
|
+
QUAL_3D_KEY: QUAL_3D_LABEL}
|
153
|
+
|
154
|
+
# Notes for conversion quality
|
155
|
+
QUAL_NOTE_IN_UNKNOWN = ("Potential data extrapolation: {} represented in the output format but its representation in "
|
156
|
+
"the input format is unknown")
|
157
|
+
QUAL_NOTE_OUT_UNKNOWN = ("Potential data loss: {} represented in the input format, but its representation in the "
|
158
|
+
"output format is unknown")
|
159
|
+
QUAL_NOTE_BOTH_UNKNOWN = ("Potential data loss or extrapolation: {} potentially not supported in either or both of "
|
160
|
+
"the input and output formats")
|
161
|
+
QUAL_NOTE_IN_MISSING = "Potential data extrapolation: {} represented in the output format but not the input format"
|
162
|
+
QUAL_NOTE_OUT_MISSING = "Potential data loss: {} represented in the input format but not the output format"
|
163
|
+
|
164
|
+
# Conversion quality strings
|
165
|
+
QUAL_UNKNOWN = 'unknown'
|
166
|
+
QUAL_VERYGOOD = 'very good'
|
167
|
+
QUAL_GOOD = 'good'
|
168
|
+
QUAL_OKAY = 'okay'
|
169
|
+
QUAL_POOR = 'poor'
|
170
|
+
QUAL_VERYPOOR = 'very poor'
|
171
|
+
|
172
|
+
# Errors
|
173
|
+
# ------
|
174
|
+
|
175
|
+
# HTTP status codes for various types of errors
|
176
|
+
STATUS_CODE_BAD_METHOD = 405
|
177
|
+
STATUS_CODE_SIZE = 413
|
178
|
+
STATUS_CODE_GENERAL = 500
|
179
|
+
|
180
|
+
# Error messages
|
181
|
+
ERR_CONVERTER_NOT_RECOGNISED = "Converter {} not recognized. Allowed converters are: "
|
182
|
+
ERR_WRONG_EXTENSIONS = "Input file '{file}' does not have expected extension '{ext}'"
|
183
|
+
ERR_EMPTY_ARCHIVE = "No files to convert were contained in archive"
|
184
|
+
ERR_CONVERSION_FAILED = ("File conversion failed for one or more files. Lines from the output log "
|
185
|
+
"{} which indicate possible sources of error: ")
|