Skip to content

Commit b0db732

Browse files
committed
feat(pypi/parse_requirements): get dists by version when no hash provided
Modify _add_dists to fetch files by version if no sha256 hashes are available, and add corresponding unit tests. - Updated _add_dists to check for the presence of hashes before fetching by hash. - Added an `else` condition that iterates through index URLs to find wheels and source distributions matching the requirement's version when no hashes are provided. - Added unit tests in //tests/pypi/parse_requirements to verify version-based fetching.
1 parent e6f79dc commit b0db732

File tree

3 files changed

+101
-21
lines changed

3 files changed

+101
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Unreleased changes template.
6161

6262
{#v0-0-0-added}
6363
### Added
64-
* Nothing added.
64+
* (pypi) Added version-based fetching in `_add_dists` when SHA256 hashes are unavailable, with unit tests in `//tests/pypi/parse_requirements`[#2023](https://github.com/bazel-contrib/rules_python/issues/2023).
6565

6666
{#v0-0-0-removed}
6767
### Removed

python/private/pypi/parse_requirements.bzl

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -315,26 +315,46 @@ def _add_dists(*, requirement, index_urls, logger = None):
315315
whls = []
316316
sdist = None
317317

318-
# TODO @aignas 2024-05-22: it is in theory possible to add all
319-
# requirements by version instead of by sha256. This may be useful
320-
# for some projects.
321-
for sha256 in requirement.srcs.shas:
322-
# For now if the artifact is marked as yanked we just ignore it.
323-
#
324-
# See https://packaging.python.org/en/latest/specifications/simple-repository-api/#adding-yank-support-to-the-simple-api
325-
326-
maybe_whl = index_urls.whls.get(sha256)
327-
if maybe_whl and not maybe_whl.yanked:
328-
whls.append(maybe_whl)
329-
continue
330-
331-
maybe_sdist = index_urls.sdists.get(sha256)
332-
if maybe_sdist and not maybe_sdist.yanked:
333-
sdist = maybe_sdist
334-
continue
335-
336-
if logger:
337-
logger.warn(lambda: "Could not find a whl or an sdist with sha256={}".format(sha256))
318+
# First try to find distributions by SHA256 if provided
319+
if requirement.srcs.shas:
320+
for sha256 in requirement.srcs.shas:
321+
# For now if the artifact is marked as yanked we just ignore it.
322+
#
323+
# See https://packaging.python.org/en/latest/specifications/simple-repository-api/#adding-yank-support-to-the-simple-api
324+
325+
maybe_whl = index_urls.whls.get(sha256)
326+
if maybe_whl and not maybe_whl.yanked:
327+
whls.append(maybe_whl)
328+
continue
329+
330+
maybe_sdist = index_urls.sdists.get(sha256)
331+
if maybe_sdist and not maybe_sdist.yanked:
332+
sdist = maybe_sdist
333+
continue
334+
335+
if logger:
336+
logger.warn(lambda: "Could not find a whl or an sdist with sha256={}".format(sha256))
337+
else:
338+
# If no SHA256s provided, try to find distributions by version
339+
version = requirement.srcs.version
340+
if version:
341+
# Look for wheels matching the version
342+
for whl in index_urls.whls.values():
343+
# Extract package name from wheel filename (format: package_name-version-python_tag-abi_tag-platform_tag.whl)
344+
whl_name = whl.filename.split("-")[0]
345+
if whl_name == requirement.distribution and whl.version == version and not whl.yanked:
346+
whls.append(whl)
347+
348+
# Look for source distributions matching the version
349+
for sdist_dist in index_urls.sdists.values():
350+
# Extract package name from sdist filename (format: package_name-version.tar.gz or package_name-version.zip)
351+
sdist_name = sdist_dist.filename.split("-")[0]
352+
if sdist_name == requirement.distribution and sdist_dist.version == version and not sdist_dist.yanked:
353+
sdist = sdist_dist
354+
break
355+
356+
if not whls and not sdist and logger:
357+
logger.warn(lambda: "Could not find any distributions for version={}".format(version))
338358

339359
yanked = {}
340360
for dist in whls + [sdist]:

tests/pypi/parse_requirements/parse_requirements_tests.bzl

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ foo[extra]==0.0.1 --hash=sha256:deadbeef
6161
"requirements_marker": """\
6262
foo[extra]==0.0.1 ;marker --hash=sha256:deadbeef
6363
bar==0.0.1 --hash=sha256:deadbeef
64+
""",
65+
"requirements_optional_hash": """
66+
foo==0.0.4 @ https://example.org/foo-0.0.4.whl
67+
foo==0.0.5 @ https://example.org/foo-0.0.5.whl --hash=sha256:deadbeef
6468
""",
6569
"requirements_osx": """\
6670
foo==0.0.3 --hash=sha256:deadbaaf
@@ -563,6 +567,62 @@ def _test_different_package_version(env):
563567

564568
_tests.append(_test_different_package_version)
565569

570+
def _test_optional_hash(env):
571+
got = parse_requirements(
572+
ctx = _mock_ctx(),
573+
requirements_by_platform = {
574+
"requirements_optional_hash": ["linux_x86_64"],
575+
},
576+
)
577+
env.expect.that_dict(got).contains_exactly({
578+
"foo": [
579+
struct(
580+
distribution = "foo",
581+
extra_pip_args = [],
582+
sdist = None,
583+
is_exposed = True,
584+
srcs = struct(
585+
marker = "",
586+
requirement = "foo==0.0.4 @ https://example.org/foo-0.0.4.whl",
587+
requirement_line = "foo==0.0.4 @ https://example.org/foo-0.0.4.whl",
588+
shas = [],
589+
version = "0.0.4",
590+
url = "https://example.org/foo-0.0.4.whl",
591+
),
592+
target_platforms = ["linux_x86_64"],
593+
whls = [struct(
594+
url = "https://example.org/foo-0.0.4.whl",
595+
filename = "foo-0.0.4.whl",
596+
sha256 = "",
597+
yanked = False,
598+
)],
599+
),
600+
struct(
601+
distribution = "foo",
602+
extra_pip_args = [],
603+
sdist = None,
604+
is_exposed = True,
605+
srcs = struct(
606+
marker = "",
607+
requirement = "foo==0.0.5 @ https://example.org/foo-0.0.5.whl --hash=sha256:deadbeef",
608+
requirement_line = "foo==0.0.5 @ https://example.org/foo-0.0.5.whl --hash=sha256:deadbeef",
609+
shas = ["deadbeef"],
610+
version = "0.0.5",
611+
url = "https://example.org/foo-0.0.5.whl",
612+
),
613+
target_platforms = ["linux_x86_64"],
614+
whls = [struct(
615+
url = "https://example.org/foo-0.0.5.whl",
616+
filename = "foo-0.0.5.whl",
617+
sha256 = "deadbeef",
618+
yanked = False,
619+
)],
620+
),
621+
],
622+
})
623+
624+
_tests.append(_test_optional_hash)
625+
566626
def parse_requirements_test_suite(name):
567627
"""Create the test suite.
568628

0 commit comments

Comments
 (0)