Skip to content

Commit

Permalink
Adds support for multiple requirements for a single package name (#12232
Browse files Browse the repository at this point in the history
)

Closes #12186 by grouping requirements that reference the same pip dependency name.

[ci skip-build-wheels]
  • Loading branch information
Christopher Neugebauer authored Jun 25, 2021
1 parent 2317eb9 commit 3aa925b
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 6 deletions.
15 changes: 10 additions & 5 deletions src/python/pants/backend/python/macros/python_requirements.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
import os
from itertools import groupby
from pathlib import Path
from typing import Iterable, Mapping, Optional

Expand Down Expand Up @@ -69,16 +70,20 @@ def __call__(
requirements = parse_requirements_file(
req_file.read_text(), rel_path=str(req_file.relative_to(get_buildroot()))
)
for parsed_req in requirements:

grouped_requirements = groupby(requirements, lambda parsed_req: parsed_req.project_name)

for project_name, parsed_reqs_ in grouped_requirements:
parsed_reqs = list(parsed_reqs_)
req_module_mapping = (
{parsed_req.project_name: module_mapping[parsed_req.project_name]}
if module_mapping and parsed_req.project_name in module_mapping
{project_name: module_mapping[project_name]}
if module_mapping and project_name in module_mapping
else None
)
self._parse_context.create_object(
"python_requirement_library",
name=parsed_req.project_name,
requirements=[parsed_req],
name=project_name,
requirements=parsed_reqs,
module_mapping=req_module_mapping,
dependencies=[requirements_dep],
)
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ def assert_python_requirements(
Targets,
[Specs(AddressSpecs([DescendantAddresses("")]), FilesystemSpecs([]))],
)

assert {expected_file_dep, *expected_targets} == set(targets)


def test_requirements_txt(rule_runner: RuleRunner) -> None:
"""This tests that we correctly create a new python_requirement_library for each entry in a
requirements.txt file.
requirements.txt file, where each dependency is unique.
Some edge cases:
* We ignore comments and options (values that start with `--`).
Expand Down Expand Up @@ -104,6 +105,55 @@ def test_requirements_txt(rule_runner: RuleRunner) -> None:
)


def test_multiple_versions(rule_runner: RuleRunner) -> None:
"""This tests that we correctly create a new python_requirement_library for each unique
dependency name in a requirements.txt file, grouping duplicated dependency names to handle
multiple requirement strings per PEP 508."""

assert_python_requirements(
rule_runner,
"python_requirements(module_mapping={'ansicolors': ['colors']})",
dedent(
"""\
Django>=3.2
Django==3.2.7
confusedmonkey==86
repletewateringcan>=7
"""
),
expected_file_dep=PythonRequirementsFile(
{"sources": ["requirements.txt"]},
Address("", target_name="requirements.txt"),
),
expected_targets=[
PythonRequirementLibrary(
{
"dependencies": [":requirements.txt"],
"requirements": [
Requirement.parse("Django>=3.2"),
Requirement.parse("Django==3.2.7"),
],
},
Address("", target_name="Django"),
),
PythonRequirementLibrary(
{
"dependencies": [":requirements.txt"],
"requirements": [Requirement.parse("confusedmonkey==86")],
},
Address("", target_name="confusedmonkey"),
),
PythonRequirementLibrary(
{
"dependencies": [":requirements.txt"],
"requirements": [Requirement.parse("repletewateringcan>=7")],
},
Address("", target_name="repletewateringcan"),
),
],
)


def test_invalid_req(rule_runner: RuleRunner) -> None:
"""Test that we give a nice error message."""
fake_file_tgt = PythonRequirementsFile({"sources": ["doesnt matter"]}, Address("doesnt_matter"))
Expand Down

0 comments on commit 3aa925b

Please sign in to comment.