Skip to main content

mypy Setup and Configuration: Step-by-Step

mypy is a static type checker for Python that reads your type hints and validates your code without running it. Getting mypy up and running takes just a few minutes: install it via pip, configure a mypy.ini file, and run it against your codebase. This article walks you through setup from zero to your first successful type check.

Installing mypy with pip

mypy is available on PyPI and installs like any standard Python package. First, verify you have Python 3.6+ installed:

python --version
# Output: Python 3.x.x

Then install mypy using pip:

pip install mypy

To verify the installation, check the version:

mypy --version
# Output: mypy X.Y.Z (compiled: yes)

If you're working in a virtual environment (recommended), activate it first, then run the pip install. This ensures mypy is isolated to your project:

# Create a virtual environment
python -m venv venv

# Activate it (on Windows)
venv\Scripts\activate

# On macOS/Linux
source venv/bin/activate

# Then install mypy
pip install mypy

Running mypy on a Python File

Once installed, mypy can check any .py file. Create a simple test file to verify setup:

# hello.py
def greet(name: str) -> str:
return f"Hello, {name}!"

message: str = greet("Alice")
print(message)

# This line will trigger a mypy error:
bad_message: str = greet(42) # Error: int passed where str expected

Run mypy against this file:

mypy hello.py

If mypy is properly installed, it will output:

hello.py:7: error: Argument 1 to "greet" has incompatible type "int"; expected "str"

This error message is the core of mypy's value: it caught a type mismatch without running the file. If you ran the file itself, Python would allow it (until greet tried to use the int in a string operation). mypy caught it earlier.

Creating a mypy.ini Configuration File

For larger projects, create a mypy.ini file in your project root to control mypy's behavior. This file specifies which directories to check, which to ignore, and what strictness level to enforce.

Here's a minimal mypy.ini for a typical project:

[mypy]
# Python version to target
python_version = 3.10

# Directory to check (usually your main source directory)
mypy_path = src

# Show error codes
show_error_codes = True

# Enforce strict mode (catches more issues)
strict = True

# Directories to ignore
ignore_patterns = tests/, build/, dist/

With this configuration, run mypy from your project root without specifying a file:

mypy

mypy will now check all .py files in the src/ directory, respect the python_version setting, and use strict mode.

Key Configuration Options Explained

python_version — Set to your target Python version. mypy uses this to determine which language features are available. For Python 3.10, use 3.10.

strict — Set strict = True to enable the strictest checking. This enforces:

  • All functions must have explicit return type annotations
  • All function parameters must be annotated (except self and cls)
  • Implicit Any types are forbidden
  • No untyped def statements allowed

For gradual type checking (adding hints incrementally), leave strict = False and enable individual strict options:

[mypy]
python_version = 3.10
check_untyped_defs = True
disallow_untyped_defs = True
warn_unused_ignores = True

warn_return_any — If a function can return Any, mypy warns about it. Useful for catching implicit Any leakage.

disallow_incomplete_defs — Forbid functions with partial type annotations (some parameters typed, some not).

exclude — A regex pattern of directories to skip. Example: exclude = ^(tests|build)/. This is an alternative to ignore_patterns.

Ignoring Specific Lines and Files

Sometimes you need to tell mypy to skip a particular line (e.g., for third-party libraries without type stubs). Use the # type: ignore comment:

import some_untyped_library  # type: ignore

result = some_untyped_library.do_something() # Type is Any, mypy won't complain

To ignore an entire file, add a # mypy: ignore-errors comment at the top:

# mypy: ignore-errors
# This file has no type hints and mypy won't check it

def untyped_function(x):
return x * 2

A Complete Project Structure Example

Here's what a real project layout with mypy might look like:

myproject/
├── mypy.ini
├── setup.py
├── src/
│ ├── __init__.py
│ ├── main.py
│ └── utils.py
├── tests/
│ ├── test_main.py
│ └── test_utils.py
└── venv/

Sample mypy.ini:

[mypy]
python_version = 3.10
mypy_path = src
strict = True
show_error_codes = True

# Exclude test and build directories
[mypy-tests.*]
ignore_errors = True

[mypy-setup]
ignore_errors = True

Sample src/main.py:

def add(a: int, b: int) -> int:
"""Add two integers and return the result."""
return a + b

def main() -> None:
"""Entry point."""
result: int = add(5, 3)
print(f"5 + 3 = {result}")

if __name__ == "__main__":
main()

Run mypy:

mypy
# Success: no issues found in src/

Integrating mypy into Your Development Workflow

In your IDE: Most editors (VS Code, PyCharm, Sublime) offer mypy extensions. Install the Python extension for your editor and configure it to use mypy as the default type checker.

In pre-commit hooks: Automatically run mypy before each commit:

pip install pre-commit

Create a .pre-commit-config.yaml in your project root:

repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
args: [--strict]

Then run:

pre-commit install

Now mypy runs automatically before each commit, ensuring no type errors slip in.

In CI/CD: Add mypy to your GitHub Actions, GitLab CI, or similar:

# .github/workflows/type-check.yml
name: Type Check
on: [push, pull_request]
jobs:
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: pip install mypy
- run: mypy src/

Common Installation Issues and Fixes

Issue: mypy: command not found Solution: Verify mypy is installed in the active Python environment. Run pip install mypy again or check that your virtual environment is activated.

Issue: error: PYTHONPATH not set; cannot find source files Solution: Add mypy_path to your mypy.ini, pointing to your source directory (usually src/).

Issue: mypy ignores your configuration file Solution: Ensure mypy.ini is in your project root (the directory where you run mypy). mypy searches for the config file starting from the current directory and moving upward.

Key Takeaways

  • Install mypy with pip install mypy and verify with mypy --version
  • Run mypy on a file or directory: mypy hello.py or mypy src/
  • Create a mypy.ini configuration file in your project root to set Python version, source paths, and strictness levels
  • Use strict = True for maximum type safety; disable it and enable individual options for gradual typing
  • Ignore specific lines with # type: ignore comments or entire files with # mypy: ignore-errors
  • Integrate mypy into pre-commit hooks, CI/CD pipelines, and your IDE for continuous type checking

Frequently Asked Questions

Do I have to use mypy.ini, or can I pass arguments on the command line?

You can do both. Command-line arguments override mypy.ini settings. For most projects, a config file is cleaner: mypy.ini lives in your repo and applies to all developers, whereas command-line args must be remembered and re-entered.

What's the difference between check_untyped_defs and disallow_untyped_defs?

check_untyped_defs analyzes function bodies but allows untyped parameters. disallow_untyped_defs forbids any function without full type annotations. The latter is stricter and enforces explicit typing.

Can I use mypy with Python 2?

mypy supports Python 2 syntax checking with some limitations. However, Python 2 reached end-of-life in 2020. Use Python 3.6+ for best mypy support.

Should I enable strict mode right away on an existing codebase?

No. Gradual typing is better. Start with strict = False, enable show_error_codes = True, and incrementally add hints to the most critical modules. Once the codebase is mostly typed, enable strict = True.

How do I exclude a third-party library that has no type stubs?

Add it to a [mypy-<package>] section in mypy.ini: [mypy-some_library] ignore_missing_imports = True.

Further Reading