Skip to content

Typing guide: add code snippets for common patterns and more resources - #1177

Open
zelihaguven wants to merge 6 commits into
napari:mainfrom
zelihaguven:typing-guide-tips
Open

zelihaguven wants to merge 6 commits into
napari:mainfrom
zelihaguven:typing-guide-tips

Conversation

@zelihaguven

Copy link
Copy Markdown

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 | None and narrowing, numpy.typing arrays, Protocols such as LayerDataProtocol), each linked to the file it comes from.
Extends "Where to read more" with the Python typing docs, 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 pass pyrefly check, and the page builds without warnings in a minimal Sphinx + MyST build (the full docs build was not run).

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
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Oct 22, 2026
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Comment thread docs/developers/contributing/typing.md Outdated
Comment on lines +109 to +118
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel repetition, as we have covered in (lines 51-60), so maybe we can reduce this section

Comment thread docs/developers/contributing/typing.md Outdated
Comment on lines +131 to +133
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already covered (line 46-50) , so i think we can get rid of this

Comment thread docs/developers/contributing/typing.md Outdated
Comment on lines +137 to +142
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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Comment thread docs/developers/contributing/typing.md Outdated
Comment on lines +159 to +160
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have discussed about cast in line 85-88, so maybe we can keep in one place

Comment thread docs/developers/contributing/typing.md Outdated
— 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have reference for static typing, do we need another one or keep one.

Comment thread docs/developers/contributing/typing.md Outdated
Comment on lines +315 to +316
- [mypy documentation](https://mypy.readthedocs.io/en/stable/) — its
[protocols](https://mypy.readthedocs.io/en/stable/protocols.html) and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we can drop this 🤔

Comment thread docs/developers/contributing/typing.md Outdated
[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`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same link in line 98

Comment thread docs/developers/contributing/typing.md Outdated
`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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Aniketsy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zelihaguven thanks for the work!

I've left some comments, please have a look and let me know if anything is unclear :)

@jacopoabramo

Copy link
Copy Markdown
Contributor

@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 why using if TYPE_CHECKING never runs the imports at runtime.

@TimMonko

Copy link
Copy Markdown
Member

must admit I feel this is a bit overkill for a gentle introduction to typing and it would cause more confusion than clarity.

This is mostly a driveby, but what are your thoughts on a typing_patterns.md -- so we move all (most?) of this to that file and then it can kind of become a "rules" file for typing in napari, which at a glance this seems to do well. Things like not just nd.array but the more verbose version seem at a glance to be covered well here, along with things like from __future__... and the like. Just want to help this move along. Thanks for stopping by to contribute @zelihaguven !

@jacopoabramo

Copy link
Copy Markdown
Contributor

This is mostly a driveby, but what are your thoughts on a typing_patterns.md -- so we move all (most?) of this to that file and then it can kind of become a "rules" file for typing in napari, which at a glance this seems to do well.

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.

@zelihaguven

Copy link
Copy Markdown
Author

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:

  • Keep the intro guide light: a short, self-contained TYPE_CHECKING example plus one sentence on why those imports never run at runtime (typing.TYPE_CHECKING is False at runtime and only treated as True by type checkers like mypy/pyright).
  • Move the rest into a new typing_patterns.md cheat sheet, rewritten with small generic snippets instead of napari internals.
  • Fold in @Aniketsy's review comments (duplicate links and repeated sections) in the same pass.

Does that structure work for you, or would you prefer the cheat sheet as a separate follow-up PR?

@jacopoabramo

Copy link
Copy Markdown
Contributor

@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.
@zelihaguven

Copy link
Copy Markdown
Author

Thanks @jacopoabramo, @TimMonko and @Aniketsy! I pushed a new commit that follows the plan:

  • typing.md stays light. It has a short self-contained TYPE_CHECKING example and one sentence on why those imports never run. There's also a tip on why annotations help (from Typing Guide: Tips and Resources #1135's review) and two tabs showing the same function without and with annotations.
  • The examples moved to a new Typing cheat sheet (typing_patterns.md) with generic snippets only. It covers from __future__ import annotations (with and without, as tabs), optional values, collections.abc inputs vs concrete outputs, TypeVar generics (kept to 3.11 syntax), npt.NDArray[np.float64] as a filled generic, and protocols.
  • The page is in the TOC and the developers index, and the two pages link to each other.
  • @Aniketsy's duplicate sections and links are all removed.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this line


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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a link directly to the main page

Comment on lines +46 to +47
Tools that read annotations at runtime, such as magicgui and Pydantic, need
extra care here, as explained in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add links to both magicgui and pydantic

Comment on lines +58 to +65
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}'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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}'

Comment on lines +70 to +71
Annotate parameters with the abstract types from `collections.abc` and return
the concrete type you built:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add a small example of what actually one of these types look like. This is tied to the follow-up text anyway.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@zelihaguven

Copy link
Copy Markdown
Author

done

Comment on lines +150 to +165
## 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)
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is incomplete, it should present what happens with read-write attributes (property is a read-only attribute) and methods

zelihaguven and others added 2 commits November 8, 2026 14:42
Keep the read-only HasShape example and show a writable attribute and
a method on a second protocol, with a class that matches it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Improve Typing Guide: Tips and Resources

4 participants