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