Mypy rules

Bazel rules for the Python linter mypy.

Setup

First ensure rules_venv is setup by referring to rules_venv setup.

Next, the mypy rules work mostly off of toolchains which are used to provide the necessary python targets (aka mypy) for the process wrappers. Users will want to make sure they have a way to get the necessary python dependencies. Tools such as req-compile can provide these.

With the appropriate dependencies available, a py_mypy_toolchain will need to be configured:

load("@rules_venv//python:defs.bzl", "py_library")
load("@rules_venv//python/mypy:defs.bzl", "py_mypy_toolchain")

py_library(
    name = "mypy_deps",
    deps = [
        "@pip_deps//mypy",

        # Types libraries can also be added here.
    ],
)

py_mypy_toolchain(
    name = "toolchain_impl",
    mypy = ":mypy_deps",
    config = "//:.mypyrc.toml",
    visibility = ["//visibility:public"]
)

toolchain(
    name = "toolchain",
    toolchain = ":toolchain_impl",
    toolchain_type = "@rules_venv//python/mypy:toolchain_type",
    visibility = ["//visibility:public"]
)

This toolchain then needs to be registered in the MODULE.bazel file.

register_toolchains("//tools/python/mypy:toolchain")

From here, py_mypy_test and the py_mypy_aspect should now be usable.

The toolchain settles both halves of what a mypy result means: the version that produced it and the settings it ran under. They are named together because they are only meaningful together -- the type information the aspect shares between targets is keyed on the two agreeing, so the aspect reads the toolchain's configuration file and no other. A py_mypy_test shares nothing, and can override it per target with its own config attribute.

The toolchain's config is optional. Left unset it tracks a global flag, which is how a workspace with no toolchain of its own picks a configuration file:

build --@rules_venv//python/mypy:config=//:.mypyrc.toml

Note that these files will need to be available via exports_files

Rules

Aspects

py_mypy_test

load("@rules_venv//python/mypy:defs.bzl", "py_mypy_test")

py_mypy_test(name, config, target)

A rule for running mypy on a Python target.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
configThe config file (mypy.ini, setup.cfg or pyproject.toml) containing mypy settings. A .toml file is read from its tool.mypy table, anything else from its [mypy] section. Defaults to the one the py_mypy_toolchain names.LabeloptionalNone
targetThe target to run mypy on.Labelrequired

py_mypy_toolchain

load("@rules_venv//python/mypy:defs.bzl", "py_mypy_toolchain")

py_mypy_toolchain(name, config, mypy)

A toolchain for the mypy formatter rules.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
configThe config file (mypy.ini) containing mypy settings.

Settings and mypy version together decide what a cache entry means, so they are named in one place. The default tracks the @rules_venv//python/mypy:config flag, which is how a workspace that has not defined its own toolchain chooses a config file.
Labeloptional"@rules_venv//python/mypy:config"
mypyThe mypy py_library to use with the rules.

Must be mypy 2.0 or newer. These rules analyze a dependency once and share the result with everything that imports it, which rests on mypy checking function bodies only where the errors are wanted -- a split mypy grew in 2.0. An older release is refused rather than tolerated, since on a large dependency the difference is seconds against half an hour.
Labelrequired

py_mypy_aspect

load("@rules_venv//python/mypy:defs.bzl", "py_mypy_aspect")

py_mypy_aspect()

An aspect for running mypy on targets with Python sources.

Every Python target the aspect reaches publishes a PyMypyCacheInfo, including the ones it does not check. Targets that opt out with a no_mypy tag, and third-party packages, are analyzed but not reported on.

It travels every attribute rather than deps alone, since a rule is free to build its PyInfo from any of them. A library reached through a toolchain cannot be followed that way at all, so a target whose Python none of its attributes accounts for caches everything it publishes as one unit. That needs nothing of the rule but the PyInfo it already returns, so a toolchain defined outside this repository gets the same treatment without doing anything.

ASPECT ATTRIBUTES

ATTRIBUTES