Skip to content

🔍 Python Type Checking with Mypy

A comprehensive guide to type checking in Python using Mypy

🚀 Quick Start

Installation

Bash
python3 -m pip install -U mypy

Basic Usage

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


# Run type checking
# mypy example.py

⚙️ Configuration

Core Settings

This Project's mypy.ini

INI
[mypy]
# ==============================================================================
# Global mypy Configuration
# ==============================================================================
# Python version: Specifies the target Python version for type checking.
# This ensures compatibility with the specified Python version's type system.
python_version = 3.11

# Strict mode: Enables all strict type checking options for maximum type safety.
# This is recommended for production code to catch potential type-related bugs early.
strict = True

# Exclusion patterns: Directories to exclude from type checking.
# - tests: Test files often have different type checking requirements
# - migrations: Database migration scripts typically don't benefit from type checking
# - docs: Documentation code examples don't need strict type checking
# - .venv: Virtual environment files should never be type checked
exclude = (?x)(
		^tests/
		| ^migrations/
		| ^docs/
		| .venv/
	)

# Source directory: Sets the base directory for import resolution.
# This helps mypy correctly resolve imports within your project structure.
mypy_path = src

# Namespace packages: Enables support for PEP 420 namespace packages.
# Useful for projects with a distributed package structure across multiple directories.
namespace_packages = True

# Explicit package bases: Ensures clear package structure with defined base directories.
# Helps prevent import confusion in complex project structures.
explicit_package_bases = True

# ==============================================================================
# Plugin Configuration
# ==============================================================================
# Mypy plugins: Extends mypy's type checking capabilities for specific libraries.
# - pydantic.mypy: Adds support for Pydantic model type checking
# - sqlalchemy.ext.mypy.plugin: Enhances SQLAlchemy ORM type checking
plugins =
    pydantic.mypy,
    sqlalchemy.ext.mypy.plugin

# ==============================================================================
# Strict Type Checking Settings
# ==============================================================================
# Function definitions: Requires explicit type annotations for all function definitions.
# This ensures that function signatures are clear and well-documented.
disallow_untyped_defs = True

# Incomplete definitions: Prohibits functions with partial type annotations.
# Ensures consistency by requiring complete type information for all parameters and returns.
disallow_incomplete_defs = True

# Untyped function checking: Enables type checking for functions without annotations.
# Helps catch type errors even in functions that haven't been properly annotated yet.
check_untyped_defs = True

# Decorator annotations: Requires type annotations for decorators.
# Important for ensuring type safety when using custom decorators.
disallow_untyped_decorators = True

# Implicit Optional handling: Disables automatic Optional typing for parameters with None defaults.
# Forces explicit declaration of Optional types for better code clarity.
no_implicit_optional = True

# Redundant cast warnings: Flags unnecessary type casts to improve code cleanliness.
# Helps maintain clean code by removing unnecessary type casting operations.
warn_redundant_casts = True

# Unused ignore warnings: Flags unnecessary # type: ignore comments.
# Prevents accumulation of outdated type-ignore directives in the codebase.
warn_unused_ignores = True

# Missing return warnings: Warns about functions that don't return a value but should.
# Catches potential bugs where a function is expected to return a value.
warn_no_return = True

# Unreachable code: Warns about code that can never be executed.
# Identifies dead code that should be removed for maintenance clarity.
warn_unreachable = True

# Optional type checking: Enables strict checking of Optional types.
# Ensures proper handling of potentially None values throughout the codebase.
strict_optional = True

# Equality checking: Ensures proper equality checks between different types.
# Prevents subtle bugs from comparing incompatible types.
strict_equality = True

# Sequence concatenation: Enforces proper type checking when concatenating sequences.
# Prevents type errors when combining lists, tuples, or other sequence types.
strict_concatenate = True

# ==============================================================================
# Additional Production Safety Checks
# ==============================================================================
# Enhanced error detection: Enables specific error codes for production code safety.
# These additional checks help catch subtle bugs that might otherwise go unnoticed.
enable_error_code = [
    "truthy-bool",          # Prevents using non-boolean expressions in boolean contexts (if x: vs if x is True:)
    "redundant-expr",       # Identifies expressions that have no effect and can be removed
    "unused-awaitable",     # Catches forgotten await statements that could lead to unexpected behavior
    "ignore-without-code",  # Requires specific error codes with # type: ignore for better documentation
    "possibly-undefined",   # Identifies variables that might be undefined in some execution paths
    "redundant-self",       # Flags unnecessary self or cls parameters in methods
]

# ==============================================================================
# Error Reporting Configuration
# ==============================================================================
# Output formatting: Configures how mypy presents error messages for better usability.
# These settings make error messages more readable and actionable.
pretty = True               # Uses a more readable format for error messages
show_error_context = True   # Displays the code context where errors occur
show_column_numbers = True  # Shows precise column positions for errors
show_error_codes = True     # Includes error codes for easier reference and suppression
color_output = True         # Uses colors in terminal output for better readability
error_summary = True        # Provides a summary of all errors at the end of the report

# ==============================================================================
# Import Discovery Configuration
# ==============================================================================
# Import handling: Controls how mypy processes and validates imports.
# These settings determine how strictly mypy enforces type checking for imported modules.
ignore_missing_imports = False      # By default, requires type stubs for all imports
follow_imports = normal             # Normal import following behavior for type checking
follow_imports_for_stubs = True     # Prioritizes type stubs over implementation modules

# ==============================================================================
# Third-party Library Configurations
# ==============================================================================
# Library-specific settings: Configures type checking behavior for external dependencies.
# Libraries without complete type annotations need special handling to prevent false positives.
[mypy-orjson.*]
ignore_missing_imports = True   # Skips import verification for this library
ignore_errors = true            # Ignores type errors within this library's code

[mypy-dotenv.*]
ignore_missing_imports = True   # Skips import verification for this library
ignore_errors = true            # Ignores type errors within this library's code

# ==============================================================================
# Database-related Configurations
# ==============================================================================
# ORM and database connectors: Special handling for database interaction libraries.
# These libraries often use dynamic attribute access that's difficult to type check.
[mypy-sqlalchemy.*]
ignore_missing_imports = True   # SQLAlchemy uses complex metaprogramming that's hard to type check

[mypy-alembic.*]
ignore_missing_imports = True   # Alembic migration tool lacks complete type annotations

[mypy-asyncpg.*]
ignore_missing_imports = True   # AsyncPG PostgreSQL client lacks complete type annotations

[mypy-databases.*]
ignore_missing_imports = True   # Databases library lacks complete type annotations

# ==============================================================================
# Web Framework Configurations
# ==============================================================================
# FastAPI and ASGI: Configuration for web framework components.
# Web frameworks often use complex dependency injection and routing that needs special handling.
[mypy-fastapi.*]
ignore_missing_imports = True   # FastAPI uses Pydantic models and complex routing

[mypy-starlette.*]
ignore_missing_imports = True   # Starlette (FastAPI foundation) lacks complete type annotations

[mypy-uvicorn.*]
ignore_missing_imports = True   # ASGI server lacks complete type annotations

[mypy-gunicorn.*]
ignore_missing_imports = True   # WSGI HTTP server lacks complete type annotations

# ==============================================================================
# HTTP Clients and Related Libraries
# ==============================================================================
# HTTP client libraries: Configuration for making network requests.
# These libraries often have complex async patterns or dynamic response handling.
[mypy-httpx.*]
ignore_missing_imports = True   # Modern async HTTP client

[mypy-requests.*]
ignore_missing_imports = True   # Popular synchronous HTTP client

[mypy-aiohttp.*]
ignore_missing_imports = True   # Async HTTP client and server framework

# ==============================================================================
# Serialization/Validation Configurations
# ==============================================================================
# Data validation libraries: Settings for data parsing and validation tools.
# These libraries often use complex validation logic and dynamic field access.
[mypy-pydantic.*]
ignore_missing_imports = True   # Data validation using Python type annotations

[mypy-pydantic_settings.*]
ignore_missing_imports = True   # Settings management with Pydantic

[mypy-email_validator.*]
ignore_missing_imports = True   # Email validation library used by Pydantic

# ==============================================================================
# Testing Frameworks
# ==============================================================================
# Test libraries: Configuration for testing tools and frameworks.
# Testing libraries use fixtures and assertions that can be complex to type check.
[mypy-pytest.*]
ignore_missing_imports = True   # Popular Python testing framework

[mypy-hypothesis.*]
ignore_missing_imports = True   # Property-based testing framework

# ==============================================================================
# Utility Libraries
# ==============================================================================
# Common utilities: Settings for general-purpose utility libraries.
# These provide infrastructure components that may use dynamic patterns.
[mypy-celery.*]
ignore_missing_imports = True   # Distributed task queue

[mypy-redis.*]
ignore_missing_imports = True   # Redis client library

# ==============================================================================
# Type Checking Adjustments for ORM Patterns
# ==============================================================================
# ORM compatibility: Relaxes certain type checks that conflict with ORM patterns.
# These adjustments allow for common ORM usage patterns while maintaining type safety.
allow_any_generics = True       # Allows Any in generic types (needed for some ORM patterns)
allow_redefinition = True       # Permits redefinition of names in certain contexts

# Disables specific error codes that often conflict with ORM and framework patterns
disable_error_code = [
    "override",    # Allows method overrides with signature differences (common in framework extensions)
    "empty-body",  # Permits empty function bodies (useful for abstract methods and protocol definitions)
    "misc",        # Disables miscellaneous checks that may be too strict for framework integration
    "arg-type",    # Relaxes argument type checking for legacy code and framework callbacks
]

# ==============================================================================
# Framework-specific Error Ignores
# ==============================================================================
# Special cases: Ignores errors in specific framework modules with complex metaprogramming.
# These modules use advanced Python features that are difficult for static type checkers.
[mypy-fastapi.dependencies.utils]
ignore_errors = True    # FastAPI dependency resolution uses complex metaprogramming

[mypy-fastapi.routing]
ignore_errors = True    # FastAPI routing system uses decorators and complex type patterns

[mypy-sqlalchemy.orm.decl_api]
ignore_errors = True    # SQLAlchemy declarative API uses metaclasses and dynamic attributes

# ==============================================================================
# Cache Backends
# ==============================================================================
# Caching libraries: Configuration for memory and distributed cache systems.
# These libraries often use dynamic key/value access patterns.
[mypy-cachetools.*]
ignore_missing_imports = True   # In-memory caching utilities

[mypy-aiocache.*]
ignore_missing_imports = True   # Async caching framework

# ==============================================================================
# Background Tasks and Scheduling
# ==============================================================================
# Task processing: Settings for background job processing libraries.
# These systems often use decorators and dynamic task registration.
[mypy-apscheduler.*]
ignore_missing_imports = True   # Advanced Python scheduler

[mypy-dramatiq.*]
ignore_missing_imports = True   # Background task processing system

INI
[mypy]
python_version = 3.11
strict = True
mypy_path = src
namespace_packages = True

🔒 Strict Mode Features

  • disallow_untyped_defs: All functions must have type annotations
  • strict_optional: No implicit Optional types
  • check_untyped_defs: Type check all functions
  • disallow_untyped_decorators: Require decorator type annotations

📝 Type Checking Guidelines

✨ Best Practices

Function Annotations

Python
# ✅ Good
def calculate_total(
    prices: list[float], tax_rate: float
) -> float:
    return sum(prices) * (1 + tax_rate)


# ❌ Bad
def calculate_total(prices, tax_rate):
    return sum(prices) * (1 + tax_rate)

Optional Types

Python
# ✅ Good
from typing import Optional


def get_user(user_id: Optional[int] = None) -> str:
    return "guest" if user_id is None else f"user_{user_id}"


# ❌ Bad
def get_user(user_id=None) -> str:
    return "guest" if user_id is None else f"user_{user_id}"

🔧 Common Patterns

🏭 Working with Classes

Python
from typing import Protocol


class Drawable(Protocol):
    def draw(self) -> None: ...


def render(entity: Drawable) -> None:
    entity.draw()

🔄 Generic Types

Python
from typing import TypeVar, Sequence

T = TypeVar("T")


def first_element(seq: Sequence[T]) -> T:
    if not seq:
        raise ValueError("Empty sequence")
    return seq[0]

❗ Troubleshooting

Common Errors

🚫 Missing Return Type

Python
# Error: Function is missing a return type annotation
def add(a: int, b: int):  # ❌
    return a + b


# Fixed:
def add(a: int, b: int) -> int:  # ✅
    return a + b

🚫 Optional Type Mismatch

Python
# Error: Incompatible return value type
def get_name(user_id: int) -> str:  # ❌
    user = find_user(user_id)
    return user.name if user else None


# Fixed:
def get_name(user_id: int) -> Optional[str]:  # ✅
    user = find_user(user_id)
    return user.name if user else None

📚 Resources

Official Resources

Additional Resources

IDE Integration

  • 💻 VS Code: Built-in support
  • 📝 PyCharm: Native support
  • 🔧 Vim/Neovim: Via ALE or Syntastic
  • ✨ Sublime Text: Via SublimeLinter-contrib-mypy

💡 Pro Tip: Use dmypy run for faster incremental type checking during development.