Skip to content

theoremlp/rules_pydeps

Repository files navigation

rules_pydeps

An aspect to enforce correct dependencies for py_* targets.

This aspect inspects Python sources to discover imports, and then verifies that imports are either local to the target or come from a declared dependency.

Usage

This aspect will run over any py_binary, py_library or py_test target.

Presently, only bzlmod setup is supported.

Add rules_pydeps to your MODULE.bazel:

bazel_dep(name = "rules_pydeps", version = "0.0.0")

Configure the external deps analyzer:

reqs = use_extension("@rules_pydeps//pydeps:reqs.bzl", "reqs")
reqs.requirements(
    requirements_in = "//:requirements.in",
    pip_requirements = "@pip//:requirements.bzl",
)
use_repo(reqs, "reqs")

Configure deps_enforcer aspect:

Define a new aspect in a .bzl file (such as ./tools/aspects.bzl):

load("@rules_pydeps//pydeps:pydeps.bzl", "deps_enforcer_aspect_factory")

deps_enforcer = deps_enforcer_aspect_factory(
    pip_deps_index = Label("@reqs//:pip_deps_index"),
)

Update your .bazelrc to include this new aspect:

# register deps_enforcer aspect with Bazel
build --aspects //tools:aspects.bzl%deps_enforcer

# optionally, default enable enforcement
build --output_groups=+pydeps

Skipping Targets

Label any target with the tag no-deps-enforcer, or customize suppression tags:

deps_enforcer = deps_enforcer_aspect_factory(
    pip_deps_index = Label("@reqs//:pip_deps_index"),
    suppression_tags = ["no-lint"],
)

Customizing OutputGroupInfo

To make it easier to manage your aspects, it's possible to set an additional OutputGroupInfo name:

deps_enforcer = deps_enforcer_aspect_factory(
    pip_deps_index = Label("@reqs//:pip_deps_index"),
    output_groups = ["lint"],
)

This may assist in configuring aspects to run together in your .bazelrc.

Non-imported/Runtime Dependencies

Some Python libraries dynamically load dependencies based on what's on PYTHONPATH (such as pyxlsb for pandas). It may be necessary to import these dependencies, but the deps enforcer will detect these as extra imports.

To overcome this, add a tag to your target of the form 'runtime:requirement("dep")' or runtime://my/internal:dep.

As an example:

py_library(
    name = "example",
    srcs = ["example.py"]
    deps = [
        requirement("pandas"),
        requirement("pyxlsb"),
    ],
    tags = [
        'runtime:requirement("pyxlsb")',
    ],
)

It may be simpler to define a macro to handle this for you.