Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 11 – Type hints and static type checking

mypy and pyright: running type checkers

Install mypy and pyright, run them on a project, configure both in pyproject.toml, read their errors and fix them instead of silencing them.

  • Intermediate
  • 15 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: Both checkers read the code and print one line per problem · Both checkers read pyproject.toml, and their defaults differ · A checker must see the same packages as the program · Check yourself. With a pass: 2 in-depth parts and 3 interview answers, about 9 minutes.

What you will learn

  • Install and run mypy and pyright, and read each part of their messages
  • Configure both checkers in pyproject.toml and explain where their defaults differ
  • Fix an import that the checker cannot find without silencing it
  • Use reveal_type and targeted ignore comments without hiding real bugs
  • Plan a strict-mode rollout and run the checkers in CI

Before you start

What you’ll learn · Before you start

What you will learn

  • Install and run mypy and pyright, and read each part of their messages
  • Configure both checkers in pyproject.toml and explain where their defaults differ
  • Fix an import that the checker cannot find without silencing it
  • Use reveal_type and targeted ignore comments without hiding real bugs
  • Plan a strict-mode rollout and run the checkers in CI

Before you start

On this page

A type checker: A program such as mypy or pyright that reads Python source without running it and reports every call, return and assignment that breaks a type hint, before the program runs. More in “Why type hints help (and what they don't do)” is a program you run on your code, the way you run tests, and on a file with three bugs it reports all three before anything runs. Install mypy and pyright into the project’s virtual environment with python -m pip install mypy pyright (pyright also comes as an npm package and needs Node.js), then run mypy src or pyright src, or both with no arguments once pyproject.toml names the files. Each prints one line per problem: the file, the line, the severity, the message and a code that names the check. Configure them in pyproject.toml, under [tool.mypy] and [tool.pyright], and switch on strict mode for new code. When a message is unclear, reveal_type(x) makes the checker print the type it inferred for x; when a message is wrong for a good reason, an ignore comment silences that one error and nothing else, and cast() tells the checker to treat a value as a given type without checking it.

Both checkers read the code and print one line per problem

The checkers here were installed as you would install them yourself, mypy with pip in a virtual environment and pyright with npm, and each line that starts with $ is a command to type in the project’s folder. inventory.py has three bugs:

inventory.py Python · check/inventory.py
from dataclasses import dataclass


@dataclass
class Item:
    name: str
    price: float
    stock: int


def find(items: list[Item], name: str) -> Item | None:
    for item in items:
        if item.name == name:
            return item
    return None


def restock(item: Item, amount: int) -> None:
    item.stock += amount


def value(items: list[Item]) -> float:
    return sum(item.price * item.stock for item in items)


items = [Item("pen", 10.0, 120), Item("notebook", 45.5, "40")]
restock(find(items, "pen"), 30)
print(value(items).rjust(10))
Two checkers, one file, three bugs Python · check/check_inventory.py
from session import run

# Both checkers on the same file: the same three bugs, worded two ways.
run("""
mypy --version
pyright --version
mypy inventory.py
pyright inventory.py
""")

Output

$ mypy --version
mypy 2.4.0 (compiled: yes)
$ pyright --version
pyright 1.1.414
$ mypy inventory.py
inventory.py:26: error: Argument 3 to "Item" has incompatible type "str"; expected "int"  [arg-type]
inventory.py:27: error: Argument 1 to "restock" has incompatible type "Item | None"; expected "Item"  [arg-type]
inventory.py:28: error: "float" has no attribute "rjust"  [attr-defined]
Found 3 errors in 1 file (checked 1 source file)
[exit status 1]
$ pyright inventory.py
inventory.py
  inventory.py:26:57 - error: Argument of type "Literal['40']" cannot be assigned to parameter "stock" of type "int" in function "__init__"
    "Literal['40']" is not assignable to "int" (reportArgumentType)
  inventory.py:27:9 - error: Argument of type "Item | None" cannot be assigned to parameter "item" of type "Item" in function "restock"
    Type "Item | None" is not assignable to type "Item"
      "None" is not assignable to "Item" (reportArgumentType)
  inventory.py:28:20 - error: Cannot access attribute "rjust" for class "float"
    Attribute "rjust" is unknown (reportAttributeAccessIssue)
3 errors, 0 warnings, 0 informations
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_inventory.py

Both checkers found the same three bugs: a stock count given as the text "40", a find() result that may be None passed where an Item is needed, and a method of str called on a float. mypy wrote one line per error; pyright added the column and, for the second error, two lines that explain why None does not fit. Both ended with a count and exited with status 1, which is what lets a CI job fail on type errors.

Read an error from the left: where it is, how serious it is, what is wrong, and which check said so. The diagram splits the line each checker printed for line 27 into those four parts:

The same error as mypy and pyright print it, split into its parts: where, severity, message and the code or rule name.mypypyrightinventory.py:27:file, lineerror:severityArgument 1 to"restock" hasincompatible type …[arg-type]error codeinventory.py:27:9file, line, column- error:severityArgument of type"Item | None" cannotbe assigned …(reportArgumentType)rule name

The parts of an error message

Text description of the diagram

The diagram has two columns, one per checker, and splits the error that each one prints for line 27 of inventory.py into four parts, read from top to bottom.

  • mypy: first the place, inventory.py:27, which is the file and the line; then the severity, error; then the message, Argument 1 to "restock" has incompatible type "Item | None"; expected "Item"; and last the error code in square brackets, [arg-type].
  • pyright: first the place, inventory.py:27:9, which adds the column; then the severity, error; then the message, Argument of type "Item | None" cannot be assigned to parameter "item", followed on later lines by an explanation; and last the rule name in brackets, (reportArgumentType).
  • The error code and the rule name are what configuration options and targeted ignore comments refer to.

The last part is the one to remember. mypy’s error code: The name of the check that a type checker's message comes from, such as mypy's [arg-type] or pyright's rule name reportArgumentType. Configuration options and targeted ignore comments refer to it., such as arg-type, return-value or attr-defined, and pyright’s rule names, such as reportArgumentType, are what configuration options and ignore comments refer to. When a message about an argument, a return value or an assignment lists two types, the first is what the code has and the second is what the hint asked for; fix whichever of the two is wrong, which is usually the code.

Checkpoint

Check: Running the checkers and reading a message

  1. Question 1 of 2 In inventory.py:27: error: Argument 1 to "restock" has incompatible type "Item | None"; expected "Item" [arg-type], which part names the check?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: [arg-type]

    Read a message from the left: the place, the severity, what is wrong, and last the code of the check, such as [arg-type] for mypy or (reportArgumentType) for pyright.

  2. Question 2 of 2 mypy reports Argument 3 to "Item" has incompatible type "str"; expected "int". Which type did the hint ask for?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: int, the second type in the message

    When a message lists two types, the first is what the code has and the second is what the hint asked for. Fix whichever is wrong, usually the code: here the stock count should be the number 40, not the text.

Both checkers read pyproject.toml, and their defaults differ

The two checkers follow one specification but are separate programs, with their own settings and defaults:

mypy and pyright side by side
Criterion mypypyright
Written in Python, compiled to C with mypycTypeScript, run by Node.js
Install pip install mypypip install pyright, or npm install pyright
Settings [tool.mypy] in pyproject.toml, or mypy.ini[tool.pyright] in pyproject.toml, or pyrightconfig.json
Functions without hints bodies skipped unless check_untyped_defs is onbodies checked, with types inferred
Names of checks error codes: [arg-type]rule names: (reportArgumentType)
Strict mode strict = truetypeCheckingMode = "strict"
When to choose Projects that want fine-grained settings for each moduleFast checks and the same engine many editors use as you type

mypy came first: PEP 484, which defined Python’s type hints, grew out of it. pyright is built for speed, and it also runs inside editors as a A program an editor runs in the background to check the code and complete names as you type. pyright works as one, so the errors an editor underlines can come from the same engine as a command-line run., which checks the code as you type. Both follow the typing specification, so they agree on most code, and they differ in defaults and inference, as the previous lesson showed for a variable whose first value is 0.

One checker for CI

Choose one checker for CI, and treat the other’s messages as a second opinion when an error is hard to read.

A project’s pyproject.toml holds the settings of both checkers, each in its own section:

pyproject.toml TOML · project/pyproject.toml
[project]
name = "shop"
version = "0.1.0"
requires-python = ">=3.14"

[tool.mypy]
files = ["src"]
strict = true
warn_unreachable = true

[tool.pyright]
include = ["src"]
typeCheckingMode = "strict"
src/shop/stock.py Python · project/src/shop/stock.py
def reorder_level(daily_sales: list[int], lead_days: int) -> int:
    """Units to keep in stock: the average daily sale times the days a delivery takes."""
    average = sum(daily_sales) / len(daily_sales)
    return round(average * lead_days)


def needs_reorder(stock: int, daily_sales: list[int], lead_days: int = 3) -> bool:
    return stock <= reorder_level(daily_sales, lead_days)


def label(name, stock):
    return f"{name}: {stock} left"
Both checkers with no arguments, reading pyproject.toml Python · project/check_project.py
from session import run

# Run in the folder that holds pyproject.toml: both checkers read their settings and their files from it.
run("""
mypy
pyright
""")

Output

$ mypy
src/shop/stock.py:11: error: Function is missing a type annotation  [no-untyped-def]
Found 1 error in 1 file (checked 2 source files)
[exit status 1]
$ pyright
src/shop/stock.py
  src/shop/stock.py:11:11 - error: Type of parameter "name" is unknown (reportUnknownParameterType)
  src/shop/stock.py:11:11 - error: Type annotation is missing for parameter "name" (reportMissingParameterType)
  src/shop/stock.py:11:17 - error: Type of parameter "stock" is unknown (reportUnknownParameterType)
  src/shop/stock.py:11:17 - error: Type annotation is missing for parameter "stock" (reportMissingParameterType)
4 errors, 0 warnings, 0 informations
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_project.py

Step through what each checker did in that folder:

Interactive · stage by stage What a checker does with no file names Step through mypy in the project folder, then pyright. Each stage is one thing the checker settles before it reports, with its value in the sub-label.
What a checker does with no file names, step 6 of 6. Showing $ mypy ($ pyright), pyproject.toml ([tool.pyright] section), What to check (include = ["src"]), How strictly (typeCheckingMode = "strict"), The report (4 errors, exit status 1). In focus: pyproject.toml, What to check, How strictly, The report.$ mypy$ pyrightpyproject.toml[tool.pyright] sectionWhat to checkinclude = ["src"]How strictlytypeCheckingMode = "strict"The report4 errors, exit status 1

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. No file names. mypy is run in the project folder with no file names, the way a CI step runs it. Everything else it needs comes from the folder.
  2. Its own section. It finds pyproject.toml and reads its own section, [tool.mypy]. The [tool.pyright] section beside it is for the other checker.
  3. The files. files = ["src"] says what to check when no file is named. mypy checked 2 source files, src/shop/__init__.py and src/shop/stock.py, as the last line of its output says.
  4. The rules. strict = true turns on the strict checks at once, among them one that reports every function without hints.
  5. mypy's report. reorder_level() and needs_reorder() passed. label(name, stock) has no hints, so mypy reported it once, as [no-untyped-def]. One error makes the exit status 1, which fails a CI job.
  6. The same for pyright. pyright, run the same way, reads [tool.pyright] instead, include = ["src"] and typeCheckingMode = "strict". It reported label() four times: for each of its two parameters, once for the missing hint and once for the unknown type.

With no arguments, mypy checked the files listed under files and pyright those under include, and strict mode: A setting that turns on a type checker's stricter checks at once, such as strict = true for mypy or typeCheckingMode = "strict" for pyright. Among other things it reports every function without hints. reported the one function without hints, label(), while the two annotated functions passed. Strict mode is the right default for new code; existing code is moved to it in steps.

Checkpoint

Check: Settings and defaults of the two checkers

  1. Question 1 of 2 Which setting tells mypy what to check when it is run with no file names?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: files, in the [tool.mypy] section of pyproject.toml

    Both checkers read their own section of pyproject.toml: mypy checks the paths under files and pyright those under include, so both run with no arguments in the project folder.

  2. Question 2 of 2 At their default settings, which checker looks inside a function that has no hints at all?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: pyright, which checks the body with the types it can infer

    The checkers share a specification but not their defaults. mypy skips the bodies of functions without hints unless check_untyped_defs is on; pyright checks them with inferred types.

A checker must see the same packages as the program

A checker reads the modules your code imports, so it must run with the same packages installed as the program. Run it inside the project’s virtual environment, and it finds them there:

imports.py Python · check/imports.py
import yaml
from shop_utils import slugify

settings = yaml.safe_load("currency: INR")
print(slugify(settings["currency"]))
A package without hints and a module that does not exist Python · check/check_imports.py
from session import run

# PyYAML and shop_utils are not installed in this environment.
run("""
mypy imports.py
pyright imports.py
""")

Output

$ mypy imports.py
imports.py:1: error: Library stubs not installed for "yaml"  [import-untyped]
imports.py:1: note: Hint: "python3 -m pip install types-PyYAML"
imports.py:1: note: (or run "mypy --install-types" to install all missing stub packages)
imports.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
imports.py:2: error: Cannot find implementation or library stub for module named "shop_utils"  [import-not-found]
Found 2 errors in 1 file (checked 1 source file)
[exit status 1]
$ pyright imports.py
imports.py
  imports.py:1:8 - warning: Import "yaml" could not be resolved from source (reportMissingModuleSource)
  imports.py:2:6 - error: Import "shop_utils" could not be resolved (reportMissingImports)
1 error, 1 warning, 0 informations
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_imports.py

The two messages mean different things. shop_utils is missing altogether, so every name taken from it is unknown: install the package, or fix the import. yaml is PyYAML, a library that ships without hints. mypy names the separate stub package: A package that adds type hints for a library that ships without them, such as types-PyYAML for PyYAML. It is installed beside the type checkers, as a development dependency. that adds them, types-PyYAML, while pyright found those stubs among the ones it bundles and only warned that the library itself is not installed here. Stub packages are ordinary development dependencies: list them beside the checkers, so that CI and every laptop check the same way.

Checkpoint

Check: Imports the checker cannot find

  1. Question 1 of 2 What does mypy's Cannot find implementation or library stub for module named "shop_utils" [import-not-found] tell you?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The module is missing altogether, so install the package or fix the import

    An import the checker cannot find at all is fixed by installing the package in the environment the checker runs in, or by fixing the import's name.

  2. Question 2 of 2 mypy reports Library stubs not installed for "yaml" [import-untyped]. What is the lasting fix?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Add types-PyYAML to the development dependencies, beside the checkers

    A library that ships without hints gets them from a separate stub package, here types-PyYAML. It is an ordinary development dependency, listed beside the checkers.

Check yourself

Practice that adapts to you

Practise this lesson

9 questions about 8 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 A mypy message ends with [arg-type]. What is that part for?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: It names the check, so configuration and ignore comments can refer to exactly that kind of error

    Every mypy error has a code such as arg-type, return-value or attr-defined. Options such as enable_error_code and disable_error_code use these codes, and so does a targeted # type: ignore[arg-type]. pyright's equivalent is the rule name in brackets, such as reportArgumentType.

  2. Question 2 of 9 A CI job runs mypy src and nothing else. How does the job know to fail when mypy finds type errors?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: mypy exits with status 1 when it reports errors, and CI fails a step whose status is not 0

    A checker's exit status is its verdict: 0 for no errors, 1 when it found some. A CI step that runs the checker therefore fails on type errors by itself.

  3. Question 3 of 9 Where in pyproject.toml does each checker read its settings?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: mypy in [tool.mypy] and pyright in [tool.pyright]

    pyproject.toml holds a section per tool: [tool.mypy] for mypy and [tool.pyright] for pyright, beside the project's own [project] table.

  4. Question 4 of 9 pyproject.toml has [tool.mypy] files = ["src"] and [tool.pyright] include = ["src"]. What is the simplest way to check the project with both checkers?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: Run mypy and pyright with no arguments in the folder that holds pyproject.toml

    Both checkers read their own section of pyproject.toml in the folder they start in, including the files to check, so mypy and pyright need no arguments. Files named on the command line replace the configured list.

  5. Question 5 of 9 In a project with strict = true for mypy, which line does mypy report in this module?

    Read the code, then choose one answer.

    def reorder_level(daily_sales: list[int], lead_days: int) -> int:
        return round(sum(daily_sales) / len(daily_sales) * lead_days)
    
    def label(name, stock):
        return f"{name}: {stock} left"
    Show the answer: Question 5 of 9

    Answer: The def line of label: Function is missing a type annotation

    Strict mode includes a check that reports a function without hints, as no-untyped-def. round() with one argument returns an int, so reorder_level is fine. pyright's strict mode reports the same function twice for each parameter: once for the missing hint and once for the unknown type.

  6. Question 6 of 9 In the strict project of this lesson, mypy reported one error for label(name, stock) and pyright reported four. Why the difference?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: pyright reports each parameter twice, for its missing hint and its unknown type; mypy reports the function once

    Both checkers found the same problem, a function without hints. mypy reports it once per function as no-untyped-def; pyright's strict rules report each parameter under reportMissingParameterType and again under reportUnknownParameterType. Error counts from two checkers are not comparable, so compare the lines they point to.

  7. Question 7 of 9 Where should a type checker run, so that it can find the packages a program imports?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: Inside the project's virtual environment, with the same packages installed as the program

    A checker follows the program's imports, so run it in the environment the program uses, with the stub packages of libraries that ship without hints installed beside it.

  8. Question 8 of 9 For import yaml, mypy says the library stubs are not installed and pyright warns that the import "could not be resolved from source". What do the two messages say together?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: PyYAML has no hints of its own; pyright found stubs it bundles, mypy needs types-PyYAML, and PyYAML is not installed here

    A library without hints needs a stub package for mypy; pyright bundles stubs for many libraries. Installing the library and its stub package beside the checkers removes both messages.

  9. Question 9 of 9 Type checks pass on a developer's laptop, but CI reports [import-untyped] for a library. What is the most likely cause, and the fix?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: The laptop has the library's stub package installed and CI does not; list it beside the checkers in the development dependencies

    A checker sees only the packages installed where it runs. Stub packages are ordinary development dependencies: list them beside the checkers so that every environment has them.

Fixing errors without silencing them: reveal_type, casts and ignores Pro: opens with a Pro, Premium or Ultimate pass

An error message is a question about your code, and silencing it only hides the question. This part traces types with reveal_type, reproduces what each way of quieting an error really does, and finishes with an exercise that fixes nine errors without hiding any.

In this part:

  • Tracing a narrowing line by line, with the type each checker reveals
  • Five ways to answer an error, and what each one really does
  • Fixing nine errors, as an exercise
    • Fix nine type errors without silencing any of them

5 runnable examples (Python) · 1 exercise with sample tests · about 7 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Strict mode, a module-by-module rollout and CI, measured Pro: opens with a Pro, Premium or Ultimate pass

Strict mode finds more bugs and demands more hints, and the two checkers’ strict modes are not the same size. This part measures them on one module, reproduces a configuration mistake that makes strictness leak into untouched code, and sets up the checks a CI job runs.

In this part:

  • What strict mode adds, measured on one module
  • Rolling strictness out module by module, and a setting that leaks
  • Running the checkers in CI

1 diagram · 7 runnable examples (Python, TOML, Text) · about 8 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Mid to senior

    CI runs mypy and passes, but half the team uses an editor whose pyright-based checker shows errors in the same files, and people have started adding ignore comments to quiet their editors. How do you resolve it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    A code base has 1,400 "# type: ignore" comments, most of them without an error code. How do you reduce them safely, and how do you stop new ones from piling up?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    Plan strict type checking for a 300-module repository shared by six teams, where the type check already takes 4 minutes in CI. In what order do you go, how do you configure it, and what do you measure?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Install mypy and pyright in the project’s environment and run them like tests: they read the code and run nothing.
  • Each error names a place, a severity, a message and a code; the code is what settings and ignores refer to.
  • Both checkers read pyproject.toml and share one specification, but their defaults differ, so CI runs one of them.
  • A checker must see the program’s packages, and the stub packages of libraries that ship without hints.

Terms in this lesson

error code
The name of the check that a type checker's message comes from, such as mypy's [arg-type] or pyright's rule name reportArgumentType. Configuration options and targeted ignore comments refer to it.
strict mode
A setting that turns on a type checker's stricter checks at once, such as strict = true for mypy or typeCheckingMode = "strict" for pyright. Among other things it reports every function without hints.
stub package
A package that adds type hints for a library that ships without them, such as types-PyYAML for PyYAML. It is installed beside the type checkers, as a development dependency.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.