Skip to content

Commit 7be54ce

Browse files
committed
Don't fail when the same req file is included more than once
1 parent 4f6aeb1 commit 7be54ce

File tree

3 files changed

+28
-9
lines changed

3 files changed

+28
-9
lines changed

news/13046.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow multiple inclusion of the same requirements file again.

src/pip/_internal/req/req_file.py

+14-9
Original file line numberDiff line numberDiff line change
@@ -324,19 +324,20 @@ def __init__(
324324
) -> None:
325325
self._session = session
326326
self._line_parser = line_parser
327-
self._parsed_files: dict[str, Optional[str]] = {}
328327

329328
def parse(
330329
self, filename: str, constraint: bool
331330
) -> Generator[ParsedLine, None, None]:
332331
"""Parse a given file, yielding parsed lines."""
333-
self._parsed_files[os.path.abspath(filename)] = (
334-
None # The primary requirements file passed
332+
yield from self._parse_and_recurse(
333+
filename, constraint, [{os.path.abspath(filename): None}]
335334
)
336-
yield from self._parse_and_recurse(filename, constraint)
337335

338336
def _parse_and_recurse(
339-
self, filename: str, constraint: bool
337+
self,
338+
filename: str,
339+
constraint: bool,
340+
parsed_files_stack: List[Dict[str, Optional[str]]],
340341
) -> Generator[ParsedLine, None, None]:
341342
for line in self._parse_file(filename, constraint):
342343
if not line.is_requirement and (
@@ -364,8 +365,9 @@ def _parse_and_recurse(
364365
req_path,
365366
)
366367
)
367-
if req_path in self._parsed_files:
368-
initial_file = self._parsed_files[req_path]
368+
parsed_files = parsed_files_stack[0]
369+
if req_path in parsed_files:
370+
initial_file = parsed_files[req_path]
369371
tail = (
370372
f" and again in {initial_file}"
371373
if initial_file is not None
@@ -375,8 +377,11 @@ def _parse_and_recurse(
375377
f"{req_path} recursively references itself in {filename}{tail}"
376378
)
377379
# Keeping a track where was each file first included in
378-
self._parsed_files[req_path] = filename
379-
yield from self._parse_and_recurse(req_path, nested_constraint)
380+
new_parsed_files = parsed_files.copy()
381+
new_parsed_files[req_path] = filename
382+
yield from self._parse_and_recurse(
383+
req_path, nested_constraint, [new_parsed_files, *parsed_files_stack]
384+
)
380385
else:
381386
yield line
382387

tests/unit/test_req_file.py

+13
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,19 @@ def test_nested_constraints_file(
347347
assert reqs[0].name == req_name
348348
assert reqs[0].constraint
349349

350+
def test_repeated_requirement_files(
351+
self, tmp_path: Path, session: PipSession
352+
) -> None:
353+
# Test that the same requirements file can be included multiple times
354+
# as long as there is no recursion. https://github.com/pypa/pip/issues/13046
355+
tmp_path.joinpath("a.txt").write_text("requests")
356+
tmp_path.joinpath("b.txt").write_text("-r a.txt")
357+
tmp_path.joinpath("c.txt").write_text("-r a.txt\n-r b.txt")
358+
parsed = parse_requirements(
359+
filename=os.fspath(tmp_path.joinpath("c.txt")), session=session
360+
)
361+
assert [r.requirement for r in parsed] == ["requests", "requests"]
362+
350363
def test_recursive_requirements_file(
351364
self, tmpdir: Path, session: PipSession
352365
) -> None:

0 commit comments

Comments
 (0)