mrkeyoor.com_
Wed 05 Aug 05:04 UTC
PyPICLI & Toolingupdated 05 Aug 2026

mypy

mypy is the original static type checker for Python and the reference implementation of PEP 484 type hints. You annotate your code, run mypy over it, and it reports type errors without executing anything: passing a str where an int is expected, forgetting a None check, breaking a Protocol. It is built for gradual typing, so you can annotate one module at a time and leave the rest dynamic. It ships with a daemon (dmypy) for fast incremental re-checks on large codebases, error codes you can suppress individually, and it is itself compiled with mypyc, which the project says makes it about 4x faster than running interpreted. It lives under the python GitHub org and effectively defines how Python typing behaves in practice.

Verdict

Still the reference type checker and the safest bet for CI and library authors, with the deepest plugin and config surface. If your main use is editor feedback or you refuse daemon setup, pyright will feel faster; many serious teams simply run both.

API stability4/5Behavior tracks the evolving typing spec, so new versions routinely flag code that passed before, and the 2.0 major raised the minimum Python to 3.10; the CLI and config surface stay compatible though, and error codes make suppressions durable.
Docs5/5mypy.readthedocs.io covers getting started, a cheat sheet, the full error-code list, a common-issues page, and daemon docs; it doubles as the de facto manual for Python typing itself.
Maintenance5/5Under the python org with pushes days ago (Aug 2026) and a steady major/minor cadence (2.0 through 2.3.0 since 2026); 3164 open issues and PRs reflects its role as the typing ecosystem's bug tracker more than neglect.
Ecosystem5/551M weekly downloads, a plugin ecosystem (django-stubs, pydantic's mypy plugin), typeshed integration, pre-commit mirrors, IDE hooks, and mypyc as a compiler built on it; the entire stubs ecosystem is tested against it.

Use it if

  • You want the checker the Python typing ecosystem treats as the baseline; most libraries test their stubs and typing behavior against mypy first
  • You need CI-grade type checking with fine-grained control: per-module strictness, error codes, and a plugin system that packages like Django (via django-stubs) and SQLAlchemy rely on
  • You have a large codebase and will use dmypy; the daemon's incremental checks turn multi-minute runs into sub-second ones
  • You are adopting types gradually and want a tool designed for half-typed codebases rather than one that assumes full coverage
Skip it if

Setup reality

pip install mypy and run mypy src/, that part is genuinely easy. The work starts on real codebases: the first run on untyped code produces a wall of errors, mostly from third-party packages without stubs, so you immediately learn --install-types and per-module ignore_missing_imports overrides. Defaults are lenient (functions without annotations are not checked), so teams end up assembling a [tool.mypy] block in pyproject.toml with strict flags, excludes for generated code, and overrides per legacy package. Caches in .mypy_cache occasionally go stale after big refactors and need deleting. The dmypy daemon is a separate habit with its own quirks: it can crash after certain edits and needs a dmypy restart. Plugins (django-stubs, pydantic) each bring their own config stanza.

Patterns

Install and check a projectfirst-run

python3 -m pip install -U mypy
mypy src/

mypy 2.x needs Python 3.10+ to run; functions without any annotations are skipped by default, so a clean first run does not mean much yet.

Configure via pyproject.tomlpyproject-config

[tool.mypy]
python_version = "3.12"
files = ["src"]
warn_unused_ignores = true
warn_redundant_casts = true
disallow_untyped_defs = true

Without a files/packages setting, what gets checked depends on how you invoke mypy, which makes CI and local runs drift apart.

Turn on strict checkingstrict-mode

mypy --strict src/

# or in pyproject.toml
[tool.mypy]
strict = true

strict is a bundle of individual flags; run mypy --help to see what it enables, then relax specific flags per module instead of abandoning it.

Silence untyped third-party importsmissing-stubs

[[tool.mypy.overrides]]
module = ["somepkg.*", "legacy_sdk"]
ignore_missing_imports = true

Scope the override to specific modules; a global ignore_missing_imports = true also hides your own typos in import paths.

Install missing stub packagesinstall-types

mypy --install-types --non-interactive src/

This pip-installs types-* stubs for imports mypy recognizes; in CI prefer pinning stub packages in requirements so builds stay reproducible.

Suppress one error with its codetargeted-ignore

value: int = get_legacy_value()  # type: ignore[assignment]

Always include the error code in brackets; a bare # type: ignore hides every future error on that line, and warn_unused_ignores flags stale ones.

Debug what mypy thinks a type isreveal-type

x = load_config()
reveal_type(x)  # note: Revealed type is "dict[str, Any]"

reveal_type needs no import for mypy runs, but it crashes at runtime under plain Python, so delete it before committing (or import it from typing_extensions).

Fast incremental checks with dmypydaemon-mode

dmypy run -- src/
# subsequent runs are incremental and usually sub-second
dmypy restart  # when results look stale after big refactors

The daemon keeps state between runs; after switching branches or upgrading mypy, restart it before trusting the output.

Narrow Optional and union typesnarrowing

def send(user: User | None) -> None:
    if user is None:
        raise ValueError('no user')
    # user is User from here on
    user.notify()

def handle(event: Click | Scroll) -> None:
    if isinstance(event, Click):
        event.press()

mypy narrows on is None, isinstance, and assert; stashing the check result in a variable first can defeat narrowing.

Strict for new code, lenient for legacygradual-adoption

[tool.mypy]
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = "legacy.*"
disallow_untyped_defs = false
ignore_errors = true

Ratchet one package at a time out of the legacy override list; this beats a repo-wide strictness that everyone ignores.

Exclude generated or vendored codeexclude-paths

[tool.mypy]
exclude = [
  "^build/",
  "_pb2\\.py$",
  "^migrations/"
]

exclude entries are regexes, not globs; an excluded file still gets checked if another checked module imports it, use per-module ignore_errors for that.

Run mypy in pre-commitpre-commit-hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v2.3.0
    hooks:
      - id: mypy
        additional_dependencies: [types-requests, pydantic]

The mirror runs mypy in an isolated venv, so it cannot see your project's dependencies unless you list them in additional_dependencies; many teams run mypy in CI instead for this reason.

Alternatives

PackageRegistryPick it when
pyrightPyPIYou want the fastest checks and the same engine as VS Code's Pylance, configured via pyrightconfig or pyproject
basedpyrightPyPIYou want pyright's engine fully on PyPI with the Pylance-only features reimplemented and no Node dependency
tyPyPIYou want to try Astral's Rust-based checker for speed and are comfortable with a young tool
pyre-checkPyPIMeta-scale monorepos where Pyre's incremental architecture and Pysa taint analysis matter