diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index e469cf4..eeb5843 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -29,7 +29,8 @@ jobs:
- name: Setup xvfb (Linux)
if: runner.os == 'Linux'
run: |
- sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0
+ sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 libglib2.0-0 libgl1-mesa-dev
+ sudo apt-get install '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev
# start xvfb in the background
sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 &
- name: Install Python dependencies
diff --git a/CHANGELOG b/CHANGELOG
index 6deb16b..ca6ed76 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,5 @@
-0.16.12
+0.17.0
+ - BREAKING CHANGE: migrate codebase from PyQt5 to PyQt6
- tests: allow to use different DCOR instance for testing
- tests: make tests independent of testing user
0.16.11
diff --git a/dcoraid/__main__.py b/dcoraid/__main__.py
index 2484b85..9991949 100644
--- a/dcoraid/__main__.py
+++ b/dcoraid/__main__.py
@@ -10,14 +10,14 @@ def main(splash=True):
setup_logging("dclab")
setup_logging("requests", level=logging.INFO)
- from PyQt5.QtWidgets import QApplication
+ from PyQt6.QtWidgets import QApplication
if platform.win32_ver()[0] == "7":
# Use software OpenGL on Windows 7, because sometimes the
# window content becomes plain white.
# Not sure whether this actually works.
- from PyQt5.QtCore import Qt, QCoreApplication
- from PyQt5.QtGui import QGuiApplication
+ from PyQt6.QtCore import Qt, QCoreApplication
+ from PyQt6.QtGui import QGuiApplication
QApplication.setAttribute(Qt.AA_UseSoftwareOpenGL)
QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL)
QGuiApplication.setAttribute(Qt.AA_UseSoftwareOpenGL)
@@ -25,18 +25,18 @@ def main(splash=True):
app = QApplication(sys.argv)
if splash:
- from PyQt5.QtWidgets import QSplashScreen
- from PyQt5.QtGui import QPixmap
- from PyQt5.QtCore import QEventLoop
+ from PyQt6.QtWidgets import QSplashScreen
+ from PyQt6.QtGui import QPixmap
+ from PyQt6.QtCore import QEventLoop
ref_splash = resources.files("dcoraid.img") / "splash.png"
with resources.as_file(ref_splash) as splash_path:
splash_pix = QPixmap(str(splash_path))
splash = QSplashScreen(splash_pix)
splash.setMask(splash_pix.mask())
splash.show()
- app.processEvents(QEventLoop.AllEvents, 300)
+ app.processEvents(QEventLoop.ProcessEventsFlag.AllEvents, 300)
- from PyQt5 import QtCore, QtGui
+ from PyQt6 import QtCore, QtGui
from .gui import DCORAid
# Set Application Icon
@@ -45,14 +45,14 @@ def main(splash=True):
app.setWindowIcon(QtGui.QIcon(str(icon_path)))
# Use dots as decimal separators
- QtCore.QLocale.setDefault(QtCore.QLocale(QtCore.QLocale.C))
+ QtCore.QLocale.setDefault(QtCore.QLocale(QtCore.QLocale.Language.C))
window = DCORAid()
if splash:
splash.finish(window)
- sys.exit(app.exec_())
+ sys.exit(app.exec())
if __name__ == "__main__":
diff --git a/dcoraid/gui/api.py b/dcoraid/gui/api.py
index ae368d2..27a2c3f 100644
--- a/dcoraid/gui/api.py
+++ b/dcoraid/gui/api.py
@@ -5,7 +5,7 @@
import shutil
import tempfile
-from PyQt5 import QtCore
+from PyQt6 import QtCore
from ..api import CKANAPI
diff --git a/dcoraid/gui/browse_public/widget_browse_public.py b/dcoraid/gui/browse_public/widget_browse_public.py
index 5701ae0..c190ddd 100644
--- a/dcoraid/gui/browse_public/widget_browse_public.py
+++ b/dcoraid/gui/browse_public/widget_browse_public.py
@@ -2,7 +2,7 @@
from importlib import resources
-from PyQt5 import uic, QtCore, QtWidgets
+from PyQt6 import uic, QtCore, QtWidgets
from ...common import ConnectionTimeoutErrors
from ...dbmodel import APIInterrogator
@@ -29,7 +29,7 @@ def __init__(self, *args, **kwargs):
@QtCore.pyqtSlot()
def on_public_search(self):
- self.setCursor(QtCore.Qt.WaitCursor)
+ self.setCursor(QtCore.Qt.CursorShape.WaitCursor)
api = get_ckan_api(
public=not self.checkBox_public_include_private.isChecked())
try:
@@ -44,7 +44,7 @@ def on_public_search(self):
self,
f"Failed to connect to {api.server}",
tb.format_exc(limit=1))
- self.setCursor(QtCore.Qt.ArrowCursor)
+ self.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
@staticmethod
def find_main_window():
diff --git a/dcoraid/gui/dbview/drag_table_widget.py b/dcoraid/gui/dbview/drag_table_widget.py
index bfff881..a9c19e0 100644
--- a/dcoraid/gui/dbview/drag_table_widget.py
+++ b/dcoraid/gui/dbview/drag_table_widget.py
@@ -1,5 +1,5 @@
-from PyQt5 import QtWidgets, QtCore, QtGui
-from PyQt5.QtCore import Qt
+from PyQt6 import QtWidgets, QtCore, QtGui
+from PyQt6.QtCore import Qt
class DragTableWidget(QtWidgets.QTableWidget):
@@ -9,7 +9,7 @@ def mouseMoveEvent(self, e):
urls = []
for item in self.selectedItems():
- data = item.data(Qt.UserRole + 1)
+ data = item.data(Qt.ItemDataRole.UserRole + 1)
urls.append(QtCore.QUrl(data))
mime_data = QtCore.QMimeData()
@@ -20,4 +20,4 @@ def mouseMoveEvent(self, e):
drag.setHotSpot(e.pos() - self.rect().topLeft())
# This magic is somehow required to get drag working.
- dropAction = drag.exec_(Qt.CopyAction) # noqa: F841
+ dropAction = drag.exec(Qt.CopyAction) # noqa: F841
diff --git a/dcoraid/gui/dbview/filter_base.py b/dcoraid/gui/dbview/filter_base.py
index 57a426a..b01d58d 100644
--- a/dcoraid/gui/dbview/filter_base.py
+++ b/dcoraid/gui/dbview/filter_base.py
@@ -1,7 +1,7 @@
import copy
from importlib import resources
-from PyQt5 import QtCore, QtGui, QtWidgets, uic
+from PyQt6 import QtCore, QtGui, QtWidgets, uic
class FilterBase(QtWidgets.QWidget):
@@ -22,7 +22,8 @@ def __init__(self, *args, **kwargs):
# resize first column
header = self.tableWidget.horizontalHeader()
- header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
+ header.setSectionResizeMode(0,
+ QtWidgets.QHeaderView.ResizeMode.Stretch)
# trigger user selection change signal
self.tableWidget.itemSelectionChanged.connect(self.on_entry_selected)
@@ -38,9 +39,10 @@ def __init__(self, *args, **kwargs):
self.tableWidget.setDragEnabled(False) # disable drag
self.tableWidget.setDragDropOverwriteMode(False) # don't overwrite
self.tableWidget.setDragDropMode(
- QtWidgets.QAbstractItemView.NoDragDrop) # drag & drop disabled
+ # drag & drop disabled
+ QtWidgets.QAbstractItemView.DragDropMode.NoDragDrop)
self.tableWidget.setDefaultDropAction(
- QtCore.Qt.IgnoreAction) # no drop by default
+ QtCore.Qt.DropAction.IgnoreAction) # no drop by default
def get_entry_actions(self, row, entry):
"""This is defined in the subclasses (Circle, Collection, etc)"""
@@ -87,8 +89,8 @@ def set_entry(self, row, entry):
horz_layout.setContentsMargins(2, 0, 2, 0)
spacer = QtWidgets.QSpacerItem(0, 0,
- QtWidgets.QSizePolicy.Expanding,
- QtWidgets.QSizePolicy.Minimum)
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ QtWidgets.QSizePolicy.Policy.Minimum)
horz_layout.addItem(spacer)
for action in self.get_entry_actions(row, entry):
diff --git a/dcoraid/gui/dbview/filter_chain.py b/dcoraid/gui/dbview/filter_chain.py
index f035057..e864237 100644
--- a/dcoraid/gui/dbview/filter_chain.py
+++ b/dcoraid/gui/dbview/filter_chain.py
@@ -2,7 +2,7 @@
from importlib import resources
-from PyQt5 import QtCore, QtWidgets, uic
+from PyQt6 import QtCore, QtWidgets, uic
from ..tools import ShowWaitCursor
from ..api import get_ckan_api
diff --git a/dcoraid/gui/dbview/filter_views.py b/dcoraid/gui/dbview/filter_views.py
index 287fc99..12f2abf 100644
--- a/dcoraid/gui/dbview/filter_views.py
+++ b/dcoraid/gui/dbview/filter_views.py
@@ -1,8 +1,8 @@
from functools import partial
import webbrowser
-from PyQt5 import QtCore, QtWidgets
-from PyQt5.QtCore import Qt
+from PyQt6 import QtCore, QtWidgets
+from PyQt6.QtCore import Qt
from ..api import get_ckan_api
from ..tools import ShowWaitCursor
@@ -64,7 +64,7 @@ def download_collection(self, collection_name, condensed=False):
for res_dict in ds_dict.get("resources", []):
self.download_resource.emit(res_dict["id"], condensed)
QtWidgets.QApplication.processEvents(
- QtCore.QEventLoop.AllEvents,
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents,
300)
def get_entry_actions(self, row, entry):
@@ -108,7 +108,7 @@ def download_dataset(self, dataset_id, condensed=False):
for res_dict in ds_dict.get("resources", []):
self.download_resource.emit(res_dict["id"], condensed)
QtWidgets.QApplication.processEvents(
- QtCore.QEventLoop.AllEvents,
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents,
300)
def get_entry_actions(self, row, entry):
@@ -139,7 +139,7 @@ def __init__(self, *args, **kwargs):
self.checkBox.setChecked(True)
self.tableWidget.setDragEnabled(True)
self.tableWidget.setDragDropMode(
- QtWidgets.QAbstractItemView.DragOnly)
+ QtWidgets.QAbstractItemView.DragDropMode.DragOnly)
self.label_info.setText("Tip: You can drag and drop your selection "
"from the resources list to Shape-Out!")
@@ -172,4 +172,4 @@ def set_entry_label(self, row, entry):
item = self.tableWidget.item(row, 0)
api = get_ckan_api()
dcor_url = f"{api.server}/api/3/action/dcserv?id={entry['id']}"
- item.setData(Qt.UserRole + 1, dcor_url)
+ item.setData(Qt.ItemDataRole.UserRole + 1, dcor_url)
diff --git a/dcoraid/gui/download/widget_download.py b/dcoraid/gui/download/widget_download.py
index f85d0d8..d75b7e2 100644
--- a/dcoraid/gui/download/widget_download.py
+++ b/dcoraid/gui/download/widget_download.py
@@ -9,8 +9,8 @@
import subprocess
import webbrowser
-from PyQt5 import uic, QtCore, QtGui, QtWidgets
-from PyQt5.QtCore import QStandardPaths
+from PyQt6 import uic, QtCore, QtGui, QtWidgets
+from PyQt6.QtCore import QStandardPaths
from ...download import DownloadQueue
@@ -37,7 +37,7 @@ def __init__(self, *args, **kwargs):
#: path to persistent shelf to be able to resume uploads on startup
self.shelf_path = os_path.join(
QStandardPaths.writableLocation(
- QStandardPaths.AppLocalDataLocation),
+ QStandardPaths.StandardLocation.AppLocalDataLocation),
"persistent_download_jobs")
#: DownloadQueue instance
@@ -85,7 +85,7 @@ def initialize(self):
@QtCore.pyqtSlot(str, bool)
def download_resource(self, resource_id, condensed=False):
fallback = QStandardPaths.writableLocation(
- QStandardPaths.DownloadLocation)
+ QStandardPaths.StandardLocation.DownloadLocation)
dl_path = self.settings.value("downloads/default path", fallback)
self.widget_jobs.jobs.new_job(resource_id, dl_path, condensed)
@@ -182,14 +182,14 @@ def update_job_status(self):
job.traceback = traceback.format_exc()
QtWidgets.QApplication.processEvents(
- QtCore.QEventLoop.AllEvents,
- 300)
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
# spacing (did not work in __init__)
header = self.horizontalHeader()
header.setSectionResizeMode(
- 0, QtWidgets.QHeaderView.ResizeToContents)
- header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+ 0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
+ header.setSectionResizeMode(
+ 1, QtWidgets.QHeaderView.ResizeMode.Stretch)
self._busy_updating_widgets = False
@@ -219,9 +219,11 @@ def set_actions_item(self, row, col, job):
horz_layout = QtWidgets.QHBoxLayout(widact)
horz_layout.setContentsMargins(2, 0, 2, 0)
- spacer = QtWidgets.QSpacerItem(0, 0,
- QtWidgets.QSizePolicy.Expanding,
- QtWidgets.QSizePolicy.Minimum)
+ spacer = QtWidgets.QSpacerItem(
+ 0, 0,
+ QtWidgets.QSizePolicy.Policy.Expanding,
+ QtWidgets.QSizePolicy.Policy.Minimum
+ )
horz_layout.addItem(spacer)
if job.state == "error":
actions = [
diff --git a/dcoraid/gui/logs/widget_log.py b/dcoraid/gui/logs/widget_log.py
index 805d618..abd635b 100644
--- a/dcoraid/gui/logs/widget_log.py
+++ b/dcoraid/gui/logs/widget_log.py
@@ -9,7 +9,7 @@
import time
import traceback
-from PyQt5 import uic, QtCore, QtWidgets
+from PyQt6 import uic, QtCore, QtWidgets
from ..._version import version
diff --git a/dcoraid/gui/main.py b/dcoraid/gui/main.py
index 4838d62..36d057c 100644
--- a/dcoraid/gui/main.py
+++ b/dcoraid/gui/main.py
@@ -12,7 +12,7 @@
import requests_toolbelt
import urllib3
-from PyQt5 import uic, QtCore, QtGui, QtWidgets
+from PyQt6 import uic, QtCore, QtGui, QtWidgets
from ..api import APIOutdatedError
from ..common import ConnectionTimeoutErrors
@@ -57,20 +57,19 @@ def __init__(self, *args, **kwargs):
QtCore.QCoreApplication.setOrganizationName("DCOR")
QtCore.QCoreApplication.setOrganizationDomain("dcor.mpl.mpg.de")
QtCore.QCoreApplication.setApplicationName("dcoraid")
- QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
+ QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
super(DCORAid, self).__init__(*args, **kwargs)
# if "--version" was specified, print the version and exit
if "--version" in sys.argv:
print(__version__)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents,
- 300)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
sys.exit(0)
#: DCOR-Aid settings
self.settings = QtCore.QSettings()
- self.settings.setIniCodec("utf-8")
ref_ui = resources.files("dcoraid.gui") / "main.ui"
with resources.as_file(ref_ui) as path_ui:
uic.loadUi(path_ui, self)
@@ -111,8 +110,8 @@ def __init__(self, *args, **kwargs):
self.user_filter_chain.download_resource.connect(
self.panel_download.download_resource)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents,
- 300)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
# Run wizard if necessary
if ((self.settings.value("user scenario", "") != "anonymous")
@@ -137,8 +136,8 @@ def closeEvent(self, event):
self.panel_upload.prepare_quit()
self.panel_download.prepare_quit()
self.status_widget.prepare_quit()
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents,
- 300)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
event.accept()
@QtCore.pyqtSlot()
@@ -214,7 +213,7 @@ def on_action_software(self):
sw_text += "Modules:\n"
for lib in libs:
sw_text += "- {} {}\n".format(lib.__name__, lib.__version__)
- sw_text += "- PyQt5 {}\n".format(QtCore.QT_VERSION_STR)
+ sw_text += "- PyQt6 {}\n".format(QtCore.QT_VERSION_STR)
sw_text += "\n Breeze icon theme by the KDE Community (LGPL)."
sw_text += "\n Font-Awesome icons by Fort Awesome (CC BY 4.0)."
if hasattr(sys, 'frozen'):
@@ -225,7 +224,7 @@ def on_action_software(self):
@QtCore.pyqtSlot()
def on_refresh_private_data(self):
- self.tab_user.setCursor(QtCore.Qt.WaitCursor)
+ self.tab_user.setCursor(QtCore.Qt.CursorShape.WaitCursor)
api = get_ckan_api()
data = DBExtract()
if api.is_available() and api.api_key:
@@ -244,12 +243,12 @@ def on_refresh_private_data(self):
self,
f"Failed to connect to {api.server}",
tb.format_exc(limit=1))
- self.tab_user.setCursor(QtCore.Qt.ArrowCursor)
+ self.tab_user.setCursor(QtCore.Qt.CursorShape.ArrowCursor)
@QtCore.pyqtSlot()
def on_wizard(self):
self.wizard = SetupWizard(self)
- self.wizard.exec_()
+ self.wizard.exec()
def excepthook(etype, value, trace):
@@ -283,13 +282,13 @@ def excepthook(etype, value, trace):
)
errorbox = QtWidgets.QMessageBox()
- errorbox.setIcon(QtWidgets.QMessageBox.Critical)
+ errorbox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
errorbox.addButton(QtWidgets.QPushButton('Close'),
- QtWidgets.QMessageBox.YesRole)
+ QtWidgets.QMessageBox.ButtonRole.YesRole)
errorbox.addButton(QtWidgets.QPushButton(
- 'Copy text && Close'), QtWidgets.QMessageBox.NoRole)
+ 'Copy text && Close'), QtWidgets.QMessageBox.ButtonRole.NoRole)
errorbox.setText(exception)
- ret = errorbox.exec_()
+ ret = errorbox.exec()
if ret == 1:
cb = QtWidgets.QApplication.clipboard()
cb.clear(mode=cb.Clipboard)
diff --git a/dcoraid/gui/maintenance/widget_maintenance.py b/dcoraid/gui/maintenance/widget_maintenance.py
index 155ce68..f3653e0 100644
--- a/dcoraid/gui/maintenance/widget_maintenance.py
+++ b/dcoraid/gui/maintenance/widget_maintenance.py
@@ -3,8 +3,8 @@
from importlib import resources
-from PyQt5 import uic, QtCore, QtWidgets
-from PyQt5.QtCore import QStandardPaths
+from PyQt6 import uic, QtCore, QtWidgets
+from PyQt6.QtCore import QStandardPaths
from ...api import dataset_draft_remove_all
from ...upload import PersistentUploadJobList
@@ -43,7 +43,7 @@ def on_clear_cache(self):
queue = mw.panel_upload.jobs
dirs = queue.find_zombie_caches()
msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Information)
+ msg.setIcon(QtWidgets.QMessageBox.Icon.Information)
if dirs:
[shutil.rmtree(pp, ignore_errors=True) for pp in dirs]
details = [f"- {pp}" for pp in dirs]
@@ -53,7 +53,7 @@ def on_clear_cache(self):
else:
msg.setText("No zombie cache data found.")
msg.setWindowTitle("Nothing to do")
- msg.exec_()
+ msg.exec()
@QtCore.pyqtSlot()
def on_remove_drafts(self):
@@ -61,7 +61,7 @@ def on_remove_drafts(self):
# get all dataset IDs that should not be removed
pers_job_path = os_path.join(
QStandardPaths.writableLocation(
- QStandardPaths.AppLocalDataLocation),
+ QStandardPaths.StandardLocation.AppLocalDataLocation),
"persistent_upload_jobs")
pers_datasets = PersistentUploadJobList(pers_job_path)
# perform deletion
@@ -69,7 +69,7 @@ def on_remove_drafts(self):
api=get_ckan_api(),
ignore_dataset_ids=pers_datasets)
msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Information)
+ msg.setIcon(QtWidgets.QMessageBox.Icon.Information)
del_titles = [f"{d['name']}" for d in deleted]
ign_titles = [f"{d['name']}" for d in ignored]
if del_titles + ign_titles:
@@ -83,4 +83,4 @@ def on_remove_drafts(self):
else:
msg.setText("No drafts found.")
msg.setWindowTitle("Nothing to do")
- msg.exec_()
+ msg.exec()
diff --git a/dcoraid/gui/preferences/dlg_preferences.py b/dcoraid/gui/preferences/dlg_preferences.py
index fa3816f..eade857 100644
--- a/dcoraid/gui/preferences/dlg_preferences.py
+++ b/dcoraid/gui/preferences/dlg_preferences.py
@@ -6,8 +6,9 @@
import string
import traceback as tb
-from PyQt5 import uic, QtCore, QtWidgets
-from PyQt5.QtCore import QStandardPaths
+from PyQt6 import uic, QtCore, QtWidgets
+from PyQt6.QtCore import QStandardPaths
+from PyQt6.QtWidgets import QMessageBox
from ...api import NoAPIKeyError, CKANAPI
from ...common import ConnectionTimeoutErrors
@@ -66,16 +67,16 @@ def ask_change_server_or_api_key(self):
...because it implies a restart of DCOR-Aid.
"""
- button_reply = QtWidgets.QMessageBox.question(
+ button_reply = QMessageBox.question(
self,
'DCOR-Aid restart required',
"Changing the server or API token requires a restart of "
+ "DCOR-Aid. If you choose 'No', then the original server "
+ "and API token are NOT changed. Do you really want to quit "
+ "DCOR-Aid?",
- QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
- QtWidgets.QMessageBox.No)
- if button_reply == QtWidgets.QMessageBox.Yes:
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
+ QMessageBox.StandardButton.No)
+ if button_reply == QMessageBox.StandardButton.Yes:
return True
else:
return False
@@ -83,10 +84,10 @@ def ask_change_server_or_api_key(self):
@QtCore.pyqtSlot()
def on_toggle_api_password_view(self):
cur_em = self.lineEdit_api_key.echoMode()
- if cur_em == QtWidgets.QLineEdit.Normal:
- new_em = QtWidgets.QLineEdit.PasswordEchoOnEdit
+ if cur_em == QtWidgets.QLineEdit.EchoMode.Normal:
+ new_em = QtWidgets.QLineEdit.EchoMode.PasswordEchoOnEdit
else:
- new_em = QtWidgets.QLineEdit.Normal
+ new_em = QtWidgets.QLineEdit.EchoMode.Normal
self.lineEdit_api_key.setEchoMode(new_em)
@QtCore.pyqtSlot()
@@ -95,7 +96,7 @@ def on_api_token_renew(self):
api_key = self.settings.value("auth/api key")
if len(api_key) == 36:
# deprecated API key
- ret = QtWidgets.QMessageBox.question(
+ ret = QMessageBox.question(
self,
"Deprecated API key",
"You are using an API key instead of an API token. "
@@ -103,7 +104,7 @@ def on_api_token_renew(self):
+ "DCOR-Aid can only remove it locally. A new API token "
+ "will be created. Proceed?"
)
- if ret != QtWidgets.QMessageBox.Yes:
+ if ret != QMessageBox.StandardButton.Yes:
# Abort
return
# Create a new token
@@ -132,14 +133,14 @@ def on_api_token_revoke(self):
api_key = self.settings.value("auth/api key")
if len(api_key) == 36:
# deprecated API key
- ret = QtWidgets.QMessageBox.question(
+ ret = QMessageBox.question(
self,
"Deprecated API key",
"You are using an API key instead of an API token. "
+ "API keys are deprecated and cannot be invalidated. "
+ "DCOR-Aid can only remove it locally. Proceed?"
)
- if ret != QtWidgets.QMessageBox.Yes:
+ if ret != QMessageBox.StandardButton.Yes:
# Abort
return
else:
@@ -167,7 +168,7 @@ def on_downloads_browse(self):
def on_downloads_init(self):
fallback = QStandardPaths.writableLocation(
- QStandardPaths.DownloadLocation)
+ QStandardPaths.StandardLocation.DownloadLocation)
dl_path = self.settings.value("downloads/default path", fallback)
self.lineEdit_downloads_path.setText(dl_path)
@@ -180,12 +181,12 @@ def on_uploads_apply(self):
path_cache = self.lineEdit_uploads_cache.text()
self.settings.setValue("uploads/cache path", path_cache)
if path_cache != current:
- msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Warning)
+ msg = QMessageBox()
+ msg.setIcon(QMessageBox.Warning)
msg.setText("In order for the new cache path to be used, please "
"restart DCOR-Aid!")
msg.setWindowTitle("Please restart DCOR-Aid")
- msg.exec_()
+ msg.exec()
def on_uploads_browse(self):
default = self.settings.value("uploads/cache path", ".")
@@ -199,7 +200,7 @@ def on_uploads_browse(self):
@QtCore.pyqtSlot()
def on_uploads_init(self):
fallback = QStandardPaths.writableLocation(
- QStandardPaths.CacheLocation)
+ QStandardPaths.StandardLocation.CacheLocation)
cache_path = self.settings.value("uploads/cache path", fallback)
if not pathlib.Path(cache_path).exists():
cache_path = fallback
@@ -225,7 +226,7 @@ def on_show_server(self):
self.lineEdit_api_key.setText(self.settings.value("auth/api key", ""))
self.tabWidget.setCurrentIndex(0) # server settings
self.lineEdit_api_key.setEchoMode(
- QtWidgets.QLineEdit.PasswordEchoOnEdit)
+ QtWidgets.QLineEdit.EchoMode.PasswordEchoOnEdit)
self.show()
self.activateWindow()
@@ -237,12 +238,12 @@ def on_show_user(self):
user_dict = api.get_user_dict()
except tuple(list(ConnectionTimeoutErrors) + [NoAPIKeyError]):
self.logger.error(tb.format_exc())
- msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Warning)
+ msg = QMessageBox()
+ msg.setIcon(QMessageBox.Warning)
msg.setText("No connection or wrong server or invalid API token!")
msg.setWindowTitle("Warning")
msg.setDetailedText(tb.format_exc())
- msg.exec_()
+ msg.exec()
self.on_show_server()
else:
self.lineEdit_user_id.setText(user_dict["name"])
@@ -268,12 +269,12 @@ def on_update_user(self):
user_dict = api.get_user_dict()
except (ConnectionError, NoAPIKeyError):
self.logger.error(tb.format_exc())
- msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Warning)
+ msg = QMessageBox()
+ msg.setIcon(QMessageBox.Warning)
msg.setText("No connection or wrong server or invalid API token!")
msg.setWindowTitle("Warning")
msg.setDetailedText(tb.format_exc())
- msg.exec_()
+ msg.exec()
self.on_show_server()
update_dict = {}
update_dict["id"] = user_dict["id"]
@@ -307,12 +308,12 @@ def on_update_server(self):
api.get_user_dict() # raises an exception if credentials are wrong
except BaseException:
self.logger.error(tb.format_exc())
- msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Critical)
+ msg = QMessageBox()
+ msg.setIcon(QMessageBox.Icon.Critical)
msg.setText("Bad server / API token combination!")
msg.setWindowTitle("Error")
msg.setDetailedText(tb.format_exc())
- msg.exec_()
+ msg.exec()
else:
if old_server != server or old_api_key != api_key:
if self.ask_change_server_or_api_key():
diff --git a/dcoraid/gui/status_widget.py b/dcoraid/gui/status_widget.py
index a913fa6..5b031eb 100644
--- a/dcoraid/gui/status_widget.py
+++ b/dcoraid/gui/status_widget.py
@@ -3,7 +3,7 @@
import requests
import traceback
-from PyQt5 import QtCore, QtGui, QtWidgets
+from PyQt6 import QtCore, QtGui, QtWidgets
from ..common import ConnectionTimeoutErrors
@@ -26,7 +26,7 @@ def __init__(self, *args, **kwargs):
self.toolButton_user = QtWidgets.QToolButton()
self.toolButton_user.setText("Initialization...")
self.toolButton_user.setToolButtonStyle(
- QtCore.Qt.ToolButtonTextBesideIcon)
+ QtCore.Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
self.toolButton_user.setAutoRaise(True)
self.layout.addWidget(self.toolButton_user)
self.toolButton_user.clicked.connect(self.clicked)
@@ -56,7 +56,8 @@ def __init__(self, *args, **kwargs):
def get_favicon(server):
dldir = pathlib.Path(
QtCore.QStandardPaths.writableLocation(
- QtCore.QStandardPaths.AppDataLocation)) / "favicons"
+ QtCore.QStandardPaths.StandardLocation.AppDataLocation)
+ ) / "favicons"
dldir.mkdir(exist_ok=True, parents=True)
favname = dldir / (server.split("://")[1] + "_favicon.ico")
diff --git a/dcoraid/gui/tools/wait_cursor.py b/dcoraid/gui/tools/wait_cursor.py
index aff913f..208d84a 100644
--- a/dcoraid/gui/tools/wait_cursor.py
+++ b/dcoraid/gui/tools/wait_cursor.py
@@ -1,16 +1,16 @@
import functools
-from PyQt5.QtWidgets import QApplication
-from PyQt5.QtGui import QCursor
-from PyQt5.QtCore import Qt, QEventLoop
+from PyQt6.QtWidgets import QApplication
+from PyQt6.QtGui import QCursor
+from PyQt6.QtCore import Qt, QEventLoop
class ShowWaitCursor:
def __enter__(self):
- QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
+ QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
# This overloaded function call makes sure that all events,
# even those triggered during the function call, are processed.
- QApplication.processEvents(QEventLoop.AllEvents, 50)
+ QApplication.processEvents(QEventLoop.ProcessEventsFlag.AllEvents, 50)
return self
def __exit__(self, type, value, traceback):
diff --git a/dcoraid/gui/updater.py b/dcoraid/gui/updater.py
index fcd2ee2..9103aa8 100644
--- a/dcoraid/gui/updater.py
+++ b/dcoraid/gui/updater.py
@@ -6,7 +6,7 @@
import urllib.request
from dclab.external.packaging import parse as parse_version
-from PyQt5 import QtCore
+from PyQt6 import QtCore
class UpdateWorker(QtCore.QObject):
diff --git a/dcoraid/gui/upload/circle_mgr.py b/dcoraid/gui/upload/circle_mgr.py
index 7f99699..9c75a31 100644
--- a/dcoraid/gui/upload/circle_mgr.py
+++ b/dcoraid/gui/upload/circle_mgr.py
@@ -1,6 +1,6 @@
from functools import lru_cache
-from PyQt5 import QtWidgets
+from PyQt6 import QtWidgets
from ..api import get_ckan_api
@@ -40,7 +40,7 @@ def ask_for_new_circle(parent_widget):
+ "a colleague to add you to a Circle (Your user name is "
+ "'{}').".format(ud["name"])
+ "\n\nTo proceed with Circle creation, please choose a name:",
- QtWidgets.QLineEdit.Normal,
+ QtWidgets.QLineEdit.EchoMode.Normal,
"{}'s Circle".format(name))
if ok_pressed and text != '':
cname = "user-circle-{}".format(ud["name"])
diff --git a/dcoraid/gui/upload/dlg_upload.py b/dcoraid/gui/upload/dlg_upload.py
index 80516fd..fb79b04 100644
--- a/dcoraid/gui/upload/dlg_upload.py
+++ b/dcoraid/gui/upload/dlg_upload.py
@@ -3,7 +3,7 @@
from importlib import resources
import traceback as tb
-from PyQt5 import uic, QtCore, QtGui, QtWidgets
+from PyQt6 import uic, QtCore, QtGui, QtWidgets
from ...api import dataset_create
@@ -38,10 +38,10 @@ def __init__(self, *args, **kwargs):
# Dialog box buttons
self.pushButton_proceed = self.buttonBox.button(
- QtWidgets.QDialogButtonBox.Apply)
+ QtWidgets.QDialogButtonBox.StandardButton.Apply)
self.pushButton_proceed.setText("Proceed with upload / Enqueue job")
self.pushButton_cancel = self.buttonBox.button(
- QtWidgets.QDialogButtonBox.Cancel)
+ QtWidgets.QDialogButtonBox.StandardButton.Cancel)
# Keep identifier
self.identifier = self.instance_counter
@@ -85,7 +85,7 @@ def __init__(self, *args, **kwargs):
self.comboBox_vis.addItem("Private", "private")
# Shortcut for testing
- self.shortcut = QtWidgets.QShortcut(
+ self.shortcut = QtGui.QShortcut(
QtGui.QKeySequence("Ctrl+Alt+Shift+E"), self)
self.shortcut.activated.connect(self._autofill_for_testing)
@@ -102,8 +102,8 @@ def __init__(self, *args, **kwargs):
self.comboBox_preset.addItems(sorted(self.presets.keys()))
# Restrict characters for line edit
- regex = QtCore.QRegExp(job.VALID_RESOURCE_REGEXP)
- validator = QtGui.QRegExpValidator(regex)
+ regex = QtCore.QRegularExpression(job.VALID_RESOURCE_REGEXP)
+ validator = QtGui.QRegularExpressionValidator(regex)
self.lineEdit_res_filename.setValidator(validator)
# Signals and slots
@@ -235,7 +235,7 @@ def on_add_resources(self, files=None):
# Select the first item
ix = self.rvmodel.index(0, 0)
sm = self.listView_resources.selectionModel()
- sm.select(ix, QtCore.QItemSelectionModel.Select)
+ sm.select(ix, QtCore.QItemSelectionModel.SelectionFlag.Select)
@QtCore.pyqtSlot()
def on_preset_load(self):
@@ -322,7 +322,7 @@ def on_proceed(self):
+ "Please select 'No' if you would like to change things. "
+ "Select 'Yes' to proceed without changes - be aware that "
+ "you will not be able to change anything later on.")
- if choice != QtWidgets.QMessageBox.Yes:
+ if choice != QtWidgets.QMessageBox.StandardButton.Yes:
return
if not self.rvmodel.supplements_were_edited():
choice = QtWidgets.QMessageBox.question(
@@ -333,7 +333,7 @@ def on_proceed(self):
+ "appear when you click on a resource. Please select 'No' "
+ "if you would like to go back (recommended). Select 'Yes' "
+ "to proceed without changes (no future changes possible).")
- if choice != QtWidgets.QMessageBox.Yes:
+ if choice != QtWidgets.QMessageBox.StandardButton.Yes:
return
# Try to create the dataset and display any issues with the metadata
@@ -343,7 +343,7 @@ def on_proceed(self):
except BaseException:
self.logger.error(tb.format_exc())
msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Critical)
+ msg.setIcon(QtWidgets.QMessageBox.Icon.Critical)
msg.setText("It was not possible to create a dataset draft. "
"If this is not a connection problem, please consider "
"creating an issue on GitHub.")
msg.setWindowTitle("Dataset creation failed")
msg.setDetailedText(tb.format_exc())
- msg.exec_()
+ msg.exec()
return
self.setHidden(True)
# Remember the dataset identifier
diff --git a/dcoraid/gui/upload/resource_schema_preset.py b/dcoraid/gui/upload/resource_schema_preset.py
index 5f2a35a..f898933 100644
--- a/dcoraid/gui/upload/resource_schema_preset.py
+++ b/dcoraid/gui/upload/resource_schema_preset.py
@@ -1,7 +1,7 @@
import json
import pathlib
-from PyQt5.QtCore import QStandardPaths
+from PyQt6.QtCore import QStandardPaths
class PersistentResourceSchemaPresets:
@@ -10,7 +10,8 @@ def __init__(self):
self._presets = {}
# This is a roaming path on Windows
self.path = pathlib.Path(QStandardPaths.writableLocation(
- QStandardPaths.AppDataLocation)) / "upload_resource_schema_presets"
+ QStandardPaths.StandardLocation.AppDataLocation)
+ ) / "upload_resource_schema_presets"
self.path.mkdir(exist_ok=True, parents=True)
for pp in self.path.glob("*.json"):
with pp.open() as fd:
diff --git a/dcoraid/gui/upload/resources_model.py b/dcoraid/gui/upload/resources_model.py
index b25726b..89f1d0b 100644
--- a/dcoraid/gui/upload/resources_model.py
+++ b/dcoraid/gui/upload/resources_model.py
@@ -4,7 +4,7 @@
from functools import lru_cache
import pathlib
-from PyQt5 import QtCore, QtGui
+from PyQt6 import QtCore, QtGui
from ...upload import job
@@ -31,9 +31,9 @@ def add_resources(self, rslist):
self.resources[ff] = {}
self.layoutChanged.emit()
- def data(self, index, role=QtCore.Qt.DisplayRole):
+ def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
"""Return data for 'View'"""
- if role == QtCore.Qt.DisplayRole:
+ if role == QtCore.Qt.ItemDataRole.DisplayRole:
_, data = self.get_data_for_index(index)
return data["file"]["filename"]
diff --git a/dcoraid/gui/upload/widget_schema.py b/dcoraid/gui/upload/widget_schema.py
index 29170d9..33ad6d0 100644
--- a/dcoraid/gui/upload/widget_schema.py
+++ b/dcoraid/gui/upload/widget_schema.py
@@ -1,4 +1,4 @@
-from PyQt5 import QtCore, QtWidgets
+from PyQt6 import QtCore, QtWidgets
from .widget_supplement_item import RSSItem, RSSTagsItem, TitleItem
@@ -113,9 +113,11 @@ def populate_schema(self, schema_dict):
self.schema_widgets[sec] = widget_list
# Finally add a stretch spacer in case there are not enough
# items.
- spacer_item = QtWidgets.QSpacerItem(20, 0,
- QtWidgets.QSizePolicy.Minimum,
- QtWidgets.QSizePolicy.Expanding)
+ spacer_item = QtWidgets.QSpacerItem(
+ 20, 0,
+ QtWidgets.QSizePolicy.Policy.Minimum,
+ QtWidgets.QSizePolicy.Policy.Expanding
+ )
self.verticalLayout.addItem(spacer_item)
def set_schema(self, schema_dict):
diff --git a/dcoraid/gui/upload/widget_supplement_item.py b/dcoraid/gui/upload/widget_supplement_item.py
index de82b9e..f94683c 100644
--- a/dcoraid/gui/upload/widget_supplement_item.py
+++ b/dcoraid/gui/upload/widget_supplement_item.py
@@ -1,6 +1,6 @@
from importlib import resources
-from PyQt5 import uic, QtCore, QtWidgets
+from PyQt6 import uic, QtCore, QtWidgets
class TitleItem(QtWidgets.QWidget):
diff --git a/dcoraid/gui/upload/widget_tablecell_actions.py b/dcoraid/gui/upload/widget_tablecell_actions.py
index 54fb921..e080fe9 100644
--- a/dcoraid/gui/upload/widget_tablecell_actions.py
+++ b/dcoraid/gui/upload/widget_tablecell_actions.py
@@ -1,7 +1,7 @@
from importlib import resources
import webbrowser
-from PyQt5 import uic, QtCore, QtWidgets
+from PyQt6 import uic, QtCore, QtWidgets
class TableCellActions(QtWidgets.QWidget):
@@ -38,7 +38,7 @@ def on_delete(self):
@QtCore.pyqtSlot()
def on_error(self):
msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Critical)
+ msg.setIcon(QtWidgets.QMessageBox.Icon.Critical)
msg.setText("There was an error during data transfer. If this happens "
+ "often or with a particular type of dataset, please "
+ ""
@@ -46,7 +46,7 @@ def on_error(self):
"to see details required for fixing the problem.")
msg.setWindowTitle(f"Job {self.job.dataset_id[:5]} error")
msg.setDetailedText(self.job.traceback)
- msg.exec_()
+ msg.exec()
@QtCore.pyqtSlot()
def on_retry(self):
diff --git a/dcoraid/gui/upload/widget_upload.py b/dcoraid/gui/upload/widget_upload.py
index ab6bd4e..d8dbdfb 100644
--- a/dcoraid/gui/upload/widget_upload.py
+++ b/dcoraid/gui/upload/widget_upload.py
@@ -9,8 +9,8 @@
import time
import traceback as tb
-from PyQt5 import uic, QtCore, QtWidgets
-from PyQt5.QtCore import QStandardPaths
+from PyQt6 import uic, QtCore, QtGui, QtWidgets
+from PyQt6.QtCore import QStandardPaths
from ...api import APINotFoundError
from ...upload import queue, task
@@ -44,10 +44,10 @@ def __init__(self, *args, **kwargs):
# menu button for adding tasks
menu = QtWidgets.QMenu()
- act1 = QtWidgets.QAction("Select task files from disk", self)
+ act1 = QtGui.QAction("Select task files from disk", self)
act1.setData("single")
menu.addAction(act1)
- act2 = QtWidgets.QAction(
+ act2 = QtGui.QAction(
"Recursively find and load task files from a folder", self)
act2.setData("bulk")
menu.addAction(act2)
@@ -57,7 +57,7 @@ def __init__(self, *args, **kwargs):
#: path to persistent shelf to be able to resume uploads on startup
self.shelf_path = os_path.join(
QStandardPaths.writableLocation(
- QStandardPaths.AppLocalDataLocation),
+ QStandardPaths.StandardLocation.AppLocalDataLocation),
"persistent_upload_jobs")
# UploadQueue instance
@@ -80,7 +80,7 @@ def __del__(self):
def cache_dir(self):
"""path to cache directory (compression)"""
fallback = QStandardPaths.writableLocation(
- QStandardPaths.CacheLocation)
+ QStandardPaths.StandardLocation.CacheLocation)
settings = QtCore.QSettings()
cache_path = settings.value("uploads/cache path", fallback)
return cache_path
@@ -138,7 +138,7 @@ def initialize(self, retry_if_fail=True):
queue.DCORAidQueueMissingResourceWarning)]
if w:
msg = QtWidgets.QMessageBox()
- msg.setIcon(QtWidgets.QMessageBox.Information)
+ msg.setIcon(QtWidgets.QMessageBox.Icon.Information)
msg.setText(
"You have created upload jobs in a previous session "
+ "and some of the resources cannot be located on "
@@ -153,7 +153,7 @@ def initialize(self, retry_if_fail=True):
msg.setDetailedText(
"\n\n".join([str(wi.message) for wi in w]))
msg.setWindowTitle("Resources for uploads missing")
- msg.exec_()
+ msg.exec()
if self.parent().parent().isVisible():
self.widget_jobs.set_job_list(self.jobs)
# upload finished signal
@@ -221,10 +221,10 @@ def on_upload_manual_ready(self, upload_dialog):
resource_names=names,
supplements=supps)
- @QtCore.pyqtSlot(QtWidgets.QAction)
+ @QtCore.pyqtSlot(QtGui.QAction)
def on_upload_task(
self,
- action: QtWidgets.QAction | pathlib.Path | list[pathlib.Path]):
+ action: QtGui.QAction | pathlib.Path | list[pathlib.Path]):
"""Import an UploadJob task file and add it to the queue
This functionality is mainly used for automation. Another
@@ -249,14 +249,14 @@ def on_upload_task(
self,
"Select folder to search for DCOR-Aid task files",
".",
- QtWidgets.QFileDialog.ShowDirsOnly,
+ QtWidgets.QFileDialog.Option.ShowDirsOnly,
)
files = pathlib.Path(tdir).rglob("*.dcoraid-task")
# Keep track of a persistent task ID to dataset ID dictionary
path_id_dict = os_path.join(
QStandardPaths.writableLocation(
- QStandardPaths.AppLocalDataLocation),
+ QStandardPaths.StandardLocation.AppLocalDataLocation),
"map_task_to_dataset_id.txt")
map_task_to_dataset_id = task.PersistentTaskDatasetIDDict(path_id_dict)
api = get_ckan_api()
@@ -320,7 +320,7 @@ def on_upload_task(
f"Would you like to create a new dataset for this "
f"task file on '{api.server}' (select 'No' if in doubt)?"
)
- if ret == QtWidgets.QMessageBox.Yes:
+ if ret == QtWidgets.QMessageBox.StandardButton.Yes:
# retry, this time forcing the creation of a new dataset
upload_job = task.load_task(force_dataset_creation=True,
**load_kw)
@@ -375,8 +375,10 @@ class UploadTableWidget(QtWidgets.QTableWidget):
def __init__(self, *args, **kwargs):
super(UploadTableWidget, self).__init__(*args, **kwargs)
self.logger = logging.getLogger(__name__).getChild("UploadTableWidget")
- self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
- self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+ self.setSelectionMode(
+ QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
+ self.setSelectionBehavior(
+ QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self._jobs = None
@@ -476,8 +478,9 @@ def update_job_status(self):
# spacing (did not work in __init__)
header = self.horizontalHeader()
header.setSectionResizeMode(
- 0, QtWidgets.QHeaderView.ResizeToContents)
- header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
+ 0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
+ header.setSectionResizeMode(
+ 1, QtWidgets.QHeaderView.ResizeMode.Stretch)
# enable updates again
self.setUpdatesEnabled(True)
diff --git a/dcoraid/gui/wizard/__init__.py b/dcoraid/gui/wizard/__init__.py
index 25a6e7e..c09fa29 100644
--- a/dcoraid/gui/wizard/__init__.py
+++ b/dcoraid/gui/wizard/__init__.py
@@ -3,7 +3,8 @@
import uuid
from dclab.rtdc_dataset.fmt_dcor import access_token
-from PyQt5 import uic, QtCore, QtWidgets
+from PyQt6 import uic, QtCore, QtWidgets
+from PyQt6.QtWidgets import QMessageBox, QWizard
from ...api import NoAPIKeyError, APINotFoundError, CKANAPI
@@ -73,7 +74,7 @@ def __init__(self, *args, **kwargs):
self.pushButton_path_access_token.clicked.connect(
self.on_browse_access_token)
- self.button(QtWidgets.QWizard.FinishButton).clicked.connect(
+ self.button(QWizard.WizardButton.FinishButton).clicked.connect(
self._finalize)
@QtCore.pyqtSlot()
@@ -94,18 +95,19 @@ def _finalize(self):
+ "of DCOR-Aid. If you choose 'No', then the original " \
+ "server and API token are NOT changed. Do you " \
+ "really want to quit DCOR-Aid?"
- button_reply = QtWidgets.QMessageBox.question(
+ button_reply = QMessageBox.question(
self,
'DCOR-Aid restart required',
msg,
- QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
- QtWidgets.QMessageBox.No)
- if button_reply == QtWidgets.QMessageBox.Yes:
+ QMessageBox.StandardButton.Yes |
+ QMessageBox.StandardButton.No,
+ QMessageBox.StandardButton.No)
+ if button_reply == QMessageBox.StandardButton.Yes:
proceed = True
else:
proceed = False
else:
- QtWidgets.QMessageBox.information(
+ QMessageBox.information(
self,
"DCOR-Aid restart required",
"Please restart DCOR-Aid to proceed."
@@ -127,7 +129,7 @@ def _finalize(self):
settings.remove("auth/certificate")
settings.sync()
QtWidgets.QApplication.processEvents(
- QtCore.QEventLoop.AllEvents, 300)
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
QtWidgets.QApplication.quit()
sys.exit(0) # if the above does not work
diff --git a/pyproject.toml b/pyproject.toml
index 0ad6560..1af7c99 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -36,7 +36,7 @@ dependencies = [
dynamic = ["version"]
[project.optional-dependencies]
-GUI = ["pyqt5"]
+GUI = ["PyQt6"]
[project.scripts]
dcoraid = "dcoraid.__main__:main"
diff --git a/tests/conftest.py b/tests/conftest.py
index 985e9b3..bdc4035 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,8 +5,8 @@
import tempfile
import time
-from PyQt5 import QtCore
-from PyQt5.QtCore import QStandardPaths
+from PyQt6 import QtCore
+from PyQt6.QtCore import QStandardPaths
import pytest
from dcoraid.api import APIConflictError
@@ -28,13 +28,13 @@ def cleanup_dcoraid_tasks():
# remove persistent upload jobs
shelf_path = os_path.join(
QStandardPaths.writableLocation(
- QStandardPaths.AppLocalDataLocation),
+ QStandardPaths.StandardLocation.AppLocalDataLocation),
"persistent_upload_jobs")
shutil.rmtree(shelf_path, ignore_errors=True)
# remove persistent upload id dict
path_id_dict = os_path.join(
QStandardPaths.writableLocation(
- QStandardPaths.AppLocalDataLocation),
+ QStandardPaths.StandardLocation.AppLocalDataLocation),
"map_task_to_dataset_id.txt")
path_id_dict = pathlib.Path(path_id_dict)
if path_id_dict.exists():
@@ -47,9 +47,8 @@ def pytest_configure(config):
QtCore.QCoreApplication.setOrganizationName("DCOR")
QtCore.QCoreApplication.setOrganizationDomain("dcor.mpl.mpg.de")
QtCore.QCoreApplication.setApplicationName("dcoraid")
- QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
+ QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
settings = QtCore.QSettings()
- settings.setIniCodec("utf-8")
settings.setValue("check for updates", "0")
settings.setValue("user scenario", "dcor-dev")
settings.setValue("auth/server", common.SERVER)
@@ -70,9 +69,8 @@ def pytest_unconfigure(config):
QtCore.QCoreApplication.setOrganizationName("DCOR")
QtCore.QCoreApplication.setOrganizationDomain("dcor.mpl.mpg.de")
QtCore.QCoreApplication.setApplicationName("dcoraid")
- QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
+ QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
settings = QtCore.QSettings()
- settings.setIniCodec("utf-8")
settings.remove("debug/without timers")
settings.remove("check for updates")
settings.sync()
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 7cbe096..3887dfe 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,4 +1,4 @@
-pyqt5
+PyQt6
pytest
pytest-qt
pluggy>=1.0
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 7f13e3b..64a62f7 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -11,8 +11,8 @@
from dcoraid.gui.upload import widget_upload
import pytest
-from PyQt5 import QtCore, QtWidgets, QtTest
-from PyQt5.QtWidgets import QInputDialog, QMessageBox
+from PyQt6 import QtCore, QtGui, QtWidgets, QtTest
+from PyQt6.QtWidgets import QInputDialog, QMessageBox
from . import common
@@ -24,25 +24,27 @@ def mw(qtbot):
QtCore.QCoreApplication.setOrganizationName("DCOR")
QtCore.QCoreApplication.setOrganizationDomain("dcor.mpl.mpg.de")
QtCore.QCoreApplication.setApplicationName("dcoraid")
- QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
+ QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
settings = QtCore.QSettings()
- settings.setIniCodec("utf-8")
settings.setValue("auth/server", common.SERVER)
QtTest.QTest.qWait(100)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 5000)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 5000)
# Code that will run before your test
mw = DCORAid()
qtbot.addWidget(mw)
QtWidgets.QApplication.setActiveWindow(mw)
QtTest.QTest.qWait(100)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 5000)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 5000)
# Run test
yield mw
# Make sure that all daemons are gone
mw.close()
# It is extremely weird, but this seems to be important to avoid segfaults!
QtTest.QTest.qWait(100)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 5000)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 5000)
@pytest.mark.filterwarnings("ignore::UserWarning",
@@ -52,7 +54,7 @@ def test_gui_anonymous(qtbot):
QtCore.QCoreApplication.setOrganizationName("DCOR")
QtCore.QCoreApplication.setOrganizationDomain("dcor.mpl.mpg.de")
QtCore.QCoreApplication.setApplicationName("dcoraid")
- QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
+ QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
settings = QtCore.QSettings()
spath = pathlib.Path(settings.fileName())
# temporarily move settings to temporary location
@@ -73,7 +75,8 @@ def test_gui_anonymous(qtbot):
mw = DCORAid()
qtbot.addWidget(mw)
QtWidgets.QApplication.setActiveWindow(mw)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 3000)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 3000)
# sanity check
assert mw.settings.value("user scenario") == "anonymous"
assert mw.settings.value("auth/server") == "dcor.mpl.mpg.de"
@@ -87,7 +90,8 @@ def test_gui_anonymous(qtbot):
shutil.copy2(stmp, spath)
mw.close()
QtTest.QTest.qWait(500)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 500)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 500)
def test_gui_mydata_dataset_add_to_collection(mw, qtbot):
@@ -145,22 +149,23 @@ def test_gui_start_with_bad_server(qtbot):
QtCore.QCoreApplication.setOrganizationName("DCOR")
QtCore.QCoreApplication.setOrganizationDomain("dcor.mpl.mpg.de")
QtCore.QCoreApplication.setApplicationName("dcoraid")
- QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
+ QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
settings = QtCore.QSettings()
- settings.setIniCodec("utf-8")
settings.setValue("auth/server", "WRONG-dcor-dev.mpl.mpg.de")
try:
mw = DCORAid()
qtbot.addWidget(mw)
QtWidgets.QApplication.setActiveWindow(mw)
QtTest.QTest.qWait(200)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 5000)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 5000)
# just make sure that DCOR-Aid thinks it is offline
assert not mw.panel_upload.isEnabled()
assert not mw.panel_download.isEnabled()
mw.close()
QtTest.QTest.qWait(500)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 5000)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 5000)
except BaseException:
raise
finally:
@@ -172,9 +177,8 @@ def test_gui_start_with_bad_api_key(qtbot):
QtCore.QCoreApplication.setOrganizationName("DCOR")
QtCore.QCoreApplication.setOrganizationDomain("dcor.mpl.mpg.de")
QtCore.QCoreApplication.setApplicationName("dcoraid")
- QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
+ QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
settings = QtCore.QSettings()
- settings.setIniCodec("utf-8")
good_key = settings.value("auth/api key")
bad_key = good_key[:-2] + "00"
settings.setValue("auth/api key", bad_key)
@@ -183,14 +187,16 @@ def test_gui_start_with_bad_api_key(qtbot):
qtbot.addWidget(mw)
QtWidgets.QApplication.setActiveWindow(mw)
QtTest.QTest.qWait(200)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 5000)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 5000)
# just make sure that DCOR-Aid thinks it is offline
assert not mw.panel_upload.isEnabled()
# downloads should still be possible
assert mw.panel_download.isEnabled()
mw.close()
QtTest.QTest.qWait(500)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 5000)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 5000)
except BaseException:
raise
finally:
@@ -208,7 +214,7 @@ def test_gui_upload_simple(mw, qtbot):
# Avoid message boxes
with mock.patch.object(QMessageBox,
"question",
- return_value=QMessageBox.Yes):
+ return_value=QMessageBox.StandardButton.Yes):
# Commence upload
dlg.on_proceed()
assert dlg.dataset_id is not None
@@ -224,7 +230,7 @@ def test_gui_upload_task(mw, qtbot):
return_value=([tpath], None)):
with mock.patch.object(QMessageBox, "information",
return_value=None):
- act = QtWidgets.QAction("some unimportant text")
+ act = QtGui.QAction("some unimportant text")
act.setData("single")
mw.panel_upload.on_upload_task(action=act)
uj = mw.panel_upload.jobs[-1]
@@ -243,9 +249,9 @@ def test_gui_upload_task_bad_dataset_id_no(mw, qtbot):
QtWidgets.QFileDialog, "getOpenFileNames",
return_value=([tpath], None)), \
mock.patch.object(QMessageBox, "question",
- return_value=QMessageBox.No), \
+ return_value=QMessageBox.StandardButton.No), \
mock.patch.object(QMessageBox, "information", return_value=None):
- act = QtWidgets.QAction("some unimportant text")
+ act = QtGui.QAction("some unimportant text")
act.setData("single")
mw.panel_upload.on_upload_task(action=act)
if len(mw.panel_upload.jobs):
@@ -264,9 +270,9 @@ def test_gui_upload_task_bad_dataset_id_yes(mw, qtbot):
QtWidgets.QFileDialog, "getOpenFileNames",
return_value=([tpath], None)), \
mock.patch.object(QMessageBox, "question",
- return_value=QMessageBox.Yes), \
+ return_value=QMessageBox.StandardButton.Yes), \
mock.patch.object(QMessageBox, "information", return_value=None):
- act = QtWidgets.QAction("some unimportant text")
+ act = QtGui.QAction("some unimportant text")
act.setData("single")
mw.panel_upload.on_upload_task(action=act)
uj = mw.panel_upload.jobs[-1]
@@ -301,7 +307,7 @@ def test_gui_upload_task_missing_circle_multiple(mw, qtbot):
mock.patch.object(QtWidgets.QInputDialog, "getItem",
return_value=(defaults["circle"], True)), \
mock.patch.object(QMessageBox, "information", return_value=None):
- act = QtWidgets.QAction("some unimportant text")
+ act = QtGui.QAction("some unimportant text")
act.setData("bulk")
request_circle = widget_upload.circle_mgr.request_circle
with mock.patch.object(widget_upload.circle_mgr,
@@ -325,7 +331,7 @@ def test_gui_upload_private(mw, qtbot):
dlg.comboBox_vis.setCurrentIndex(dlg.comboBox_vis.findData("private"))
# Avoid message boxes
with mock.patch.object(QMessageBox, "question",
- return_value=QMessageBox.Yes), \
+ return_value=QMessageBox.StandardButton.Yes), \
mock.patch.object(QMessageBox, "information", return_value=None):
# Commence upload
dlg.on_proceed()
@@ -349,7 +355,8 @@ def test_gui_upload_task_missing_circle(mw, qtbot):
dataset_dict.pop("owner_org")
tpath = common.make_upload_task(task_id=task_id,
dataset_dict=dataset_dict)
- QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 300)
+ QtWidgets.QApplication.processEvents(
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
defaults = common.get_test_defaults()
with mock.patch.object(
QtWidgets.QFileDialog, "getOpenFileNames",
@@ -357,7 +364,7 @@ def test_gui_upload_task_missing_circle(mw, qtbot):
mock.patch.object(QtWidgets.QInputDialog, "getItem",
return_value=(defaults["circle"], True)), \
mock.patch.object(QMessageBox, "information", return_value=None):
- act = QtWidgets.QAction("some unimportant text")
+ act = QtGui.QAction("some unimportant text")
act.setData("single")
mw.panel_upload.on_upload_task(action=act)
uj = mw.panel_upload.jobs[-1]