Repository navigation
Typing guide: add code snippets for common patterns and more resources - #1177
zelihaguven wants to merge 6 commits into
Conversation
Add a 'Common patterns, with examples' section with snippets trimmed from napari's source (TYPE_CHECKING imports, X | None and narrowing, numpy.typing arrays, Protocols such as LayerDataProtocol), and extend 'Where to read more' with the typing, mypy, NumPy typing and pyrefly error-kind docs. Closes napari#1166
| needs `Dims` only to annotate a parameter, so the import sits under | ||
| `TYPE_CHECKING` and is never executed at runtime: | ||
|
|
||
| ```python | ||
| from __future__ import annotations | ||
|
|
||
| from typing import TYPE_CHECKING, Any, Protocol | ||
|
|
||
| if TYPE_CHECKING: | ||
| from napari.components import Dims |
There was a problem hiding this comment.
I feel repetition, as we have covered in (lines 51-60), so maybe we can reduce this section
| On Python versions before 3.14, without the `__future__` import, | ||
| `dims: Dims` would be evaluated when the class is defined and raise a | ||
| `NameError`, because `Dims` only exists for the checker. |
There was a problem hiding this comment.
this is already covered (line 46-50) , so i think we can get rid of this
| Write `X | None` for an optional value and `X | Y` for a union. They mean the | ||
| same as `Optional[X]` and `Union[X, Y]`, which you will still see in older | ||
| modules. Ruff's automatic rewrite (`UP007`, `UP045`) is currently disabled in | ||
| napari's `pyproject.toml`, so leave existing annotations as they are unless you | ||
| are changing that code anyway. From | ||
| [`utils/status_messages.py`](https://github.com/napari/napari/blob/main/src/napari/utils/status_messages.py): |
There was a problem hiding this comment.
how about keeping only this
Write `X | None` for an optional value and `X | Y` for a union. They mean the
same as `Optional[X]` and `Union[X, Y]`, which you will still see in older
modules while napari's ruff config disables the automatic rewrite (`UP007`,
`UP045`). From
[`utils/status_messages.py`](https://github.com/napari/napari/blob/main/src/napari/utils/status_messages.py):
| The `is None` check is the kind of narrowing that should be preferred to | ||
| `cast()`. Because napari requires Python 3.11 or newer, `int | None` also works |
There was a problem hiding this comment.
we have discussed about cast in line 85-88, so maybe we can keep in one place
| — the standard library documentation for `TYPE_CHECKING`, `Protocol`, | ||
| `cast` and the other names used above. | ||
| - [Static Typing with Python](https://typing.python.org/en/latest/) — the | ||
| checker-neutral guides and the typing specification, including a |
There was a problem hiding this comment.
we already have reference for static typing, do we need another one or keep one.
| - [mypy documentation](https://mypy.readthedocs.io/en/stable/) — its | ||
| [protocols](https://mypy.readthedocs.io/en/stable/protocols.html) and |
There was a problem hiding this comment.
i think we can drop this 🤔
| [kinds of types](https://mypy.readthedocs.io/en/stable/kinds_of_types.html) | ||
| pages explain unions, `None` and narrowing with examples. | ||
| - [NumPy typing](https://numpy.org/doc/stable/reference/typing.html) — | ||
| `npt.NDArray`, `npt.ArrayLike` and `npt.DTypeLike`. |
| `npt.NDArray`, `npt.ArrayLike` and `npt.DTypeLike`. | ||
| - [pyrefly error kinds](https://pyrefly.org/en/docs/error-kinds/) — what each | ||
| `[error-kind]` in a pyrefly message means, for writing targeted | ||
| suppressions. |
There was a problem hiding this comment.
don't think we need explicit reference for the errors, as we have reference in line 300 ( typing for developer, i think thats enough )
Aniketsy
left a comment
There was a problem hiding this comment.
@zelihaguven thanks for the work!
I've left some comments, please have a look and let me know if anything is unclear :)
|
@zelihaguven first off thanks for picking this up! I must admit I feel this is a bit overkill for a gentle introduction to typing and it would cause more confusion than clarity. The example snippets shouldn't refer to the real napari code base and should instead show small examples that focus on typing. Additionally it is not clearly stated |
This is mostly a driveby, but what are your thoughts on a |
That would probably fit better in the context of these changes and would have been the natural follow-up step, indeed. I think probably something like a "typing cheat sheet" kind of document. Still they should be generalized, not tied to the specifics of the napari internals. |
|
Thanks @jacopoabramo and @TimMonko, that makes sense, and I agree the current version is too much for an intro page. My plan for this PR:
Does that structure work for you, or would you prefer the cheat sheet as a separate follow-up PR? |
|
@zelihaguven yes this plan's outlook looks reasonable, thank you! I would encourage you to have a look at this review because it also pinpoints where to put specific mentions as additions in the documentation page. Some additional guidelines:
Just overall look at that review and address it as best as you can, including your plan. |
Keep the type checking page light and move the longer examples into a new typing cheat sheet with small, generic snippets instead of napari internals. - typing.md: add a tip on why annotations help, tabs showing code without and with annotations, and a short self-contained TYPE_CHECKING example with one sentence on why those imports never run at runtime. - typing.md: remove the "Common patterns" section and the duplicated links in "Where to read more". - typing_patterns.md: new cheat sheet covering the __future__ import (with tabs), optional values, collections.abc inputs, TypeVar generics, NumPy arrays and protocols. - Add the new page to the TOC and the developers index, and cross-link the two pages.
|
Thanks @jacopoabramo, @TimMonko and @Aniketsy! I pushed a new commit that follows the plan:
Happy to adjust anything, including splitting the cheat sheet into a follow-up PR if you'd prefer. |
| # Typing cheat sheet | ||
|
|
||
| Short, self-contained examples of the rules in [Type checking](napari-typing). | ||
| Each snippet focuses on one typing pattern rather than on napari's own code. |
|
|
||
| Short, self-contained examples of the rules in [Type checking](napari-typing). | ||
| Each snippet focuses on one typing pattern rather than on napari's own code. | ||
| For how to run the checker and how to handle its errors, see the main page. |
There was a problem hiding this comment.
Add a link directly to the main page
| Tools that read annotations at runtime, such as magicgui and Pydantic, need | ||
| extra care here, as explained in |
There was a problem hiding this comment.
Add links to both magicgui and pydantic
| DEFAULT_PRECISION = 3 | ||
|
|
||
|
|
||
| def format_float(value: float, precision: int | None = None) -> str: | ||
| if precision is None: | ||
| precision = DEFAULT_PRECISION | ||
| # from here on, the checker knows that `precision` is an `int` | ||
| return f'{value:.{precision}f}' |
There was a problem hiding this comment.
| DEFAULT_PRECISION = 3 | |
| def format_float(value: float, precision: int | None = None) -> str: | |
| if precision is None: | |
| precision = DEFAULT_PRECISION | |
| # from here on, the checker knows that `precision` is an `int` | |
| return f'{value:.{precision}f}' | |
| def format_float(value: float, precision: int | None = None) -> str: | |
| if precision is None: | |
| # since the input `precision` is `None`, set it to a default value | |
| precision = 3 | |
| # from here on, the checker knows that `precision` is an `int` | |
| return f'{value:.{precision}f}' |
| Annotate parameters with the abstract types from `collections.abc` and return | ||
| the concrete type you built: |
There was a problem hiding this comment.
| Annotate parameters with the abstract types from `collections.abc` and return | |
| the concrete type you built: | |
| `collections.abc` is a python module enstablishing abstract, reusable and pythonic-like interfaces for customizeable classes that implement common patterns. | |
| It's best to annotate the input types of a function or method through these types. Instead, output types should return clearly defined types. In general the idea is "flexible inputs, strict outputs". |
There was a problem hiding this comment.
Also add a small example of what actually one of these types look like. This is tied to the follow-up text anyway.
|
done |
| ## Protocols for "anything with these members" | ||
|
|
||
| A `Protocol` describes what a function uses instead of which class it expects: | ||
|
|
||
| ```python | ||
| from typing import Protocol | ||
|
|
||
|
|
||
| class HasShape(Protocol): | ||
| @property | ||
| def shape(self) -> tuple[int, ...]: ... | ||
|
|
||
|
|
||
| def ndim(data: HasShape) -> int: | ||
| return len(data.shape) | ||
| ``` |
There was a problem hiding this comment.
This section is incomplete, it should present what happens with read-write attributes (property is a read-only attribute) and methods
Keep the read-only HasShape example and show a writable attribute and a method on a second protocol, with a class that matches it.
Closes #1166. Adds a "Common patterns, with examples" section to the typing guide, with short snippets trimmed from napari's source (TYPE_CHECKING imports,
X | Noneand narrowing,numpy.typingarrays, Protocols such asLayerDataProtocol), each linked to the file it comes from.Extends "Where to read more" with the Python
typingdocs, typing.python.org, the mypy docs (protocols, kinds of types), NumPy typing and pyrefly's error kinds; all links checked.Checked locally:
rumdl check(incl. ruff format of code blocks) and codespell pass, the snippets run and passpyrefly check, and the page builds without warnings in a minimal Sphinx + MyST build (the full docs build was not run).