MYPY(1)

πŸ’– MYPY(1) β€” the mypy manual, bestie edition πŸ’–

okay so. sit down. hydrate. we're talking about mypy(1) and honestly? it's giving main character energy for your whole codebase. 🫢


✨ NAME ✨

mypy β€” optional static typing for Python. yes that's the whole name. iconic. periodt. πŸ’…


🌸 SYNOPSIS 🌸

mypy [-h] [-v] [-V] [-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT]
[OPTIONS...] [FILES ...]

πŸ¦‹ DESCRIPTION πŸ¦‹

So basically, mypy is a static type checker for Python 3 and Python 2.7. If you sprinkle type annotations all over your code, mypy can type check it and sniff out those common bugs before they ruin your whole day. no cap.

Mypy is a static analyzer, aka a lint-like tool β€” which means those type annotations are literally just hints for mypy. They do not get in the way when you actually run your program. You run your program with a standard Python interpreter and the annotations are treated effectively as comments. lowkey magical. ✨

You can annotate using the Python 3 function annotation syntax (the PEP 484 notation), or a comment-based annotation syntax if you're on Python 2 code. Either way, you'll be able to efficiently annotate your code and then let mypy check it for common errors. And the type system?? It's powerful and easy to use, with modern features like:

  • type inference
  • generics
  • callable types
  • tuple types
  • union types
  • structural subtyping

obsessed, honestly. πŸ’œ

You invoke mypy with the paths you need checked:

$ mypy foo.py bar.py some_directory

And the directories get checked recursively to find Python source files. it just keeps going. πŸŒ€


⭐ OPTIONS ⭐

This section documents mypy's command line interface. Want a quick summary of the available flags? Just run mypy --help. easy.

Note: Command line flags are liable to change between releases. πŸ’€

πŸŽ€ Specifying what to type check πŸŽ€

By default, you tell mypy what code to type check by passing in the paths to whatever you want checked:

$ mypy foo.py bar.py some_directory

Note that directories are checked recursively. yes, all the way down. πŸ•³οΈ

Mypy also lets you specify what code to type check in several other ways. Here's a short summary of the relevant flags β€” for full details, see running-mypy.

  • -m MODULE, --module MODULE β€” asks mypy to type check the provided module. This flag may be repeated multiple times. Heads up though: mypy will not recursively type check any submodules of the provided module. it's a one-module kinda deal.
  • -p PACKAGE, --package PACKAGE β€” asks mypy to type check the provided package. This flag may be repeated multiple times too. And unlike -m, mypy will recursively type check any submodules of the provided package. This flag is identical to --module apart from this behavior. the more you know πŸ’«
  • -c PROGRAM_TEXT, --command PROGRAM_TEXT β€” asks mypy to type check the provided string as a program. tiny program, big dreams.
  • --exclude β€” a regular expression that matches file names, directory names and paths which mypy should ignore while recursively discovering files to check. Use forward slashes on all platforms, bestie, don't be cute about it.

For instance, to avoid discovering any files named setup.py you could pass --exclude '/setup\.py$'. Similarly, you can ignore discovering directories with a given name by e.g. --exclude /build/ or those matching a subpath with --exclude /project/vendor/. To ignore multiple files / directories / paths, you can provide the --exclude flag more than once, e.g --exclude '/setup\.py$' --exclude '/build/'.

Note that this flag only affects recursive directory tree discovery, that is, when mypy is discovering files within a directory tree or submodules of a package to check. If you pass a file or module explicitly it will still be checked. For instance, mypy --exclude '/setup.py$' but_still_check/setup.py.

In particular, --exclude does not affect mypy's discovery of files via import following. You can use a per-module ignore_errors config option to silence errors from a given module, or a per-module follow_imports config option to additionally avoid mypy from following imports and checking code you do not wish to be checked.

Note that mypy will never recursively discover files and directories named "site-packages", "node_modules" or "__pycache__", or those whose name starts with a period, exactly as --exclude '/(site-packages|node_modules|__pycache__|\..*)/$' would. Mypy will also never recursively discover files with extensions other than .py or .pyi. 🚫

  • --exclude-gitignore β€” this flag will add everything that matches .gitignore file(s) to --exclude. teamwork makes the dream work.

🌈 Optional arguments 🌈

  • -h, --help β€” show help message and exit.
  • -v, --verbose β€” more verbose messages. mypy gets chatty. πŸ’¬
  • -V, --version β€” show program's version number and exit.
  • -O FORMAT, --output FORMAT {json} β€” set a custom output format.

πŸ“ Config file πŸ“

  • --config-file CONFIG_FILE β€” this flag makes mypy read configuration settings from the given file.

By default settings are read from mypy.ini, .mypy.ini, pyproject.toml, or setup.cfg in the current directory. Settings override mypy's built-in defaults and command line flags can override settings.

Specifying --config-file= (with no filename) will ignore all config files.

See config-file for the syntax of configuration files.

  • --warn-unused-configs β€” this flag makes mypy warn about unused [mypy-<pattern>] config file sections. (This requires turning off incremental mode using --no-incremental.) ngl that parenthetical is doing a lot of work.

🧚 Import discovery 🧚

The following flags customize how exactly mypy discovers and follows imports.

  • --explicit-package-bases β€” this flag tells mypy that top-level packages will be based in either the current directory, or a member of the MYPYPATH environment variable or mypy_path config option. This option is only useful in the absence of __init__.py. See Mapping file paths to modules for details.
  • --ignore-missing-imports β€” this flag makes mypy ignore all missing imports. It is equivalent to adding # type: ignore comments to all unresolved imports within your codebase.

Note that this flag does not suppress errors about missing names in successfully resolved modules. For example, if one has the following files:

package/__init__.py
package/mod.py

Then mypy will generate the following errors with --ignore-missing-imports:

import package.unknown      # No error, ignored
x = package.unknown.func()  # OK. 'func' is assumed to be of type 'Any'

from package import unknown          # No error, ignored
from package.mod import NonExisting  # Error: Module has no attribute 'NonExisting'

For more details, see ignore-missing-imports.

  • --follow-untyped-imports β€” this flag makes mypy analyze imports from installed packages even if missing a py.typed marker or stubs.
Warning: Note that analyzing all unannotated modules might result in issues when analyzing code not designed to be type checked and may significantly increase how long mypy takes to run. 🐌 so like, be aware.
  • --follow-imports {normal,silent,skip,error} β€” this flag adjusts how mypy follows imported modules that were not explicitly passed in via the command line.

The default option is normal: mypy will follow and type check all modules. For more information on what the other options do, see Following imports.

  • --python-executable EXECUTABLE β€” this flag will have mypy collect type information from PEP 561 <https://peps.python.org/pep-0561/> compliant packages installed for the Python executable EXECUTABLE. If not provided, mypy will use PEP 561 compliant packages installed for the Python executable running mypy.

See installed-packages for more on making PEP 561 compliant packages.

  • --no-site-packages β€” this flag will disable searching for PEP 561 <https://peps.python.org/pep-0561/> compliant packages. This will also disable searching for a usable Python executable.

Use this flag if mypy cannot find a Python executable for the version of Python being checked, and you don't need to use PEP 561 typed packages. Otherwise, use --python-executable.

  • --no-silence-site-packages β€” by default, mypy will suppress any error messages generated within PEP 561 <https://peps.python.org/pep-0561/> compliant packages. Adding this flag will disable this behavior. un-silence it, queen πŸ‘‘
  • --fast-module-lookup β€” the default logic used to scan through search paths to resolve imports has a quadratic worse-case behavior in some cases, which is for instance triggered by a large number of folders sharing a top-level namespace as in:
foo/
    company/
        foo/
            a.py
bar/
    company/
        bar/
            b.py
baz/
    company/
        baz/
            c.py
...

If you are in this situation, you can enable an experimental fast path by setting the --fast-module-lookup option.

  • --no-namespace-packages β€” this flag disables import discovery of namespace packages (see PEP 420 <https://peps.python.org/pep-0420/>). In particular, this prevents discovery of packages that don't have an __init__.py (or __init__.pyi) file.

This flag affects how mypy finds modules and packages explicitly passed on the command line. It also affects how mypy determines fully qualified module names for files passed on the command line. See Mapping file paths to modules for details.

πŸ‘‘ Platform configuration πŸ‘‘

By default, mypy will assume that you intend to run your code using the same operating system and Python version you are using to run mypy itself. The following flags let you modify this behavior.

For more information on how to use these flags, see version_and_platform_checks.

  • --python-version X.Y β€” this flag will make mypy type check your code as if it were run under Python version X.Y. Without this option, mypy will default to using whatever version of Python is running mypy.

This flag will attempt to find a Python executable of the corresponding version to search for PEP 561 <https://peps.python.org/pep-0561/> compliant packages. If you'd like to disable this, use the --no-site-packages flag (see Import discovery <#import-discovery> for more details).

  • --platform PLATFORM β€” makes mypy type check your code as if it were running on that operating system πŸ–₯️. Skip this flag and mypy just assumes "whatever OS you're on rn, bestie, that one." The PLATFORM value can be literally any string sys.platform is down with. no cap.
  • --always-true NAME β€” tells mypy to treat every variable named NAME as a compile-time constant that's always true βœ…. You can repeat this flag as many times as your heart desires πŸ’–.
  • --always-false NAME β€” the flip side πŸͺž: every variable named NAME gets treated as a compile-time constant that's always false ❌. Also repeatable, go off πŸ‘‘.

πŸŒ€ Disallow dynamic typing πŸŒ€

okay so, the Any type is how we represent a value that's got a dynamic type β€” aka "idk what this is, don't ask me." The --disallow-any glow-up family says nope to various uses of Any inside a module, which lets you strategically ban dynamic typing in a controlled, measured way πŸ’…. it's giving discipline.

The following options are available:

  • --disallow-any-unimported β€” bans usage of types that come from unfollowed imports (those types just quietly become aliases for Any πŸ™ˆ). Unfollowed imports happen either when the imported module literally doesn't exist, or when --follow-imports=skip is set.
  • --disallow-any-expr β€” bans all expressions in the module that have type Any. If an expression of type Any shows up anywhere in your module, mypy will output an error β€” unless the expression is immediately used as an argument to cast() or assigned to a variable with an explicit type annotation 🎯.

Also, declaring a variable of type Any or casting to type Any? not allowed, sorry bestie. Note that calling functions that take parameters of type Any is still allowed tho. we're not monsters.

  • --disallow-any-decorated β€” bans functions that have Any in their signature after decorator transformation πŸŽ€.
  • --disallow-any-explicit β€” bans explicit Any in type positions, like type annotations and generic type parameters 🚫.
  • --disallow-any-generics β€” bans generic types that don't specify explicit type parameters. For example, you can't use a bare x: list. Instead you must always write something like x: list[int]. periodt πŸ’€.
  • --disallow-subclassing-any β€” reports an error whenever a class subclasses a value of type Any. This can happen when the base class is imported from a module that doesn't exist (when you're using --ignore-missing-imports) or is ignored because of --follow-imports=skip or a # type: ignore comment on the import statement.

Since the module is silenced, the imported class just gets handed a type of Any 🀷. By default mypy assumes the subclass correctly inherited the base class, even though that might genuinely not be the case. This flag makes mypy raise an error instead. lowkey important πŸ”₯.


πŸ“ž Untyped definitions and calls πŸ“ž

The following flags configure how mypy handles untyped function definitions or calls.

  • --disallow-untyped-calls β€” reports an error whenever a function that has type annotations calls a function defined without annotations 😀.
  • --untyped-calls-exclude β€” lets you selectively disable --disallow-untyped-calls for functions and methods defined in specific packages, modules, or classes πŸ™Œ. Note that each exclude entry acts as a prefix. For example (assuming there are no type annotations for third_party_lib available):
# mypy --disallow-untyped-calls
#      --untyped-calls-exclude=third_party_lib.module_a
#      --untyped-calls-exclude=foo.A
from third_party_lib.module_a import some_func
from third_party_lib.module_b import other_func
import foo

some_func()  # OK, function comes from module `third_party_lib.module_a`
other_func()  # E: Call to untyped function "other_func" in typed context

foo.A().meth()  # OK, method was defined in class `foo.A`
foo.B().meth()  # E: Call to untyped function "meth" in typed context

# file foo.py
class A:
    def meth(self): pass
class B:
    def meth(self): pass
  • --disallow-untyped-defs β€” reports an error whenever it hits a function definition with no type annotations, or with incomplete type annotations (so it's a superset of --disallow-incomplete-defs, bestie πŸ‘‘). For example, it would report an error for def f(a, b) and def f(a: int, b).
  • --disallow-incomplete-defs β€” reports an error whenever it hits a partly annotated function definition, while still letting fully unannotated definitions slide πŸ›. For example, it would report an error for def f(a: int, b) but not def f(a, b).
  • --check-untyped-defs β€” this one's less severe than the previous two, it's giving gentle πŸ₯Ί. It type checks the body of every function, regardless of whether it has type annotations (by default, the bodies of functions without annotations don't get type checked at all). It will assume all arguments have type Any and always infer Any as the return type.
  • --disallow-untyped-decorators β€” reports an error whenever a function with type annotations gets decorated by a decorator without annotations πŸŽ€πŸš«.

πŸ«₯ None and Optional handling πŸ«₯

The following flags adjust how mypy handles values of type None.

  • --implicit-optional β€” makes mypy treat parameters with a None default value as having an implicit optional type ( T | None ) πŸ‘».

For example, if this flag is set, mypy would assume the x parameter is actually of type int | None in the code snippet below, since the default parameter is None:

def foo(x: int = None) -> None:
    print(x)
Note: This was disabled by default starting in mypy 0.980. πŸ“Œ
  • --no-strict-optional β€” effectively disables checking of optional types and None values. With this option, mypy doesn't generally check the use of None values β€” None gets treated as compatible with every type πŸ˜΅β€πŸ’«.
Warning: --no-strict-optional is evil 😈. Avoid using it, and definitely do not use it without understanding what it does. fr fr.

⚠️ Configuring warnings ⚠️

The following flags enable warnings for code that is technically sound but is potentially problematic or redundant in some way. it's giving "not wrong, but why tho."

  • --warn-redundant-casts β€” makes mypy report an error whenever your code uses an unnecessary cast that can safely be removed πŸ—‘οΈ.
  • --warn-unused-ignores β€” makes mypy report an error whenever your code uses a # type: ignore comment on a line that isn't actually generating an error message 😳.

This flag, along with the --warn-redundant-casts flag, is especially useful when you're upgrading mypy πŸ†™. Back in the day you may have needed to add casts or # type: ignore annotations to work around bugs in mypy or missing stubs for 3rd party libraries. These two flags let you discover the cases where those workarounds are no longer necessary. so satisfying ✨.

  • --no-warn-no-return β€” by default, mypy generates errors when a function is missing return statements in some execution paths 🚧. The only exceptions are when:
    • The function has a None or Any return type
    • The function has an empty body and is marked as an abstract method, is in a protocol class, or is in a stub file
    • The execution path can never return; for example, if an exception is always raised

Passing in --no-warn-no-return will disable these error messages in all cases. gone, reduced to atoms πŸ’€.

  • --warn-return-any β€” causes mypy to generate a warning when returning a value with type Any from a function declared with a non-Any return type πŸ™ƒ.
  • --warn-unreachable β€” makes mypy report an error whenever it encounters code that's determined to be unreachable or redundant after it does its type analysis πŸ”. This can be a genuinely helpful way of detecting certain kinds of bugs in your code. ate.

For example, enabling this flag will make mypy report that the x > 7 check is redundant and that the else block below is unreachable:

def process(x: int) -> None:
    # Error: Right operand of "or" is never evaluated
    if isinstance(x, int) or x > 7:
        # Error: Unsupported operand types for + ("int" and "str")
        print(x + "bad")
    else:
        # Error: 'Statement is unreachable' error
        print(x + "bad")

To help prevent mypy from generating spurious warnings, the "Statement is unreachable" warning gets silenced in exactly three cases πŸ•ŠοΈ:

  1. When the unreachable statement is a raise statement, is an assert False statement, or calls a function that has the NoReturn return type hint. In other words, when the unreachable statement throws an error or terminates the program in some way.
  2. When the unreachable statement is return NotImplemented. This is allowed by mypy because of its use in operator overloading.
  3. When the unreachable statement was intentionally marked as unreachable using version_and_platform_checks.
Note: Mypy currently cannot detect and report unreachable or redundant code inside any functions using value-constrained type variables. This limitation will be removed in future releases of mypy. πŸ™
  • --report-deprecated-as-note β€” if error code deprecated is enabled, mypy emits errors when your code imports or uses deprecated features 🫠. This flag converts those errors into notes, which means mypy eventually finishes with a zero exit code πŸŽ‰. Features are considered deprecated when decorated with warnings.deprecated.
  • --deprecated-calls-exclude β€” lets you selectively disable deprecated warnings for functions and methods defined in specific packages, modules, or classes πŸ™Œ. Note that each exclude entry acts as a prefix. For example (assuming foo.A.func is deprecated):
# mypy --enable-error-code deprecated
#      --deprecated-calls-exclude=foo.A
import foo

foo.A().func()  # OK, the deprecated warning is ignored
# file foo.py
from typing_extensions import deprecated
class A:
    @deprecated("Use A.func2 instead")
    def func(self): pass

✨ Miscellaneous strictness flags ✨

bestie this is the ✨ catch-all ✨ corner of the manpage β€” literally every flag that doesn't fit neatly under any of the sections above lives down here. 🫢

  • --allow-untyped-globals β€” makes mypy suppress the errors it would otherwise throw when it can't fully infer the types of global and class variables. like, it just can't figure it out, so it stops yelling. πŸ’…
  • --allow-redefinition β€” okay so by default mypy will not let you redefine a variable with an unrelated type. this flag enables the redefinition of unannotated variables with an arbitrary type. it also requires --local-partial-types, which is enabled by default starting from mypy 2.0. here's the vibe:
def maybe_convert(n: int, b: bool) -> int | str:
    if b:
        x = str(n)  # Assign "str"
    else:
        x = n       # Assign "int"
    # Type of "x" is "int | str" here.
    return x

without this flag, mypy only supports inferring optional types (X | None) from multiple assignments. with this option on, mypy can infer arbitrary union types. 🌈

it also lets an unannotated variable have different types in different code locations:

if check():
    for x in range(n):
        # Type of "x" is "int" here.
        ...
else:
    for x in ['a', 'b']:
        # Type of "x" is "str" here.
        ...

function arguments are special, bestie β€” changing their type within the function body is allowed even if they're annotated, but that annotation is used to infer types of the r.h.s. of all subsequent assignments. this middle-ground semantics gives a good balance for the majority of common use cases. for example:

def process(values: list[float]) -> None:
    if not values:
        values = [0, 0, 0]
    reveal_type(values)  # Revealed type is list[float]
πŸ“’ note: we are planning to turn this flag on by default in a future mypy release. so like… get used to it ig. πŸ‘‘
  • --allow-redefinition-new β€” deprecated alias for --allow-redefinition. it's giving legacy, it's giving "we renamed this". πŸ™ˆ
  • --allow-redefinition-old β€” this is an older, more limited variant of --allow-redefinition. it enables redefinition of a variable with an arbitrary type in some contexts: only redefinitions within the same block and nesting depth as the original definition are allowed. so it's the same energy but on a leash. πŸŽ€

we have no plans to remove this flag, but --allow-redefinition is recommended for new use cases.

here's an example where this can be useful:

def process(items: list[str]) -> None:
    # 'items' has type list[str]
    items = [item.split() for item in items]
    # 'items' now has type list[list[str]]

the variable must be used before it can be redefined, okay?? crucially:

def process(items: list[str]) -> None:
   items = "mypy"  # invalid redefinition to str because the variable hasn't been used yet
   print(items)
   items = "100"  # valid, items now has type str
   items = int(items)  # valid, items now has type int
  • --no-local-partial-types β€” disables local partial types to enable the legacy type inference mode for containers. local partial types prevent inferring a container type for a variable when the initial assignment happens at module top level or in a class body, and the container item type is only set in a function. like this:
a = []  # Need type annotation unless using --no-local-partial-types

def func() -> None:
    a.append(1)

reveal_type(a)  # "list[int]" if using --no-local-partial-types

local partial types are enabled by default starting from mypy 2.0. and bestie, the mypy daemon requires local partial types. πŸ’€

  • --no-implicit-reexport β€” by default, values imported into a module are treated as exported and mypy lets other modules import them. this flag flips that: no re-exporting unless the item is imported using from-as or is included in __all__. ngl this is always treated as enabled for stub files. example time:
# This won't re-export the value
from foo import bar

# Neither will this
from foo import bar as bang

# This will re-export it as bar and allow other modules to import it
from foo import bar as bar

# This will also re-export bar
from foo import bar
__all__ = ['bar']

see how as bar re-exports but as bang doesn't? yeah. sneaky. ✨

  • --strict-equality β€” by default mypy allows always-false comparisons like 42 == 'no'. use this flag to prohibit such comparisons of non-overlapping types, plus similar identity and container checks:
items: list[int]
if 'some string' in items:  # Error: non-overlapping container check!
    ...

text: str
if text != b'other bytes':  # Error: non-overlapping equality check!
    ...

assert text is not None  # OK, check against None is allowed
  • --strict-equality-for-none β€” extends --strict-equality to checks against None:
text: str
assert text is not None  # Error: non-overlapping identity check!
⚠️ note that --strict-equality-for-none only works in combination with --strict-equality. it does not solo. it needs its bestie. 🫢
  • --no-strict-bytes β€” treats bytearray and memoryview as subtypes of bytes. this is not true at runtime and can lead to unexpected behavior 😭 β€” it was the default behavior prior to mypy 2.0 though, so if you're time-traveling from old mypy, hii.
def f(buf: bytes) -> None:
    assert isinstance(buf, bytes)  # Raises runtime AssertionError with bytearray/memoryview
    with open("binary_file", "wb") as fp:
        fp.write(buf)

# Using --no-strict-bytes disables the following errors
f(bytearray(b""))  # Argument 1 to "f" has incompatible type "bytearray"; expected "bytes"
f(memoryview(b""))  # Argument 1 to "f" has incompatible type "memoryview"; expected "bytes"

# If `f` accepts any object that implements the buffer protocol,
# consider using Buffer instead:
from collections.abc import Buffer  # "from typing_extensions" in Python 3.11 and earlier

def f(buf: Buffer) -> None:
    with open("binary_file", "wb") as fp:
        fp.write(buf)

f(b"")  # Ok
f(bytearray(b""))  # Ok
f(memoryview(b""))  # Ok

the Buffer glow-up is iconic, honestly. πŸ¦‹

  • --extra-checks β€” enables additional checks that are technically correct but may be impractical. specifically: it prohibits partial overlap in TypedDict updates, and makes arguments prepended via Concatenate positional-only. like:
from typing import TypedDict

class Foo(TypedDict):
    a: int

class Bar(TypedDict):
    a: int
    b: int

def test(foo: Foo, bar: Bar) -> None:
    # This is technically unsafe since foo can have a subtype of Foo at
    # runtime, where type of key "b" is incompatible with int, see below
    bar.update(foo)

class Bad(Foo):
    b: str
bad: Bad = {"a": 0, "b": "no"}
test(bad, bar)

in future, more checks may be added to this flag if:

  • The corresponding use cases are rare, thus not justifying a dedicated strictness flag.
  • The new check cannot be supported as an opt-in error code.
  • --strict β€” this flag mode enables a defined subset of optional error-checking flags. this subset primarily includes checks for inadvertent type unsoundness (i.e strict will catch type errors as long as intentional methods like type ignore or casting were not used.)
πŸ“’ note: the --warn-unreachable flag is not automatically enabled by the strict flag. don't @ me, that's just how it is.

the strict flag does not take precedence over other strict-related flags. directly specifying a flag of alternate behavior will override the behavior of strict, regardless of the order in which they are passed. you can see the list of flags enabled by strict mode in the full mypy --help output. ✨

πŸŒ€ note: the exact list of flags enabled by running --strict may change over time.
  • --disable-error-code β€” lets you disable one or multiple error codes globally. see error-codes for more info. watch:
# no flag
x = 'a string'
x.trim()  # error: "str" has no attribute "trim"  [attr-defined]

# When using --disable-error-code attr-defined
x = 'a string'
x.trim()

poof. error gone. πŸ’…

  • --enable-error-code β€” lets you enable one or multiple error codes globally. see error-codes for more info.
πŸ‘‘ note: this flag will override disabled error codes from the --disable-error-code flag. it's giving main character energy over the other flag.
# When using --disable-error-code attr-defined
x = 'a string'
x.trim()

# --disable-error-code attr-defined --enable-error-code attr-defined
x = 'a string'
x.trim()  # error: "str" has no attribute "trim"  [attr-defined]

🌸 Configuring error messages 🌸

the following flags let you adjust how much detail mypy displays in error messages. basically: how much yapping does mypy do when something's wrong. πŸŽ€

  • --show-error-context β€” this flag will precede all errors with note messages explaining the context of the error. so instead of just the roast, you get the roast and the explanation of why you're being roasted. consider this program:
class Test:
    def foo(self, x: int) -> int:
        return x + "bar"

mypy normally displays an error message that looks like this:

main.py:3: error: Unsupported operand types for + ("int" and "str")

if we enable this flag, the error message now looks like this:

main.py: note: In member "foo" of class "Test":
main.py:3: error: Unsupported operand types for + ("int" and "str")

obsessed. so much more context. 🫢

  • --show-column-numbers β€” this flag will add column offsets to error messages. for example, the following indicates an error in line 12, column 9. and bestie, note that column offsets are 0-based πŸ‘€:
main.py:12:9: error: Unsupported operand types for / ("int" and "str")
  • --show-error-code-links β€” okay so this one ALSO drops a link to the docs for whatever error code mypy just yelled about, and it anchors straight to that code so the code gets highlighted all pretty on the docs page. Turn it on and your messages start looking like:
main.py:3: error: Unsupported operand types for - ("int" and "str")  [operator]
main.py:3: note: See 'https://mypy.rtfd.io/en/stable/_refs.html#code-operator' for more info
  • --show-error-end β€” bestie, this gives you the whole span, not just where the error starts. You get the end position of the relevant expression too, so your editor/tooling can highlight the entire thing instead of one lonely character. Format is file:line:column:end_line:end_column. Heads up: this one implies --show-column-numbers. πŸ’…
  • --hide-error-codes β€” hides the [<code>] bit from error messages. By default that code is sitting right there after every message:
prog.py:1: error: "str" has no attribute "trim"  [attr-defined]

See error-codes if you wanna know more.

  • --pretty β€” makes the output actually cute: soft word wrap, source code snippets, and little markers showing you where the error is. Visually nicer. We love that.
  • --no-color-output β€” kills the color in error messages. (Color is on by default, jsyk. 🎨)
  • --no-error-summary β€” nope, no summary line for you. By default mypy gives you a lil wrap-up with the total number of errors, how many files had errors, and how many files got checked. This flag says "don't."
  • --show-absolute-path β€” shows you absolute paths to your files, no relative-path games.
  • --soft-error-limit N β€” this tweaks the limit where mypy (sometimes!) stops reporting most of the extra errors. And it only kicks in if it seems like the rest of those errors probably won't be useful, or they're gonna be way too noisy. If N is negative, there's no limit at all. Default limit is -1.

🌈 Incremental mode 🌈

So by default, mypy stores your type info in a cache. Next time it type checks your code it peeks at that info so it doesn't redo work it already did, which speeds things up β€” especially when most of your program hasn't changed since the last run. Huge. 🫢

And if incremental mode isn't fast enough for you, try running mypy in daemon mode. That's the next level up.

  • --no-incremental β€” incremental mode? Off. mypy stops referencing the cache when you re-run it.
Ngl though, mypy will still write out to the cache even when incremental mode is off β€” peep the --cache-dir flag below for the details. πŸ™ˆ
  • --cache-dir DIR β€” by default all the cache data lives in a folder called .mypy_cache in the current directory. This flag lets you change that folder. It's also handy for controlling cache use when you're doing remote caching.

This setting overrides the MYPY_CACHE_DIR environment variable if that's set.

Also bestie, mypy always writes to the cache even with incremental mode disabled, so it can "warm up" the cache. Wanna stop the writing entirely? Use --cache-dir=/dev/null (UNIX) or --cache-dir=nul (Windows).

  • --no-sqlite-cache β€” avoids using an SQLite <https://www.sqlite.org/> database to store the cache, and writes the cache data out to individual files instead.
  • --cache-fine-grained β€” includes fine-grained dependency information in the cache, for the mypy daemon.
  • --skip-version-check β€” by default mypy ignores cache data that came from a different version of mypy. This flag disables that behavior.
  • --skip-cache-mtime-checks β€” skips the cache's internal consistency checks based on mtime.

πŸ”₯ Parallel type-checking πŸ”₯

By default, mypy checks all your modules in the same Python process. For a big codebase that can be slow, so mypy offers an experimental parallel type-checking mode that uses multiple worker processes. In parallel mode, modules that don't depend on each other get type-checked in parallel. Incremental cache <#incremental> manages most of the shared state. Parallel type-checking also requires --local-partial-types, which is enabled by default starting from mypy 2.0.

  • -n NUMBER, --num-workers NUMBER β€” uses NUMBER parallel worker processes (on top of the coordinator process) to do the type-checking. Passing --num-workers 0 (the default) disables parallel checking. And no, automatic detection of the optimal number of workers isn't supported yet. 😭

This setting overrides the MYPY_NUM_WORKERS environment variable if it's set.

Notes:

  • An import cycle is always processed as a whole by a worker process. So avoiding large import cycles in your code will significantly improve type-checking speed. ✨
  • Specifying more workers than you have physical CPU cores isn't beneficial, since mypy is usually CPU bound. Best way to tune the number of workers on a given machine: start from 3-4 workers and increase the number while you keep seeing a performance improvement.
  • Parallel mode requires and automatically enables --native-parser.

🧚 Advanced options 🧚

These flags are mostly for the people who are out here developing or debugging mypy internals. Respect. πŸ‘‘

  • --pdb β€” invokes the Python debugger when mypy hits a fatal error.
  • --show-traceback, --tb β€” if set, shows a full traceback when mypy hits a fatal error.
  • --raise-exceptions β€” raises the exception on a fatal error.
  • --custom-typing-module MODULE β€” lets you use a custom module as a substitute for the typing module.
  • --custom-typeshed-dir DIR β€” tells mypy where to look for the standard library typeshed stubs, instead of the typeshed that ships with mypy. Mainly this makes it easier to test typeshed changes before sending them upstream, but it also lets you use a forked version of typeshed.
Important: this doesn't affect third-party library stubs. Wanna test third-party stubs? For example, try MYPYPATH=stubs/six mypy ....
  • --warn-incomplete-stub β€” this one modifies both --disallow-untyped-defs and --disallow-incomplete-defs so they also report errors if stubs in typeshed are missing type annotations or have incomplete annotations. If both of those flags are missing, --warn-incomplete-stub does nothing.

It's mainly for people who wanna contribute to typeshed and want a convenient way to find gaps and omissions.

If you want mypy to report an error when your codebase uses an untyped function β€” whether that function is defined in typeshed or not β€” use the --disallow-untyped-calls flag. See Untyped definitions and calls <#untyped-definitions-and-calls> for more details.

  • --shadow-file SOURCE_FILE SHADOW_FILE β€” when mypy is asked to type check SOURCE_FILE, this flag makes it read from and type check the contents of SHADOW_FILE instead. But the diagnostics still refer to SOURCE_FILE. Iconic behavior honestly.

You can pass this multiple times (--shadow-file X1 Y1 --shadow-file X2 Y2) and mypy will do multiple substitutions.

It lets tooling create temporary files with helpful modifications without changing the source file in place. Example: say you have a pipeline that adds reveal_type for certain variables. You run that pipeline on original.py to produce temp.py. Then running mypy --shadow-file original.py temp.py original.py makes mypy type check the contents of temp.py instead of original.py, but the error messages still reference original.py. Slay.

  • --native-parser β€” enables the fast Rust-based parser that parses directly to mypy AST. It'll become the default parser in one of the next mypy releases. πŸ‘€

πŸŽ€ Report generation πŸŽ€

If these flags are set, mypy will generate a report in the specified format, into the specified directory.

  • --any-exprs-report DIR β€” generates a text file report documenting how many expressions of type Any are present in your codebase.
  • --cobertura-xml-report DIR β€” generates a Cobertura XML type checking coverage report.
To generate this report you gotta either install the lxml <https://pypi.org/project/lxml/> library manually, or specify your mypy installation with the setuptools extra mypy[reports].
  • --html-report / --xslt-html-report DIR β€” generates an HTML type checking coverage report.
To generate this report you gotta either install the lxml <https://pypi.org/project/lxml/> library manually, or specify your mypy installation with the setuptools extra mypy[reports].
  • --linecount-report DIR β€” generates a text file report documenting the functions and lines that are typed and untyped within your codebase.
  • --linecoverage-report DIR β€” generates a JSON file that maps each source file's absolute filename to a list of line numbers belonging to typed functions in that file.
  • --lineprecision-report DIR β€” generates a flat text file report with per-module statistics of how many lines are typechecked etc.
  • --txt-report / --xslt-txt-report DIR β€” generates a text file type checking coverage report.
To generate this report you gotta either install the lxml <https://pypi.org/project/lxml/> library manually, or specify your mypy installation with the setuptools extra mypy[reports].
  • --xml-report DIR β€” generates an XML type checking coverage report.
To generate this report you gotta either install the lxml <https://pypi.org/project/lxml/> library manually, or specify your mypy installation with the setuptools extra mypy[reports].

✨ Enabling incomplete/experimental features ✨

  • --enable-incomplete-feature {PreciseTupleTypes,InlineTypedDict} β€” okay so some features just take several releases to land properly, bestie. Maybe they're super complex, maybe they'd break backwards compat, maybe the semantics are still ambiguous and the team wants the community's feedback first. Whatever the reason, this flag lets you preview them early. But heads up: it is not guaranteed that all of these will eventually be enabled by default. In rare cases they might decide to just not go ahead with a feature at all. πŸ’€

List of currently incomplete/experimental features:

  • PreciseTupleTypes: this one infers more precise tuple types in all sorts of scenarios. Back before variadic types got added to the Python type system by PEP 646 (https://peps.python.org/pep-0646/), there was literally no way to express a type like "a tuple with at least two integers". The best you could do was tuple[int, ...], so mypy was super lenient with variable-length tuples. Now you can write it as tuple[int, int, *tuple[int, ...]]. For these more precise types β€” when the user explicitly defines them β€” mypy will, for example, warn you about unsafe index access, and generally handle them in a type-safe way. But to avoid breaking existing code, mypy does not infer these precise types even when it technically could. Here are the notable examples where PreciseTupleTypes infers more precise types:
numbers: tuple[int, ...]

more_numbers = (1, *numbers, 1)
reveal_type(more_numbers)
# Without PreciseTupleTypes: tuple[int, ...]
# With PreciseTupleTypes: tuple[int, *tuple[int, ...], int]

other_numbers = (1, 1) + numbers
reveal_type(other_numbers)
# Without PreciseTupleTypes: tuple[int, ...]
# With PreciseTupleTypes: tuple[int, int, *tuple[int, ...]]

if len(numbers) > 2:
    reveal_type(numbers)
    # Without PreciseTupleTypes: tuple[int, ...]
    # With PreciseTupleTypes: tuple[int, int, int, *tuple[int, ...]]
else:
    reveal_type(numbers)
    # Without PreciseTupleTypes: tuple[int, ...]
    # With PreciseTupleTypes: tuple[()] | tuple[int] | tuple[int, int]
  • InlineTypedDict: this one unlocks the non-standard syntax for inline TypedDicts, like literally this:
def test_values() -> {"width": int, "description": str}:
    return {"width": 42, "description": "test"}

🌈 Miscellaneous 🌈

  • --install-types β€” this flag makes mypy install known missing stub packages for third-party libraries using pip. It'll show you the exact pip command it's about to run, and it expects a confirmation before it installs anything. For security reasons these stubs are limited to only a tiny curated subset of manually selected packages that the typeshed team has verified. And slay, these packages contain only stub files and no executable code. πŸŽ€

If you run this option without giving mypy any files or modules to type check, it'll install the stub packages that were suggested during your previous mypy run. But if there are files or modules to type check, mypy type checks those first, and only proposes installing the missing stubs at the end of the run β€” and only if any missing modules were actually detected.

Note: this is new in mypy 0.900. Previous mypy versions just included a selection of third-party package stubs instead of installing them separately. Not me being nostalgic or anything. πŸ™ˆ
  • --non-interactive β€” when you use this together with --install-types, mypy installs all the suggested stub packages using pip with no confirmation prompt, and then keeps on type checking using the installed stubs, if some files or modules were provided to type check. No questions asked, we just go. πŸ’…

This is implemented as up to two mypy runs internally. The first run is used to find missing stub packages, and its output is shown only if no missing stub packages were found. If missing stub packages were found, they get installed and then another run is performed.

  • --junit-xml JUNIT_XML_OUTPUT_FILE β€” makes mypy generate a JUnit XML test result document with your type checking results. Lowkey makes it way easier to plug mypy into continuous integration (CI) tools. πŸ”₯
  • --junit-format {global,per_file} β€” if --junit-xml is set, this specifies the format. global (the default): a single test with all the errors. per_file: one test entry per file with failures.
  • --find-occurrences CLASS.MEMBER β€” this flag makes mypy print out all usages of a class member based on static type information. Fair warning bestie: this feature is experimental. 🧚
  • --scripts-are-modules β€” this flag gives command line arguments that look like scripts (i.e. files whose name does not end in .py) a module name derived from the script name, rather than the fixed name __main__.

This lets you check more than one script in a single mypy invocation. (The default __main__ is technically more correct, but if you've got a bunch of scripts importing a large package, the behavior this flag enables is often way more convenient.)


🌸 ENVIRONMENT 🌸

  • MYPYPATH β€” additional module search path entries. The format is the same as the shell's $PATH: one or more directory pathnames separated by colons. Classic. 🫢

πŸ’œ SEE ALSO πŸ’œ

Full documentation is available online at http://mypy.readthedocs.io/en/latest/getting_started.html, or locally at /usr/share/doc/mypy/html (requires the mypy-doc package). πŸ“š


πŸ‘‘ Author πŸ‘‘

Jukka Lehtosalo and contributors. Iconic behavior honestly, thank you for your service. 🫑


And that's a wrap on the whole page, bestie β€” this is MYPY(1), dated Aug 16, 2026, and it ate. Go forth and type check everything. βœ¨πŸ’–πŸ¦‹

πŸ“¦ where this page comes from