
What is a Mypy Plugin?
A Mypy plugin is a mechanism that allows developers to extend or customize the behavior of the Mypy static type checker. While Mypy supports standard PEP 484-style Python type hints out of the box, some advanced use cases—like type-safe Django ORM models or custom data validation libraries—require deeper type inference than static annotations alone provide.
Mypy plugins enable integration with complex Python frameworks or libraries by providing hook functions that modify how Mypy interprets classes, functions, and expressions during type checking. These plugins are written in Python and imported dynamically when Mypy is executed.
In short, Mypy plugins allow framework-specific or library-specific typing intelligence, going beyond what standard static analysis can infer.
Major Use Cases of Mypy Plugins
Mypy plugins are useful when dealing with dynamic or meta-programmed Python code. Common use cases include:
1. Framework Integration
Frameworks like Django or SQLAlchemy use dynamic code generation (e.g., adding attributes at runtime), which static type checkers can’t analyze. Mypy plugins bridge this gap.
- Example: The
django-stubs
plugin enables static typing for Django’s dynamically generated model fields.
2. Advanced Type Inference
Plugins can dynamically infer or alter the return type of a function or the attribute type of a class based on custom logic or decorators.
- Example: A plugin might make a function return
List[int]
orList[str]
depending on an argument value.
3. Library Customization
For libraries with heavy use of decorators, metaclasses, or custom descriptors, plugins allow for proper type resolution without modifying the source code.
- Example: Libraries like
attrs
,pydantic
, andmarshmallow
use plugins to provide type hints for generated fields.
4. Custom DSL or Domain Modeling
If you’re building a DSL (domain-specific language) or a configuration system using Python, a Mypy plugin can help enforce typing rules specific to your model.
How Mypy Plugin Works (Architecture Overview)
The Mypy plugin system hooks into Mypy’s semantic analysis and type checking phases. Here’s how it functions in simplified terms:
1. Mypy Core
Mypy loads Python modules and parses them into an Abstract Syntax Tree (AST).
2. Plugin Loader
If the mypy.ini
or pyproject.toml
config specifies a plugin, Mypy dynamically imports it and registers it with the type checker.
3. Hook Registration
The plugin implements hook functions like:
get_class_decorator_hook
get_function_hook
get_type_analyze_hook
get_dynamic_class_hook
These hooks are registered through the Plugin
base class in mypy.plugin
.
4. Type Manipulation
When Mypy encounters the relevant code (a function, class, or decorator), it calls the plugin’s hook function, which can:
- Change the inferred type
- Inject new type information
- Modify behavior conditionally based on arguments
5. Type Checking Continuation
Once hooks return modified type information, Mypy continues checking the program using the adjusted data.
Basic Workflow of a Mypy Plugin
Here’s a high-level flow of how a Mypy plugin interacts during a type-checking run:
- Developer writes code using a dynamic framework or pattern.
- Plugin is registered in Mypy configuration.
- Mypy runs and encounters plugin-eligible code.
- Plugin hook is called, returning custom type logic.
- Mypy continues type checking with enhanced information.
- Errors or warnings are shown, if types do not match.
Step-by-Step Getting Started Guide for Mypy Plugins
Prerequisites
- Python 3.8+
mypy
installed (pip install mypy
)- Basic familiarity with Python typing and static analysis
Step 1: Create a New Python Project
Set up your working directory:
mkdir mypy-plugin-demo && cd mypy-plugin-demo
python -m venv venv
source venv/bin/activate
pip install mypy
Step 2: Define Your Plugin File
Create myplugin.py
:
from mypy.plugin import Plugin, FunctionContext
from mypy.types import Type
class MyCustomPlugin(Plugin):
def get_function_hook(self, fullname: str):
if fullname == "mymodule.dynamic_func":
return self.analyze_dynamic_func
return None
def analyze_dynamic_func(self, ctx: FunctionContext) -> Type:
# Override return type regardless of argument
return ctx.api.named_generic_type("builtins.str", [])
def plugin(version: str):
return MyCustomPlugin
Step 3: Write the Target Code to Be Typed
Create mymodule.py
:
def dynamic_func(x):
return x * 5
Create test.py
:
from mymodule import dynamic_func
result = dynamic_func(10)
reveal_type(result) # Mypy will report this as 'str' due to plugin
Step 4: Configure Mypy to Use Your Plugin
Create a mypy.ini
file:
[mypy]
plugins = myplugin
Step 5: Run Mypy
mypy test.py
You should see:
test.py:4: note: Revealed type is "builtins.str"
Step 6: Expand with More Hooks
You can now add hooks for class decorators, attribute types, metaclasses, etc., to handle more complex typing cases.