psdi-data-conversion 0.0.39__tar.gz → 0.1.0__tar.gz

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.
Files changed (142) hide show
  1. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/PKG-INFO +2 -2
  2. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/README.md +1 -1
  3. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/app.py +57 -21
  4. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/constants.py +1 -0
  5. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/converter.py +140 -12
  6. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/database.py +17 -9
  7. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/main.py +29 -22
  8. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/index-versions/psdi-common-footer.html +1 -1
  9. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/index-versions/psdi-common-header.html +1 -1
  10. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/psdi-common-footer.html +1 -1
  11. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/psdi-common-header.html +1 -1
  12. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/templates/index.htm +8 -9
  13. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/testing/conversion_test_specs.py +12 -4
  14. psdi_data_conversion-0.1.0/psdi_data_conversion/utils.py +21 -0
  15. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/tests/python/cli_test.py +22 -18
  16. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/tests/python/converter_test.py +4 -2
  17. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/tests/python/database_test.py +8 -4
  18. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/.gitignore +0 -0
  19. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/CHANGELOG.md +0 -0
  20. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/CONTRIBUTING.md +0 -0
  21. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/LICENSE +0 -0
  22. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/__init__.py +0 -0
  23. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/bin/LICENSE_ATOMSK +0 -0
  24. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/bin/LICENSE_C2X +0 -0
  25. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/bin/linux/atomsk +0 -0
  26. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/bin/linux/c2x +0 -0
  27. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/bin/mac/atomsk +0 -0
  28. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/bin/mac/c2x +0 -0
  29. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/converters/__init__.py +0 -0
  30. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/converters/atomsk.py +0 -0
  31. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/converters/base.py +0 -0
  32. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/converters/c2x.py +0 -0
  33. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/converters/openbabel.py +0 -0
  34. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/dist.py +0 -0
  35. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/file_io.py +0 -0
  36. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/log_utility.py +0 -0
  37. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/scripts/atomsk.sh +0 -0
  38. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/scripts/c2x.sh +0 -0
  39. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/security.py +0 -0
  40. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/accessibility.htm +0 -0
  41. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/convert.htm +0 -0
  42. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/convertato.htm +0 -0
  43. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/convertc2x.htm +0 -0
  44. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/documentation.htm +0 -0
  45. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/download.htm +0 -0
  46. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/feedback.htm +0 -0
  47. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/header-links.html +0 -0
  48. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/index-versions/header-links.html +0 -0
  49. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/content/report.htm +0 -0
  50. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/data/data.json +0 -0
  51. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/colormode-toggle-dm.svg +0 -0
  52. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/colormode-toggle-lm.svg +0 -0
  53. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/psdi-icon-dark.svg +0 -0
  54. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/psdi-icon-light.svg +0 -0
  55. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/psdi-logo-darktext-simple.png +0 -0
  56. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/psdi-logo-darktext.png +0 -0
  57. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/psdi-logo-lighttext-simple.png +0 -0
  58. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/psdi-logo-lighttext.png +0 -0
  59. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-bluesky-black.svg +0 -0
  60. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-bluesky-white.svg +0 -0
  61. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-instagram-black.svg +0 -0
  62. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-instagram-white.svg +0 -0
  63. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-linkedin-black.png +0 -0
  64. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-linkedin-white.png +0 -0
  65. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-mastodon-black.svg +0 -0
  66. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-mastodon-white.svg +0 -0
  67. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-x-black.svg +0 -0
  68. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-x-white.svg +0 -0
  69. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-youtube-black.png +0 -0
  70. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/social-logo-youtube-white.png +0 -0
  71. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/ukri-epsr-logo-darktext.png +0 -0
  72. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/ukri-epsr-logo-lighttext.png +0 -0
  73. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/ukri-logo-darktext.png +0 -0
  74. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/img/ukri-logo-lighttext.png +0 -0
  75. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/accessibility.js +0 -0
  76. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/common.js +0 -0
  77. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/convert.js +0 -0
  78. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/convert_common.js +0 -0
  79. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/convertato.js +0 -0
  80. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/convertc2x.js +0 -0
  81. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/data.js +0 -0
  82. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/format.js +0 -0
  83. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/load_accessibility.js +0 -0
  84. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/psdi-common.js +0 -0
  85. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/javascript/report.js +0 -0
  86. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/styles/format.css +0 -0
  87. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/static/styles/psdi-common.css +0 -0
  88. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/testing/__init__.py +0 -0
  89. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/testing/constants.py +0 -0
  90. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/testing/conversion_callbacks.py +0 -0
  91. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/testing/gui.py +0 -0
  92. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/psdi_data_conversion/testing/utils.py +0 -0
  93. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/pyproject.toml +0 -0
  94. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/scripts/setup_bin.py +0 -0
  95. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/1ARJ.mmcif +0 -0
  96. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/1NE6.mmcif +0 -0
  97. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/5a9z-assembly1.cif +0 -0
  98. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/Fapatite.ins +0 -0
  99. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/aceticacid.mol +0 -0
  100. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/benzyne.molden +0 -0
  101. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/caffeine-smi.tar +0 -0
  102. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/caffeine-smi.tar.gz +0 -0
  103. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/caffeine-smi.zip +0 -0
  104. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/caffeine.inchi +0 -0
  105. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/ch3cl-esp.cub +0 -0
  106. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/cyclopropane_err.mol +0 -0
  107. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/ethanol.xyz +0 -0
  108. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/fullRhinovirus.pdb +0 -0
  109. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/hemoglobin.pdb +0 -0
  110. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/nacl.cif +0 -0
  111. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/nacl.mol +0 -0
  112. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/aceticacid.log.txt +0 -0
  113. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/aceticacid.mol2 +0 -0
  114. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine-2D-fastest.xyz +0 -0
  115. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine-3D-best.xyz +0 -0
  116. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine.smi +0 -0
  117. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine.xyz +0 -0
  118. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine_a_in.smi +0 -0
  119. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine_a_in_kx_f4_l5_out.smi +0 -0
  120. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine_a_in_kx_f4_out.smi +0 -0
  121. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine_a_in_kx_out.smi +0 -0
  122. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/caffeine_a_in_x_out.smi +0 -0
  123. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/hemoglobin_Atomsk.xyz +0 -0
  124. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/hemoglobin_c2x.xyz +0 -0
  125. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/nacl.log +0 -0
  126. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/nacl.mol +0 -0
  127. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/quartz_OB.cif +0 -0
  128. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/quartz_OB.log.txt +0 -0
  129. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/quartz_atomsk.cif +0 -0
  130. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/quartz_atomsk.log.txt +0 -0
  131. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/standard_test.inchi +0 -0
  132. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/xyz_files-mol.zip +0 -0
  133. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/output/xyz_files.log.txt +0 -0
  134. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/periodic_dmol3.outmol +0 -0
  135. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/quartz.xyz +0 -0
  136. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/quartz_err.xyz +0 -0
  137. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/test_data/standard_test.cdxml +0 -0
  138. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/tests/gui/gui_test.py +0 -0
  139. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/tests/python/dist_test.py +0 -0
  140. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/tests/python/file_io_test.py +0 -0
  141. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/tests/python/logging_test.py +0 -0
  142. {psdi_data_conversion-0.0.39 → psdi_data_conversion-0.1.0}/tests/python/security_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psdi_data_conversion
3
- Version: 0.0.39
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
- Version: Pre-release 2024-04-14
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,6 +1,6 @@
1
1
  # PSDI Data Conversion
2
2
 
3
- Version: Pre-release 2024-04-14
3
+ Release date: 2024-04-29
4
4
 
5
5
  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:
6
6
 
@@ -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
- data = [{'token': token,
139
- 'max_file_size': max_file_size,
140
- 'max_file_size_ob': max_file_size_ob,
141
- 'service_mode': service_mode,
142
- 'production_mode': production_mode,
143
- 'sha': get_last_sha()}]
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 = filename = file.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
- log_utility.append_to_log_file("feedback", entry)
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.")
@@ -41,6 +41,7 @@ LOG_MODE_EV = "LOG_MODE"
41
41
  LOG_LEVEL_EV = "LOG_LEVEL"
42
42
  MAX_FILESIZE_EV = "MAX_FILESIZE"
43
43
  MAX_FILESIZE_OB_EV = "MAX_FILESIZE_OB"
44
+ SERVICE_MODE_EV = "SERVICE_MODE"
44
45
 
45
46
  # Files and Folders
46
47
  # -----------------
@@ -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
- name = converter_class.name
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 stores. Allowed values are:
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 = D_REGISTERED_CONVERTERS[name]
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 stores. Allowed values are:
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
- to_format,
359
- *args,
360
- from_format=from_format,
361
- download_dir=download_dir,
362
- max_file_size=max_file_size,
363
- log_file=log_file,
364
- log_mode=log_mode,
365
- **converter_kwargs).run())
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'
@@ -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 D_REGISTERED_CONVERTERS, D_SUPPORTED_CONVERTERS
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 = D_REGISTERED_CONVERTERS[name].database_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 D_REGISTERED_CONVERTERS[converter_name].supports_ambiguous_extensions:
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]],