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.
- {rcdb-2.3.2/rcdb.egg-info → rcdb-2.4.0}/PKG-INFO +1 -1
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/app_context.py +1 -1
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/db.py +4 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/rp.py +3 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/provider.py +28 -13
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/version.py +1 -1
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/__init__.py +88 -13
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/runs.py +5 -2
- rcdb-2.4.0/rcdb/web/templates/db_error.html +20 -0
- {rcdb-2.3.2 → rcdb-2.4.0/rcdb.egg-info}/PKG-INFO +1 -1
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/SOURCES.txt +3 -1
- rcdb-2.4.0/tests/test_web_db_failure.py +119 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/MANIFEST.in +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/README.md +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/pyproject.toml +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/__init__.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/__main__.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/alias.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/__init__.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/__main__.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/add.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/app.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/context.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/file.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/info.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/ls.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/repair/__init__.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/repair/evio_files.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/run.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/select.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/test_data.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/cli/web.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/coda_parser.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/config_parser.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/constants.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/errors.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/file_archiver.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/halld_daq_config_parser.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/lexer.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/log_format.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/model.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/stopwatch.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/update.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/__main__.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/__init__.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/conditions.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/files.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/logs.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/select_values.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/modules/statistics.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/pagination.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/run_table.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.bootstrap.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.bootstrap.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.foundation.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.foundation.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.jqueryui.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.jqueryui.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/jquery.dataTables.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/jquery.dataTables.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/jquery.dataTables_themeroller.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_asc.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_asc_disabled.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_both.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_desc.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_desc_disabled.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.bootstrap.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.bootstrap.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.foundation.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.foundation.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.jqueryui.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.jqueryui.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/jquery.dataTables.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/jquery.dataTables.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.bootstrap.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.bootstrap.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.dataTables.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.dataTables.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.foundation.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.foundation.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.jqueryui.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.jqueryui.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/dataTables.responsive.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/dataTables.responsive.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.bootstrap.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.bootstrap.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.foundation.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.foundation.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.jqueryui.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/responsive.jqueryui.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.bootstrap.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.bootstrap.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.dataTables.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.dataTables.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.foundation.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.foundation.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.jqueryui.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/css/rowReorder.jqueryui.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/js/dataTables.rowReorder.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/RowReorder-1.1.0/js/dataTables.rowReorder.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/datatables.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/datatables.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/datatables.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/datatables.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap-theme.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap-theme.css.map +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap-theme.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap-theme.min.css.map +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap.css.map +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/bootstrap.min.css.map +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/buttons.dataTables.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/dataTables.tableTools.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery-ui.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery-ui.structure.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery-ui.theme.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery.dataTables.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery.dataTables.yadcf.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/jquery.jsonview.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/main.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/select.dataTables.min.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/css/webintellisense.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/favicon.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.eot +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.svg +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.ttf +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.woff +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/fonts/glyphicons-halflings-regular.woff2 +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/humans.txt +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/img/glyphicons-halflings-white.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/img/glyphicons-halflings.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/img/logo.png +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/bootstrap.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/bootstrap.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/clipboard.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/copy_clipboard.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/dataTables.buttons.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/dataTables.select.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery-2.1.0.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery-2.1.4.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery-ui.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery.dataTables.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery.dataTables.yadcf.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery.flot.min.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/jquery.jsonview.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/npm.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/webintellisense-ace.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/webintellisense-codemirror.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/webintellisense-textbox.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/js/webintellisense.js +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/robots.txt +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/style.css +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/404.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/admin/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/admin/layout.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/admin/user.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/admin/users.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/conditions/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/custom_column_run_table.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/default_run_table.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/errors/forbidden_page.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/errors/layout.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/errors/page_not_found.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/errors/server_error.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/files/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/files/info.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/files/not_found.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/create_profile.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/footers/help.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/login.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/login_openid.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/reauth.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/reset_password.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/search.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/frontend/signup.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/layout.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/layouts/base.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/layouts/base2.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/login.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/logs/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_ask_user_to_update_ie.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_form.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_google_analytics.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_misc.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/macros/_reset_password.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/render_pagination.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/run_search_box.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/conditions.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/example.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/info.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/runs/not_found.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/select_values/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/settings/layout.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/settings/password.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/settings/profile.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/show_entries.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/statistics/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/user/index.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/templates/user/profile.html +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/dependency_links.txt +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/entry_points.txt +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/requires.txt +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/rcdb.egg-info/top_level.txt +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/setup.cfg +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_add_condition.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_add_file.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_add_type.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_file.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_cli_run.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_coda_parser.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_conditions.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_config_parser.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_get_run_periods.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_get_runs.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_halld_run_config_parser.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_save_files.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_select_runs.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_select_values.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_sql_schema_version.py +0 -0
- {rcdb-2.3.2 → rcdb-2.4.0}/tests/test_value_comparison.py +0 -0
|
@@ -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.
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
message
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
# -------------------------------------------------
|
|
@@ -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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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, '
|
|
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
|
-
|
|
161
|
-
|
|
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 %}
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.bootstrap.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/dataTables.jqueryui.css
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/css/jquery.dataTables.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_asc_disabled.png
RENAMED
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_both.png
RENAMED
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/images/sort_desc.png
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.bootstrap.js
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.foundation.js
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/dataTables.jqueryui.js
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/jquery.dataTables.js
RENAMED
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/DataTables-1.10.10/js/jquery.dataTables.min.js
RENAMED
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.bootstrap.css
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.dataTables.css
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.foundation.css
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/css/responsive.jqueryui.css
RENAMED
|
File without changes
|
|
File without changes
|
{rcdb-2.3.2 → rcdb-2.4.0}/rcdb/web/static/DataTables/Responsive-2.0.0/js/dataTables.responsive.js
RENAMED
|
File without changes
|