cleanup: Typos, doc, and formatting cleanup in pip extension et al (#1275)
* Corrects some typos in docs
* Expands the user-facing documentation
* Fixes errors having newlines in the middle of them
* Renames some internal functions to be more self-describing.
diff --git a/docs/pip_repository.md b/docs/pip_repository.md
index 0e0fea3..8536052 100644
--- a/docs/pip_repository.md
+++ b/docs/pip_repository.md
@@ -10,7 +10,7 @@
pip_hub_repository_bzlmod(<a href="#pip_hub_repository_bzlmod-name">name</a>, <a href="#pip_hub_repository_bzlmod-repo_mapping">repo_mapping</a>, <a href="#pip_hub_repository_bzlmod-repo_name">repo_name</a>, <a href="#pip_hub_repository_bzlmod-whl_library_alias_names">whl_library_alias_names</a>)
</pre>
-A rule for bzlmod mulitple pip repository creation. Intended for private use only.
+A rule for bzlmod mulitple pip repository creation. PRIVATE USE ONLY.
**ATTRIBUTES**
diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel
index e09e029..a61e094 100644
--- a/examples/bzlmod/MODULE.bazel
+++ b/examples/bzlmod/MODULE.bazel
@@ -34,16 +34,17 @@
# You only need to load this repositories if you are using multiple Python versions.
# See the tests folder for various examples on using multiple Python versions.
# The names "python_3_9" and "python_3_10" are autmatically created by the repo
-# rules based on the python_versions.
+# rules based on the `python_version` arg values.
use_repo(python, "python_3_10", "python_3_9", "python_aliases")
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
-# For each pip setup we call pip.parse. We can pass in various options
-# but typically we are passing in a requirements and an interpreter.
-# If you provide the python_version we will attempt to determine
-# the interpreter target automatically. Otherwise use python_interpreter_target
-# to override the lookup.
+# To fetch pip dependencies, use pip.parse. We can pass in various options,
+# but typically we pass requirements and the Python version. The Python
+# version must have been configured by a corresponding `python.toolchain()`
+# call.
+# Alternatively, `python_interpreter_target` can be used to directly specify
+# the Python interpreter to run to resolve dependencies.
pip.parse(
hub_name = "pip",
python_version = "3.9",
@@ -57,7 +58,8 @@
requirements_windows = "//:requirements_windows_3_10.txt",
)
-# we use the pip_39 repo because entry points are not yet supported.
+# NOTE: The pip_39 repo is only used because the plain `@pip` repo doesn't
+# yet support entry points; see https://github.com/bazelbuild/rules_python/issues/1262
use_repo(pip, "pip", "pip_39")
bazel_dep(name = "other_module", version = "", repo_name = "our_other_module")
diff --git a/python/extensions/pip.bzl b/python/extensions/pip.bzl
index e4eb9b5..5cad3b1 100644
--- a/python/extensions/pip.bzl
+++ b/python/extensions/pip.bzl
@@ -27,21 +27,25 @@
)
load("@rules_python//python/pip_install:requirements_parser.bzl", parse_requirements = "parse")
-def _create_pip(module_ctx, pip_attr, whl_map):
+def _create_versioned_pip_and_whl_repos(module_ctx, pip_attr, whl_map):
python_interpreter_target = pip_attr.python_interpreter_target
# if we do not have the python_interpreter set in the attributes
# we programtically find it.
+ hub_name = pip_attr.hub_name
if python_interpreter_target == None:
python_name = "python_{}".format(pip_attr.python_version.replace(".", "_"))
if python_name not in INTERPRETER_LABELS.keys():
- fail("""
-Unable to find '{}' in the list of interpreters please update your pip.parse call with the correct python name
-""".format(pip_attr.python_name))
-
+ fail((
+ "Unable to find interpreter for pip hub '{hub_name}' for " +
+ "python_version={version}: Make sure a corresponding " +
+ '`python.toolchain(python_version="{version}")` call exists'
+ ).format(
+ hub_name = hub_name,
+ version = pip_attr.python_version,
+ ))
python_interpreter_target = INTERPRETER_LABELS[python_name]
- hub_name = pip_attr.hub_name
pip_name = hub_name + "_{}".format(pip_attr.python_version.replace(".", ""))
requrements_lock = locked_requirements_label(module_ctx, pip_attr)
@@ -93,10 +97,10 @@
whl_map[hub_name][whl_name][pip_attr.python_version] = pip_name + "_"
def _pip_impl(module_ctx):
- """Implmentation of a class tag that creates the pip hub(s) and corresponding pip spoke, alias and whl repositories.
+ """Implementation of a class tag that creates the pip hub(s) and corresponding pip spoke, alias and whl repositories.
- This implmentation iterates through all of the "pip.parse" calls and creates
- different pip hubs repositories based on the "hub_name". Each of the
+ This implmentation iterates through all of the `pip.parse` calls and creates
+ different pip hub repositories based on the "hub_name". Each of the
pip calls create spoke repos that uses a specific Python interpreter.
In a MODULES.bazel file we have:
@@ -115,13 +119,13 @@
)
- For instance we have a hub with the name of "pip".
+ For instance, we have a hub with the name of "pip".
A repository named the following is created. It is actually called last when
all of the pip spokes are collected.
- @@rules_python~override~pip~pip
- As show in the example code above we have the following.
+ As shown in the example code above we have the following.
Two different pip.parse statements exist in MODULE.bazel provide the hub_name "pip".
These definitions create two different pip spoke repositories that are
related to the hub "pip".
@@ -185,22 +189,32 @@
for mod in module_ctx.modules:
for pip_attr in mod.tags.parse:
- if pip_attr.hub_name in pip_hub_map:
+ hub_name = pip_attr.hub_name
+ if hub_name in pip_hub_map:
# We cannot have two hubs with the same name in different
# modules.
- if pip_hub_map[pip_attr.hub_name].module_name != mod.name:
- fail("""Unable to create pip with the hub_name '{}', same hub name
- in a different module found.""".format(pip_attr.hub_name))
+ if pip_hub_map[hub_name].module_name != mod.name:
+ fail((
+ "Duplicate cross-module pip hub named '{hub}': pip hub " +
+ "names must be unique across modules. First defined " +
+ "by module '{first_module}', second attempted by " +
+ "module '{second_module}'"
+ ).format(
+ hub = hub_name,
+ first_module = pip_hub_map[hub_name].module_name,
+ second_module = mod.name,
+ ))
- if pip_attr.python_version in pip_hub_map[pip_attr.hub_name].python_versions:
- fail(
- """Unable to create pip with the hub_name '{}', same hub name
- using the same Python repo name '{}' found in module '{}'.""".format(
- pip_attr.hub_name,
- pip_attr.python_version,
- mod.name,
- ),
- )
+ if pip_attr.python_version in pip_hub_map[hub_name].python_versions:
+ fail((
+ "Duplicate pip python version '{version}' for hub " +
+ "'{hub}' in module '{module}': the Python versions " +
+ "used for a hub must be unique"
+ ).format(
+ hub = hub_name,
+ module = mod.name,
+ version = pip_attr.python_version,
+ ))
else:
pip_hub_map[pip_attr.hub_name].python_versions.append(pip_attr.python_version)
else:
@@ -209,17 +223,19 @@
python_versions = [pip_attr.python_version],
)
- _create_pip(module_ctx, pip_attr, hub_whl_map)
+ _create_versioned_pip_and_whl_repos(module_ctx, pip_attr, hub_whl_map)
for hub_name, whl_map in hub_whl_map.items():
for whl_name, version_map in whl_map.items():
if DEFAULT_PYTHON_VERSION not in version_map:
- fail(
- """
-Unable to find the default python version in the version map, please update your requirements files
-to include Python '{}'.
-""".format(DEFAULT_PYTHON_VERSION),
- )
+ fail((
+ "Default python version '{version}' missing in pip " +
+ "hub '{hub}': update your pip.parse() calls so that " +
+ 'includes `python_version = "{version}"`'
+ ).format(
+ version = DEFAULT_PYTHON_VERSION,
+ hub = hub_name,
+ ))
# Create the alias repositories which contains different select
# statements These select statements point to the different pip
@@ -247,14 +263,34 @@
"hub_name": attr.string(
mandatory = True,
doc = """
-The unique hub name. Mulitple pip.parse calls that contain the same hub name,
-create spokes for specific Python versions.
+The name of the repo pip dependencies will be accessible from.
+
+This name must be unique between modules; unless your module is guaranteed to
+always be the root module, it's highly recommended to include your module name
+in the hub name. Repo mapping, `use_repo(..., pip="my_modules_pip_deps")`, can
+be used for shorter local names within your module.
+
+Within a module, the same `hub_name` can be specified to group different Python
+versions of pip dependencies under one repository name. This allows using a
+Python version-agnostic name when referring to pip dependencies; the
+correct version will be automatically selected.
+
+Typically, a module will only have a single hub of pip dependencies, but this
+is not required. Each hub is a separate resolution of pip dependencies. This
+means if different programs need different versions of some library, separate
+hubs can be created, and each program can use its respective hub's targets.
+Targets from different hubs should not be used together.
""",
),
"python_version": attr.string(
mandatory = True,
doc = """
-The Python version for the pip spoke.
+The Python version to use for resolving the pip dependencies. If not specified,
+then the default Python version (as set by the root module or rules_python)
+will be used.
+
+The version specified here must have a corresponding `python.toolchain()`
+configured.
""",
),
}, **pip_repository_attrs)
@@ -270,12 +306,16 @@
pip = module_extension(
doc = """\
-This extension is used to create a pip hub and all of the spokes that are part of that hub.
-We can have multiple different hubs, but we cannot have hubs that have the same name in
-different modules. Each hub needs one or more spokes. A spoke contains a specific version
-of Python, and the requirement(s) files that are unquie to that Python version.
-In order to add more spokes you call this extension mulitiple times using the same hub
-name.
+This extension is used to make dependencies from pip available.
+
+To use, call `pip.parse()` and specify `hub_name` and your requirements file.
+Dependencies will be downloaded and made available in a repo named after the
+`hub_name` argument.
+
+Each `pip.parse()` call configures a particular Python version. Multiple calls
+can be made to configure different Python versions, and will be grouped by
+the `hub_name` argument. This allows the same logical name, e.g. `@pip//numpy`
+to automatically resolve to different, Python version-specific, libraries.
""",
implementation = _pip_impl,
tag_classes = {
diff --git a/python/pip_install/pip_repository.bzl b/python/pip_install/pip_repository.bzl
index f18b69b..866a834 100644
--- a/python/pip_install/pip_repository.bzl
+++ b/python/pip_install/pip_repository.bzl
@@ -363,7 +363,7 @@
pip_hub_repository_bzlmod = repository_rule(
attrs = pip_hub_repository_bzlmod_attrs,
- doc = """A rule for bzlmod mulitple pip repository creation. Intended for private use only.""",
+ doc = """A rule for bzlmod mulitple pip repository creation. PRIVATE USE ONLY.""",
implementation = _pip_hub_repository_bzlmod_impl,
)