Skip to content

Commit 03cc4ca

Browse files
authored
Merge branch 'main' into main
2 parents d73c673 + 102d818 commit 03cc4ca

File tree

12 files changed

+93
-15
lines changed

12 files changed

+93
-15
lines changed

news/12653.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Detect recursively referencing requirements files and help users identify
2+
the source.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ select = [
181181
"PLR0",
182182
"W",
183183
"RUF100",
184-
"UP032",
184+
"UP",
185185
]
186186

187187
[tool.ruff.lint.isort]

src/pip/_internal/cli/index_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class SessionCommandMixin(CommandContextMixIn):
5454

5555
def __init__(self) -> None:
5656
super().__init__()
57-
self._session: Optional["PipSession"] = None
57+
self._session: Optional[PipSession] = None
5858

5959
@classmethod
6060
def _get_index_urls(cls, options: Values) -> Optional[List[str]]:

src/pip/_internal/commands/list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def run(self, options: Values, args: List[str]) -> int:
176176
if options.excludes:
177177
skip.update(canonicalize_name(n) for n in options.excludes)
178178

179-
packages: "_ProcessedDists" = [
179+
packages: _ProcessedDists = [
180180
cast("_DistWithLatestInfo", d)
181181
for d in get_environment(options.path).iter_installed_distributions(
182182
local_only=options.local,

src/pip/_internal/commands/search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
8989
packages with the list of versions stored inline. This converts the
9090
list from pypi into one we can use.
9191
"""
92-
packages: Dict[str, "TransformedHit"] = OrderedDict()
92+
packages: Dict[str, TransformedHit] = OrderedDict()
9393
for hit in hits:
9494
name = hit["name"]
9595
summary = hit["summary"]

src/pip/_internal/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class HashErrors(InstallationError):
429429
"""Multiple HashError instances rolled into one for reporting"""
430430

431431
def __init__(self) -> None:
432-
self.errors: List["HashError"] = []
432+
self.errors: List[HashError] = []
433433

434434
def append(self, error: "HashError") -> None:
435435
self.errors.append(error)

src/pip/_internal/req/constructors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requireme
8080
assert (
8181
pre is not None and post is not None
8282
), f"regex group selection for requirement {req} failed, this should never happen"
83-
extras: str = "[%s]" % ",".join(sorted(new_extras)) if new_extras else ""
83+
extras: str = "[{}]".format(",".join(sorted(new_extras)) if new_extras else "")
8484
return get_requirement(f"{pre}{extras}{post}")
8585

8686

src/pip/_internal/req/req_file.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,15 @@ def __init__(
324324
) -> None:
325325
self._session = session
326326
self._line_parser = line_parser
327+
self._parsed_files: dict[str, Optional[str]] = {}
327328

328329
def parse(
329330
self, filename: str, constraint: bool
330331
) -> Generator[ParsedLine, None, None]:
331332
"""Parse a given file, yielding parsed lines."""
333+
self._parsed_files[os.path.abspath(filename)] = (
334+
None # The primary requirements file passed
335+
)
332336
yield from self._parse_and_recurse(filename, constraint)
333337

334338
def _parse_and_recurse(
@@ -353,11 +357,25 @@ def _parse_and_recurse(
353357
# original file and nested file are paths
354358
elif not SCHEME_RE.search(req_path):
355359
# do a join so relative paths work
356-
req_path = os.path.join(
357-
os.path.dirname(filename),
358-
req_path,
360+
# and then abspath so that we can identify recursive references
361+
req_path = os.path.abspath(
362+
os.path.join(
363+
os.path.dirname(filename),
364+
req_path,
365+
)
359366
)
360-
367+
if req_path in self._parsed_files:
368+
initial_file = self._parsed_files[req_path]
369+
tail = (
370+
f" and again in {initial_file}"
371+
if initial_file is not None
372+
else ""
373+
)
374+
raise RequirementsFileParseError(
375+
f"{req_path} recursively references itself in {filename}{tail}"
376+
)
377+
# Keeping a track where was each file first included in
378+
self._parsed_files[req_path] = filename
361379
yield from self._parse_and_recurse(req_path, nested_constraint)
362380
else:
363381
yield line

tests/functional/test_search.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def test_pypi_xml_transformation() -> None:
4545
"version": "1.0",
4646
},
4747
]
48-
expected: List["TransformedHit"] = [
48+
expected: List[TransformedHit] = [
4949
{
5050
"versions": ["1.0", "2.0"],
5151
"name": "foo",
@@ -159,7 +159,7 @@ def test_latest_prerelease_install_message(
159159
"""
160160
Test documentation for installing pre-release packages is displayed
161161
"""
162-
hits: List["TransformedHit"] = [
162+
hits: List[TransformedHit] = [
163163
{
164164
"name": "ni",
165165
"summary": "For knights who say Ni!",
@@ -188,7 +188,7 @@ def test_search_print_results_should_contain_latest_versions(
188188
"""
189189
Test that printed search results contain the latest package versions
190190
"""
191-
hits: List["TransformedHit"] = [
191+
hits: List[TransformedHit] = [
192192
{
193193
"name": "testlib1",
194194
"summary": "Test library 1.",

tests/unit/resolution_resolvelib/test_provider.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def build_requirement_information(
1919
install_requirement = install_req_from_req_string(name)
2020
# RequirementInformation is typed as a tuple, but it is a namedtupled.
2121
# https://github.com/sarugaku/resolvelib/blob/7bc025aa2a4e979597c438ad7b17d2e8a08a364e/src/resolvelib/resolvers.pyi#L20-L22
22-
requirement_information: "PreferenceInformation" = RequirementInformation(
22+
requirement_information: PreferenceInformation = RequirementInformation(
2323
requirement=SpecifierRequirement(install_requirement), # type: ignore[call-arg]
2424
parent=parent,
2525
)

0 commit comments

Comments
 (0)