diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 2b231a81..04aef733 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -27,12 +27,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] include: - os: windows-latest - python-version: "3.11" + python-version: "3.12" - os: macos-latest - python-version: "3.11" + python-version: "3.12" runs-on: ${{ matrix.os }} steps: diff --git a/ibridgesgui/search.py b/ibridgesgui/search.py index 6f574267..5e28b69e 100644 --- a/ibridgesgui/search.py +++ b/ibridgesgui/search.py @@ -87,22 +87,24 @@ def search(self): self.error_label.clear() self.current_batch_num = 0 self.results = None + case_sensitive = self.case_sensitive_box.isChecked() msg, search_path, path_pattern, meta_searches, checksum = self._validate_search_params() self.logger.debug( - "Search parameters %s, %s, %s, %s, %s", + "Search parameters %s, %s, %s, %s, %s, %s", msg, str(search_path), path_pattern, str(meta_searches), checksum, + str(case_sensitive), ) if msg is not None: self.error_label.setText(msg) self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor)) return - self._start_search(search_path, path_pattern, meta_searches, checksum) + self._start_search(search_path, path_pattern, meta_searches, checksum, case_sensitive) def next_batch(self): """Load next batch of results.""" @@ -283,7 +285,7 @@ def _download_fetch_result(self, thread: dict): self.error_label.setText("Errors occurred during download. Consult the logs.") self.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor)) - def _start_search(self, search_path, path_pattern, meta_searches, checksum): + def _start_search(self, search_path, path_pattern, meta_searches, checksum, case_sensitive): self.search_button.setEnabled(False) # check if session comes from env file in ibridges config if is_session_from_config(self.session): @@ -296,7 +298,13 @@ def _start_search(self, search_path, path_pattern, meta_searches, checksum): self.error_label.setText("Searching ...") try: self.search_thread = SearchThread( - self.logger, env_path, search_path, path_pattern, meta_searches, checksum + self.logger, + env_path, + search_path, + path_pattern, + meta_searches, + checksum, + case_sensitive, ) except Exception: self.error_label.setText( diff --git a/ibridgesgui/threads.py b/ibridgesgui/threads.py index 37a6b724..1b7d7dbe 100644 --- a/ibridgesgui/threads.py +++ b/ibridgesgui/threads.py @@ -13,8 +13,16 @@ class SearchThread(QThread): result = pyqtSignal(dict) - def __init__(self, logger, ienv_path: Path, search_path: IrodsPath, path_pattern: str, - meta_searches: list, checksum: str): + def __init__( + self, + logger, + ienv_path: Path, + search_path: IrodsPath, + path_pattern: str, + meta_searches: list, + checksum: str, + case_sensitive: bool, + ): """Pass searh parameters.""" super().__init__() self.logger = logger @@ -24,6 +32,7 @@ def __init__(self, logger, ienv_path: Path, search_path: IrodsPath, path_pattern self.search_path = search_path self.path_pattern = path_pattern self.checksum = checksum + self.case_sensitive = case_sensitive self.ms = meta_searches def _delete_session(self): @@ -38,8 +47,12 @@ def run(self): search_out = {} try: res = search_data( - self.thread_session, path=self.search_path, path_pattern = self.path_pattern, - checksum=self.checksum, metadata=self.ms + self.thread_session, + path=self.search_path, + path_pattern=self.path_pattern, + checksum=self.checksum, + metadata=self.ms, + case_sensitive=self.case_sensitive, ) # convert IrodsPaths to strings, the session will be destroyed at the end of the thread search_out["results"] = [str(ipath) for ipath in res] @@ -49,6 +62,7 @@ def run(self): search_out["error"] = "Search takes too long. Please provide more parameters." self.result.emit(search_out) + class TransferDataThread(QThread): """Transfer data between local and iRODS.""" @@ -128,12 +142,9 @@ def run(self): transfer_out["error"] + f"\nTransfer failed, cannot upload {str(local_path)}: {repr(error)}" ) - self.current_progress.emit([self.up_sizes, - transferred_size, - obj_count, - len(self.ops.upload), - obj_failed]) - + self.current_progress.emit( + [self.up_sizes, transferred_size, obj_count, len(self.ops.upload), obj_failed] + ) transferred_size = 0 for irods_path, local_path in self.ops.download: @@ -166,11 +177,9 @@ def run(self): transfer_out["error"] + f"\nTransfer failed, cannot download {str(irods_path)}: {repr(error)}" ) - self.current_progress.emit([self.down_sizes, - transferred_size, - file_count, - len(self.ops.download), - file_failed]) + self.current_progress.emit( + [self.down_sizes, transferred_size, file_count, len(self.ops.download), file_failed] + ) self.ops.execute_meta_download() self._delete_session() diff --git a/ibridgesgui/ui_files/tabBrowser.py b/ibridgesgui/ui_files/tabBrowser.py index fcbfc2ef..e7861fde 100644 --- a/ibridgesgui/ui_files/tabBrowser.py +++ b/ibridgesgui/ui_files/tabBrowser.py @@ -405,7 +405,7 @@ def retranslateUi(self, tabBrowser): self.delete_meta_button.setText(_translate("tabBrowser", "Delete")) self.label_4.setText(_translate("tabBrowser", "Key")) self.label_3.setText(_translate("tabBrowser", "Edit")) - self.update_meta_button.setText(_translate("tabBrowser", "Set")) + self.update_meta_button.setText(_translate("tabBrowser", "Set all keys ..")) self.label_6.setText(_translate("tabBrowser", "Value")) self.info_tabs.setTabText(self.info_tabs.indexOf(self.metadata), _translate("tabBrowser", "Metadata")) self.info_tabs.setTabText(self.info_tabs.indexOf(self.preview), _translate("tabBrowser", "Preview")) diff --git a/ibridgesgui/ui_files/tabBrowser.ui b/ibridgesgui/ui_files/tabBrowser.ui index 87799a00..052b0465 100644 --- a/ibridgesgui/ui_files/tabBrowser.ui +++ b/ibridgesgui/ui_files/tabBrowser.ui @@ -444,7 +444,7 @@ QTabWidget#info_tabs - Set + Set all keys .. diff --git a/ibridgesgui/ui_files/tabSearch.py b/ibridgesgui/ui_files/tabSearch.py index 116d733d..a9a9d419 100644 --- a/ibridgesgui/ui_files/tabSearch.py +++ b/ibridgesgui/ui_files/tabSearch.py @@ -138,6 +138,11 @@ def setupUi(self, tabSearch): self.verticalLayout.addLayout(self.gridLayout) spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) self.verticalLayout.addItem(spacerItem3) + self.case_sensitive_box = QtWidgets.QCheckBox(parent=tabSearch) + self.case_sensitive_box.setObjectName("case_sensitive_box") + self.verticalLayout.addWidget(self.case_sensitive_box) + spacerItem4 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) + self.verticalLayout.addItem(spacerItem4) self.horizontalLayout_5 = QtWidgets.QHBoxLayout() self.horizontalLayout_5.setObjectName("horizontalLayout_5") self.search_button = QtWidgets.QPushButton(parent=tabSearch) @@ -162,8 +167,8 @@ def setupUi(self, tabSearch): self.load_more_button = QtWidgets.QPushButton(parent=tabSearch) self.load_more_button.setObjectName("load_more_button") self.horizontalLayout_5.addWidget(self.load_more_button) - spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.horizontalLayout_5.addItem(spacerItem4) + spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_5.addItem(spacerItem5) self.download_button = QtWidgets.QPushButton(parent=tabSearch) font = QtGui.QFont() font.setPointSize(16) @@ -203,11 +208,11 @@ def setupUi(self, tabSearch): self.search_table.setHorizontalHeaderItem(4, item) self.search_table.horizontalHeader().setStretchLastSection(True) self.verticalLayout.addWidget(self.search_table) - spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - self.verticalLayout.addItem(spacerItem5) - self.verticalLayout_3.addLayout(self.verticalLayout) spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - self.verticalLayout_3.addItem(spacerItem6) + self.verticalLayout.addItem(spacerItem6) + self.verticalLayout_3.addLayout(self.verticalLayout) + spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + self.verticalLayout_3.addItem(spacerItem7) self.retranslateUi(tabSearch) QtCore.QMetaObject.connectSlotsByName(tabSearch) @@ -223,6 +228,7 @@ def retranslateUi(self, tabSearch): self.label1.setText(_translate("tabSearch", "Search by Metadata")) self.label_7.setText(_translate("tabSearch", "Units")) self.label_4.setText(_translate("tabSearch", "Key")) + self.case_sensitive_box.setText(_translate("tabSearch", "Case sensitive")) self.search_button.setText(_translate("tabSearch", "Search")) self.clear_button.setText(_translate("tabSearch", "Clear Results")) self.load_more_button.setText(_translate("tabSearch", "Next 25")) diff --git a/ibridgesgui/ui_files/tabSearch.ui b/ibridgesgui/ui_files/tabSearch.ui index 4b7244c6..7e9c3300 100644 --- a/ibridgesgui/ui_files/tabSearch.ui +++ b/ibridgesgui/ui_files/tabSearch.ui @@ -236,6 +236,29 @@ QTabWidget#info_tabs + + + + Case sensitive + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + diff --git a/pyproject.toml b/pyproject.toml index 95f46838..da35c274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,12 +17,13 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Development Status :: 3 - Alpha", ] dependencies = [ "PyQt6>=6.4.2", - "ibridges==1.1.1", + "ibridges>=1.2.0, < 1.3", "setproctitle==1.3.3", "importlib-resources;python_version<='3.10'", ]