diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index 2b69fa70..d5a08772 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -17,6 +17,7 @@ jobs: fetch-depth: 0 - uses: astral-sh/setup-uv@v7 with: + python-version: "3.13" enable-cache: true - name: Deploy docs to GitHub Pages diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 4ffda84a..3674baa4 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11"] + python-version: ["3.10", "3.13"] os: [ubuntu-latest, macos-latest, windows-latest] add-group: [pyqt6, pyside6] include: @@ -37,11 +37,11 @@ jobs: - python-version: "3.9" os: ubuntu-latest add-group: pyside2 - - python-version: "3.11" + - python-version: "3.12" os: windows-latest add-group: pyqt5 - python-version: "3.10" - os: ubuntu-latest + os: windows-latest add-group: pyside2 - python-version: "3.12" os: ubuntu-latest @@ -49,10 +49,10 @@ jobs: - python-version: "3.12" os: ubuntu-latest add-group: pyside6 - - python-version: "3.13" + - python-version: "3.14" os: ubuntu-latest add-group: pyside6 - - python-version: "3.13" + - python-version: "3.14" os: windows-latest add-group: pyqt6 diff --git a/pyproject.toml b/pyproject.toml index f9b4d843..b5830af8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Desktop Environment", "Topic :: Software Development", "Topic :: Software Development :: User Interfaces", @@ -47,7 +48,11 @@ dependencies = [ # extras # https://peps.python.org/pep-0621/#dependencies-optional-dependencies [project.optional-dependencies] -pyqt5 = ["PyQt5>=5.15.8", "pyqt5-qt5<=5.15.2; sys_platform == 'win32'"] +pyqt5 = [ + "PyQt5>=5.15.8", + "pyqt5-qt5<=5.15.2; sys_platform == 'win32'", + "pyqt5-qt5>5.15.2; sys_platform != 'win32'", +] pyqt6 = ["pyqt6>=6.4.0"] pyside2 = ["pyside2>=5.15"] pyside6 = ["pyside6>=6.4.0"] @@ -62,6 +67,7 @@ third-party-support = [ "attrs>=25.3.0", "ipykernel>=6.29.5", "matplotlib>=3.9.4", + "numpy>=2.1.0; python_version >= '3.13'", "numpy>=1.26.4", "pandas>=2.2.3; python_version >= '3.11'", "pandas>=2.1", @@ -77,7 +83,7 @@ test = [ ] pyqt5 = ["magicgui[pyqt5]", { include-group = "test-qt" }] pyqt6 = ["magicgui[pyqt6]", { include-group = "test-qt" }] -pyside2 = ["magicgui[pyside2]", { include-group = "test-qt" }] +pyside2 = ["magicgui[pyside2]", { include-group = "test-qt" }, "numpy<2; python_version < '3.13'"] pyside6 = ["magicgui[pyside6]", { include-group = "test-qt" }] dev = [ { include-group = "test" }, @@ -128,7 +134,7 @@ line-length = 88 target-version = "py39" src = ["src", "tests"] fix = true -# unsafe-fixes = true +unsafe-fixes = true [tool.ruff.lint] pydocstyle = { convention = "numpy" } @@ -178,6 +184,7 @@ filterwarnings = [ "ignore:.*read_binary is deprecated:", "ignore:Pickle, copy, and deepcopy support:DeprecationWarning", "ignore:'count' is passed as positional argument::vispy", + "ignore::DeprecationWarning:matplotlib", ] # https://mypy.readthedocs.io/en/stable/config_file.html diff --git a/src/magicgui/signature.py b/src/magicgui/signature.py index d67ed6ae..3a84cb85 100644 --- a/src/magicgui/signature.py +++ b/src/magicgui/signature.py @@ -182,14 +182,19 @@ def __repr__(self) -> str: rep = rep.replace(": NoneType = ", "=") return rep - def __str__(self) -> str: - """Return string representation of the Parameter in a signature.""" + def _format(self, *, quote_annotation_strings: bool = True) -> str: + """Return formatted string for use in Signature.format() (Python 3.14+).""" hint, _ = get_args(self.annotation) - return str( - inspect.Parameter( - self.name, self.kind, default=self.default, annotation=hint - ) + param = inspect.Parameter( + self.name, self.kind, default=self.default, annotation=hint ) + if hasattr(param, "_format"): # python 3.14+ + return param._format(quote_annotation_strings=quote_annotation_strings) # type: ignore[no-any-return] + return str(param) + + def __str__(self) -> str: + """Return string representation of the Parameter in a signature.""" + return self._format() def to_widget( self, diff --git a/tests/conftest.py b/tests/conftest.py index 82c2f11b..ba60c7b3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,19 @@ +import os + import pytest from magicgui.application import use_app +# Disable tqdm's TMonitor thread to prevent race conditions with Qt threading +# that can cause intermittent segfaults on CI (especially with PySide6 on Linux). +# See: https://github.com/tqdm/tqdm/issues/469 +try: + from tqdm import tqdm as _tqdm_std + + _tqdm_std.monitor_interval = 0 +except ImportError: + pass + @pytest.fixture(scope="session") def qapp(): @@ -14,10 +26,12 @@ def qapp(): @pytest.fixture(autouse=True, scope="function") def always_qapp(qapp): yield qapp - for w in qapp.topLevelWidgets(): - w.close() - w.deleteLater() - qapp.processEvents() + if not os.getenv("CI"): + # I suspect, but can't prove, that this code causes occasional segfaults on CI. + for w in qapp.topLevelWidgets(): + w.close() + w.deleteLater() + qapp.processEvents() @pytest.fixture(autouse=True, scope="function")