Skip to main content
Version: 2.8 (deprecated)

check

How to use MyPy.


Activating MyPy

To opt-in, add pants.backend.python.typecheck.mypy to backend_packages in your config file.

pants.toml
[GLOBAL]
backend_packages.add = [
"pants.backend.python",
"pants.backend.python.typecheck.mypy",
]

This will register a new check goal:

$ ./pants check helloworld/util/lang.py
$ ./pants check ::
Benefit of Pants: typecheck Python 2-only and Python 3-only code at the same time

MyPy determines which Python version to use based on its python_version option. If that's undefined, MyPy uses the interpreter the tool is run with. Because you can only use one config file at a time with MyPy, you cannot normally say to use 2.7 for part of your codebase but 3.6 for the rest; you must choose a single version.

Instead, Pants will group your targets based on their interpreter constraints, and run all the Python 2 targets together and all the Python 3 targets together. It will automatically set python_version to the minimum compatible interpreter, such as a constraint like ["==2.7.*", ">3.6"] using 2.7.

To turn this off, you can still set python_version in mypy.ini or --python-version/--py2 in --mypy-args; Pants will respect the value you set.

Hook up a MyPy config file

Pants will automatically include your config file if it's located at mypy.ini, .mypy.ini, setup.cfg, or pyproject.toml.

Otherwise, you must set the option [mypy].config for Pants to include the config file in the process's sandbox and to instruct MyPy to load it.

pants.toml
[mypy]
config = "build-support/mypy.ini"

Change the MyPy version

Use the version option in the [mypy] scope:

pants.toml
[mypy]
version = "mypy==0.910"

If you change this option, Pants's default lockfile for MyPy will not work. Either set the lockfile option to a custom path or "<none>" to opt out. See Third-party dependencies.

Incrementally adopt MyPy with skip_mypy=True

You can tell Pants to skip running MyPy on certain files by adding skip_mypy=True to the relevant targets.

project/BUILD
# Skip MyPy for all the Python files in this directory
# (both test and non-test files).
python_sources(name="lib", skip_mypy=True)
python_tests(name="tests", skip_mypy=True)

# To only skip certain files, use the `overrides` field.
python_sources(
name="lib",
overrides={
"util.py": {"skip_mypy": True},
# Use a tuple to specify multiple files.
("user.py", "admin.py"): {"skip_mypy": True},
},
)

When you run ./pants check ::, Pants will skip any files belonging to skipped targets.

MyPy may still try to check the skipped files!

The skip_mypy field only tells Pants not to provide the skipped files as direct input to MyPy. But MyPy, by default, will still try to check files that are dependencies of the direct inputs. So if your skipped files are dependencies of unskipped files, they may still be checked.

To change this behavior, use MyPy's --follow-imports option, typically by setting it to silent. You can do so either by adding it to the args option in the [mypy] section of your Pants config file, or by setting it in mypy.ini.

First-party type stubs (.pyi files)

You can use .pyi files for both first-party and third-party code. Include the .pyi files in the sources field for python_source / python_sources and python_test / python_tests targets. MyPy will use these stubs rather than looking at the implementation.

Pants's dependency inference knows to infer a dependency both on the implementation and the type stub. You can verify this by running ./pants dependencies path/to/file.py.

When writing stubs for third-party libraries, you may need the set up the [source].root_patterns option so that source roots are properly stripped. For example:

[source]
root_patterns = ["mypy-stubs", "src/python"]

Third-party type stubs

You can install third-party type stubs (e.g. types-requests) like normal Python requirements. Pants will infer a dependency on both the type stub and the actual dependency, e.g. both types-requests and requests, which you can confirm by running ./pants dependencies path/to/f.py.

You can also install the type stub via the option [mypy].extra_type_stubs, which ensures the stubs are only used when running MyPy and are not included when, for example, packaging a PEX.

pants.toml
[mypy]
extra_type_stubs = ["types-requests==2.25.12"]

Add a third-party plugin

Add the plugin to the extra_requirements option in the [mypy] scope, then update your mypy.ini to load the plugin:

[mypy]
extra_requirements.add = ["pydantic==1.6.1"]

If you change this option, Pants's default lockfile for MyPy will not work. Either set the lockfile option to a custom path or "<none>" to opt out. See Third-party dependencies.

For some plugins, like django-stubs, you may need to always load certain source files, such as a settings.py file. You can make sure that this source file is always used by hijacking the source_plugins option, which allows you to specify targets whose sources should always be used when running MyPy. See the below section for more information about source plugins.

Some MyPy plugins also include type stubs, such as django-stubs. For type stubs to be used, the requirement must either be included in [mypy].extra_type_stubs or be loaded like a normal third-party dependency, such as including in a requirements.txt file.

For example, to fully use the django-stubs plugin, your setup might look like this:

[source]
root_patterns = ["src/python"]

[mypy]
extra_requirements = ["django-stubs==1.5.0"]
extra_type_stubs = ["django-stubs==1.5.0"]
source_plugins = ["src/python/project:django_settings"]
MyPy Protobuf support

Add mypy_plugin = true to the [python-protobuf] scope. See Protobuf for more information.

Add a first-party plugin

To add a MyPy plugin you wrote, add a python_source or python_sources target with the plugin's Python file(s) included in the sources field.

Then, add plugins = path.to.module to your MyPy config file, using the name of the module without source roots. For example, if your Python file is called pants-plugins/mypy_plugins/custom_plugin.py, and you set pants-plugins as a source root, then set plugins = mypy_plugins.custom_plugin. Set the config option in the [mypy] scope in your pants.toml to point to your MyPy config file.

Finally, set the option source_plugins in the [mypy] scope to include this target's address, e.g. source_plugins = ["pants-plugins/mypy_plugins:plugin"]. This will ensure that your plugin's sources are always included in the subprocess.

For example:

[mypy]
source_plugins = ["pants-plugins/mypy_plugins:plugin"]

Because this is a python_source or python_sources target, Pants will treat this code like your other Python files, such as running linters on it or allowing you to write a python_distribution target to distribute the plugin externally.

Reports

MyPy can generate various report files.

For Pants to properly preserve the reports, instruct MyPy to write to the reports/ folder by updating its config file or --mypy-args. For example, in your pants.toml:

pants.toml
[mypy]
args = ["--linecount-report=reports"]

Pants will copy all reports into the folder dist/typecheck/mypy.

Known limitations

Performance is often slower than normal

Pants does not yet leverage MyPy's caching mechanism and daemon, so a typical run with Pants will likely be slower than using MyPy directly.

We are working to figure out how to leverage MyPy's cache in a way that is safe and allows for things like remote execution.

Tip: only run over changed files and their dependees

When changing type hints code, you not only need to run over the changed files, but also any code that depends on the changed files:

$ ./pants --changed-since=HEAD --changed-dependees=transitive check

See Advanced target selection for more information.