diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ce762d5c..3d1b9dc2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,30 +31,25 @@ jobs: fail-fast: false matrix: platform: [macos-13, windows-latest] - python-version: ["3.8", "3.11"] - backend: [pyside2, pyqt5] - include: - - platform: macos-13 - python-version: "3.10" - backend: pyside6 - - platform: macos-13 - python-version: "3.11" - backend: pyqt6 + python-version: ["3.10", "3.12"] + backend: [pyside6, pyqt6] + exclude: - platform: windows-latest python-version: "3.10" backend: pyside6 - - platform: windows-latest - python-version: "3.11" - backend: pyqt6 - - platform: windows-latest + include: + - platform: macos-13 python-version: "3.9" backend: pyside2 - platform: windows-latest python-version: "3.9" - backend: pyqt5 - exclude: - - python-version: "3.11" backend: pyside2 + - platform: windows-latest + python-version: "3.11" + backend: pyqt5 + # - platform: windows-latest + # python-version: "3.13" + # backend: pyqt6 steps: - uses: actions/checkout@v4 @@ -63,6 +58,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Install dependencies run: | diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index d0364687a..5bcc6e01f 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -5,18 +5,17 @@ name: --pre Test on: schedule: - - cron: '0 */12 * * *' # every 12 hours + - cron: "0 */12 * * *" # every 12 hours workflow_dispatch: jobs: - test: name: ${{ matrix.platform }} (${{ matrix.python-version }}) runs-on: ${{ matrix.platform }} strategy: fail-fast: false matrix: - python-version: ['3.8', '3.9', '3.10'] + python-version: ["3.11"] platform: [macos-13, windows-latest] backend: [pyside2, pyqt5] @@ -55,7 +54,7 @@ jobs: PLATFORM: ${{ matrix.platform }} PYTHON: ${{ matrix.python }} RUN_ID: ${{ github.run_id }} - TITLE: '[test-bot] pip install --pre is failing' + TITLE: "[test-bot] pip install --pre is failing" with: filename: .github/TEST_FAIL_TEMPLATE.md update_existing: true diff --git a/README.md b/README.md index e80e4a427..4a70d97c3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ It forms the basis of [`napari-micromanager`](https://github.com/pymmcore-plus/n See complete list of available widgets in the [documentation](https://pymmcore-plus.github.io/pymmcore-widgets/#widgets) - ## Installation ```sh @@ -28,5 +27,5 @@ pip install pymmcore-widgets # you must install one yourself, for example: pip install PyQt5 -# package is tested against PyQt5, PyQt6, PySide2, and PySide6 +# package is tested against PyQt5, PyQt6, PySide2, and PySide6(==6.7) ``` diff --git a/docs/getting_started.md b/docs/getting_started.md index 495ed6b84..6cef83567 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -12,7 +12,18 @@ pip install pymmcore-widgets ### Installing PyQt or PySide -Since [pymmcore-widgets](./index.md) relies on either the [PyQt](https://riverbankcomputing.com/software/pyqt/) or [PySide](https://www.qt.io/qt-for-python) libraries, you also **need** to install one of these packages. You can use any of the available versions of these libraries: [PyQt5](https://pypi.org/project/PyQt5/), [PyQt6](https://pypi.org/project/PyQt6/), [PySide2](https://pypi.org/project/PySide2/) or [PySide6](https://pypi.org/project/PySide6/). For example, to install [PyQt6](https://riverbankcomputing.com/software/pyqt/download), you can use: +Since [pymmcore-widgets](./index.md) relies on either the +[PyQt](https://riverbankcomputing.com/software/pyqt/) or +[PySide](https://www.qt.io/qt-for-python) libraries, you also **need** to +install one of these packages. You can use any of the available versions of +these libraries: [PyQt5](https://pypi.org/project/PyQt5/), +[PyQt6](https://pypi.org/project/PyQt6/), +[PySide2](https://pypi.org/project/PySide2/) or +[PySide6](https://pypi.org/project/PySide6/). We strongly recommend using PyQt6 +if possible. If you must use a specific backend version and run into problems, +please open an issue + +For example, to install [PyQt6](https://riverbankcomputing.com/software/pyqt/download), you can use: ```sh pip install PyQt6 @@ -21,10 +32,9 @@ pip install PyQt6 !!! Note Widgets are tested on: - * `macOS & Windows` - * `Python 3.8, 3.9 3.10 & 3.11` - * `PyQt5 & PyQt6` - * `PySide2 & PySide6` + * macOS & Windows + * Python 3.9 and above + * PyQt5, PyQt6, PySide2 & PySide6(==6.7) ### Installing Micro-Manager diff --git a/pyproject.toml b/pyproject.toml index c162201bf..5f9648e0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ sources = ["src"] name = "pymmcore-widgets" description = "A set of Qt-based widgets onto the pymmcore-plus model" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "BSD 3-Clause License" } authors = [ { email = "federico.gasparoli@gmail.com", name = "Federico Gasparoli" }, @@ -34,10 +34,11 @@ classifiers = [ "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python", "Topic :: Software Development :: Widget Sets", "Topic :: System :: Hardware :: Hardware Drivers", @@ -60,11 +61,19 @@ allow-direct-references = true # extras # https://peps.python.org/pep-0621/#dependencies-optional-dependencies [project.optional-dependencies] -test = ["pytest>=6.0", "pytest-cov", "pytest-qt", "PyYAML", "vispy", "cmap", "zarr"] +test = [ + "pytest>=6.0", + "pytest-cov", + "pytest-qt", + "PyYAML", + "vispy", + "cmap", + "zarr", +] pyqt5 = ["PyQt5"] pyside2 = ["PySide2"] pyqt6 = ["PyQt6"] -pyside6 = ["PySide6<6.5"] +pyside6 = ["PySide6==6.7.3"] # pretty hard to find a good match here... image = ["vispy"] dev = [ @@ -97,8 +106,9 @@ Documentation = "https://pymmcore-plus.github.io/pymmcore-widgets" # https://beta.ruff.rs/docs/rules/ [tool.ruff] line-length = 88 -target-version = "py38" +target-version = "py39" src = ["src", "tests"] + [tool.ruff.lint] pydocstyle = { convention = "numpy" } select = [ @@ -115,7 +125,6 @@ select = [ "RUF", # ruff-specific rules "TID", # tidy "TCH", # typecheck - # "SLF", # private-access ] ignore = [ "D100", # Missing docstring in public module @@ -135,6 +144,7 @@ testpaths = ["tests"] filterwarnings = [ "error", "ignore:distutils Version classes are deprecated", + "ignore:Failed to disconnect:RuntimeWarning:", # warning, but not error, that will show up on useq<0.3.3 ] @@ -178,4 +188,3 @@ ignore = [ [tool.typos.default] extend-ignore-identifiers-re = ["(?i)nd2?.*", "(?i)ome", "FO(Vs?)?"] - diff --git a/src/pymmcore_widgets/_util.py b/src/pymmcore_widgets/_util.py index 1055dfee7..4436e0efc 100644 --- a/src/pymmcore_widgets/_util.py +++ b/src/pymmcore_widgets/_util.py @@ -2,7 +2,7 @@ import re from pathlib import Path -from typing import Any, ContextManager, Sequence +from typing import TYPE_CHECKING, Any import useq from psygnal import SignalInstance @@ -21,6 +21,10 @@ ) from superqt.utils import signals_blocked +if TYPE_CHECKING: + from collections.abc import Sequence + from contextlib import AbstractContextManager + class ComboMessageBox(QDialog): """Dialog that presents a combo box of `items`.""" @@ -91,7 +95,7 @@ def guess_objective_or_prompt( return None -def block_core(obj: Any) -> ContextManager: +def block_core(obj: Any) -> AbstractContextManager: """Block core signals.""" if isinstance(obj, QObject): return signals_blocked(obj) # type: ignore [no-any-return] diff --git a/src/pymmcore_widgets/config_presets/_group_preset_widget/_cfg_table.py b/src/pymmcore_widgets/config_presets/_group_preset_widget/_cfg_table.py index 5e8672b60..39a3316d0 100644 --- a/src/pymmcore_widgets/config_presets/_group_preset_widget/_cfg_table.py +++ b/src/pymmcore_widgets/config_presets/_group_preset_widget/_cfg_table.py @@ -1,12 +1,15 @@ from __future__ import annotations -from typing import Any, Sequence, cast +from typing import TYPE_CHECKING, Any, cast from qtpy.QtCore import Qt from qtpy.QtWidgets import QTableWidget, QTableWidgetItem from pymmcore_widgets.device_properties._property_widget import PropertyWidget +if TYPE_CHECKING: + from collections.abc import Sequence + DEV_PROP_ROLE = Qt.ItemDataRole.UserRole + 1 diff --git a/src/pymmcore_widgets/config_presets/_pixel_configuration_widget.py b/src/pymmcore_widgets/config_presets/_pixel_configuration_widget.py index cf85559ad..b628045c6 100644 --- a/src/pymmcore_widgets/config_presets/_pixel_configuration_widget.py +++ b/src/pymmcore_widgets/config_presets/_pixel_configuration_widget.py @@ -3,7 +3,7 @@ import itertools import warnings from collections import Counter -from typing import Any, Sequence, cast +from typing import TYPE_CHECKING, Any, cast from pymmcore_plus import CMMCorePlus, DeviceProperty from pymmcore_plus.model import PixelSizeGroup, PixelSizePreset, Setting @@ -35,6 +35,9 @@ from pymmcore_widgets.useq_widgets import DataTable, DataTableWidget from pymmcore_widgets.useq_widgets._column_info import FloatColumn, TextColumn +if TYPE_CHECKING: + from collections.abc import Sequence + FIXED = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) PX = "px" ID = "id" diff --git a/src/pymmcore_widgets/device_properties/_device_property_table.py b/src/pymmcore_widgets/device_properties/_device_property_table.py index aeffda804..e9b490897 100644 --- a/src/pymmcore_widgets/device_properties/_device_property_table.py +++ b/src/pymmcore_widgets/device_properties/_device_property_table.py @@ -1,7 +1,7 @@ from __future__ import annotations from logging import getLogger -from typing import Iterable, cast +from typing import TYPE_CHECKING, cast from pymmcore_plus import CMMCorePlus, DeviceProperty, DeviceType from qtpy.QtCore import Qt @@ -13,6 +13,9 @@ from ._property_widget import PropertyWidget +if TYPE_CHECKING: + from collections.abc import Iterable + logger = getLogger(__name__) diff --git a/src/pymmcore_widgets/device_properties/_properties_widget.py b/src/pymmcore_widgets/device_properties/_properties_widget.py index de3ed2854..215428f09 100644 --- a/src/pymmcore_widgets/device_properties/_properties_widget.py +++ b/src/pymmcore_widgets/device_properties/_properties_widget.py @@ -5,7 +5,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, cast +from typing import TYPE_CHECKING, cast from pymmcore_plus import CMMCorePlus from qtpy.QtWidgets import QGridLayout, QLabel, QWidget @@ -14,6 +14,7 @@ if TYPE_CHECKING: import re + from collections.abc import Iterable class PropertiesWidget(QWidget): diff --git a/src/pymmcore_widgets/hcs/_plate_calibration_widget.py b/src/pymmcore_widgets/hcs/_plate_calibration_widget.py index 651cb7264..711143b91 100644 --- a/src/pymmcore_widgets/hcs/_plate_calibration_widget.py +++ b/src/pymmcore_widgets/hcs/_plate_calibration_widget.py @@ -1,7 +1,7 @@ from __future__ import annotations from contextlib import suppress -from typing import Mapping +from typing import TYPE_CHECKING import numpy as np import useq @@ -28,6 +28,9 @@ ) from pymmcore_widgets.useq_widgets._well_plate_widget import WellPlateView +if TYPE_CHECKING: + from collections.abc import Mapping + class PlateCalibrationWidget(QWidget): """Widget to calibrate a well plate. diff --git a/src/pymmcore_widgets/hcs/_util.py b/src/pymmcore_widgets/hcs/_util.py index 4430c67f3..26868abc5 100644 --- a/src/pymmcore_widgets/hcs/_util.py +++ b/src/pymmcore_widgets/hcs/_util.py @@ -1,9 +1,12 @@ from __future__ import annotations -from typing import Iterable +from typing import TYPE_CHECKING import numpy as np +if TYPE_CHECKING: + from collections.abc import Iterable + def find_circle_center( coords: Iterable[tuple[float, float]], diff --git a/src/pymmcore_widgets/hcs/_well_calibration_widget.py b/src/pymmcore_widgets/hcs/_well_calibration_widget.py index ba8e65118..474788b9e 100644 --- a/src/pymmcore_widgets/hcs/_well_calibration_widget.py +++ b/src/pymmcore_widgets/hcs/_well_calibration_widget.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import Iterator, NamedTuple, cast +from typing import TYPE_CHECKING, NamedTuple, cast from fonticon_mdi6 import MDI6 from pymmcore_plus import CMMCorePlus @@ -23,6 +23,9 @@ from ._util import find_circle_center, find_rectangle_center +if TYPE_CHECKING: + from collections.abc import Iterator + COMBO_ROLE = Qt.ItemDataRole.UserRole + 1 ICON_PATH = Path(__file__).parent / "icons" ONE_CIRCLE = QIcon(str(ICON_PATH / "circle-center.svg")) diff --git a/src/pymmcore_widgets/hcwizard/_dev_setup_dialog.py b/src/pymmcore_widgets/hcwizard/_dev_setup_dialog.py index 84b74297f..375c6d7b5 100644 --- a/src/pymmcore_widgets/hcwizard/_dev_setup_dialog.py +++ b/src/pymmcore_widgets/hcwizard/_dev_setup_dialog.py @@ -85,7 +85,7 @@ import logging from contextlib import suppress -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING from pymmcore_plus import CMMCorePlus, Keyword from qtpy.QtCore import Qt @@ -104,6 +104,8 @@ from ._simple_prop_table import PropTable if TYPE_CHECKING: + from collections.abc import Sequence + from qtpy.QtGui import QFocusEvent logger = logging.getLogger(__name__) diff --git a/src/pymmcore_widgets/hcwizard/_peripheral_setup_dialog.py b/src/pymmcore_widgets/hcwizard/_peripheral_setup_dialog.py index a13bfc387..31e00a973 100644 --- a/src/pymmcore_widgets/hcwizard/_peripheral_setup_dialog.py +++ b/src/pymmcore_widgets/hcwizard/_peripheral_setup_dialog.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, cast +from typing import TYPE_CHECKING, cast from pymmcore_plus.model import Device, Microscope from qtpy.QtCore import QSize, Qt @@ -20,6 +20,8 @@ from ._dev_setup_dialog import DeviceSetupDialog if TYPE_CHECKING: + from collections.abc import Iterable + from pymmcore_plus import CMMCorePlus FLAGS = Qt.WindowType.MSWindowsFixedSizeDialogHint | Qt.WindowType.Sheet diff --git a/src/pymmcore_widgets/hcwizard/_simple_prop_table.py b/src/pymmcore_widgets/hcwizard/_simple_prop_table.py index a1abc5cbb..b30240190 100644 --- a/src/pymmcore_widgets/hcwizard/_simple_prop_table.py +++ b/src/pymmcore_widgets/hcwizard/_simple_prop_table.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Iterator, Sequence +from typing import TYPE_CHECKING from pymmcore_plus import CMMCorePlus, Keyword from qtpy.QtCore import Signal @@ -10,6 +10,9 @@ from pymmcore_widgets.device_properties._property_widget import PropertyWidget +if TYPE_CHECKING: + from collections.abc import Iterator, Sequence + class PortSelector(QComboBox): """Simple combobox that emits (device_name, library_name) when changed.""" diff --git a/src/pymmcore_widgets/mda/_core_positions.py b/src/pymmcore_widgets/mda/_core_positions.py index f89d7b5eb..b0e35b281 100644 --- a/src/pymmcore_widgets/mda/_core_positions.py +++ b/src/pymmcore_widgets/mda/_core_positions.py @@ -1,7 +1,7 @@ from __future__ import annotations from contextlib import suppress -from typing import TYPE_CHECKING, Any, Sequence +from typing import TYPE_CHECKING, Any from fonticon_mdi6 import MDI6 from pymmcore_plus import CMMCorePlus @@ -27,6 +27,8 @@ from pymmcore_widgets.useq_widgets._positions import AF_DEFAULT_TOOLTIP if TYPE_CHECKING: + from collections.abc import Sequence + from useq import Position UPDATE_POSITIONS = "Update Positions List" diff --git a/src/pymmcore_widgets/useq_widgets/_channels.py b/src/pymmcore_widgets/useq_widgets/_channels.py index 64c3a69bb..d2c304552 100644 --- a/src/pymmcore_widgets/useq_widgets/_channels.py +++ b/src/pymmcore_widgets/useq_widgets/_channels.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections import Counter -from typing import Any, Iterable, Mapping, Sequence +from typing import TYPE_CHECKING, Any import useq from pymmcore_plus import Keyword @@ -18,6 +18,9 @@ ) from ._data_table import DataTableWidget +if TYPE_CHECKING: + from collections.abc import Iterable, Mapping, Sequence + NAMED_CONFIG = TextColumn(key="config", default=None, is_row_selector=True) DEFAULT_GROUP = Keyword.Channel diff --git a/src/pymmcore_widgets/useq_widgets/_column_info.py b/src/pymmcore_widgets/useq_widgets/_column_info.py index 70fbd0d89..81f2cf154 100644 --- a/src/pymmcore_widgets/useq_widgets/_column_info.py +++ b/src/pymmcore_widgets/useq_widgets/_column_info.py @@ -4,7 +4,7 @@ import re from dataclasses import dataclass from datetime import timedelta -from typing import TYPE_CHECKING, Callable, ClassVar, Generic, Iterable, TypeVar, cast +from typing import TYPE_CHECKING, Callable, ClassVar, Generic, TypeVar, cast import pint from qtpy.QtCore import Qt, Signal, SignalInstance @@ -25,6 +25,7 @@ from superqt.fonticon import icon if TYPE_CHECKING: + from collections.abc import Iterable from typing import Any diff --git a/src/pymmcore_widgets/useq_widgets/_data_table.py b/src/pymmcore_widgets/useq_widgets/_data_table.py index 4d87f46cc..7a853a7cb 100644 --- a/src/pymmcore_widgets/useq_widgets/_data_table.py +++ b/src/pymmcore_widgets/useq_widgets/_data_table.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, ClassVar, Iterable, cast +from typing import TYPE_CHECKING, ClassVar, cast from fonticon_mdi6 import MDI6 from qtpy.QtCore import QSize, Qt, Signal @@ -23,7 +23,8 @@ from ._column_info import ColumnInfo if TYPE_CHECKING: - from typing import Any, Iterator + from collections.abc import Iterable, Iterator + from typing import Any ValueWidget = type[QCheckBox | QSpinBox | QDoubleSpinBox | QComboBox] from PyQt6.QtGui import QAction diff --git a/src/pymmcore_widgets/useq_widgets/_mda_sequence.py b/src/pymmcore_widgets/useq_widgets/_mda_sequence.py index 0205b8f7a..6b8a725d4 100644 --- a/src/pymmcore_widgets/useq_widgets/_mda_sequence.py +++ b/src/pymmcore_widgets/useq_widgets/_mda_sequence.py @@ -3,7 +3,7 @@ from importlib.util import find_spec from itertools import permutations from pathlib import Path -from typing import Sequence, cast +from typing import TYPE_CHECKING, cast import useq from qtpy.QtCore import Qt, Signal @@ -28,6 +28,9 @@ from pymmcore_widgets.useq_widgets._time import TimePlanWidget from pymmcore_widgets.useq_widgets._z import ZPlanWidget +if TYPE_CHECKING: + from collections.abc import Sequence + try: from pint import Quantity diff --git a/src/pymmcore_widgets/useq_widgets/_positions.py b/src/pymmcore_widgets/useq_widgets/_positions.py index 78f7f4c25..3aa39cc67 100644 --- a/src/pymmcore_widgets/useq_widgets/_positions.py +++ b/src/pymmcore_widgets/useq_widgets/_positions.py @@ -3,7 +3,7 @@ import json from dataclasses import dataclass from pathlib import Path -from typing import Sequence, cast +from typing import TYPE_CHECKING, cast import useq from fonticon_mdi6 import MDI6 @@ -24,6 +24,9 @@ from ._column_info import FloatColumn, TextColumn, WdgGetSet, WidgetColumn from ._data_table import DataTableWidget +if TYPE_CHECKING: + from collections.abc import Sequence + OK_CANCEL = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel NULL_SEQUENCE = useq.MDASequence() MAX = 9999999 diff --git a/src/pymmcore_widgets/useq_widgets/_well_plate_widget.py b/src/pymmcore_widgets/useq_widgets/_well_plate_widget.py index 6a52bb96b..07ea6ecf9 100644 --- a/src/pymmcore_widgets/useq_widgets/_well_plate_widget.py +++ b/src/pymmcore_widgets/useq_widgets/_well_plate_widget.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Mapping, cast +from typing import TYPE_CHECKING, cast import numpy as np import useq @@ -26,6 +26,8 @@ from pymmcore_widgets._util import ResizingGraphicsView if TYPE_CHECKING: + from collections.abc import Iterable, Mapping + from qtpy.QtGui import QMouseEvent Index = int | list[int] | tuple[int] | slice diff --git a/src/pymmcore_widgets/useq_widgets/points_plans/_grid_row_column_widget.py b/src/pymmcore_widgets/useq_widgets/points_plans/_grid_row_column_widget.py index 4c24c3817..670bbaf56 100644 --- a/src/pymmcore_widgets/useq_widgets/points_plans/_grid_row_column_widget.py +++ b/src/pymmcore_widgets/useq_widgets/points_plans/_grid_row_column_widget.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Mapping +from typing import TYPE_CHECKING from qtpy.QtCore import Qt, Signal from qtpy.QtWidgets import ( @@ -14,6 +14,9 @@ ) from useq import GridRowsColumns, OrderMode +if TYPE_CHECKING: + from collections.abc import Mapping + class GridRowColumnWidget(QWidget): """Widget to generate a grid of FOVs within a specified area.""" diff --git a/src/pymmcore_widgets/useq_widgets/points_plans/_random_points_widget.py b/src/pymmcore_widgets/useq_widgets/points_plans/_random_points_widget.py index a5e61d366..baaeaaa10 100644 --- a/src/pymmcore_widgets/useq_widgets/points_plans/_random_points_widget.py +++ b/src/pymmcore_widgets/useq_widgets/points_plans/_random_points_widget.py @@ -1,7 +1,7 @@ from __future__ import annotations import random -from typing import Mapping +from typing import TYPE_CHECKING from qtpy.QtCore import Qt, Signal from qtpy.QtWidgets import ( @@ -17,6 +17,9 @@ ) from useq import RandomPoints, RelativePosition, Shape, TraversalOrder +if TYPE_CHECKING: + from collections.abc import Mapping + class RandomPointWidget(QWidget): """Widget to generate random points within a specified area.""" diff --git a/src/pymmcore_widgets/views/_stack_viewer/_stack_viewer.py b/src/pymmcore_widgets/views/_stack_viewer/_stack_viewer.py index 987ed495f..eac639962 100644 --- a/src/pymmcore_widgets/views/_stack_viewer/_stack_viewer.py +++ b/src/pymmcore_widgets/views/_stack_viewer/_stack_viewer.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, cast import numpy as np +import qtpy import superqt from fonticon_mdi6 import MDI6 from qtpy import QtCore, QtWidgets @@ -56,6 +57,13 @@ def __init__( save_button: bool = True, ): super().__init__(parent=parent) + if qtpy.API_NAME.startswith("PySide"): + warnings.warn( + "StackViewer is not tested on PySide, it may not work as expected. " + "You can disable this warning by setting `warn_on_pyside6=False`.", + stacklevel=2, + ) + self._reload_position() self.sequence = sequence self.canvas_size = size diff --git a/tests/test_presets_widget.py b/tests/test_presets_widget.py index 1499bc0c3..8b26e69b5 100644 --- a/tests/test_presets_widget.py +++ b/tests/test_presets_widget.py @@ -11,7 +11,7 @@ from pytestqt.qtbot import QtBot -def test_preset_widget(qtbot: QtBot, global_mmcore: CMMCorePlus): +def test_preset_widget(qtbot: QtBot, global_mmcore: CMMCorePlus) -> None: for group in global_mmcore.getAvailableConfigGroups(): wdg = PresetsWidget(group) qtbot.addWidget(wdg) diff --git a/tests/test_stack_viewer.py b/tests/test_stack_viewer.py index 6f7bc4c66..f70bb225d 100644 --- a/tests/test_stack_viewer.py +++ b/tests/test_stack_viewer.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os from typing import TYPE_CHECKING import pytest @@ -19,6 +18,11 @@ from pytestqt.qtbot import QtBot +if qtpy.API_NAME.startswith("PySide"): + pytest.skip( + "Fails too often on CI. Usually (but not only) PySide6", allow_module_level=True + ) + sequence = MDASequence( channels=[{"config": "DAPI", "exposure": 10}, {"config": "FITC", "exposure": 10}], time_plan={"interval": 0.2, "loops": 3}, @@ -171,9 +175,7 @@ def test_disconnect(qtbot: QtBot) -> None: assert not canvas.ready -@pytest.mark.skipif( - bool(os.getenv("CI") and qtpy.API_NAME == "PySide6"), reason="Fails too often on CI" -) +@pytest.mark.skipif(bool(qtpy.API_NAME == "PySide6"), reason="Fails too often on CI") def test_not_ready(qtbot: QtBot) -> None: mmcore = CMMCorePlus.instance() canvas = StackViewer(mmcore=mmcore)