diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5b721d9..39c1ab5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: sudo apt install graphviz --yes - name: Build Docs - uses: aganders3/headless-gui@v1 + uses: aganders3/headless-gui@v2 with: run: make html working-directory: ./docs diff --git a/.github/workflows/napari_hub_preview.yml b/.github/workflows/napari_hub_preview.yml deleted file mode 100644 index c204ac4..0000000 --- a/.github/workflows/napari_hub_preview.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: napari hub Preview Page # we use this name to find your preview page artifact, so don't change it! -# For more info on this action, see https://github.com/chanzuckerberg/napari-hub-preview-action/blob/main/action.yml - -on: - pull_request: - types: [ labeled ] - -jobs: - preview-page: - if: ${{ github.event.label.name == 'napari hub preview' }} - name: Preview Page Deploy - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v3 - - - name: napari hub Preview Page Builder - uses: chanzuckerberg/napari-hub-preview-action@v0.1 - with: - hub-ref: main diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 8665e1d..2a8b731 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -12,6 +12,10 @@ on: workflow_dispatch: merge_group: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: name: ${{ matrix.platform }} py${{ matrix.python-version }} @@ -20,7 +24,7 @@ jobs: fail-fast: false matrix: platform: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 @@ -58,13 +62,15 @@ jobs: if: ${{ always() }} - name: Coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 # Don't run coverage on merge queue CI to avoid duplicating reports # to codecov. See https://github.com/matplotlib/napari-matplotlib/issues/155 if: github.event_name != 'merge_group' with: token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true + fail_ci_if_error: false + + deploy: # this will run when you have tagged a commit, starting with "v*" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8df635a..ebae2d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,13 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-docstring-first - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 23.12.1 + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.4.2 hooks: - id: black @@ -17,14 +17,14 @@ repos: - id: napari-plugin-checks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 + rev: v1.10.1 hooks: - id: mypy additional_dependencies: [numpy, matplotlib] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.1.11' + rev: 'v0.5.1' hooks: - id: ruff diff --git a/docs/changelog.rst b/docs/changelog.rst index 255982a..697e483 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,5 +1,18 @@ Changelog ========= + +2.0.2 +----- +Dependencies +~~~~~~~~~~~~ +napari-matplotlib now adheres to `SPEC 0 `_, and has: + +- Dropped support for Python 3.9 +- Added support for Python 3.12 +- Added a minimum required numpy verison of 1.23 +- Pinned the maximum napari version to ``< 0.5``. + Version 3.0 of ``napari-matplotlib`` will introduce support for ``napari`` version 0.5. + 2.0.1 ----- Bug fixes diff --git a/docs/conf.py b/docs/conf.py index 2517a59..f153383 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,7 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) -import qtgallery +from sphinx_gallery import scrapers # -- Project information ----------------------------------------------------- @@ -35,18 +35,58 @@ "sphinx.ext.intersphinx", ] + +def reset_napari(gallery_conf, fname): # type: ignore[no-untyped-def] + from napari.settings import get_settings + from qtpy.QtWidgets import QApplication + + settings = get_settings() + settings.appearance.theme = "dark" + + # Disabling `QApplication.exec_` means example scripts can call `exec_` + # (scripts work when run normally) without blocking example execution by + # sphinx-gallery. (from qtgallery) + QApplication.exec_ = lambda _: None + + +def napari_scraper(block, block_vars, gallery_conf): # type: ignore[no-untyped-def] + """Basic napari window scraper. + + Looks for any QtMainWindow instances and takes a screenshot of them. + + `app.processEvents()` allows Qt events to propagateo and prevents hanging. + """ + import napari + + imgpath_iter = block_vars["image_path_iterator"] + + if app := napari.qt.get_app(): + app.processEvents() + else: + return "" + + img_paths = [] + for win, img_path in zip( + reversed(napari._qt.qt_main_window._QtMainWindow._instances), + imgpath_iter, + strict=False, + ): + img_paths.append(img_path) + win._window.screenshot(img_path, canvas_only=False) + + napari.Viewer.close_all() + app.processEvents() + + return scrapers.figure_rst(img_paths, gallery_conf["src_dir"]) + + sphinx_gallery_conf = { "filename_pattern": ".", - "image_scrapers": (qtgallery.qtscraper,), - "reset_modules": (qtgallery.reset_qapp,), + "image_scrapers": (napari_scraper,), + "reset_modules": (reset_napari,), } +suppress_warnings = ["config.cache"] -qtgallery_conf = { - "xvfb_size": (640, 480), - "xvfb_color_depth": 24, - "xfvb_use_xauth": False, - "xfvb_extra_args": [], -} numpydoc_show_class_members = False automodapi_inheritance_diagram = True diff --git a/examples/histogram.py b/examples/histogram.py index ccda491..b9ceb37 100644 --- a/examples/histogram.py +++ b/examples/histogram.py @@ -2,6 +2,7 @@ Histograms ========== """ + import napari viewer = napari.Viewer() diff --git a/examples/scatter.py b/examples/scatter.py index cd81240..00e01ec 100644 --- a/examples/scatter.py +++ b/examples/scatter.py @@ -2,6 +2,7 @@ Scatter plots ============= """ + import napari viewer = napari.Viewer() diff --git a/examples/slice.py b/examples/slice.py index 3e43443..242a16c 100644 --- a/examples/slice.py +++ b/examples/slice.py @@ -2,6 +2,7 @@ 1D slices ========= """ + import napari viewer = napari.Viewer() diff --git a/pyproject.toml b/pyproject.toml index ba9f9e6..05f7df6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel", "setuptools_scm"] +requires = ["setuptools", "setuptools_scm"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] @@ -12,9 +12,15 @@ filterwarnings = [ # Coming from vispy "ignore:distutils Version classes are deprecated:DeprecationWarning", "ignore:`np.bool8` is a deprecated alias for `np.bool_`:DeprecationWarning", + # Coming from pydantic via napari + "ignore:Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.:DeprecationWarning" ] qt_api = "pyqt6" -addopts = "--mpl --mpl-baseline-relative" +addopts = ["--mpl", "--mpl-baseline-relative", "--strict-config", "--strict-markers", "-ra"] +minversion = "7" +testpaths = ["src/napari_matplotlib/tests"] +log_cli_level = "INFO" +xfail_strict = true [tool.black] line-length = 79 @@ -24,8 +30,11 @@ profile = "black" line_length = 79 [tool.ruff] -target-version = "py39" -select = ["I", "UP", "F", "E", "W", "D"] +target-version = "py310" +fix = true + +[tool.ruff.lint] +select = ["B", "I", "UP", "F", "E", "W", "D"] ignore = [ "D100", # Missing docstring in public module "D104", # Missing docstring in public package @@ -35,24 +44,25 @@ ignore = [ "D401", # First line of docstring should be in imperative mood ] -fix = true -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "docs/*" = ["D"] "examples/*" = ["D"] "src/napari_matplotlib/tests/*" = ["D"] -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention = "numpy" [tool.mypy] -python_version = "3.9" +python_version = "3.10" # Block below are checks that form part of mypy 'strict' mode strict = true disallow_subclassing_any = false # TODO: fix warn_return_any = false # TODO: fix ignore_missing_imports = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] + [[tool.mypy.overrides]] module = [ "napari_matplotlib/tests/*", diff --git a/setup.cfg b/setup.cfg index 41e4e34..073478a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,10 +28,10 @@ project_urls = packages = find: install_requires = matplotlib - napari - numpy + napari<0.5 + numpy>=1.23 tinycss2 -python_requires = >=3.9 +python_requires = >=3.10 include_package_data = True package_dir = =src @@ -47,11 +47,10 @@ napari.manifest = [options.extras_require] docs = - napari[all]==0.4.19rc3 + napari[all] numpydoc pydantic<2 pydata-sphinx-theme - qtgallery sphinx sphinx-automodapi sphinx-gallery diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py index fb9e485..c455335 100644 --- a/src/napari_matplotlib/base.py +++ b/src/napari_matplotlib/base.py @@ -1,6 +1,5 @@ import os from pathlib import Path -from typing import Optional import matplotlib.style as mplstyle import napari @@ -38,7 +37,7 @@ class BaseNapariMPLWidget(QWidget): def __init__( self, napari_viewer: napari.Viewer, - parent: Optional[QWidget] = None, + parent: QWidget | None = None, ): super().__init__(parent=parent) self.viewer = napari_viewer @@ -173,7 +172,7 @@ class NapariMPLWidget(BaseNapariMPLWidget): def __init__( self, napari_viewer: napari.viewer.Viewer, - parent: Optional[QWidget] = None, + parent: QWidget | None = None, ): super().__init__(napari_viewer=napari_viewer, parent=parent) self._setup_callbacks() @@ -282,7 +281,7 @@ class SingleAxesWidget(NapariMPLWidget): def __init__( self, napari_viewer: napari.viewer.Viewer, - parent: Optional[QWidget] = None, + parent: QWidget | None = None, ): super().__init__(napari_viewer=napari_viewer, parent=parent) self.add_single_axes() diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 4076528..adbbae6 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, cast +from typing import Any, cast import napari import numpy as np @@ -44,7 +44,7 @@ class HistogramWidget(SingleAxesWidget): def __init__( self, napari_viewer: napari.viewer.Viewer, - parent: Optional[QWidget] = None, + parent: QWidget | None = None, ): super().__init__(napari_viewer, parent=parent) self._update_layers(None) @@ -60,7 +60,7 @@ def on_update_layers(self) -> None: def _update_contrast_lims(self) -> None: for lim, line in zip( - self.layers[0].contrast_limits, self._contrast_lines + self.layers[0].contrast_limits, self._contrast_lines, strict=False ): line.set_xdata(lim) @@ -121,7 +121,7 @@ class FeaturesHistogramWidget(SingleAxesWidget): def __init__( self, napari_viewer: napari.viewer.Viewer, - parent: Optional[QWidget] = None, + parent: QWidget | None = None, ): super().__init__(napari_viewer, parent=parent) @@ -137,12 +137,12 @@ def __init__( self._update_layers(None) @property - def x_axis_key(self) -> Optional[str]: + def x_axis_key(self) -> str | None: """Key to access x axis data from the FeaturesTable""" return self._x_axis_key @x_axis_key.setter - def x_axis_key(self, key: Optional[str]) -> None: + def x_axis_key(self, key: str | None) -> None: self._x_axis_key = key self._draw() @@ -166,7 +166,7 @@ def _get_valid_axis_keys(self) -> list[str]: else: return self.layers[0].features.keys() - def _get_data(self) -> tuple[Optional[npt.NDArray[Any]], str]: + def _get_data(self) -> tuple[npt.NDArray[Any] | None, str]: """Get the plot data. Returns diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index 67d6896..98ebe92 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Any import napari import numpy.typing as npt @@ -100,7 +100,7 @@ class FeaturesScatterWidget(ScatterBaseWidget): def __init__( self, napari_viewer: napari.viewer.Viewer, - parent: Optional[QWidget] = None, + parent: QWidget | None = None, ): super().__init__(napari_viewer, parent=parent) @@ -118,7 +118,7 @@ def __init__( self._update_layers(None) @property - def x_axis_key(self) -> Union[str, None]: + def x_axis_key(self) -> str | None: """ Key for the x-axis data. """ @@ -133,7 +133,7 @@ def x_axis_key(self, key: str) -> None: self._draw() @property - def y_axis_key(self) -> Union[str, None]: + def y_axis_key(self) -> str | None: """ Key for the y-axis data. """ diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py index 9459fa9..1924bf2 100644 --- a/src/napari_matplotlib/slice.py +++ b/src/napari_matplotlib/slice.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any import matplotlib.ticker as mticker import napari @@ -30,7 +30,7 @@ class SliceWidget(SingleAxesWidget): def __init__( self, napari_viewer: napari.viewer.Viewer, - parent: Optional[QWidget] = None, + parent: QWidget | None = None, ): # Setup figure/axes super().__init__(napari_viewer, parent=parent) diff --git a/src/napari_matplotlib/tests/test_util.py b/src/napari_matplotlib/tests/test_util.py index a8792d4..e966cc2 100644 --- a/src/napari_matplotlib/tests/test_util.py +++ b/src/napari_matplotlib/tests/test_util.py @@ -26,7 +26,7 @@ def test_interval(): assert 10 not in interval with pytest.raises(ValueError, match="must be an integer"): - "string" in interval # type: ignore + assert "string" in interval # type: ignore[operator] with pytest.raises(ValueError, match="must be <= upper_bound"): Interval(5, 3) @@ -69,7 +69,10 @@ def test_fallback_if_missing_dimensions(mocker): test_css = " Flobble { background-color: rgb(0, 97, 163); } " mocker.patch("napari.qt.get_current_stylesheet").return_value = test_css with pytest.warns(RuntimeWarning, match="Unable to find DimensionToken"): - assert from_napari_css_get_size_of("Flobble", (1, 2)) == QSize(1, 2) + with pytest.warns(RuntimeWarning, match="Unable to find Flobble"): + assert from_napari_css_get_size_of("Flobble", (1, 2)) == QSize( + 1, 2 + ) def test_fallback_if_prelude_not_in_css(): diff --git a/src/napari_matplotlib/util.py b/src/napari_matplotlib/util.py index ed99425..8d4150c 100644 --- a/src/napari_matplotlib/util.py +++ b/src/napari_matplotlib/util.py @@ -1,4 +1,3 @@ -from typing import Optional, Union from warnings import warn import napari.qt @@ -12,7 +11,7 @@ class Interval: An integer interval. """ - def __init__(self, lower_bound: Optional[int], upper_bound: Optional[int]): + def __init__(self, lower_bound: int | None, upper_bound: int | None): """ Parameters ---------- @@ -48,7 +47,7 @@ def __contains__(self, val: int) -> bool: return True @property - def _helper_text(self) -> Optional[str]: + def _helper_text(self) -> str | None: """ Helper text for widgets. """ @@ -86,9 +85,7 @@ def _has_id(nodes: list[tinycss2.ast.Node], id_name: str) -> bool: ) -def _get_dimension( - nodes: list[tinycss2.ast.Node], id_name: str -) -> Union[int, None]: +def _get_dimension(nodes: list[tinycss2.ast.Node], id_name: str) -> int | None: """ Get the value of the DimensionToken for the IdentToken `id_name`. @@ -97,14 +94,18 @@ def _get_dimension( None if no IdentToken is found. """ cleaned_nodes = [node for node in nodes if node.type != "whitespace"] - for name, _, value, _ in zip(*(iter(cleaned_nodes),) * 4): + for name, _, value, _ in zip(*(iter(cleaned_nodes),) * 4, strict=False): if ( name.type == "ident" and value.type == "dimension" and name.value == id_name ): return value.int_value - warn(f"Unable to find DimensionToken for {id_name}", RuntimeWarning) + warn( + f"Unable to find DimensionToken for {id_name}", + RuntimeWarning, + stacklevel=1, + ) return None @@ -137,6 +138,7 @@ def from_napari_css_get_size_of( f"Unable to find {qt_element_name} or unable to find its size in " f"the current Napari stylesheet, falling back to {fallback}", RuntimeWarning, + stacklevel=1, ) return QSize(*fallback) diff --git a/tox.ini b/tox.ini index 4ec0c70..f4aed6a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,12 @@ [tox] -envlist = py{39,310,311} +envlist = py{310,311,312} isolated_build = true [gh-actions] python = - 3.9: py39 3.10: py310 3.11: py311 + 3.12: py312 [testenv] extras = testing pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy