Skip to content

refactor: lots of typing updates, add pyright tests on CI #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ jobs:
- uses: actions/checkout@v4
- run: pipx run check-manifest

pyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- run: uv run pyright

test:
name: ${{ matrix.os }} py${{ matrix.python-version }} ${{ matrix.gui }} ${{ matrix.canvas }}
runs-on: ${{ matrix.os }}
Expand Down
2 changes: 1 addition & 1 deletion .github_changelog_generator
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ project=ndv
issues=false
exclude-labels=duplicate,question,invalid,wontfix,hide
add-sections={"tests":{"prefix":"**Tests & CI:**","labels":["tests"]}, "documentation":{"prefix":"**Documentation:**", "labels":["documentation"]}}
exclude-tags-regex=.*rc
exclude-tags-regex=.*rc
24 changes: 24 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ ci:
autoupdate_commit_msg: "ci(pre-commit.ci): autoupdate"

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
exclude: ".*\\.md"
- id: end-of-file-fixer
- id: check-yaml
args: ["--unsafe"]
- id: check-added-large-files

- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.24.1
hooks:
Expand Down Expand Up @@ -32,3 +42,17 @@ repos:
- pydantic
- psygnal
- IPython
- types-wxpython
- qtpy
- git+https://github.com/tlambert03/PyQt6-stubs.git@v6.7.3

- repo: local
hooks:
- id: pyright
stages: [manual]
exclude: "^docs/"
name: pyright
language: system
types_or: [python, pyi]
require_serial: true
entry: uv run pyright
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ docs:
docs-serve:
uv run --group docs mkdocs serve --no-strict

lint:
uv run pre-commit run --all-files
lint:
uv run pre-commit run --all-files
10 changes: 5 additions & 5 deletions docs/css/material.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@
.md-main__inner {
margin-bottom: 1.5rem;
}

/* Custom admonition: preview */
:root {
--md-admonition-icon--preview: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.5 12a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z"/><path d="M12 3.5c3.432 0 6.124 1.534 8.054 3.241 1.926 1.703 3.132 3.61 3.616 4.46a1.6 1.6 0 0 1 0 1.598c-.484.85-1.69 2.757-3.616 4.461-1.929 1.706-4.622 3.24-8.054 3.24-3.432 0-6.124-1.534-8.054-3.24C2.02 15.558.814 13.65.33 12.8a1.6 1.6 0 0 1 0-1.598c.484-.85 1.69-2.757 3.616-4.462C5.875 5.034 8.568 3.5 12 3.5ZM1.633 11.945a.115.115 0 0 0-.017.055c.001.02.006.039.017.056.441.774 1.551 2.527 3.307 4.08C6.691 17.685 9.045 19 12 19c2.955 0 5.31-1.315 7.06-2.864 1.756-1.553 2.866-3.306 3.307-4.08a.111.111 0 0 0 .017-.056.111.111 0 0 0-.017-.056c-.441-.773-1.551-2.527-3.307-4.08C17.309 6.315 14.955 5 12 5 9.045 5 6.69 6.314 4.94 7.865c-1.756 1.552-2.866 3.306-3.307 4.08Z"/></svg>');
}

.md-typeset .admonition.preview,
.md-typeset details.preview {
border-color: rgb(220, 139, 240);
}

.md-typeset .preview>.admonition-title,
.md-typeset .preview>summary {
background-color: rgba(142, 43, 155, 0.1);
}

.md-typeset .preview>.admonition-title::before,
.md-typeset .preview>summary::before {
background-color: rgb(220, 139, 240);
-webkit-mask-image: var(--md-admonition-icon--preview);
mask-image: var(--md-admonition-icon--preview);
}
}
2 changes: 2 additions & 0 deletions examples/cookbook/microscope_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- move the stage in the x and y directions.
"""

from __future__ import annotations

from typing import Any, cast

import astropy.units as u
Expand Down
29 changes: 23 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,12 @@ pyside = [
"superqt[iconify,pyside6] >=0.7.2",
# https://github.com/pyapp-kit/ndv/issues/59
"pyside6 ==6.6.3; sys_platform == 'win32'",
"numpy >=1.23,<2; sys_platform == 'win32'", # needed for pyside6.6
"numpy >=1.23,<2; sys_platform == 'win32'", # needed for pyside6.6
"pyside6 >=6.4",
"pyside6 >=6.6; python_version >= '3.12'",
"qtpy >=2",
]
wxpython = [
"pyconify>=0.2.1",
"wxpython >=4.2.2",
]
wxpython = ["pyconify>=0.2.1", "wxpython >=4.2.2"]

# Supported Canavs backends
vispy = ["vispy>=0.14.3", "pyopengl >=3.1"]
Expand Down Expand Up @@ -110,7 +107,7 @@ dev = [
# omitting wxpython from dev env for now
# because `uv sync && pytest hangs` on a wx test in the "full" env
# use `make test extras=wx,[pygfx|vispy] isolated=1` to test
"ndv[vispy,pygfx,pyqt,jupyter]",
"ndv[vispy,pygfx,pyqt,jupyter]",
"imageio[tifffile] >=2.20",
"ipykernel>=6.29.5",
"ipython>=8.18.1",
Expand All @@ -119,6 +116,9 @@ dev = [
"pre-commit>=4.1.0",
"rich>=13.9.4",
"ruff>=0.9.4",
"pyright>=1.1.400",
"types-wxpython",
"pydevd",
]
docs = [
"mkdocs >=1.6.1",
Expand Down Expand Up @@ -189,6 +189,23 @@ plugins = ["pydantic.mypy"]
module = ["jupyter_rfb.*", "vispy.*", "ipywidgets.*"]
ignore_missing_imports = true

# https://github.com/microsoft/pyright/blob/main/docs/configuration.md
[tool.pyright]
include = ["src"]
pythonVersion = "3.9"
enableExperimentalFeatures = true
# reportMissingImports = false
reportOptionalMemberAccess = "none"
reportIncompatibleMethodOverride = "none"
reportAttributeAccessIssue = "none"
reportMissingModuleSource = "none"
reportPrivateImportUsage = "none"
reportMissingImports = "information"
reportArgumentType = "none" # because of pydantic
reportCallIssue = "none"
verboseOutput = true
venv = ".venv"

# https://docs.pytest.org/
[tool.pytest.ini_options]
addopts = ["-v"]
Expand Down
4 changes: 3 additions & 1 deletion src/ndv/controllers/_array_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from concurrent.futures import Future
from typing import Any, Unpack

import numpy.typing as npt

from ndv._types import ChannelKey, MouseMoveEvent
from ndv.models._array_display_model import ArrayDisplayModelKwargs
from ndv.models._viewer_model import ArrayViewerModelKwargs
Expand Down Expand Up @@ -272,7 +274,7 @@ def _add_histogram(self, channel: ChannelKey = None) -> None:
self._histograms[channel] = hist

def _update_channel_dtype(
self, channel: ChannelKey, dtype: np.typing.DTypeLike | None = None
self, channel: ChannelKey, dtype: npt.DTypeLike | None = None
) -> None:
if not (ctrl := self._lut_controllers.get(channel, None)):
return
Expand Down
2 changes: 1 addition & 1 deletion src/ndv/data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Sample data for testing and examples."""

# pyright: reportMissingImports=none
from __future__ import annotations

from typing import Any
Expand Down Expand Up @@ -41,7 +42,6 @@ def nd_sine_wave(
angle = np.pi / angle_dim * angle_idx
# Rotate x and y coordinates
xr = np.cos(angle) * x - np.sin(angle) * y
np.sin(angle) * x + np.cos(angle) * y

# Compute the sine wave
sine_wave = (amplitude * 0.5) * np.sin(frequency * xr + phase)
Expand Down
17 changes: 9 additions & 8 deletions src/ndv/models/_array_display_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from enum import Enum
from typing import TYPE_CHECKING, Literal, Optional, TypedDict, Union, cast

from cmap import Colormap
from pydantic import Field, computed_field, model_validator
from typing_extensions import Self, TypeAlias

Expand All @@ -27,21 +28,21 @@ class LutModelKwargs(TypedDict, total=False):
"""Keyword arguments for `LUTModel`."""

visible: bool
cmap: cmap.Colormap | cmap._colormap.ColorStopsLike
clims: tuple[float, float] | None
cmap: "cmap.Colormap | cmap._colormap.ColorStopsLike"
clims: "tuple[float, float] | None"
gamma: float
autoscale: AutoscaleType

class ArrayDisplayModelKwargs(TypedDict, total=False):
"""Keyword arguments for `ArrayDisplayModel`."""

visible_axes: tuple[AxisKey, AxisKey, AxisKey] | tuple[AxisKey, AxisKey]
visible_axes: "tuple[AxisKey, AxisKey, AxisKey] | tuple[AxisKey, AxisKey]"
current_index: Mapping[AxisKey, Union[int, slice]]
channel_mode: "ChannelMode" | Literal["grayscale", "composite", "color", "rgba"]
channel_mode: 'ChannelMode | Literal["grayscale", "composite", "color", "rgba"]'
channel_axis: Optional[AxisKey]
reducers: Mapping[AxisKey | None, ReducerType]
luts: Mapping[int | None, LUTModel | LutModelKwargs]
default_lut: LUTModel | LutModelKwargs
reducers: Mapping["AxisKey | None", ReducerType]
luts: Mapping["int | None", "LUTModel | LutModelKwargs"]
default_lut: "LUTModel | LutModelKwargs"


# map of axis to index/slice ... i.e. the current subset of data being displayed
Expand All @@ -59,7 +60,7 @@ class ArrayDisplayModelKwargs(TypedDict, total=False):
def _default_luts() -> LutMap:
colors = ["green", "magenta", "cyan", "red", "blue", "yellow"]
return ValidatedEventedDict(
(i, LUTModel(cmap=color)) for i, color in enumerate(colors)
(i, LUTModel(cmap=Colormap(color))) for i, color in enumerate(colors)
)


Expand Down
2 changes: 1 addition & 1 deletion src/ndv/models/_data_display_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class _ArrayDataDisplayModel(NDVModel):
The data wrapper. Provides the actual data to be displayed
"""

display: ArrayDisplayModel = Field(default_factory=ArrayDisplayModel)
display: ArrayDisplayModel = Field(default_factory=lambda: ArrayDisplayModel())
data_wrapper: Optional[DataWrapper] = None

def model_post_init(self, __context: Any) -> None:
Expand Down
6 changes: 4 additions & 2 deletions src/ndv/models/_data_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""In this module, we provide built-in support for many array types."""

# pyright: reportMissingImports=none
from __future__ import annotations

import json
Expand All @@ -19,7 +20,7 @@

if TYPE_CHECKING:
from collections.abc import Container, Iterator
from typing import Any, TypeAlias, TypeGuard
from typing import Any, Union

import dask.array.core as da
import numpy.typing as npt
Expand All @@ -30,8 +31,9 @@
import torch
import xarray as xr
from pydantic import GetCoreSchemaHandler
from typing_extensions import TypeAlias, TypeGuard

Index: TypeAlias = int | slice
Index: TypeAlias = Union[int, slice]


class SupportsIndexing(Protocol):
Expand Down
4 changes: 2 additions & 2 deletions src/ndv/models/_lut_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@
center: Optional[float] = None # None means center around the mean

def get_limits(self, data: npt.NDArray) -> tuple[float, float]:
center = np.nanmean(data) if self.center is None else self.center
diff = self.n_stdev * np.nanstd(data)
center = float(np.nanmean(data) if self.center is None else self.center)
diff = float(self.n_stdev * np.nanstd(data))

Check warning on line 131 in src/ndv/models/_lut_model.py

View check run for this annotation

Codecov / codecov/patch

src/ndv/models/_lut_model.py#L130-L131

Added lines #L130 - L131 were not covered by tests
return center - diff, center + diff

def __eq__(self, other: object) -> bool:
Expand Down
10 changes: 5 additions & 5 deletions src/ndv/models/_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ValidatedEventedDict(MutableMapping[_KT, _VT]):
def __init__(self) -> None: ...
@overload
def __init__( # type: ignore[misc]
self: dict[str, _VT],
self: dict,
key_validator: Callable[[Any], _KT] | None = None,
value_validator: Callable[[Any], _VT] | None = None,
**kwargs: _VT,
Expand All @@ -58,10 +58,10 @@ def __init__(
) -> None: ...
@overload
def __init__( # type: ignore[misc]
self: dict[str, _VT],
self: dict,
map: SupportsKeysAndGetItem[str, _VT],
/,
key_validator: Callable[[Any], _KT] | None = ...,
key_validator: Callable[[Any], _KT] | None = None,
value_validator: Callable[[Any], _VT] | None = ...,
validate_lookup: bool = ...,
**kwargs: _VT,
Expand All @@ -77,10 +77,10 @@ def __init__(
) -> None: ...
@overload
def __init__( # type: ignore[misc]
self: dict[str, _VT],
self: dict,
iterable: Iterable[tuple[str, _VT]],
/,
key_validator: Callable[[Any], _KT] | None = ...,
key_validator: Callable[[Any], _KT] | None = None,
value_validator: Callable[[Any], _VT] | None = ...,
validate_lookup: bool = ...,
**kwargs: _VT,
Expand Down
1 change: 1 addition & 0 deletions src/ndv/models/_reducer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Reducer(Protocol):

def __call__(self, a: npt.ArrayLike, axis: _ShapeLike = ...) -> npt.ArrayLike:
"""Reduce an array along an axis."""
raise NotImplementedError()


def _str_to_callable(obj: Any) -> Callable:
Expand Down
Loading
Loading