rcdb 2.3.2__tar.gz → 2.4.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 (223) hide show
  1. {rcdb-2.3.2/rcdb.egg-info → rcdb-2.4.0}/PKG-INFO +1 -1
  2. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/app_context.py +1 -1
  3. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/db.py +4 -0
  4. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/rp.py +3 -0
  5. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/provider.py +28 -13
  6. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/version.py +1 -1
  7. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/__init__.py +88 -13
  8. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/runs.py +5 -2
  9. rcdb-2.4.0/rcdb/web/templates/db_error.html +20 -0
  10. {rcdb-2.3.2 → rcdb-2.4.0/rcdb.egg-info}/PKG-INFO +1 -1
  11. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/SOURCES.txt +3 -1
  12. rcdb-2.4.0/tests/test_web_db_failure.py +119 -0
  13. {rcdb-2.3.2 → rcdb-2.4.0}/MANIFEST.in +0 -0
  14. {rcdb-2.3.2 → rcdb-2.4.0}/README.md +0 -0
  15. {rcdb-2.3.2 → rcdb-2.4.0}/pyproject.toml +0 -0
  16. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/__init__.py +0 -0
  17. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/__main__.py +0 -0
  18. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/alias.py +0 -0
  19. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/__init__.py +0 -0
  20. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/__main__.py +0 -0
  21. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/add.py +0 -0
  22. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/app.py +0 -0
  23. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/context.py +0 -0
  24. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/file.py +0 -0
  25. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/info.py +0 -0
  26. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/ls.py +0 -0
  27. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/repair/__init__.py +0 -0
  28. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/repair/evio_files.py +0 -0
  29. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/run.py +0 -0
  30. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/select.py +0 -0
  31. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/test_data.py +0 -0
  32. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/web.py +0 -0
  33. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/coda_parser.py +0 -0
  34. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/config_parser.py +0 -0
  35. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/constants.py +0 -0
  36. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/errors.py +0 -0
  37. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/file_archiver.py +0 -0
  38. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/halld_daq_config_parser.py +0 -0
  39. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/lexer.py +0 -0
  40. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/log_format.py +0 -0
  41. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/model.py +0 -0
  42. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/stopwatch.py +0 -0
  43. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/update.py +0 -0
  44. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/__main__.py +0 -0
  45. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/__init__.py +0 -0
  46. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/conditions.py +0 -0
  47. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/files.py +0 -0
  48. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/logs.py +0 -0
  49. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/select_values.py +0 -0
  50. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/statistics.py +0 -0
  51. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/pagination.py +0 -0
  52. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/run_table.py +0 -0
  53. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.bootstrap.css +0 -0
  54. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.bootstrap.min.css +0 -0
  55. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.foundation.css +0 -0
  56. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.foundation.min.css +0 -0
  57. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.jqueryui.css +0 -0
  58. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.jqueryui.min.css +0 -0
  59. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/jquery.dataTables.css +0 -0
  60. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/jquery.dataTables.min.css +0 -0
  61. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/jquery.dataTables_themeroller.css +0 -0
  62. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_asc.png +0 -0
  63. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_asc_disabled.png +0 -0
  64. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_both.png +0 -0
  65. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_desc.png +0 -0
  66. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_desc_disabled.png +0 -0
  67. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.bootstrap.js +0 -0
  68. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.bootstrap.min.js +0 -0
  69. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.foundation.js +0 -0
  70. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.foundation.min.js +0 -0
  71. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.jqueryui.js +0 -0
  72. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.jqueryui.min.js +0 -0
  73. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/jquery.dataTables.js +0 -0
  74. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/jquery.dataTables.min.js +0 -0
  75. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.bootstrap.css +0 -0
  76. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.bootstrap.min.css +0 -0
  77. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.dataTables.css +0 -0
  78. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.dataTables.min.css +0 -0
  79. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.foundation.css +0 -0
  80. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.foundation.min.css +0 -0
  81. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.jqueryui.css +0 -0
  82. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.jqueryui.min.css +0 -0
  83. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/dataTables.responsive.js +0 -0
  84. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/dataTables.responsive.min.js +0 -0
  85. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.bootstrap.js +0 -0
  86. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.bootstrap.min.js +0 -0
  87. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.foundation.js +0 -0
  88. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.foundation.min.js +0 -0
  89. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.jqueryui.js +0 -0
  90. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.jqueryui.min.js +0 -0
  91. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.bootstrap.css +0 -0
  92. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.bootstrap.min.css +0 -0
  93. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.dataTables.css +0 -0
  94. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.dataTables.min.css +0 -0
  95. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.foundation.css +0 -0
  96. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.foundation.min.css +0 -0
  97. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.jqueryui.css +0 -0
  98. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.jqueryui.min.css +0 -0
  99. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/js/dataTables.rowReorder.js +0 -0
  100. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/js/dataTables.rowReorder.min.js +0 -0
  101. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/datatables.css +0 -0
  102. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/datatables.js +0 -0
  103. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/datatables.min.css +0 -0
  104. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/datatables.min.js +0 -0
  105. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap-theme.css +0 -0
  106. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap-theme.css.map +0 -0
  107. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap-theme.min.css +0 -0
  108. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap-theme.min.css.map +0 -0
  109. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap.css +0 -0
  110. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap.css.map +0 -0
  111. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap.min.css +0 -0
  112. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap.min.css.map +0 -0
  113. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/buttons.dataTables.min.css +0 -0
  114. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/dataTables.tableTools.css +0 -0
  115. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery-ui.min.css +0 -0
  116. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery-ui.structure.min.css +0 -0
  117. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery-ui.theme.min.css +0 -0
  118. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery.dataTables.min.css +0 -0
  119. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery.dataTables.yadcf.css +0 -0
  120. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery.jsonview.css +0 -0
  121. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/main.css +0 -0
  122. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/select.dataTables.min.css +0 -0
  123. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/webintellisense.css +0 -0
  124. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/favicon.png +0 -0
  125. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.eot +0 -0
  126. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.svg +0 -0
  127. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.ttf +0 -0
  128. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.woff +0 -0
  129. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.woff2 +0 -0
  130. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/humans.txt +0 -0
  131. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/img/glyphicons-halflings-white.png +0 -0
  132. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/img/glyphicons-halflings.png +0 -0
  133. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/img/logo.png +0 -0
  134. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/bootstrap.js +0 -0
  135. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/bootstrap.min.js +0 -0
  136. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/clipboard.min.js +0 -0
  137. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/copy_clipboard.js +0 -0
  138. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/dataTables.buttons.min.js +0 -0
  139. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/dataTables.select.min.js +0 -0
  140. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery-2.1.0.min.js +0 -0
  141. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery-2.1.4.min.js +0 -0
  142. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery-ui.min.js +0 -0
  143. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery.dataTables.min.js +0 -0
  144. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery.dataTables.yadcf.js +0 -0
  145. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery.flot.min.js +0 -0
  146. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery.jsonview.js +0 -0
  147. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/npm.js +0 -0
  148. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/webintellisense-ace.js +0 -0
  149. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/webintellisense-codemirror.js +0 -0
  150. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/webintellisense-textbox.js +0 -0
  151. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/webintellisense.js +0 -0
  152. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/robots.txt +0 -0
  153. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/style.css +0 -0
  154. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/404.html +0 -0
  155. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/admin/index.html +0 -0
  156. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/admin/layout.html +0 -0
  157. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/admin/user.html +0 -0
  158. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/admin/users.html +0 -0
  159. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/conditions/index.html +0 -0
  160. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/custom_column_run_table.html +0 -0
  161. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/default_run_table.html +0 -0
  162. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/errors/forbidden_page.html +0 -0
  163. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/errors/layout.html +0 -0
  164. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/errors/page_not_found.html +0 -0
  165. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/errors/server_error.html +0 -0
  166. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/files/index.html +0 -0
  167. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/files/info.html +0 -0
  168. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/files/not_found.html +0 -0
  169. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/create_profile.html +0 -0
  170. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/footers/help.html +0 -0
  171. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/login.html +0 -0
  172. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/login_openid.html +0 -0
  173. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/reauth.html +0 -0
  174. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/reset_password.html +0 -0
  175. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/search.html +0 -0
  176. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/signup.html +0 -0
  177. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/index.html +0 -0
  178. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/layout.html +0 -0
  179. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/layouts/base.html +0 -0
  180. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/layouts/base2.html +0 -0
  181. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/login.html +0 -0
  182. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/logs/index.html +0 -0
  183. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_ask_user_to_update_ie.html +0 -0
  184. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_form.html +0 -0
  185. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_google_analytics.html +0 -0
  186. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_misc.html +0 -0
  187. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_reset_password.html +0 -0
  188. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/render_pagination.html +0 -0
  189. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/run_search_box.html +0 -0
  190. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/conditions.html +0 -0
  191. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/example.html +0 -0
  192. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/index.html +0 -0
  193. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/info.html +0 -0
  194. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/not_found.html +0 -0
  195. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/select_values/index.html +0 -0
  196. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/settings/layout.html +0 -0
  197. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/settings/password.html +0 -0
  198. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/settings/profile.html +0 -0
  199. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/show_entries.html +0 -0
  200. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/statistics/index.html +0 -0
  201. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/user/index.html +0 -0
  202. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/user/profile.html +0 -0
  203. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/dependency_links.txt +0 -0
  204. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/entry_points.txt +0 -0
  205. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/requires.txt +0 -0
  206. {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/top_level.txt +0 -0
  207. {rcdb-2.3.2 → rcdb-2.4.0}/setup.cfg +0 -0
  208. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_add_condition.py +0 -0
  209. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_add_file.py +0 -0
  210. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_add_type.py +0 -0
  211. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_file.py +0 -0
  212. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_run.py +0 -0
  213. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_coda_parser.py +0 -0
  214. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_conditions.py +0 -0
  215. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_config_parser.py +0 -0
  216. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_get_run_periods.py +0 -0
  217. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_get_runs.py +0 -0
  218. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_halld_run_config_parser.py +0 -0
  219. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_save_files.py +0 -0
  220. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_select_runs.py +0 -0
  221. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_select_values.py +0 -0
  222. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_sql_schema_version.py +0 -0
  223. {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_value_comparison.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rcdb
3
- Version: 2.3.2
3
+ Version: 2.4.0
4
4
  Summary: RCDB - Run Conditions DataBase
5
5
  Author-email: Dmitry Romanov <romanov@jlab.org>
6
6
  License-Expression: MIT
@@ -28,7 +28,7 @@ class RcdbApplicationContext(object):
28
28
  Python 3.13+ leaks a ``ResourceWarning: unclosed database``).
29
29
  """
30
30
  if self._db_instance is not None:
31
- self._db_instance.disconnect()
31
+ self._db_instance.close()
32
32
  self._db_instance = None
33
33
 
34
34
  def require_connected_db(self) -> RCDBProvider:
@@ -24,6 +24,7 @@ def db_command(ctx):
24
24
  print("ERROR connection string is missing.")
25
25
  exit(1)
26
26
  provider.connect(connection_str, check_version=False)
27
+ ctx.call_on_close(provider.close)
27
28
  query = select(SchemaVersion).order_by(SchemaVersion.version.desc())
28
29
  schema_version, = provider.session.execute(query).first()
29
30
  print("Schema version: {} - '{}'".format(schema_version.version, schema_version.comment))
@@ -88,6 +89,7 @@ def _print_sqlite_table_sizes(engine):
88
89
  @pass_rcdb_context
89
90
  def update(context):
90
91
  provider = RCDBProvider(context.connection_str, check_version=False)
92
+ click.get_current_context().call_on_close(provider.close)
91
93
 
92
94
  # Check something exists
93
95
  if not sqlalchemy.inspect(provider.engine).has_table(SchemaVersion.__tablename__):
@@ -117,6 +119,7 @@ def update(context):
117
119
  return
118
120
 
119
121
  provider = RCDBProvider(context.connection_str, check_version=False)
122
+ click.get_current_context().call_on_close(provider.close)
120
123
 
121
124
  # Create alias table
122
125
  Alias.__table__.create(provider.engine)
@@ -181,6 +184,7 @@ def init(context, drop_all, no_defaults, confirm, add_tests, add_cpp_tests):
181
184
  # That we will need for DB
182
185
  metadata = rcdb.model.Base.metadata
183
186
  provider = RCDBProvider(context.connection_str, check_version=False)
187
+ click.get_current_context().call_on_close(provider.close)
184
188
 
185
189
  # Drop all if needed
186
190
  if drop_all:
@@ -55,6 +55,7 @@ def add(context, name, description, run_min, run_max, start_date, end_date):
55
55
 
56
56
  provider = RCDBProvider()
57
57
  provider.connect(connection_str, check_version=False)
58
+ click.get_current_context().call_on_close(provider.close)
58
59
  session = provider.session
59
60
 
60
61
  # Convert date strings to Date objects if provided
@@ -104,6 +105,7 @@ def rm(context, period_id, yes):
104
105
 
105
106
  provider = RCDBProvider()
106
107
  provider.connect(connection_str, check_version=False)
108
+ click.get_current_context().call_on_close(provider.close)
107
109
  session = provider.session
108
110
 
109
111
  rp_item = session.query(RunPeriod).filter(RunPeriod.id == period_id).one_or_none()
@@ -142,6 +144,7 @@ def update(context, period_id, name, description, run_min, run_max, start_date,
142
144
 
143
145
  provider = RCDBProvider()
144
146
  provider.connect(connection_str, check_version=False)
147
+ click.get_current_context().call_on_close(provider.close)
145
148
  session = provider.session
146
149
 
147
150
  rp_item = session.query(RunPeriod).filter(RunPeriod.id == period_id).one_or_none()
@@ -133,23 +133,33 @@ class RCDBProvider(object):
133
133
  self._connection_string = connection_string
134
134
 
135
135
  if check_version:
136
- db_version = self.get_schema_version()
137
- if db_version != rcdb.SQL_SCHEMA_VERSION:
138
- message = "SQL schema version doesn't match. " \
139
- "Retrieved DB version is {0}, required version is {1}. " \
140
- .format(db_version, rcdb.SQL_SCHEMA_VERSION)
141
- if db_version is not None and db_version < rcdb.SQL_SCHEMA_VERSION:
142
- message += "The database schema is older than this RCDB version. " \
143
- "Run 'rcdb db update' to migrate the database to the current schema."
144
- else:
145
- message += "The database schema is newer than this RCDB version. " \
146
- "Update your RCDB installation to match the database."
147
- raise rcdb.errors.SqlSchemaVersionError(message)
136
+ # This version query is the first statement that actually opens a
137
+ # DB connection. If it fails (unreachable host, access denied, wrong
138
+ # schema, ...) the engine/session we just built must be disposed
139
+ # here: the exception propagates out of connect()/__init__, so the
140
+ # caller never receives this provider to disconnect() it themselves,
141
+ # and the engine's open connection would otherwise leak until GC.
142
+ try:
143
+ db_version = self.get_schema_version()
144
+ if db_version != rcdb.SQL_SCHEMA_VERSION:
145
+ message = "SQL schema version doesn't match. " \
146
+ "Retrieved DB version is {0}, required version is {1}. " \
147
+ .format(db_version, rcdb.SQL_SCHEMA_VERSION)
148
+ if db_version is not None and db_version < rcdb.SQL_SCHEMA_VERSION:
149
+ message += "The database schema is older than this RCDB version. " \
150
+ "Run 'rcdb db update' to migrate the database to the current schema."
151
+ else:
152
+ message += "The database schema is newer than this RCDB version. " \
153
+ "Update your RCDB installation to match the database."
154
+ raise rcdb.errors.SqlSchemaVersionError(message)
155
+ except Exception:
156
+ self.close()
157
+ raise
148
158
 
149
159
  # ------------------------------------------------
150
160
  # Closes connection to data
151
161
  # ------------------------------------------------
152
- def disconnect(self):
162
+ def close(self):
153
163
  """Closes connection to database.
154
164
 
155
165
  Closes the ORM session and disposes the SQLAlchemy engine so the
@@ -164,6 +174,11 @@ class RCDBProvider(object):
164
174
  if self.engine is not None:
165
175
  self.engine.dispose()
166
176
 
177
+ # ``disconnect`` is the historical name for ``close``; kept as an alias so
178
+ # existing RCDB code and users keep working while new code standardizes on
179
+ # ``close()`` (the conventional name other resource-holding objects use).
180
+ disconnect = close
181
+
167
182
  # -------------------------------------------------
168
183
  # indicates ether the connection is open or not
169
184
  # -------------------------------------------------
@@ -1,5 +1,5 @@
1
1
  # String version. Used both for setup.py and for internal code
2
- version = '2.3.2'
2
+ version = '2.4.0'
3
3
 
4
4
  # version as the tuple of ints
5
5
  version_tuple = tuple(int(val) for val in version.split('.'))
@@ -4,6 +4,7 @@ from datetime import datetime
4
4
  from urllib.parse import urlparse
5
5
 
6
6
  from flask import Flask, render_template, g, request, url_for
7
+ from sqlalchemy.exc import SQLAlchemyError
7
8
  from sqlalchemy.orm import subqueryload
8
9
 
9
10
  import rcdb
@@ -60,8 +61,13 @@ app = Flask(__name__, template_folder=template_folder)
60
61
 
61
62
  app.config.from_object(__name__)
62
63
 
63
- @app.before_request
64
- def before_request():
64
+ def _resolve_active_database():
65
+ """Pick the connection string for this request and expose selector state.
66
+
67
+ Sets ``g.active_db_name`` / ``g.available_databases`` (used by the navbar DB
68
+ selector) and returns the connection string to connect to. Does not touch
69
+ the database -- only reads config and the ``rcdb_database`` cookie.
70
+ """
65
71
  available_dbs = app.config.get("AVAILABLE_DATABASES", {})
66
72
 
67
73
  if available_dbs:
@@ -90,24 +96,93 @@ def before_request():
90
96
  # No default set, use first available
91
97
  active_name = next(iter(available_dbs))
92
98
 
93
- connection_string = available_dbs[active_name]
94
99
  g.active_db_name = active_name
95
100
  g.available_databases = available_dbs
96
- else:
97
- # Original single-database behavior
98
- connection_string = app.config["SQL_CONNECTION_STRING"]
99
- g.active_db_name = None
100
- g.available_databases = {}
101
-
102
- g.tdb = rcdb.ConfigurationProvider()
103
- g.tdb.connect(connection_string)
101
+ return available_dbs[active_name]
102
+
103
+ # Original single-database behavior
104
+ g.active_db_name = None
105
+ g.available_databases = {}
106
+ return app.config["SQL_CONNECTION_STRING"]
107
+
108
+
109
+ @app.before_request
110
+ def before_request():
111
+ # Never gate static assets on the database -- the graceful DB-error page
112
+ # below still needs its CSS/JS to load when the DB is unreachable.
113
+ if request.endpoint == "static":
114
+ return None
115
+
104
116
  app.jinja_env.globals['datetime_now'] = datetime.now
105
117
 
118
+ # Resolve the selector state first so the navbar (and the DB-error page)
119
+ # can render even if the connection below fails.
120
+ connection_string = _resolve_active_database()
121
+
122
+ # Connect fresh, per request. A DB access/connection failure (access
123
+ # denied, too many connections, timeout, host down, ...) must degrade
124
+ # gracefully instead of surfacing a raw 500 -- and must not poison any
125
+ # cookie or cached engine, so a later reload recovers once the DB is back.
126
+ try:
127
+ g.tdb = rcdb.ConfigurationProvider()
128
+ g.tdb.connect(connection_string)
129
+ except SQLAlchemyError:
130
+ return _render_db_error(connection_string)
131
+
132
+ return None
133
+
134
+
135
+ def _render_db_error(connection_string):
136
+ """Log the full failure detail and render the sanitized DB-error page.
137
+
138
+ The *log* gets everything an admin needs (which database, the underlying
139
+ pymysql/SQLAlchemy error code + message, and the stack trace). The *page*
140
+ gets none of it -- no connection string, host, user, or exception text --
141
+ only a red "DB connection failure" strip with the top menu (and the DB
142
+ selector) intact so the visitor can switch to a working database.
143
+ """
144
+ logger.exception(
145
+ "DB connection/access failure for database '%s' (%s); serving graceful "
146
+ "error page instead of a 500.",
147
+ getattr(g, "active_db_name", None) or "<default>",
148
+ _connection_hint(connection_string),
149
+ )
150
+
151
+ # Drop the half-built provider so its engine/pool is released now rather
152
+ # than lingering as a broken cached connection -- the teardown below only
153
+ # disposes fully-connected providers.
154
+ broken = getattr(g, "tdb", None)
155
+ if broken is not None:
156
+ try:
157
+ broken.close()
158
+ except Exception:
159
+ logger.debug("Ignoring error while disposing failed DB engine.",
160
+ exc_info=True)
161
+ g.tdb = None
162
+
163
+ # 503 Service Unavailable: the failure is transient-friendly (e.g. "too
164
+ # many connections" recovers), so signal a retryable condition rather than
165
+ # a 200 or a permanent error. A plain reload after recovery renders fine.
166
+ return render_template("db_error.html"), 503
167
+
168
+
169
+ @app.errorhandler(SQLAlchemyError)
170
+ def handle_db_error(error):
171
+ """Catch DB errors raised while a view runs (not just at connect time).
172
+
173
+ ``before_request`` already handles the common case (``connect()`` exercises
174
+ the connection via the schema-version check), but a lazily-issued query in
175
+ a view can still raise. Route those through the same graceful page instead
176
+ of a 500. ``g.active_db_name`` / ``g.available_databases`` were set by
177
+ ``before_request`` before the failure, so the selector still renders.
178
+ """
179
+ return _render_db_error(getattr(g.get("tdb", None), "connection_string", ""))
180
+
106
181
 
107
182
  @app.teardown_request
108
183
  def teardown_request(exception):
109
- tdb = getattr(g, 'db', None)
110
- if tdb:
184
+ tdb = getattr(g, 'tdb', None)
185
+ if tdb is not None:
111
186
  tdb.close()
112
187
 
113
188
 
@@ -157,8 +157,11 @@ def elog(run_number):
157
157
  from urllib.request import urlopen
158
158
  from urllib.error import HTTPError
159
159
  try:
160
- elog_json = urlopen('https://logbooks.jlab.org/api/elog/entries?book=hdrun&title=Run_{}&limit=1'
161
- .format(run_number)).read()
160
+ # Context-manage the response so the underlying socket is closed
161
+ # deterministically instead of leaking until GC.
162
+ with urlopen('https://logbooks.jlab.org/api/elog/entries?book=hdrun&title=Run_{}&limit=1'
163
+ .format(run_number)) as response:
164
+ elog_json = response.read()
162
165
  except HTTPError as e:
163
166
  return jsonify(stat=str(e.code))
164
167
 
@@ -0,0 +1,20 @@
1
+ {% extends "layouts/base.html" %}
2
+
3
+ {% block title %}Database unavailable{% endblock %}
4
+
5
+ {#
6
+ Graceful page shown when the selected database cannot be reached or used
7
+ (access denied, too many connections, timeout, host down, ...). The top
8
+ menu -- including the database selector -- is inherited from base.html so
9
+ the visitor can switch to a working database and reload. No connection
10
+ string, host, user, or exception text is exposed here; the full detail is
11
+ written to the application log instead.
12
+ #}
13
+
14
+ {% block body %}
15
+ <div class="alert alert-danger"
16
+ role="alert"
17
+ style="text-align: center; font-weight: bold; margin-top: 20px;">
18
+ DB connection failure
19
+ </div>
20
+ {% endblock %}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rcdb
3
- Version: 2.3.2
3
+ Version: 2.4.0
4
4
  Summary: RCDB - Run Conditions DataBase
5
5
  Author-email: Dmitry Romanov <romanov@jlab.org>
6
6
  License-Expression: MIT
@@ -155,6 +155,7 @@ rcdb/web/static/js/webintellisense-textbox.js
155
155
  rcdb/web/static/js/webintellisense.js
156
156
  rcdb/web/templates/404.html
157
157
  rcdb/web/templates/custom_column_run_table.html
158
+ rcdb/web/templates/db_error.html
158
159
  rcdb/web/templates/default_run_table.html
159
160
  rcdb/web/templates/index.html
160
161
  rcdb/web/templates/layout.html
@@ -217,4 +218,5 @@ tests/test_save_files.py
217
218
  tests/test_select_runs.py
218
219
  tests/test_select_values.py
219
220
  tests/test_sql_schema_version.py
220
- tests/test_value_comparison.py
221
+ tests/test_value_comparison.py
222
+ tests/test_web_db_failure.py
@@ -0,0 +1,119 @@
1
+ """Web DB-failure handling.
2
+
3
+ A database selected in the navbar (stored in the ``rcdb_database`` cookie) may
4
+ be unreachable or access-denied server-side (e.g. the MariaDB ``rcdb`` user has
5
+ no GRANT on ``xem2``/``pionct`` -> pymysql ``(1044, "Access denied ...")``).
6
+
7
+ These tests pin the required behaviour:
8
+
9
+ * such a failure renders a graceful page (top menu + DB selector + a red
10
+ "DB connection failure" strip), never a raw 500;
11
+ * no sensitive detail (connection string, host, user, error code/text) leaks
12
+ into the page, while the full exception is written to the application log;
13
+ * the failure is not sticky -- reloading after the DB recovers, or switching to
14
+ a working DB, renders normally with no poisoned cookie or cached engine.
15
+ """
16
+
17
+ import unittest
18
+
19
+ from sqlalchemy.exc import OperationalError
20
+
21
+ import rcdb
22
+ import rcdb.provider
23
+ import rcdb.web
24
+
25
+
26
+ class TestWebDbFailure(unittest.TestCase):
27
+ def setUp(self):
28
+ self.app = rcdb.web.app
29
+ self.app.testing = True
30
+
31
+ # Save config we mutate so other tests see the app untouched.
32
+ self._saved = {k: self.app.config.get(k) for k in
33
+ ("AVAILABLE_DATABASES", "DEFAULT_DATABASE")}
34
+ self.app.config["AVAILABLE_DATABASES"] = {
35
+ "good": "sqlite:///good",
36
+ "bad": "sqlite:///bad",
37
+ }
38
+ self.app.config["DEFAULT_DATABASE"] = "sqlite:///good"
39
+
40
+ self.client = self.app.test_client()
41
+
42
+ # Replace connect(): raise an access-denied-style OperationalError for the
43
+ # "bad" DB (while it is "down"); connect a fresh in-memory schema for the
44
+ # "good" one. ``bad_down`` flips to False to simulate recovery.
45
+ self.bad_down = True
46
+ self._orig_connect = rcdb.ConfigurationProvider.connect
47
+ test = self
48
+
49
+ def fake_connect(provider, connection_string="", check_version=True):
50
+ if "bad" in connection_string and test.bad_down:
51
+ raise OperationalError(
52
+ "SELECT 1", {},
53
+ Exception("(1044, \"Access denied for user 'rcdb'@'%' "
54
+ "to database 'bad'\")"))
55
+ test._orig_connect(provider, "sqlite://", check_version=False)
56
+ rcdb.provider.destroy_all_create_schema(provider)
57
+
58
+ rcdb.ConfigurationProvider.connect = fake_connect
59
+
60
+ def tearDown(self):
61
+ rcdb.ConfigurationProvider.connect = self._orig_connect
62
+ for k, v in self._saved.items():
63
+ if v is None:
64
+ self.app.config.pop(k, None)
65
+ else:
66
+ self.app.config[k] = v
67
+
68
+ def _get(self, path="/", db=None):
69
+ # Fresh client per call so the cookie jar carries only what we set here
70
+ # (the Werkzeug test client ignores a raw Cookie header, hence set_cookie).
71
+ client = self.app.test_client()
72
+ if db:
73
+ client.set_cookie("rcdb_database", db)
74
+ return client.get(path)
75
+
76
+ def test_failure_renders_graceful_page_not_500(self):
77
+ with self.assertLogs("rcdb.web", level="ERROR") as cm:
78
+ resp = self._get("/", db="bad")
79
+
80
+ # Graceful, retryable status -- never a raw 500.
81
+ self.assertEqual(resp.status_code, 503)
82
+ self.assertNotEqual(resp.status_code, 500)
83
+
84
+ body = resp.get_data(as_text=True)
85
+ # Red strip + intact top menu incl. the DB selector to switch away.
86
+ self.assertIn("DB connection failure", body)
87
+ self.assertIn('id="dbSelector"', body)
88
+ self.assertIn(">bad<", body)
89
+ self.assertIn(">good<", body)
90
+
91
+ # No sensitive detail leaks into the page ...
92
+ for secret in ("1044", "Access denied", "sqlite:///bad", "rcdb@",
93
+ "OperationalError", "Traceback"):
94
+ self.assertNotIn(secret, body)
95
+
96
+ # ... but the log has the full picture (db name + underlying error).
97
+ logtext = "\n".join(cm.output)
98
+ self.assertIn("bad", logtext)
99
+ self.assertIn("1044", logtext)
100
+
101
+ def test_reload_recovers_after_db_comes_back(self):
102
+ # Same cookie, DB down -> graceful 503.
103
+ self.assertEqual(self._get("/", db="bad").status_code, 503)
104
+
105
+ # DB recovers; a plain reload (identical cookie) must render normally.
106
+ self.bad_down = False
107
+ resp = self._get("/", db="bad")
108
+ self.assertEqual(resp.status_code, 200)
109
+ self.assertNotIn("DB connection failure", resp.get_data(as_text=True))
110
+
111
+ def test_switching_to_working_db_recovers(self):
112
+ self.assertEqual(self._get("/", db="bad").status_code, 503)
113
+ resp = self._get("/", db="good")
114
+ self.assertEqual(resp.status_code, 200)
115
+ self.assertNotIn("DB connection failure", resp.get_data(as_text=True))
116
+
117
+
118
+ if __name__ == "__main__":
119
+ unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes