Mypy Configuration & Strictness: A Practical Guide for Python Teams
Establishing a robust type-checking pipeline requires balancing developer velocity with rigorous static analysis. This guide details progressive strictness adoption, configuration file architecture, and CI enforcement strategies.
Define a baseline Static Analysis Tools & CI Integration strategy before enforcing strict type checking. Adopt incremental strictness to prevent build failures during legacy code migration. Leverage modern pyproject.toml standards for centralized configuration management.
Baseline Configuration Architecture
Establish foundational mypy.ini and pyproject.toml structures with essential flags for immediate type coverage. Configure python_version, ignore_missing_imports, and warn_return_any for safe initial adoption. Map module-specific overrides using [[mypy.overrides]] to isolate third-party library gaps. Align configuration syntax with modern packaging standards to avoid deprecated .cfg formats.
[tool.mypy]
python_version = "3.11"
strict = true
disallow_subclassing_any = true
warn_unreachable = true
[[tool.mypy.overrides]]
module = ["untyped_lib.*", "legacy_module.*"]
ignore_errors = true
disallow_untyped_defs = false
This configuration demonstrates centralized TOML management. It enables strict mode globally while isolating legacy dependencies via targeted overrides. Mypy 1.5+ fully supports this syntax. Pin mypy>=1.8.0 in your lockfile to ensure consistent AST parsing across environments.
Progressive Strictness Tuning
Step-by-step activation of strict mode flags incrementally raises type safety thresholds. Enable the strict umbrella flag and selectively disable problematic sub-flags during early rollout. Evaluate trade-offs between disallow_untyped_defs and disallow_incomplete_defs for legacy codebases. Reference architectural differences in Pyright vs Mypy Comparison when selecting strictness baselines for mixed-type environments.
from typing import TypeVar, Generic, Callable
T = TypeVar("T")
class Container(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
def transform(self, func: Callable[[T], T]) -> T:
reveal_type(self.value) # Debugging: prints inferred type during check
return func(self.value)
# Strict mode will flag missing type hints on legacy functions
def process_data(raw: dict) -> list: # type: ignore[no-untyped-def]
return list(raw.values())
Mypy’s strict flag enables nine distinct checks simultaneously. Pyright handles strict differently by enforcing stricter nullability rules by default. Ruff focuses on syntax and style, leaving deep type inference to dedicated checkers. Start with strict = false and enable warn_return_any and check_untyped_defs first.
CI Pipeline Integration & Pre-commit Workflows
Automate type checking execution to enforce blocking gates. Optimize runner resource allocation for predictable feedback loops. Implement pre-commit hooks to catch type violations before code reaches the main branch. Configure CI runners to utilize incremental caching and parallel execution. Synchronize static analysis rules with Ruff Linter Integration to eliminate redundant linting overhead.
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
args: [--config-file=pyproject.toml, --show-error-codes, --incremental, --jobs, 4]
additional_dependencies: [types-requests, types-PyYAML]
This hook binds mypy to pre-commit with explicit type stubs and error code visibility. Always pin the hook revision to avoid silent behavior shifts. Pass --incremental to reuse previous AST caches. Set --jobs to match your runner’s CPU cores. Cache the .mypy_cache directory in GitHub Actions or GitLab CI to achieve sub-5-minute execution.
Debugging & Error Suppression Strategies
Systematically resolve false positives and manage suppression annotations. Apply scoped # type: ignore[error-code] annotations instead of blanket suppressions. Utilize reveal_type() for runtime debugging of complex type inference chains. Implement targeted performance tuning via Optimizing mypy.ini for large codebases to reduce memory overhead during strict checks.
import json
from typing import Any
def load_config(path: str) -> dict[str, Any]:
with open(path) as f:
data: Any = json.load(f)
# Scoped suppression prevents masking unrelated violations
return data # type: ignore[return-value]
Avoid enabling strict mode on day one for legacy repositories. This causes immediate CI failures across thousands of files. It also triggers widespread blanket # type: ignore abuse that defeats static analysis. Neglecting incremental mode and cache directories forces full AST parsing on every commit. This increases pipeline execution time by 300-500% and blocks rapid merge workflows.
FAQ
Should I use mypy.ini or pyproject.toml for configuration?
Use pyproject.toml for modern Python projects to centralize build, lint, and type-checking settings in a single manifest file.
How do I safely enable strict mode on an existing codebase?
Start with strict = false, enable individual strict flags incrementally, and use [[mypy.overrides]] to exclude legacy modules until refactored.
Why does mypy report ‘Cannot find implementation or library stub’ for third-party packages?
The package lacks inline type hints or external stubs. Install types-<package> from PyPI or set ignore_missing_imports = true for that specific module.