Skip to content

Commit 9facc3e

Browse files
authored
feat: expose 'pip_utils.normalize_name' function (#1542)
With this change users can use a previously private function to normalize a PyPI package name into something that bazel can use.
1 parent c1a5885 commit 9facc3e

File tree

8 files changed

+53
-50
lines changed

8 files changed

+53
-50
lines changed

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ A brief description of the categories of changes:
3333
dependencies is now done as part of `py_repositories` call.
3434

3535
* (pip_parse) The generated `requirements.bzl` file now has an additional symbol
36-
`all_whl_requirements_by_package` which provides a map from the original package name
37-
(as it appears in requirements.txt) to the target that provides the built wheel file.
36+
`all_whl_requirements_by_package` which provides a map from the normalized
37+
PyPI package name to the target that provides the built wheel file. Use
38+
`pip_utils.normalize_name` function from `@rules_python//python:pip.bzl` to
39+
convert a PyPI package name to a key in the `all_whl_requirements_by_package`
40+
map.
3841

3942
* (pip_parse) The flag `incompatible_generate_aliases` has been flipped to
4043
`True` by default on `non-bzlmod` setups allowing users to use the same label
@@ -88,6 +91,9 @@ Breaking changes:
8891
* (pip) Support for using [PEP621](https://peps.python.org/pep-0621/) compliant
8992
`pyproject.toml` for creating a resolved `requirements.txt` file.
9093

94+
* (utils) Added a `pip_utils` struct with a `normalize_name` function to allow users
95+
to find out how `rules_python` would normalize a PyPI distribution name.
96+
9197
## [0.26.0] - 2023-10-06
9298

9399
### Changed

examples/pip_parse_vendored/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ genrule(
1919
cmd = " | ".join([
2020
"cat $<",
2121
# Insert our load statement after the existing one so we don't produce a file with buildifier warnings
22-
"""sed -e '/^load.*.whl_library/i\\'$$'\\n''load("@python39//:defs.bzl", "interpreter")'""",
22+
"""sed -e '/^load.*.pip.bzl/i\\'$$'\\n''load("@python39//:defs.bzl", "interpreter")'""",
2323
# Replace the bazel 6.0.0 specific comment with something that bazel 5.4.0 would produce.
2424
# This enables this example to be run as a test under bazel 5.4.0.
2525
"""sed -e 's#@//#//#'""",

examples/pip_parse_vendored/requirements.bzl

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ from //:requirements.txt
55
"""
66

77
load("@python39//:defs.bzl", "interpreter")
8+
load("@rules_python//python:pip.bzl", "pip_utils")
89
load("@rules_python//python/pip_install:pip_repository.bzl", "whl_library")
910

1011
all_requirements = ["@pip//certifi:pkg", "@pip//charset_normalizer:pkg", "@pip//idna:pkg", "@pip//requests:pkg", "@pip//urllib3:pkg"]
1112

12-
all_whl_requirements_by_package = {"certifi": "@pip//certifi:whl", "charset-normalizer": "@pip//charset_normalizer:whl", "idna": "@pip//idna:whl", "requests": "@pip//requests:whl", "urllib3": "@pip//urllib3:whl"}
13+
all_whl_requirements_by_package = {"certifi": "@pip//certifi:whl", "charset_normalizer": "@pip//charset_normalizer:whl", "idna": "@pip//idna:whl", "requests": "@pip//requests:whl", "urllib3": "@pip//urllib3:whl"}
1314

1415
all_whl_requirements = all_whl_requirements_by_package.values()
1516

@@ -19,25 +20,22 @@ _packages = [("pip_certifi", "certifi==2023.7.22 --hash=sha256:539cc1d13202e
1920
_config = {"download_only": False, "enable_implicit_namespace_pkgs": False, "environment": {}, "extra_pip_args": [], "isolated": True, "pip_data_exclude": [], "python_interpreter": "python3", "python_interpreter_target": interpreter, "quiet": True, "repo": "pip", "repo_prefix": "pip_", "timeout": 600}
2021
_annotations = {}
2122

22-
def _clean_name(name):
23-
return name.replace("-", "_").replace(".", "_").lower()
24-
2523
def requirement(name):
26-
return "@pip//{}:{}".format(_clean_name(name), "pkg")
24+
return "@pip//{}:{}".format(pip_utils.normalize_name(name), "pkg")
2725

2826
def whl_requirement(name):
29-
return "@pip//{}:{}".format(_clean_name(name), "whl")
27+
return "@pip//{}:{}".format(pip_utils.normalize_name(name), "whl")
3028

3129
def data_requirement(name):
32-
return "@pip//{}:{}".format(_clean_name(name), "data")
30+
return "@pip//{}:{}".format(pip_utils.normalize_name(name), "data")
3331

3432
def dist_info_requirement(name):
35-
return "@pip//{}:{}".format(_clean_name(name), "dist_info")
33+
return "@pip//{}:{}".format(pip_utils.normalize_name(name), "dist_info")
3634

3735
def entry_point(pkg, script = None):
3836
if not script:
3937
script = pkg
40-
return "@pip_" + _clean_name(pkg) + "//:rules_python_wheel_entry_point_" + script
38+
return "@pip_" + pip_utils.normalize_name(pkg) + "//:rules_python_wheel_entry_point_" + script
4139

4240
def _get_annotation(requirement):
4341
# This expects to parse `setuptools==58.2.0 --hash=sha256:2551203ae6955b9876741a26ab3e767bb3242dafe86a32a749ea0d78b6792f11`

python/pip.bzl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ load("//python/pip_install:pip_repository.bzl", "pip_repository", _package_annot
2323
load("//python/pip_install:requirements.bzl", _compile_pip_requirements = "compile_pip_requirements")
2424
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
2525
load("//python/private:full_version.bzl", "full_version")
26+
load("//python/private:normalize_name.bzl", "normalize_name")
2627
load("//python/private:render_pkg_aliases.bzl", "NO_MATCH_ERROR_MESSAGE_TEMPLATE")
2728

2829
compile_pip_requirements = _compile_pip_requirements
@@ -86,7 +87,7 @@ _process_requirements(
8687
requirements_bzl = """\
8788
# Generated by python/pip.bzl
8889
89-
load("@{rules_python}//python:pip.bzl", "whl_library_alias")
90+
load("@{rules_python}//python:pip.bzl", "whl_library_alias", "pip_utils")
9091
{load_statements}
9192
9293
_wheel_names = []
@@ -106,20 +107,17 @@ def _process_requirements(pkg_labels, python_version, repo_prefix):
106107
107108
{process_requirements_calls}
108109
109-
def _clean_name(name):
110-
return name.replace("-", "_").replace(".", "_").lower()
111-
112110
def requirement(name):
113-
return "{macro_tmpl}".format(_clean_name(name), "pkg")
111+
return "{macro_tmpl}".format(pip_utils.normalize_name(name), "pkg")
114112
115113
def whl_requirement(name):
116-
return "{macro_tmpl}".format(_clean_name(name), "whl")
114+
return "{macro_tmpl}".format(pip_utils.normalize_name(name), "whl")
117115
118116
def data_requirement(name):
119-
return "{macro_tmpl}".format(_clean_name(name), "data")
117+
return "{macro_tmpl}".format(pip_utils.normalize_name(name), "data")
120118
121119
def dist_info_requirement(name):
122-
return "{macro_tmpl}".format(_clean_name(name), "dist_info")
120+
return "{macro_tmpl}".format(pip_utils.normalize_name(name), "dist_info")
123121
124122
def entry_point(pkg, script = None):
125123
fail("Not implemented yet")
@@ -278,3 +276,8 @@ def multi_pip_parse(name, default_version, python_versions, python_interpreter_t
278276
default_version = default_version,
279277
pip_parses = pip_parses,
280278
)
279+
280+
# Extra utilities visible to rules_python users.
281+
pip_utils = struct(
282+
normalize_name = normalize_name,
283+
)

python/pip_install/pip_repository.bzl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,11 @@ def _pip_repository_impl(rctx):
276276

277277
packages = [(normalize_name(name), requirement) for name, requirement in parsed_requirements_txt.requirements]
278278

279-
bzl_packages = dict(sorted([[name, normalize_name(name)] for name, _ in parsed_requirements_txt.requirements]))
279+
bzl_packages = sorted([normalize_name(name) for name, _ in parsed_requirements_txt.requirements])
280280

281281
imports = [
282+
# NOTE: Maintain the order consistent with `buildifier`
283+
'load("@rules_python//python:pip.bzl", "pip_utils")',
282284
'load("@rules_python//python/pip_install:pip_repository.bzl", "whl_library")',
283285
]
284286

@@ -314,7 +316,7 @@ def _pip_repository_impl(rctx):
314316

315317
if rctx.attr.incompatible_generate_aliases:
316318
macro_tmpl = "@%s//{}:{}" % rctx.attr.name
317-
aliases = render_pkg_aliases(repo_name = rctx.attr.name, bzl_packages = bzl_packages.values())
319+
aliases = render_pkg_aliases(repo_name = rctx.attr.name, bzl_packages = bzl_packages)
318320
for path, contents in aliases.items():
319321
rctx.file(path, contents)
320322
else:
@@ -324,20 +326,20 @@ def _pip_repository_impl(rctx):
324326
rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
325327
"%%ALL_DATA_REQUIREMENTS%%": _format_repr_list([
326328
macro_tmpl.format(p, "data")
327-
for p in bzl_packages.values()
329+
for p in bzl_packages
328330
]),
329331
"%%ALL_REQUIREMENTS%%": _format_repr_list([
330332
macro_tmpl.format(p, "pkg")
331-
for p in bzl_packages.values()
333+
for p in bzl_packages
332334
]),
333335
"%%ALL_WHL_REQUIREMENTS_BY_PACKAGE%%": _format_dict(_repr_dict({
334-
name: macro_tmpl.format(p, "whl")
335-
for name, p in bzl_packages.items()
336+
p: macro_tmpl.format(p, "whl")
337+
for p in bzl_packages
336338
})),
337339
"%%ANNOTATIONS%%": _format_dict(_repr_dict(annotations)),
338340
"%%CONFIG%%": _format_dict(_repr_dict(config)),
339341
"%%EXTRA_PIP_ARGS%%": json.encode(options),
340-
"%%IMPORTS%%": "\n".join(sorted(imports)),
342+
"%%IMPORTS%%": "\n".join(imports),
341343
"%%MACRO_TMPL%%": macro_tmpl,
342344
"%%NAME%%": rctx.attr.name,
343345
"%%PACKAGES%%": _format_repr_list(

python/pip_install/pip_repository_requirements.bzl.tmpl

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,22 @@ _packages = %%PACKAGES%%
1818
_config = %%CONFIG%%
1919
_annotations = %%ANNOTATIONS%%
2020

21-
def _clean_name(name):
22-
return name.replace("-", "_").replace(".", "_").lower()
23-
2421
def requirement(name):
25-
return "%%MACRO_TMPL%%".format(_clean_name(name), "pkg")
22+
return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "pkg")
2623

2724
def whl_requirement(name):
28-
return "%%MACRO_TMPL%%".format(_clean_name(name), "whl")
25+
return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "whl")
2926

3027
def data_requirement(name):
31-
return "%%MACRO_TMPL%%".format(_clean_name(name), "data")
28+
return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "data")
3229

3330
def dist_info_requirement(name):
34-
return "%%MACRO_TMPL%%".format(_clean_name(name), "dist_info")
31+
return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "dist_info")
3532

3633
def entry_point(pkg, script = None):
3734
if not script:
3835
script = pkg
39-
return "@%%NAME%%_" + _clean_name(pkg) + "//:rules_python_wheel_entry_point_" + script
36+
return "@%%NAME%%_" + pip_utils.normalize_name(pkg) + "//:rules_python_wheel_entry_point_" + script
4037

4138
def _get_annotation(requirement):
4239
# This expects to parse `setuptools==58.2.0 --hash=sha256:2551203ae6955b9876741a26ab3e767bb3242dafe86a32a749ea0d78b6792f11`

python/private/bzlmod/pip_repository.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def _pip_repository_impl(rctx):
5656
for p in bzl_packages
5757
}),
5858
"%%MACRO_TMPL%%": macro_tmpl,
59-
"%%NAME%%": rctx.attr.name,
59+
"%%NAME%%": rctx.attr.repo_name,
6060
})
6161

6262
pip_repository_attrs = {

python/private/bzlmod/requirements.bzl.tmpl

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
@generated by rules_python pip.parse bzlmod extension.
44
"""
55

6+
load("@rules_python//python:pip.bzl", "pip_utils")
7+
68
all_requirements = %%ALL_REQUIREMENTS%%
79

810
all_whl_requirements_by_package = %%ALL_WHL_REQUIREMENTS_BY_PACKAGE%%
@@ -11,39 +13,34 @@ all_whl_requirements = all_whl_requirements_by_package.values()
1113

1214
all_data_requirements = %%ALL_DATA_REQUIREMENTS%%
1315

14-
def _clean_name(name):
15-
return name.replace("-", "_").replace(".", "_").lower()
16-
1716
def requirement(name):
18-
return "%%MACRO_TMPL%%".format(_clean_name(name), "pkg")
17+
return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "pkg")
1918

2019
def whl_requirement(name):
21-
return "%%MACRO_TMPL%%".format(_clean_name(name), "whl")
20+
return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "whl")
2221

2322
def data_requirement(name):
24-
return "%%MACRO_TMPL%%".format(_clean_name(name), "data")
23+
return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "data")
2524

2625
def dist_info_requirement(name):
27-
return "%%MACRO_TMPL%%".format(_clean_name(name), "dist_info")
26+
return "%%MACRO_TMPL%%".format(pip_utils.normalize_name(name), "dist_info")
2827

2928
def entry_point(pkg, script = None):
3029
"""entry_point returns the target of the canonical label of the package entrypoints.
3130
"""
32-
if not script:
33-
script = pkg
31+
actual_script = script or pkg
32+
3433
fail("""Please replace this instance of entry_point with the following:
3534

3635
```
3736
load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
3837

3938
py_console_script_binary(
4039
name = "{pkg}",
41-
pkg = "@%%{pkg_label}",
42-
script = "{script}",
40+
pkg = "@%%NAME%%//{pkg}",{script}
4341
)
4442
```
4543
""".format(
46-
pkg = _clean_name(pkg),
47-
pkg_label = "%%MACRO_TMPL%%".format(_clean_name(pkg), "pkg"),
48-
script = script,
44+
pkg = pip_utils.normalize_name(pkg),
45+
script = "" if not script else "\n script = \"%s\"," % actual_script,
4946
))

0 commit comments

Comments
 (0)