mrkeyoor.com_
Sat 19 Sept 15:51 UTC
PyPICLI & Toolingupdated 19 Sept 2026

mypy review

mypy 2.3.1 installed as a 57 MB compiled tool in our Python 3.12 sandbox, then our generated import probe failed before module loading with a leading-zero SyntaxError. Its actual job is static analysis: it follows imports, combines annotations with stubs, narrows unions from control flow, checks protocols and generics, and reuses a module cache. Version 2.3 previews a native parser, improves unannotated async inference and narrowing, and changes mypyc for free-threaded Python. Version 2.3.1 repairs crashes and state errors in mypyc and overload return handling.

Verdict

mypy 2.3.1 installed in 0.7 seconds and used 57 MB across 6 packages, but our generated module import failed with a leading-zero SyntaxError. Use the CLI for annotated-code CI after a real command smoke test; use runtime validators for external data and compare Pyright when Pylance parity is required.

We installed it

Lab card: what happened when we installed mypyScreenshot of mypy documentation
Install✓ · 0.7s6 packages on disk · 57 MB
Importimport 08ae81f72d5a2b5fa9e0__mypyc · compiled extensions · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does mypy install cleanly?

Yes. In a fresh container with an empty cache, pip install mypy finished in 0.7s, leaving 6 packages and 57 MB on disk. pip-audit reported no known vulnerabilities.

What does mypy need to run?

Python >=3.10, and a platform wheel with compiled extensions. In our run import 08ae81f72d5a2b5fa9e0__mypyc failed, so it needs extra system packages, and the package ships py.typed for type checkers.

mypy or pyright: which should you use?

pyright: Choose it for fast project analysis when matching Pylance behavior matters more than matching mypy diagnostics. mypy 2.3.1 installed in 0.7 seconds and used 57 MB across 6 packages, but our generated module import failed with a leading-zero SyntaxError.

When should you not use mypy?

Skip 2.3.1 when the tooling interpreter is below Python 3.10, even if the source being analyzed targets an older language version.

API stability3/5Protocol, TypedDict, overloads, generics, and type guards retain their Python meaning, yet mypy's accepted programs and error codes change when inference is corrected or tightened. Plugins use checker internals with less compatibility than the CLI. Version 2.3 also asks projects to test a future default native parser, so a pinned checker and reviewed upgrades are part of using it responsibly.
Docs5/5The official site documents gradual adoption, type forms, protocols, TypedDict, generics, class rules, runtime annotation traps, configuration precedence, CLI flags, dmypy, stub authoring, plugins, and error codes. Examples mark both accepted and rejected expressions. The volume can obscure the small set of settings a team needs first, but the reference material is specific and searchable.
Maintenance5/5GitHub showed an unarchived repository pushed on August 25, 2026, with 20,603 stars and 3,203 open issues and pull requests. Version 2.3 delivered parser preparation, narrowing and async inference fixes, daemon corrections, and free-threading changes in mypyc. Patch 2.3.1 then repaired iterator, inherited dataclass factory, coroutine cleanup, and overload unpacking failures.
Ecosystem5/5The stored PyPI count is 50,292,038 downloads per week. Frameworks publish mypy plugins or checker guidance, typed libraries ship py.typed, and typeshed distributes standard-library and third-party stubs through types packages. Its error codes appear in many CI and pyproject policies. Pyright forms a separate large ecosystem, so teams using both must reconcile some differing inference results.

Discussed on

  1. hnMypyc: Compile type-annotated Python to C234 points
  2. hnMypy: Optional static typing for Python4 points
  3. hnDropbox mypy team considering static compilation for typed python3 points

Use it if

  • Use it when annotated Python code should fail CI for incompatible calls, absent attributes, and incorrect return types.
  • Choose it for a gradual rollout that needs module-specific settings, named error codes, and narrow suppressions.
  • Run it against a library that publishes py.typed or stubs to check the interface consumers will see.
  • Adopt its incremental cache or dmypy daemon when a large import graph makes cold checks too slow for edits.
Skip it if

Setup reality

We installed mypy 2.3.1 in 0.7 seconds in a fresh Python 3.12 sandbox. The environment ended with 6 packages using 57 MB. pip-audit reported 0 known vulnerabilities. The distribution declares 12 direct dependencies, requires Python 3.10 or newer, contains compiled extensions, and ships py.typed. Its measured metadata did not identify a license.

Our import probe failed. It ran import 08ae81f72d5a2b5fa9e0__mypyc, which Python rejected with SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers. Because an identifier started with digits, parsing stopped before import resolution. This result says nothing about whether the normal CLI works. Acceptance should separately run python -m mypy --version and check a real package.

Choose one config location from mypy.ini, .mypy.ini, setup.cfg, or pyproject.toml; precedence makes duplicates hard to reason about. Set the source roots and target Python version in CI. For an untyped dependency, install its matching types package, maintain a local stub, or add a module-specific override. Global ignore_missing_imports lets Any spread through callers and can silence errors far beyond that dependency.

Incremental mode writes .mypy_cache, which should be keyed by the checker version, Python target, and config in CI. dmypy retains the graph in a process and must be restarted after stale plugin or interpreter state. Pin mypy because strict is a moving preset and inference fixes can create new diagnostics. Version 2.3 invites testing --native-parser; keep that experiment nonblocking until its results match the existing parser on your repository.

Patterns

Analyze one package with the active interpreter check-package

python -m mypy --show-error-codes src/myapp

python -m selects the checker installed beside that Python executable. Named error codes make a suppression identify the exact diagnostic.

Define a strict 3.12 project in TOML configure-strict-project

[tool.mypy]
python_version = "3.12"
files = ["src", "tests"]
strict = true
show_error_codes = true
warn_unused_ignores = true

The strict flag expands to a release-dependent setting set. Pinning mypy keeps that policy from changing on an unrelated CI install.

Quarantine relaxed checks to a legacy module phase-module-strictness

[[tool.mypy.overrides]]
module = ["legacy_vendor.*"]
ignore_missing_imports = true
disallow_untyped_defs = false

This override limits missing imports and untyped definitions to legacy_vendor. Applying either option globally weakens every import consumer.

Print the checker's inferred union inspect-inferred-type

def choose(flag: bool) -> int | str:
    value = 1 if flag else "one"
    reveal_type(value)
    return value

reveal_type produces a mypy diagnostic rather than runtime output. Remove it unless the repository deliberately tests inferred types.

Prove one side of a union with isinstance narrow-union

def render(value: int | str) -> str:
    if isinstance(value, int):
        return f"count={value}"
    return value.upper()

The int branch narrows value only along that control-flow path. An untyped helper or intervening shared-state mutation may prevent the proof from carrying.

Describe required and optional dictionary keys describe-dictionary-shape

from typing import NotRequired, TypedDict

class UserRow(TypedDict):
    id: int
    email: str
    nickname: NotRequired[str]

def label(row: UserRow) -> str:
    return row.get("nickname", row["email"])

UserRow remains a plain dict when Python runs. TypedDict checks annotated callers but performs no validation on decoded JSON.

Accept any object with a close method accept-structural-interface

from typing import Protocol

class Closable(Protocol):
    def close(self) -> None: ...

def finish(resource: Closable) -> None:
    resource.close()

Nominal inheritance is unnecessary because the method shape satisfies Closable. Runtime isinstance needs runtime_checkable and verifies only the exposed shape.

Return the element type supplied by the caller define-generic-function

from collections.abc import Sequence
from typing import TypeVar

T = TypeVar("T")

def first(items: Sequence[T]) -> T:
    if not items:
        raise ValueError("empty sequence")
    return items[0]

T ties the sequence element to the result. Allowing Any inside the implementation would erase that relationship at the call site.

Tie a literal flag to the return type type-overloads

from typing import Literal, overload

@overload
def parse(raw: str, as_bytes: Literal[False] = False) -> str: ...
@overload
def parse(raw: str, as_bytes: Literal[True]) -> bytes: ...

def parse(raw: str, as_bytes: bool = False) -> str | bytes:
    return raw.encode() if as_bytes else raw

The concrete implementation covers both overload signatures and both result types. Passing an ordinary bool yields str or bytes unless another overload handles it.

Ignore one missing-attribute report ignore-one-error

value = third_party.dynamic_attr  # type: ignore[attr-defined]

attr-defined targets one checker category. warn_unused_ignores later reports the comment when upstream typing makes it unnecessary.

Add a local interface for an untyped client write-local-stub

# typings/acme_client/__init__.pyi
class Client:
    def __init__(self, token: str) -> None: ...
    def fetch(self, item_id: int) -> dict[str, object]: ...

MYPYPATH or stub packaging must expose this directory. The pyi file is now your contract and must track changes in acme_client.

Reuse the type graph with dmypy run-daemon

dmypy run -- --strict src/myapp
dmypy status
dmypy stop

dmypy keeps the dependency graph in memory between runs. Restart it when config, Python, or plugin changes appear stale.

Alternatives

PackageRegistryPick it when
pyrightPyPIChoose it for fast project analysis when matching Pylance behavior matters more than matching mypy diagnostics.
pyre-checkPyPIChoose it for a large service whose team wants Pyre's daemon and query workflow.
pytypePyPIChoose it when inference over lightly annotated CPython code matters more than mypy's annotation-led policy.

More cli & tooling guides

commander · chalk · typescript · esbuild · yargs · click · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.