diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 5bc76d5..9edb9f0 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -15,75 +15,93 @@ on: jobs: test: - name: Test - uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2 - with: - os: ${{ matrix.platform }} - python-version: ${{ matrix.python-version }} - qt: ${{ matrix.backend }} - pip-install-pre-release: ${{ github.event_name == 'schedule' }} - coverage-upload: artifact + name: ${{ matrix.os }} (${{ matrix.python-version }}) ${{ matrix.add-extra || '' }} ${{ matrix.resolution || ''}} + runs-on: ${{ matrix.os }} + env: + UV_PRERELEASE: ${{ github.event_name == 'schedule' && 'allow' || 'if-necessary-or-explicit' }} + UV_NO_SYNC: 1 + UV_MANAGED_PYTHON: 1 strategy: fail-fast: false matrix: - platform: [ubuntu-latest, windows-latest, macos-13] - python-version: ["3.9", "3.10", "3.11", "3.12"] - backend: [pyqt5, pyside2, pyqt6] - exclude: - # Abort (core dumped) on linux pyqt6, unknown reason - - platform: ubuntu-latest - backend: pyqt6 - # lack of wheels for pyside2/py3.11 - - python-version: "3.11" - backend: pyside2 - - python-version: "3.12" - backend: pyside2 - - python-version: "3.12" - backend: pyqt5 + python-version: ["3.9", "3.13"] + os: [ubuntu-latest, macos-latest, windows-latest] + add-extra: [pyqt6, pyside6] include: - - python-version: "3.13" - platform: windows-latest - backend: "pyqt6" - - python-version: "3.13" - platform: ubuntu-latest - backend: "pyqt6" - - - python-version: "3.10" - platform: macos-latest - backend: "'pyside6<6.8'" + - os: windows-latest + python-version: "3.11" + resolution: "lowest-direct" + add-extra: pyside6 + - os: macos-latest + python-version: "3.12" + resolution: "lowest-direct" + add-extra: pyqt6 + - python-version: "3.9" + os: ubuntu-latest + add-extra: pyqt5 + - python-version: "3.9" + os: ubuntu-latest + add-extra: pyside2 - python-version: "3.11" - platform: macos-latest - backend: "'pyside6<6.8'" + os: windows-latest + add-extra: pyqt5 - python-version: "3.10" - platform: windows-latest - backend: "'pyside6<6.8'" + os: ubuntu-latest + add-extra: pyside2 - python-version: "3.12" - platform: windows-latest - backend: "'pyside6<6.8'" + os: ubuntu-latest + add-extra: pyqt6 + - python-version: "3.12" + os: ubuntu-latest + add-extra: pyside6 - # legacy Qt - - python-version: 3.9 - platform: ubuntu-latest - backend: "pyqt5==5.12.*" - - python-version: 3.9 - platform: ubuntu-latest - backend: "pyqt5==5.13.*" - - python-version: 3.9 - platform: ubuntu-latest - backend: "pyqt5==5.14.*" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true - test-qt-minreqs: - uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2 - with: - python-version: "3.9" - qt: pyqt5 - pip-post-installs: "qtpy==1.1.0 typing-extensions==4.5.0" # 4.5.0 is just for pint - pip-install-flags: -e - coverage-upload: artifact + - name: ๐Ÿ Set up Python ${{ matrix.python-version }} + uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ matrix.python-version }} + enable-cache: true + cache-dependency-glob: "**/pyproject.toml" + + - uses: pyvista/setup-headless-display-action@v4 + with: + qt: true + + - name: Install Dependencies + run: uv sync --no-dev --group test --extra ${{ matrix.add-extra }} --resolution ${{ matrix.resolution || 'highest'}} + + - name: ๐Ÿงช Run Tests + run: uv run coverage run -p -m pytest -v + + # If something goes wrong with --pre tests, we can open an issue in the repo + - name: ๐Ÿ“ Report --pre Failures + if: failure() && github.event_name == 'schedule' + uses: JasonEtco/create-an-issue@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PLATFORM: ${{ matrix.os }} + PYTHON: ${{ matrix.python-version }} + RUN_ID: ${{ github.run_id }} + TITLE: "[test-bot] pip install --pre is failing" + with: + filename: .github/TEST_FAIL_TEMPLATE.md + update_existing: true + + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: covreport-${{ matrix.os }}-py${{ matrix.python-version }}-${{ matrix.add-extra }}-${{ matrix.resolution }} + path: ./.coverage* + include-hidden-files: true upload_coverage: if: always() - needs: [test, test-qt-minreqs] + needs: [test] uses: pyapp-kit/workflows/.github/workflows/upload-coverage.yml@v2 secrets: inherit @@ -91,17 +109,16 @@ jobs: uses: pyapp-kit/workflows/.github/workflows/test-dependents.yml@v2 with: dependency-repo: napari/napari - dependency-ref: ${{ matrix.napari-version }} dependency-extras: "testing" qt: ${{ matrix.qt }} pytest-args: 'src/napari/_qt --import-mode=importlib -k "not async and not qt_dims_2 and not qt_viewer_console_focus and not keybinding_editor and not preferences_dialog_not_dismissed"' python-version: "3.10" - post-install-cmd: "pip install lxml_html_clean" + # napari hasn't pinned pytest-qt, but still requires pyside2 tests + post-install-cmd: "pip install lxml_html_clean pytest-qt==4.4.0" strategy: fail-fast: false matrix: - napari-version: [ "" ] - qt: [ "pyqt5", "pyside2" ] + qt: ["pyqt5", "pyside2"] check-manifest: name: Check Manifest @@ -110,35 +127,33 @@ jobs: - uses: actions/checkout@v4 - run: pipx run check-manifest - deploy: - # this will run when you have tagged a commit, starting with "v*" - # and requires that you have put your twine API key in your - # github secrets (see readme for details) - needs: [test, check-manifest] - if: ${{ github.repository == 'pyapp-kit/superqt' && contains(github.ref, 'tags') }} + build-and-inspect-package: + name: Build & inspect package. + needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build twine - - name: Build and publish - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }} - run: | - git tag - python -m build - twine check dist/* - twine upload dist/* + - uses: hynek/build-and-inspect-python-package@v2 + upload-to-pypi: + if: success() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'schedule' && github.repository == 'pyapp-kit/superqt' + name: Upload package to PyPI + needs: build-and-inspect-package + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + steps: + - name: Download built artifact to dist/ + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - name: ๐Ÿšข Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 - uses: softprops/action-gh-release@v2 with: generate_release_notes: true + files: "./dist/*" diff --git a/.gitignore b/.gitignore index 0bbefc5..a610993 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,4 @@ screenshots .mypy_cache docs/_auto_images/ +uv.lock diff --git a/pyproject.toml b/pyproject.toml index 4582fc9..ec32de6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,12 @@ requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.targets.sdist] +include = ["src", "tests", "CHANGELOG.md"] + # https://peps.python.org/pep-0621/ [project] name = "superqt" @@ -10,7 +16,7 @@ description = "Missing widgets and components for PyQt/PySide" readme = "README.md" requires-python = ">=3.9" license = { text = "BSD 3-Clause License" } -authors = [{ email = "talley.lambert@gmail.com", name = "Talley Lambert" }] +authors = [{ name = "superqt community" }] keywords = [ "qt", "pyqt", @@ -40,32 +46,64 @@ classifiers = [ dynamic = ["version"] dependencies = [ "pygments>=2.4.0", - "qtpy>=1.1.0", - "typing-extensions >=3.7.4.3,!=3.10.0.0", # however, pint requires >4.5.0 + "qtpy>=2.4.0", + "typing-extensions >=4.5.0", + "typing-extensions >=4.12.0; python_version >= '3.13'", ] -# extras -# https://peps.python.org/pep-0621/#dependencies-optional-dependencies [project.optional-dependencies] +cmap = ["cmap >=0.2"] +font-fa5 = ["fonticon-fontawesome5>=5.15.4"] +font-fa6 = ["fonticon-fontawesome6>=6.4.0"] +font-mi6 = ["fonticon-materialdesignicons6>=6.9.96"] +font-mi7 = ["fonticon-materialdesignicons7>=7.2.96"] +iconify = ["pyconify >=0.1.4"] +pyqt5 = [ + "PyQt5>=5.15.10", + "pyqt5-qt5>=5.15.16; sys_platform != 'win32'", + "pyqt5-qt5==5.15.2; sys_platform == 'win32'", +] +pyqt6 = [ # 6.6 has segfaults with QElidingLabel + "pyqt6>=6.4.0,!=6.6", + "pyqt6>=6.7.0,!=6.6; python_version >= '3.12'", +] +pyside2 = [ + "pyside2>=5.15", + "numpy>=1.19,<2; python_version < '3.11'", + "numpy>=1.26,<2; python_version >= '3.11' and python_version < '3.13'", +] +# see issues surrounding usage of Generics in pyside6.5.x +# https://github.com/pyapp-kit/superqt/pull/177 +# https://github.com/pyapp-kit/superqt/pull/164 +# https://bugreports.qt.io/browse/PYSIDE-2627 +pyside6 = [ + "pyside6>=6.4.0,!=6.5.0,!=6.5.1,!=6.6.2", + "pyside6>=6.7.0; python_version >= '3.12'", +] +quantity = ["pint>=0.21"] + +[dependency-groups] test = [ - "pint", - "pytest", - "pytest-cov", - "pytest-qt==4.4.0", - "numpy", - "cmap", - "pyconify", + "cmap>=0.6.2", + "numpy>=1.19; python_version < '3.11'", + "numpy>=1.26; python_version >= '3.11' and python_version < '3.13'", + "numpy>=2.1; python_version >= '3.13'", + "pint>=0.24.4", + "pyconify>=0.2.1", + "pytest>=8.4.1", + "pytest-cov>=6.2.1", + "pytest-qt==4.4.0", # for pyside2 support ] dev = [ - "ipython", - "ruff", - "mypy", - "pdbpp", - "pre-commit", - "pydocstyle", - "rich", - "types-Pygments", - "superqt[test,pyqt6]", + { include-group = "test" }, + "ipython>=8.18.1", + "mypy>=1.17.0", + "pre-commit-uv>=4.1.4", + "pdbpp>=0.11.6; sys_platform != 'win32'", + "pydocstyle>=6.3.0", + "rich>=14.0.0", + "ruff>=0.12.3", + "types-pygments>=2.19.0.20250715", ] docs = [ "mkdocs-macros-plugin ==1.3.7", @@ -74,21 +112,7 @@ docs = [ "mkdocstrings-python ==1.13.0", "superqt[font-fa5, cmap, quantity]", ] -quantity = ["pint"] -cmap = ["cmap >=0.1.1"] -pyside2 = ["pyside2"] -# see issues surrounding usage of Generics in pyside6.5.x -# https://github.com/pyapp-kit/superqt/pull/177 -# https://github.com/pyapp-kit/superqt/pull/164 -# https://bugreports.qt.io/browse/PYSIDE-2627 -pyside6 = ["pyside6 !=6.5.0,!=6.5.1,!=6.6.2,<6.8"] -pyqt5 = ["pyqt5"] -pyqt6 = ["pyqt6<6.7"] -font-fa5 = ["fonticon-fontawesome5"] -font-fa6 = ["fonticon-fontawesome6"] -font-mi6 = ["fonticon-materialdesignicons6"] -font-mi7 = ["fonticon-materialdesignicons7"] -iconify = ["pyconify >=0.1.4"] + [project.urls] Documentation = "https://pyapp-kit.github.io/superqt/" @@ -96,45 +120,9 @@ Source = "https://github.com/pyapp-kit/superqt" Tracker = "https://github.com/pyapp-kit/superqt/issues" Changelog = "https://github.com/pyapp-kit/superqt/blob/main/CHANGELOG.md" -[tool.hatch.version] -source = "vcs" +[tool.uv.sources] +superqt = { workspace = true } -[tool.hatch.build.targets.sdist] -include = ["src", "tests", "CHANGELOG.md"] - -# these let you run tests across all backends easily with: -# hatch run test:test -[tool.hatch.envs.test] - -[tool.hatch.envs.test.scripts] -test = "pytest" - -[[tool.hatch.envs.test.matrix]] -qt = ["pyside6", "pyqt6"] -python = ["3.11"] - -[[tool.hatch.envs.test.matrix]] -qt = ["pyside2", "pyqt5", "pyqt5.12"] -python = ["3.9"] - -[tool.hatch.envs.test.overrides] -matrix.qt.extra-dependencies = [ - { value = "pyside2", if = [ - "pyside2", - ] }, - { value = "pyside6", if = [ - "pyside6", - ] }, - { value = "pyqt5", if = [ - "pyqt5", - ] }, - { value = "pyqt6", if = [ - "pyqt6", - ] }, - { value = "pyqt5==5.12", if = [ - "pyqt5.12", - ] }, -] [tool.ruff] line-length = 88