Skip to content

Commit

Permalink
Don't fail when the same req file is included more than once
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed Oct 27, 2024
1 parent 4f6aeb1 commit 7be54ce
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 9 deletions.
1 change: 1 addition & 0 deletions news/13046.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow multiple inclusion of the same requirements file again.
23 changes: 14 additions & 9 deletions src/pip/_internal/req/req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,19 +324,20 @@ def __init__(
) -> None:
self._session = session
self._line_parser = line_parser
self._parsed_files: dict[str, Optional[str]] = {}

def parse(
self, filename: str, constraint: bool
) -> Generator[ParsedLine, None, None]:
"""Parse a given file, yielding parsed lines."""
self._parsed_files[os.path.abspath(filename)] = (
None # The primary requirements file passed
yield from self._parse_and_recurse(
filename, constraint, [{os.path.abspath(filename): None}]
)
yield from self._parse_and_recurse(filename, constraint)

def _parse_and_recurse(
self, filename: str, constraint: bool
self,
filename: str,
constraint: bool,
parsed_files_stack: List[Dict[str, Optional[str]]],
) -> Generator[ParsedLine, None, None]:
for line in self._parse_file(filename, constraint):
if not line.is_requirement and (
Expand Down Expand Up @@ -364,8 +365,9 @@ def _parse_and_recurse(
req_path,
)
)
if req_path in self._parsed_files:
initial_file = self._parsed_files[req_path]
parsed_files = parsed_files_stack[0]
if req_path in parsed_files:
initial_file = parsed_files[req_path]
tail = (
f" and again in {initial_file}"
if initial_file is not None
Expand All @@ -375,8 +377,11 @@ def _parse_and_recurse(
f"{req_path} recursively references itself in {filename}{tail}"
)
# Keeping a track where was each file first included in
self._parsed_files[req_path] = filename
yield from self._parse_and_recurse(req_path, nested_constraint)
new_parsed_files = parsed_files.copy()
new_parsed_files[req_path] = filename
yield from self._parse_and_recurse(
req_path, nested_constraint, [new_parsed_files, *parsed_files_stack]
)
else:
yield line

Expand Down
13 changes: 13 additions & 0 deletions tests/unit/test_req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,19 @@ def test_nested_constraints_file(
assert reqs[0].name == req_name
assert reqs[0].constraint

def test_repeated_requirement_files(
self, tmp_path: Path, session: PipSession
) -> None:
# Test that the same requirements file can be included multiple times
# as long as there is no recursion. https://github.com/pypa/pip/issues/13046
tmp_path.joinpath("a.txt").write_text("requests")
tmp_path.joinpath("b.txt").write_text("-r a.txt")
tmp_path.joinpath("c.txt").write_text("-r a.txt\n-r b.txt")
parsed = parse_requirements(
filename=os.fspath(tmp_path.joinpath("c.txt")), session=session
)
assert [r.requirement for r in parsed] == ["requests", "requests"]

def test_recursive_requirements_file(
self, tmpdir: Path, session: PipSession
) -> None:
Expand Down

0 comments on commit 7be54ce

Please sign in to comment.