dcoraid 1.1.2__tar.gz → 1.2.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.
- {dcoraid-1.1.2 → dcoraid-1.2.0}/.github/workflows/check.yml +1 -1
- {dcoraid-1.1.2 → dcoraid-1.2.0}/.gitignore +5 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/CHANGELOG +85 -1
- {dcoraid-1.1.2 → dcoraid-1.2.0}/MANIFEST.in +1 -1
- {dcoraid-1.1.2/dcoraid.egg-info → dcoraid-1.2.0}/PKG-INFO +1 -2
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/hook-dcoraid.py +1 -1
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/_version.py +3 -3
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/api/__init__.py +1 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/api/ckan_api.py +47 -12
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/api/dataset.py +42 -133
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/api/errors.py +0 -12
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/api/s3_api.py +73 -23
- dcoraid-1.2.0/dcoraid/api/users.py +60 -0
- dcoraid-1.2.0/dcoraid/bagit/archive.py +354 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/bagit/info.py +2 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/bagit/manifest.py +18 -11
- dcoraid-1.2.0/dcoraid/cli/__init__.py +3 -0
- dcoraid-1.2.0/dcoraid/cli/task_archive_circle.py +145 -0
- dcoraid-1.1.2/dcoraid/cli.py → dcoraid-1.2.0/dcoraid/cli/task_upload_task.py +46 -25
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/dbmodel/db_api.py +130 -34
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/dbmodel/db_api_cached.py +34 -8
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/dbmodel/db_core.py +32 -23
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/dbmodel/extract.py +10 -3
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/dbmodel/meta_cache.py +20 -22
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/dbmodel/meta_cache_datasets.py +3 -2
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/dbmodel/meta_cache_sqlite.py +2 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/download/job.py +21 -15
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/download/queue.py +3 -4
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/api.py +39 -1
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/dbview/filter_base.py +28 -25
- dcoraid-1.2.0/dcoraid/gui/dbview/filter_base_ui.py +81 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/dbview/filter_chain.py +31 -32
- dcoraid-1.2.0/dcoraid/gui/dbview/filter_chain_ui.py +62 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/dbview/filter_views.py +26 -26
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/logs/widget_log.py +15 -16
- dcoraid-1.2.0/dcoraid/gui/logs/widget_log_ui.py +47 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/main.py +54 -32
- dcoraid-1.2.0/dcoraid/gui/main_ui.py +166 -0
- dcoraid-1.1.2/dcoraid/gui/panel_downloads/widget_download.py → dcoraid-1.2.0/dcoraid/gui/panel_downloads/download_table.py +2 -146
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_downloads/widget_actions_download.py +21 -22
- dcoraid-1.2.0/dcoraid/gui/panel_downloads/widget_actions_download_ui.py +63 -0
- dcoraid-1.2.0/dcoraid/gui/panel_downloads/widget_download.py +149 -0
- dcoraid-1.2.0/dcoraid/gui/panel_downloads/widget_download_ui.py +67 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_find_data/widget_find_data.py +18 -19
- dcoraid-1.2.0/dcoraid/gui/panel_find_data/widget_find_data_ui.py +75 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_maintenance/widget_maintenance.py +11 -13
- dcoraid-1.2.0/dcoraid/gui/panel_maintenance/widget_maintenance_ui.py +75 -0
- dcoraid-1.2.0/dcoraid/gui/panel_my_data/dlg_share.py +120 -0
- dcoraid-1.2.0/dcoraid/gui/panel_my_data/dlg_share_ui.py +68 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_my_data/filter_chain_my_data.py +15 -15
- dcoraid-1.2.0/dcoraid/gui/panel_my_data/user_table.py +122 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_my_data/widget_my_data.py +20 -20
- dcoraid-1.2.0/dcoraid/gui/panel_my_data/widget_my_data_ui.py +65 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/dlg_upload.py +93 -97
- dcoraid-1.2.0/dcoraid/gui/panel_uploads/dlg_upload_ui.py +371 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/resources_model.py +1 -1
- dcoraid-1.2.0/dcoraid/gui/panel_uploads/upload_table.py +196 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/widget_actions_upload.py +27 -28
- dcoraid-1.2.0/dcoraid/gui/panel_uploads/widget_actions_upload_ui.py +63 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/widget_schema.py +2 -2
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/widget_supplement_item.py +80 -81
- dcoraid-1.2.0/dcoraid/gui/panel_uploads/widget_supplement_item_ui.py +128 -0
- dcoraid-1.2.0/dcoraid/gui/panel_uploads/widget_supplement_tags_ui.py +72 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/widget_upload.py +28 -218
- dcoraid-1.2.0/dcoraid/gui/panel_uploads/widget_upload_ui.py +142 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/preferences/dlg_preferences.py +57 -61
- dcoraid-1.2.0/dcoraid/gui/preferences/dlg_preferences_ui.py +240 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/settings.py +2 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/status_widget.py +10 -2
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/wizard_bagit/__init__.py +31 -32
- dcoraid-1.2.0/dcoraid/gui/wizard_bagit/wizard_bagit_ui.py +166 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/wizard_init/__init__.py +30 -50
- dcoraid-1.2.0/dcoraid/gui/wizard_init/wizard_init_ui.py +239 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/collect_icons.py +3 -3
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/upload/job.py +18 -17
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/upload/queue.py +5 -7
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/upload/task.py +3 -4
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/worker/daemon.py +4 -2
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/worker/kthread.py +1 -1
- {dcoraid-1.1.2 → dcoraid-1.2.0/dcoraid.egg-info}/PKG-INFO +1 -2
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid.egg-info/SOURCES.txt +55 -21
- dcoraid-1.2.0/dcoraid.egg-info/entry_points.txt +4 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid.egg-info/requires.txt +0 -1
- dcoraid-1.2.0/dcoraid.egg-info/scm_file_list.json +303 -0
- dcoraid-1.2.0/dcoraid.egg-info/scm_version.json +8 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/pyproject.toml +23 -3
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/common.py +116 -8
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/conftest.py +4 -2
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_api_base.py +22 -0
- dcoraid-1.2.0/tests/test_api_s3_retry.py +83 -0
- dcoraid-1.2.0/tests/test_api_users.py +54 -0
- dcoraid-1.2.0/tests/test_bagit_archive.py +211 -0
- dcoraid-1.2.0/tests/test_cli_archive_circle.py +140 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_cli_upload_task.py +43 -1
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_dbmodel_api.py +84 -29
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_dbmodel_api_cache.py +15 -20
- dcoraid-1.2.0/tests/test_dbmodel_api_lists.py +165 -0
- dcoraid-1.2.0/tests/test_dbmodel_api_search.py +89 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_dbmodel_extract.py +27 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_dbmodel_meta_cache.py +54 -0
- dcoraid-1.2.0/tests/test_dbmodel_meta_cache_datasets.py +36 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_gui.py +96 -65
- dcoraid-1.2.0/tests/test_gui_api.py +84 -0
- dcoraid-1.2.0/tests/test_gui_find_data.py +40 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_upload_task.py +1 -1
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_downloads/widget_download.ui +1 -1
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_my_data/dlg_share.ui +1 -1
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_uploads/widget_upload.ui +1 -1
- dcoraid-1.2.0/ui/run_uic.sh +18 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/wizard_init/wizard_init.ui +1 -1
- dcoraid-1.1.2/dcoraid/bagit/archive.py +0 -221
- dcoraid-1.1.2/dcoraid/gui/panel_my_data/dlg_share.py +0 -235
- dcoraid-1.1.2/dcoraid/gui/wizard_init/dcordevapi.ui +0 -190
- dcoraid-1.1.2/dcoraid.egg-info/entry_points.txt +0 -3
- dcoraid-1.1.2/tests/test_bagit_archive.py +0 -78
- {dcoraid-1.1.2 → dcoraid-1.2.0}/.github/workflows/deploy_github.yml +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/.github/workflows/deploy_github_signed_windows.yml +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/.github/workflows/deploy_pypi.yml +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/LICENSE +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/README.rst +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/DCOR-Aid.icns +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/DCOR-Aid.ico +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/DCOR-AidLauncher.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/Readme.md +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/macos_DCOR-Aid.spec +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/macos_build_app.sh +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/macos_build_requirements.txt +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/win_DCOR-Aid.spec +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/win_build_requirements.txt +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/win_dcoraid.iss_dummy +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/build-recipes/win_make_iss.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/__main__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/bagit/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/common.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/dbmodel/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/download/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/download/task.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/dbview/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/dbview/drag_table_widget.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/logs/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_downloads/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_find_data/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_maintenance/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_my_data/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/circle_mgr.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/panel_uploads/resource_schema_preset.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/preferences/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/tools/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/tools/wait_cursor.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/gui/updater.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/COPYING-ICONS +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/COPYING.LIB +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/README +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/application-exit.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/dialog-cancel.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/dialog-close.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/dialog-messages.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/dialog-ok-apply.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/dialog-ok.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/document-open-folder.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/document-open.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/document-save.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/documentinfo.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/edit-clear.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/gtk-preferences.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/messagebox_warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/16/preferences-activities.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/application-exit.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/dialog-cancel.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/dialog-close.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/dialog-messages.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/dialog-ok-apply.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/dialog-ok.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/document-open-folder.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/document-open.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/document-save.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/documentinfo.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/edit-clear.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/gtk-preferences.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/messagebox_warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/22/preferences-activities.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/application-exit.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/dialog-cancel.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/dialog-close.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/dialog-messages.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/dialog-ok-apply.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/dialog-ok.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/document-open-folder.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/document-open.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/document-save.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/documentinfo.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/edit-clear.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/gtk-preferences.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/messagebox_warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/24/preferences-activities.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/application-exit.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/dialog-cancel.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/dialog-messages.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/dialog-ok-apply.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/dialog-ok.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/document-open-folder.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/document-open.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/document-save.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/actions/32/gtk-preferences.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/16/dialog-error.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/16/dialog-information.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/16/dialog-question.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/16/dialog-warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/22/dialog-error.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/22/dialog-information.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/22/dialog-question.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/22/dialog-warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/24/dialog-error.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/24/dialog-information.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/24/dialog-question.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/24/dialog-warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/32/dialog-warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/48/dialog-warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/64/dialog-error.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/64/dialog-information.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/64/dialog-question.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/breeze/status/64/dialog-warning.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/dcoraid/dcor.png +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/LICENSE.txt +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/angle-down.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/angles-down.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/ban.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/book.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/broom.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/check-double.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/child.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/circle.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/clinic-medical.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/code-branch.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/cogs.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/download.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/exclamation-triangle.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/eye.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/filter.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/folder-tree.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/folder.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/globe.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/hat-wizard.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/hourglass.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/info.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/magnifying-glass.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/pencil.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/people-arrows.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/people-carry.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/puzzle-piece.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/redo.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/scroll.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/share-nodes.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/shipping-fast.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/slash.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/street-view.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/tag.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/trash-alt.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/trash-can-arrow-up.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/trash.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/undo.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/upload.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/user-lock.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/user-times.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/user.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/fontawesome/solid/users.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon-theme/index.theme +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/icon.png +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/img/splash.png +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/loggers.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/upload/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid/worker/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid.egg-info/dependency_links.txt +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/dcoraid.egg-info/top_level.txt +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/docs/artwork/Readme.md +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/docs/artwork/dcoraid_icon.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/docs/artwork/dcoraid_splash.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/docs/notes/software_design.svg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/setup.cfg +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/.gitignore +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/__init__.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/data/calibration_beads_47.rtdc +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/data/calibration_beads_47_nocomp.rtdc +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/data/text_file.txt +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/requirements.txt +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_api_dataset.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_api_s3.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_bagit_info.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_bagit_manifest.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_common.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_dbmodel_meta_cache_sqlite.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_download_job.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_download_queue.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_download_task.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_gui_updater.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_upload_job.py +0 -0
- {dcoraid-1.1.2 → dcoraid-1.2.0}/tests/test_upload_queue.py +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/dbview/filter_base.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/dbview/filter_chain.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/logs/widget_log.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/main.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_downloads/widget_actions_download.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_find_data/widget_find_data.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_maintenance/widget_maintenance.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_my_data/widget_my_data.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_uploads/dlg_upload.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_uploads/widget_actions_upload.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_uploads/widget_supplement_item.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/panel_uploads/widget_supplement_tags.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/preferences/dlg_preferences.ui +0 -0
- {dcoraid-1.1.2/dcoraid/gui → dcoraid-1.2.0/ui}/wizard_bagit/wizard_bagit.ui +0 -0
|
@@ -57,7 +57,7 @@ jobs:
|
|
|
57
57
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
58
58
|
DCOR_API_KEY: ${{ secrets.DCOR_API_KEY }}
|
|
59
59
|
run: |
|
|
60
|
-
coverage run --source=dcoraid -m pytest tests
|
|
60
|
+
coverage run --source=dcoraid -m pytest --durations=20 tests
|
|
61
61
|
- name: Lint with flake8
|
|
62
62
|
run: |
|
|
63
63
|
flake8 --exclude _version.py .
|
|
@@ -111,6 +111,7 @@ pytest-failures.txt
|
|
|
111
111
|
_version.py
|
|
112
112
|
|
|
113
113
|
_dev_*.py
|
|
114
|
+
_dev_*
|
|
114
115
|
|
|
115
116
|
# QtCreator temporary files
|
|
116
117
|
*.ui.autosave
|
|
@@ -118,3 +119,7 @@ _dev_*.py
|
|
|
118
119
|
|
|
119
120
|
# line_profiler
|
|
120
121
|
*.lprof
|
|
122
|
+
|
|
123
|
+
# API keys of (auto-generated) test users
|
|
124
|
+
tests/api_key
|
|
125
|
+
tests/api_key_2
|
|
@@ -1,3 +1,87 @@
|
|
|
1
|
+
1.2.0
|
|
2
|
+
- feat: new CLI `dcoraid-archive-circle` for archiving an entire circle
|
|
3
|
+
in the BagIt format
|
|
4
|
+
- fix: `dcoraid-upload-task` ignored the `--cache_dir` argument
|
|
5
|
+
- fix: `dcoraid-upload-task` SERVER argument had a non-functional default
|
|
6
|
+
- fix: cleanup `dcoraid-upload-task` with ruff
|
|
7
|
+
- fix: `dcoraid-upload-task` silenced exceptions by returning in `finally`
|
|
8
|
+
- fix: BagIt archiving silently created bags for resources whose download
|
|
9
|
+
or verification failed
|
|
10
|
+
- fix: BagIt archives of recently uploaded datasets could be invalid,
|
|
11
|
+
because the condensed file or the SHA256 sum of a resource was missing
|
|
12
|
+
in the manifest
|
|
13
|
+
- fix: datasets inserted in bulk into the local database were not
|
|
14
|
+
registered with their circle, so updating the database again in the
|
|
15
|
+
same session failed or created duplicate entries
|
|
16
|
+
- fix: comparing the cached dataset list with a list of a different
|
|
17
|
+
length returned True or raised an IndexError
|
|
18
|
+
- fix: dataset search via the API returned more results than `limit`
|
|
19
|
+
for limits above 1000 and requested more than 1000 rows at once
|
|
20
|
+
- fix: dataset search via the API looped forever when datasets were
|
|
21
|
+
deleted in-between paginated requests
|
|
22
|
+
- fix: only the first 25 circles and collections on a server were
|
|
23
|
+
considered for the local database and for shared datasets (CKAN
|
|
24
|
+
limits `all_fields` list results to 25 items)
|
|
25
|
+
- fix: datasets shared with the user were searched in all circles
|
|
26
|
+
and collections instead of only those the user is a member of
|
|
27
|
+
- fix: "Find data" tab failed to fetch the site title when the server
|
|
28
|
+
was stored without scheme (e.g. in the playground)
|
|
29
|
+
- fix: retry twice on read errors
|
|
30
|
+
- enh: automatically create a new DCOR-dev user in the playground user
|
|
31
|
+
scenario if the previous user was purged
|
|
32
|
+
- enh: `dcoraid-upload-task` reads the API key from the `DCOR_API_KEY`
|
|
33
|
+
environment variable if it is not passed as an argument
|
|
34
|
+
- enh: retry downloading resources and archiving datasets in BagIt
|
|
35
|
+
archiving when transient errors occur
|
|
36
|
+
- enh: new methods `get_circles_user` and `get_collections_user`
|
|
37
|
+
for database interrogators
|
|
38
|
+
- enh: `get_users` of database interrogators accepts `query` and `limit`
|
|
39
|
+
(only up to `limit` users are returned, by default 20)
|
|
40
|
+
- enh: wait between retries of failed S3 upload requests (5 s, doubling
|
|
41
|
+
with each retry) instead of retrying immediately
|
|
42
|
+
- enh: use the API action `dcor_group_list_for_user` (ckanext-dcor_schemas
|
|
43
|
+
1.2.0) for listing the collections of a user, with a fallback for
|
|
44
|
+
older servers (#108)
|
|
45
|
+
- enh: do not retry API requests when the server name cannot be
|
|
46
|
+
resolved (DCOR-Aid froze for minutes on Windows with a wrong server)
|
|
47
|
+
- ref: remove the fallback to uploading resources via CKAN
|
|
48
|
+
(`resource_add_upload_legacy_indirect_ckan`); DCOR only accepts
|
|
49
|
+
uploads to S3
|
|
50
|
+
- ref: convert `dcoraid.cli` module into a package; the upload task CLI
|
|
51
|
+
now lives in `dcoraid.cli.task_upload_task`
|
|
52
|
+
- ref: use `super()`, PEP 604 optional type hints, and builtin/
|
|
53
|
+
`collections.abc` types instead of deprecated `typing` imports
|
|
54
|
+
- ref: use `from __future__ import annotations` in all modules with
|
|
55
|
+
type annotations and `Callable` instead of `callable` in type hints
|
|
56
|
+
- ref: fix various ruff findings
|
|
57
|
+
- ref: `search_dataset` now accepts `query` instead of `text` globally
|
|
58
|
+
- ref: correct return type hint of `meta_cache._values_only`
|
|
59
|
+
- ref: `DBExtract.__add__` accepts lists like `DBExtract.__iadd__`
|
|
60
|
+
- ref: correct type hints for `CKANAPI.get` kwargs and CLI exit status
|
|
61
|
+
- ref: migrate GUI design to the one of DCscope: all .ui files are in
|
|
62
|
+
the top-level `ui` directory and converted with `ui/run_uic.sh` to
|
|
63
|
+
`*_ui.py` modules, which are used via `self.ui`
|
|
64
|
+
- ref: move `UploadTableWidget`, `DownloadTableWidget`, and
|
|
65
|
+
`UserTableWidget` to separate modules
|
|
66
|
+
- ref: fix remaining ruff findings and add missing type annotations
|
|
67
|
+
- ref: new `dcoraid.api.create_dcor_dev_user` and
|
|
68
|
+
`dcoraid.api.is_valid_api_key` used by the setup wizard and the tests
|
|
69
|
+
- setup: add tool.ruff.lint section to pyproject.toml
|
|
70
|
+
- setup: remove `requests_toolbelt` dependency (was only used for
|
|
71
|
+
uploads via CKAN)
|
|
72
|
+
- tests: automatically create test users on DCOR-dev if `DCOR_API_KEY`
|
|
73
|
+
is not set or `tests/api_key` is missing or invalid
|
|
74
|
+
- tests: replace tests that required access to the `figshare-import`
|
|
75
|
+
circle with a circle, collection, and dataset shared by a second
|
|
76
|
+
automatically created test user
|
|
77
|
+
- tests: clear persistent upload jobs before and after each GUI test,
|
|
78
|
+
so that new main windows do not upload jobs of previous tests again
|
|
79
|
+
- tests: let name resolution fail immediately in
|
|
80
|
+
`test_gui_start_with_bad_server` (took 20 minutes on Windows)
|
|
81
|
+
1.1.3
|
|
82
|
+
- enh: use `HTTPAdapter` with `urlib3`'s `Retry` to handle connection issues
|
|
83
|
+
- enh: use `requests.Session` when caching is disabled
|
|
84
|
+
- enh: print UTC time in error messages to make debugging easier
|
|
1
85
|
1.1.2
|
|
2
86
|
- fix: archiving to BagIt format circle selection did not include all circles
|
|
3
87
|
1.1.1
|
|
@@ -574,7 +658,7 @@
|
|
|
574
658
|
- fix: start even if DCOR is down
|
|
575
659
|
- fix: do not display mean upload rate during upload
|
|
576
660
|
- fix: require restart when server is changed
|
|
577
|
-
- enh: ask user to create a Circle when the user is not member of any
|
|
661
|
+
- enh: ask user to create a Circle when the user is not member of any
|
|
578
662
|
- enh: add basic wizard functionalities
|
|
579
663
|
- enh: resource upload supports supplementary resource schemas
|
|
580
664
|
- enh: stick to package_revise when finalizing dataset
|
|
@@ -3,4 +3,4 @@ include LICENSE
|
|
|
3
3
|
include README.rst
|
|
4
4
|
recursive-include examples *.py
|
|
5
5
|
recursive-include tests *.py
|
|
6
|
-
recursive-include dcoraid *.css *.png *.py *.svg *.theme *.txt
|
|
6
|
+
recursive-include dcoraid *.css *.png *.py *.svg *.theme *.txt COPYING* LICENSE README
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dcoraid
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: GUI/CLI for managing data on DCOR
|
|
5
5
|
Author: Paul Müller, Eoghan O'Connell, Raghava Alajangi
|
|
6
6
|
Maintainer-email: Paul Müller <dev@craban.de>
|
|
@@ -21,7 +21,6 @@ Requires-Dist: numpy>=1.21
|
|
|
21
21
|
Requires-Dist: requests>=2.31
|
|
22
22
|
Requires-Dist: urllib3>=2.0
|
|
23
23
|
Requires-Dist: requests_cache
|
|
24
|
-
Requires-Dist: requests_toolbelt>=1.0.0
|
|
25
24
|
Provides-Extra: gui
|
|
26
25
|
Requires-Dist: PyQt6; extra == "gui"
|
|
27
26
|
Dynamic: license-file
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 2, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g45bf2a16c'
|
|
@@ -3,6 +3,7 @@ from .ckan_api import CKANAPI
|
|
|
3
3
|
from .dataset import (dataset_create, dataset_activate, dataset_draft_remove,
|
|
4
4
|
dataset_draft_remove_all)
|
|
5
5
|
from .dataset import resource_add, resource_exists
|
|
6
|
+
from .users import create_dcor_dev_user, is_valid_api_key
|
|
6
7
|
from .errors import (APIError, APIBadGatewayError, APIConflictError,
|
|
7
8
|
APIAuthorizationError, NoAPIKeyError, APINotFoundError,
|
|
8
9
|
APIGatewayTimeoutError, APIOutdatedError)
|
|
@@ -4,15 +4,16 @@ import copy
|
|
|
4
4
|
import functools
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
|
-
import numbers
|
|
8
7
|
import pathlib
|
|
9
8
|
import random
|
|
10
9
|
import traceback
|
|
11
10
|
import urllib.parse
|
|
12
|
-
|
|
11
|
+
from urllib3.exceptions import MaxRetryError, NameResolutionError
|
|
12
|
+
from urllib3.util.retry import Retry
|
|
13
13
|
|
|
14
14
|
from dclab.external.packaging import parse as parse_version
|
|
15
15
|
import requests
|
|
16
|
+
from requests.adapters import HTTPAdapter
|
|
16
17
|
import requests_cache
|
|
17
18
|
|
|
18
19
|
from .._version import version
|
|
@@ -26,13 +27,44 @@ from .errors import (APIConflictError, APINotFoundError, NoAPIKeyError,
|
|
|
26
27
|
MIN_CKAN_VERSION = "2.9.4"
|
|
27
28
|
|
|
28
29
|
#: List of license lists for each DCOR server
|
|
29
|
-
SERVER_LICENCES = {}
|
|
30
|
+
SERVER_LICENCES: dict[str, list[dict]] = {}
|
|
30
31
|
|
|
31
32
|
#: List of supplementary resource schema dictionaries
|
|
32
|
-
SERVER_RSS = {}
|
|
33
|
+
SERVER_RSS: dict[str, dict] = {}
|
|
33
34
|
|
|
34
35
|
#: List of supported resource suffixes
|
|
35
|
-
SERVER_RSUFFIX = {}
|
|
36
|
+
SERVER_RSUFFIX: dict[str, list[str]] = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RetryExceptNameResolution(Retry):
|
|
40
|
+
"""Retry strategy that gives up immediately if a host cannot be resolved
|
|
41
|
+
|
|
42
|
+
A failed name lookup is rarely transient (e.g. a typo in the server
|
|
43
|
+
name), and on Windows every failed lookup may take half a minute.
|
|
44
|
+
"""
|
|
45
|
+
def increment(self, method=None, url=None, response=None, error=None,
|
|
46
|
+
_pool=None, _stacktrace=None):
|
|
47
|
+
if isinstance(error, NameResolutionError):
|
|
48
|
+
raise MaxRetryError(_pool, url, error) from error
|
|
49
|
+
return super().increment(method=method,
|
|
50
|
+
url=url,
|
|
51
|
+
response=response,
|
|
52
|
+
error=error,
|
|
53
|
+
_pool=_pool,
|
|
54
|
+
_stacktrace=_stacktrace)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Configure retry strategy
|
|
58
|
+
retry_strategy = RetryExceptNameResolution(
|
|
59
|
+
total=3, # Total retries
|
|
60
|
+
backoff_factor=1.5, # {backoff factor} * (2 ** ({number prev. retries}))
|
|
61
|
+
connect=3, # Handle connection errors (e.g. because the server is busy)
|
|
62
|
+
read=2, # Retry twice on read errors (e.g. DCOR closed connection)
|
|
63
|
+
redirect=False, # implies raise_on_redirect=False
|
|
64
|
+
status=0, # Fail on bad status codes
|
|
65
|
+
allowed_methods=["HEAD", "GET", "OPTIONS"] # Retry only for safe methods
|
|
66
|
+
)
|
|
67
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
36
68
|
|
|
37
69
|
|
|
38
70
|
class CKANAPI:
|
|
@@ -107,7 +139,10 @@ class CKANAPI:
|
|
|
107
139
|
},
|
|
108
140
|
**kwargs)
|
|
109
141
|
else:
|
|
110
|
-
self.req_ses = requests
|
|
142
|
+
self.req_ses = requests.Session()
|
|
143
|
+
|
|
144
|
+
self.req_ses.mount("https://", adapter)
|
|
145
|
+
self.req_ses.mount("http://", adapter)
|
|
111
146
|
|
|
112
147
|
def __repr__(self):
|
|
113
148
|
return f"<CKANAPI {self.api_url} at {hex(id(self))}>"
|
|
@@ -292,7 +327,7 @@ class CKANAPI:
|
|
|
292
327
|
def get(self,
|
|
293
328
|
api_call: str,
|
|
294
329
|
timeout: float = 27.9,
|
|
295
|
-
**kwargs: str |
|
|
330
|
+
**kwargs: str | float):
|
|
296
331
|
"""GET request
|
|
297
332
|
|
|
298
333
|
Parameters
|
|
@@ -316,8 +351,8 @@ class CKANAPI:
|
|
|
316
351
|
if kwargs:
|
|
317
352
|
# Add keyword arguments
|
|
318
353
|
kwv = []
|
|
319
|
-
for kw in kwargs:
|
|
320
|
-
kwv.append(f"{kw}={
|
|
354
|
+
for kw, val in kwargs.items():
|
|
355
|
+
kwv.append(f"{kw}={val}")
|
|
321
356
|
api_call += "?" + "&".join(kwv)
|
|
322
357
|
|
|
323
358
|
req = self.req_ses.get(self.api_url + api_call,
|
|
@@ -400,7 +435,7 @@ class CKANAPI:
|
|
|
400
435
|
----------
|
|
401
436
|
api_call: str
|
|
402
437
|
An API call function (e.g. "package_create")
|
|
403
|
-
data: dict
|
|
438
|
+
data: dict
|
|
404
439
|
The data connected to the post request. For
|
|
405
440
|
"package_create", this would be a dictionary
|
|
406
441
|
with the dataset name, author, license, etc.
|
|
@@ -444,7 +479,7 @@ class CKANAPI:
|
|
|
444
479
|
|
|
445
480
|
def require_collection(self,
|
|
446
481
|
name: str,
|
|
447
|
-
title: str = None,
|
|
482
|
+
title: str | None = None,
|
|
448
483
|
exist_ok: bool = True,
|
|
449
484
|
):
|
|
450
485
|
"""Return a collection dict with the given name and optional title
|
|
@@ -464,7 +499,7 @@ class CKANAPI:
|
|
|
464
499
|
rand = ""
|
|
465
500
|
title = (title or name).strip()
|
|
466
501
|
# create the collection
|
|
467
|
-
for
|
|
502
|
+
for _ in range(10):
|
|
468
503
|
try:
|
|
469
504
|
col_dict = self.post("group_create",
|
|
470
505
|
{"title": title,
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"""Convenience wrappers for commonly used API calls"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
2
4
|
import copy
|
|
3
5
|
import functools
|
|
4
6
|
import logging
|
|
5
7
|
import pathlib
|
|
6
|
-
import
|
|
7
|
-
from typing import Callable, Dict
|
|
8
|
-
|
|
9
|
-
import requests
|
|
10
|
-
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
|
|
11
|
-
|
|
12
|
-
from ..common import ConnectionTimeoutErrors
|
|
8
|
+
from collections.abc import Callable
|
|
13
9
|
|
|
14
10
|
from .errors import (
|
|
15
11
|
APIBadRequest, APIConflictError, APINotFoundError, NoS3UploadAvailableError
|
|
@@ -33,22 +29,18 @@ def dataset_activate(dataset_id: str, api: CKANAPI):
|
|
|
33
29
|
api: dcoraid.api.CKANAPI
|
|
34
30
|
API instance with server, api_key (and optional certificate)
|
|
35
31
|
"""
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
revise_dict,
|
|
49
|
-
# Dataset activation may take long when there
|
|
50
|
-
# are a lot of resources.
|
|
51
|
-
timeout=500)
|
|
32
|
+
# First check whether the dataset is already active. Use a long
|
|
33
|
+
# timeout for this critical step.
|
|
34
|
+
ds_dict = api.get("package_show", id=dataset_id, timeout=500)
|
|
35
|
+
if ds_dict["state"] != "active":
|
|
36
|
+
revise_dict = {
|
|
37
|
+
"match": {"id": dataset_id},
|
|
38
|
+
"update": {"state": "active"}}
|
|
39
|
+
api.post("package_revise",
|
|
40
|
+
revise_dict,
|
|
41
|
+
# Dataset activation may take long when there
|
|
42
|
+
# are a lot of resources.
|
|
43
|
+
timeout=500)
|
|
52
44
|
|
|
53
45
|
|
|
54
46
|
def dataset_create(dataset_dict, api, resources=None,
|
|
@@ -202,16 +194,18 @@ def resource_add(dataset_id, path, api, resource_name=None,
|
|
|
202
194
|
it, only update the resource_dict.
|
|
203
195
|
monitor_callback: None or callable
|
|
204
196
|
This callable is used to monitor the upload progress. It must
|
|
205
|
-
accept one argument:
|
|
206
|
-
`
|
|
197
|
+
accept one argument: an object with the attributes `len` (file
|
|
198
|
+
size) and `bytes_read` (number of bytes uploaded).
|
|
207
199
|
|
|
208
200
|
Returns
|
|
209
201
|
-------
|
|
210
202
|
srv_time: float
|
|
211
|
-
|
|
203
|
+
Always 0 (kept for backwards compatibility; this was the time the
|
|
204
|
+
server needed to process uploads via CKAN, which are not supported
|
|
205
|
+
anymore)
|
|
212
206
|
etag: str
|
|
213
|
-
ETag of the resource on S3; returns None if the
|
|
214
|
-
|
|
207
|
+
ETag of the resource on S3; returns None if the resource already
|
|
208
|
+
existed and was not uploaded
|
|
215
209
|
|
|
216
210
|
See Also
|
|
217
211
|
--------
|
|
@@ -234,31 +228,16 @@ def resource_add(dataset_id, path, api, resource_name=None,
|
|
|
234
228
|
resource_name=resource_name,
|
|
235
229
|
api=api):
|
|
236
230
|
|
|
237
|
-
# Perform the upload
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
timeout=27.9
|
|
248
|
-
)
|
|
249
|
-
except NoS3UploadAvailableError as e:
|
|
250
|
-
# This is the fall-back option that causes a performance hit
|
|
251
|
-
# on the DCOR server and will be unsupported in the future.
|
|
252
|
-
logger.warning(str(e))
|
|
253
|
-
resource_add_upload_legacy_indirect_ckan(
|
|
254
|
-
api=api,
|
|
255
|
-
resource_path=path,
|
|
256
|
-
dataset_id=dataset_id,
|
|
257
|
-
resource_name=resource_name,
|
|
258
|
-
monitor_callback=monitor_callback,
|
|
259
|
-
logger=logger,
|
|
260
|
-
timeout=500,
|
|
261
|
-
)
|
|
231
|
+
# Perform the upload (DCOR only accepts uploads to S3)
|
|
232
|
+
etag = resource_add_upload_direct_s3(
|
|
233
|
+
api=api,
|
|
234
|
+
resource_path=path,
|
|
235
|
+
dataset_id=dataset_id,
|
|
236
|
+
resource_name=resource_name,
|
|
237
|
+
monitor_callback=monitor_callback,
|
|
238
|
+
logger=logger,
|
|
239
|
+
timeout=27.9
|
|
240
|
+
)
|
|
262
241
|
|
|
263
242
|
# If we are here, then the upload was successful
|
|
264
243
|
logger.info(f"Finished upload of {dataset_id}/{resource_name}")
|
|
@@ -282,8 +261,8 @@ def resource_add_upload_direct_s3(
|
|
|
282
261
|
dataset_id: str,
|
|
283
262
|
resource_path: str | pathlib.Path,
|
|
284
263
|
resource_name: str,
|
|
285
|
-
monitor_callback: Callable = None,
|
|
286
|
-
logger: logging.Logger = None,
|
|
264
|
+
monitor_callback: Callable | None = None,
|
|
265
|
+
logger: logging.Logger | None = None,
|
|
287
266
|
timeout: float = 27.9) -> str:
|
|
288
267
|
"""Direct resource upload to S3
|
|
289
268
|
|
|
@@ -335,9 +314,10 @@ def resource_add_upload_direct_s3(
|
|
|
335
314
|
raise NoS3UploadAvailableError(f"Server {api.server} does not yet "
|
|
336
315
|
f"support direct upload to S3")
|
|
337
316
|
|
|
338
|
-
logger
|
|
339
|
-
|
|
340
|
-
|
|
317
|
+
if logger is not None:
|
|
318
|
+
logger.info(
|
|
319
|
+
f"We have {len(upload_info['upload_urls'])} upload parts for "
|
|
320
|
+
f"a resource of size {file_size/1024**2:.2f} MiB of {upload_id}")
|
|
341
321
|
|
|
342
322
|
etag = upload_s3_presigned(
|
|
343
323
|
path=resource_path,
|
|
@@ -361,82 +341,11 @@ def resource_add_upload_direct_s3(
|
|
|
361
341
|
return etag
|
|
362
342
|
|
|
363
343
|
|
|
364
|
-
def resource_add_upload_legacy_indirect_ckan(
|
|
365
|
-
api: CKANAPI,
|
|
366
|
-
resource_path: str | pathlib.Path,
|
|
367
|
-
dataset_id: str,
|
|
368
|
-
resource_name: str,
|
|
369
|
-
monitor_callback: Callable = None,
|
|
370
|
-
logger: logging.Logger = None,
|
|
371
|
-
timeout: float = 27.9):
|
|
372
|
-
"""Legacy upload procedure for resources
|
|
373
|
-
|
|
374
|
-
This method uses the legacy path (nginx -> uwsgi -> CKAN API) to
|
|
375
|
-
upload resources to the block storage of the DCOR instance. It is
|
|
376
|
-
termed legacy, because it is very inefficient to process resources
|
|
377
|
-
like this. A better approach is to upload resources directly to S3.
|
|
378
|
-
DCOR supports uploading resources directly to S3, which is facilitated
|
|
379
|
-
via the `resource_upload_s3_url` API action providing a presigned
|
|
380
|
-
upload link. (see :func:`resource_add_upload_direct_s3`)
|
|
381
|
-
"""
|
|
382
|
-
upload_id = f"{dataset_id}/{resource_name}"
|
|
383
|
-
if logger is not None:
|
|
384
|
-
logger.info(f"Commencing legacy upload of {upload_id}")
|
|
385
|
-
|
|
386
|
-
with pathlib.Path(resource_path).open("rb") as fd:
|
|
387
|
-
# use package_revise to upload the resource
|
|
388
|
-
# https://github.com/DCOR-dev/DCOR-Aid/issues/28
|
|
389
|
-
e = MultipartEncoder(fields={
|
|
390
|
-
'match__id': dataset_id,
|
|
391
|
-
'update__resources__extend': f'[{{"name":"{resource_name}"}}]',
|
|
392
|
-
'update__resources__-1__upload': (resource_name, fd)})
|
|
393
|
-
m = MultipartEncoderMonitor(e, monitor_callback)
|
|
394
|
-
# Increase the read size to speed-up upload (the default chunk
|
|
395
|
-
# size for uploads in urllib is 8k which results in a lot of
|
|
396
|
-
# Python code being involved in uploading a 20GB file; Setting
|
|
397
|
-
# the chunk size to 4MB should increase the upload speed):
|
|
398
|
-
# https://github.com/requests/toolbelt/issues/75
|
|
399
|
-
# #issuecomment-237189952
|
|
400
|
-
m._read = m.read
|
|
401
|
-
m.read = lambda size: m._read(4 * 1024 * 1024)
|
|
402
|
-
|
|
403
|
-
try:
|
|
404
|
-
api.post("package_revise",
|
|
405
|
-
data=m,
|
|
406
|
-
dump_json=False,
|
|
407
|
-
headers={"Content-Type": m.content_type},
|
|
408
|
-
timeout=timeout)
|
|
409
|
-
except requests.exceptions.Timeout:
|
|
410
|
-
# This means that the server does not respond. This is ok,
|
|
411
|
-
# because we can just check whether the resource was
|
|
412
|
-
# processed correctly.
|
|
413
|
-
if logger is not None:
|
|
414
|
-
logger.info(f"Timeout for upload {upload_id}")
|
|
415
|
-
start_wait_srv = time.monotonic()
|
|
416
|
-
wait_time_minutes = 60
|
|
417
|
-
for ii in range(wait_time_minutes):
|
|
418
|
-
if resource_exists(dataset_id=dataset_id,
|
|
419
|
-
resource_name=resource_name,
|
|
420
|
-
api=api):
|
|
421
|
-
srv_time = timeout + time.monotonic() - start_wait_srv
|
|
422
|
-
if logger is not None:
|
|
423
|
-
logger.info(
|
|
424
|
-
f"Waited {srv_time / 60:.1f} min for {upload_id}")
|
|
425
|
-
break
|
|
426
|
-
else:
|
|
427
|
-
if logger is not None:
|
|
428
|
-
logger.info(f"Waiting {ii + 1} min for {upload_id}")
|
|
429
|
-
time.sleep(60)
|
|
430
|
-
else:
|
|
431
|
-
raise ValueError(f"Timeout or {upload_id} not processed after "
|
|
432
|
-
+ f"{wait_time_minutes} minutes!")
|
|
433
|
-
|
|
434
|
-
|
|
435
344
|
def resource_exists(dataset_id: str,
|
|
436
345
|
resource_name: str,
|
|
437
346
|
api: CKANAPI,
|
|
438
|
-
check_resource_dict:
|
|
439
|
-
dataset_dict:
|
|
347
|
+
check_resource_dict: dict | None = None,
|
|
348
|
+
dataset_dict: dict | None = None):
|
|
440
349
|
"""Check whether a resource exists in a dataset on DCOR
|
|
441
350
|
|
|
442
351
|
Parameters
|
|
@@ -463,6 +372,7 @@ def resource_exists(dataset_id: str,
|
|
|
463
372
|
if dataset_dict is None:
|
|
464
373
|
dataset_dict = api.get("package_show", id=dataset_id,
|
|
465
374
|
timeout=500)
|
|
375
|
+
assert dataset_dict is not None
|
|
466
376
|
for resource in dataset_dict.get("resources", []):
|
|
467
377
|
if resource["name"] == resource_name:
|
|
468
378
|
# check that the resource dict matches
|
|
@@ -473,5 +383,4 @@ def resource_exists(dataset_id: str,
|
|
|
473
383
|
# or it is wrong.
|
|
474
384
|
return False
|
|
475
385
|
return True
|
|
476
|
-
|
|
477
|
-
return False
|
|
386
|
+
return False
|
|
@@ -1,58 +1,46 @@
|
|
|
1
1
|
class APIError(BaseException):
|
|
2
2
|
"""General CKANAPI error"""
|
|
3
|
-
pass
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
class APIAuthorizationError(APIError):
|
|
7
6
|
"""User credentials are invalid"""
|
|
8
|
-
pass
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
class APIBadGatewayError(APIError):
|
|
12
10
|
"""Cannot connect to DCOR server"""
|
|
13
|
-
pass
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
class APIBadRequest(APIError):
|
|
17
14
|
"""An API command cannot be found"""
|
|
18
|
-
pass
|
|
19
15
|
|
|
20
16
|
|
|
21
17
|
class APIConflictError(APIError):
|
|
22
18
|
"""Invalid payload to DCOR server"""
|
|
23
|
-
pass
|
|
24
19
|
|
|
25
20
|
|
|
26
21
|
class APIGatewayTimeoutError(APIError):
|
|
27
22
|
"""Timeout due to network connection"""
|
|
28
|
-
pass
|
|
29
23
|
|
|
30
24
|
|
|
31
25
|
class APINotFoundError(APIError):
|
|
32
26
|
"""Requested object not found on DCOR"""
|
|
33
|
-
pass
|
|
34
27
|
|
|
35
28
|
|
|
36
29
|
class APIOutdatedError(APIError):
|
|
37
30
|
"""DCOR-Aid is outdated, the server requests a newer version"""
|
|
38
|
-
pass
|
|
39
31
|
|
|
40
32
|
|
|
41
33
|
class NoAPIKeyError(APIError):
|
|
42
34
|
"""DCOR does not have an API token"""
|
|
43
|
-
pass
|
|
44
35
|
|
|
45
36
|
|
|
46
37
|
class NoS3UploadAvailableError(BaseException):
|
|
47
38
|
"""Used for identifying DCOR servers that don't support direct S3 upload"""
|
|
48
|
-
pass
|
|
49
39
|
|
|
50
40
|
|
|
51
41
|
class S3UploadError(BaseException):
|
|
52
42
|
"""Raised when an upload to S3 failed"""
|
|
53
|
-
pass
|
|
54
43
|
|
|
55
44
|
|
|
56
45
|
class ServerUnderMaintenanceError(BaseException):
|
|
57
46
|
"""Raised when the DCOR server is under maintenance"""
|
|
58
|
-
pass
|