Skip to content

Commit fb68794

Browse files
authored
Merge pull request #8026 from sbidoul/requested-sbi
2 parents b966e13 + 21bf4f5 commit fb68794

File tree

12 files changed

+164
-22
lines changed

12 files changed

+164
-22
lines changed

news/7811.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Generate PEP 376 REQUESTED metadata for user supplied requirements installed
2+
by pip.

src/pip/_internal/cli/req_command.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -309,25 +309,25 @@ def get_requirements(
309309
req_to_add = install_req_from_parsed_requirement(
310310
parsed_req,
311311
isolated=options.isolated_mode,
312+
user_supplied=False,
312313
)
313-
req_to_add.is_direct = True
314314
requirements.append(req_to_add)
315315

316316
for req in args:
317317
req_to_add = install_req_from_line(
318318
req, None, isolated=options.isolated_mode,
319319
use_pep517=options.use_pep517,
320+
user_supplied=True,
320321
)
321-
req_to_add.is_direct = True
322322
requirements.append(req_to_add)
323323

324324
for req in options.editables:
325325
req_to_add = install_req_from_editable(
326326
req,
327+
user_supplied=True,
327328
isolated=options.isolated_mode,
328329
use_pep517=options.use_pep517,
329330
)
330-
req_to_add.is_direct = True
331331
requirements.append(req_to_add)
332332

333333
# NOTE: options.require_hashes may be set if --require-hashes is True
@@ -338,9 +338,9 @@ def get_requirements(
338338
req_to_add = install_req_from_parsed_requirement(
339339
parsed_req,
340340
isolated=options.isolated_mode,
341-
use_pep517=options.use_pep517
341+
use_pep517=options.use_pep517,
342+
user_supplied=True,
342343
)
343-
req_to_add.is_direct = True
344344
requirements.append(req_to_add)
345345

346346
# If any requirement has hash options, enable hash checking.

src/pip/_internal/operations/install/wheel.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ def install_unpacked_wheel(
339339
pycompile=True, # type: bool
340340
warn_script_location=True, # type: bool
341341
direct_url=None, # type: Optional[DirectUrl]
342+
requested=False, # type: bool
342343
):
343344
# type: (...) -> None
344345
"""Install a wheel.
@@ -645,6 +646,13 @@ def _generate_file(path, **kwargs):
645646
direct_url_file.write(direct_url.to_json().encode("utf-8"))
646647
generated.append(direct_url_path)
647648

649+
# Record the REQUESTED file
650+
if requested:
651+
requested_path = os.path.join(dest_info_dir, 'REQUESTED')
652+
with open(requested_path, "w"):
653+
pass
654+
generated.append(requested_path)
655+
648656
# Record details of all files installed
649657
record_path = os.path.join(dest_info_dir, 'RECORD')
650658
with open(record_path, **csv_io_kwargs('r')) as record_file:
@@ -671,6 +679,7 @@ def install_wheel(
671679
warn_script_location=True, # type: bool
672680
_temp_dir_for_testing=None, # type: Optional[str]
673681
direct_url=None, # type: Optional[DirectUrl]
682+
requested=False, # type: bool
674683
):
675684
# type: (...) -> None
676685
with TempDirectory(
@@ -686,4 +695,5 @@ def install_wheel(
686695
pycompile=pycompile,
687696
warn_script_location=warn_script_location,
688697
direct_url=direct_url,
698+
requested=requested,
689699
)

src/pip/_internal/req/constructors.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ def install_req_from_editable(
219219
use_pep517=None, # type: Optional[bool]
220220
isolated=False, # type: bool
221221
options=None, # type: Optional[Dict[str, Any]]
222-
constraint=False # type: bool
222+
constraint=False, # type: bool
223+
user_supplied=False, # type: bool
223224
):
224225
# type: (...) -> InstallRequirement
225226

@@ -228,6 +229,7 @@ def install_req_from_editable(
228229
return InstallRequirement(
229230
parts.requirement,
230231
comes_from=comes_from,
232+
user_supplied=user_supplied,
231233
editable=True,
232234
link=parts.link,
233235
constraint=constraint,
@@ -382,6 +384,7 @@ def install_req_from_line(
382384
options=None, # type: Optional[Dict[str, Any]]
383385
constraint=False, # type: bool
384386
line_source=None, # type: Optional[str]
387+
user_supplied=False, # type: bool
385388
):
386389
# type: (...) -> InstallRequirement
387390
"""Creates an InstallRequirement from a name, which might be a
@@ -400,14 +403,16 @@ def install_req_from_line(
400403
hash_options=options.get("hashes", {}) if options else {},
401404
constraint=constraint,
402405
extras=parts.extras,
406+
user_supplied=user_supplied,
403407
)
404408

405409

406410
def install_req_from_req_string(
407411
req_string, # type: str
408412
comes_from=None, # type: Optional[InstallRequirement]
409413
isolated=False, # type: bool
410-
use_pep517=None # type: Optional[bool]
414+
use_pep517=None, # type: Optional[bool]
415+
user_supplied=False, # type: bool
411416
):
412417
# type: (...) -> InstallRequirement
413418
try:
@@ -429,14 +434,19 @@ def install_req_from_req_string(
429434
)
430435

431436
return InstallRequirement(
432-
req, comes_from, isolated=isolated, use_pep517=use_pep517
437+
req,
438+
comes_from,
439+
isolated=isolated,
440+
use_pep517=use_pep517,
441+
user_supplied=user_supplied,
433442
)
434443

435444

436445
def install_req_from_parsed_requirement(
437446
parsed_req, # type: ParsedRequirement
438447
isolated=False, # type: bool
439-
use_pep517=None # type: Optional[bool]
448+
use_pep517=None, # type: Optional[bool]
449+
user_supplied=False, # type: bool
440450
):
441451
# type: (...) -> InstallRequirement
442452
if parsed_req.is_editable:
@@ -446,6 +456,7 @@ def install_req_from_parsed_requirement(
446456
use_pep517=use_pep517,
447457
constraint=parsed_req.constraint,
448458
isolated=isolated,
459+
user_supplied=user_supplied,
449460
)
450461

451462
else:
@@ -457,5 +468,6 @@ def install_req_from_parsed_requirement(
457468
options=parsed_req.options,
458469
constraint=parsed_req.constraint,
459470
line_source=parsed_req.line_source,
471+
user_supplied=user_supplied,
460472
)
461473
return req

src/pip/_internal/req/req_install.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ def __init__(
111111
global_options=None, # type: Optional[List[str]]
112112
hash_options=None, # type: Optional[Dict[str, List[str]]]
113113
constraint=False, # type: bool
114-
extras=() # type: Iterable[str]
114+
extras=(), # type: Iterable[str]
115+
user_supplied=False, # type: bool
115116
):
116117
# type: (...) -> None
117118
assert req is None or isinstance(req, Requirement), req
@@ -172,7 +173,10 @@ def __init__(
172173
self.hash_options = hash_options if hash_options else {}
173174
# Set to True after successful preparation of this requirement
174175
self.prepared = False
175-
self.is_direct = False
176+
# User supplied requirement are explicitly requested for installation
177+
# by the user via CLI arguments or requirements files, as opposed to,
178+
# e.g. dependencies, extras or constraints.
179+
self.user_supplied = user_supplied
176180

177181
# Set by the legacy resolver when the requirement has been downloaded
178182
# TODO: This introduces a strong coupling between the resolver and the
@@ -820,6 +824,7 @@ def install(
820824
pycompile=pycompile,
821825
warn_script_location=warn_script_location,
822826
direct_url=direct_url,
827+
requested=self.user_supplied,
823828
)
824829
self.install_succeeded = True
825830
return

src/pip/_internal/req/req_set.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,8 @@ def add_requirement(
107107
)
108108

109109
# This next bit is really a sanity check.
110-
assert install_req.is_direct == (parent_req_name is None), (
111-
"a direct req shouldn't have a parent and also, "
112-
"a non direct req should have a parent"
110+
assert not install_req.user_supplied or parent_req_name is None, (
111+
"a user supplied req shouldn't have a parent"
113112
)
114113

115114
# Unnamed requirements are scanned again and the requirement won't be
@@ -165,6 +164,10 @@ def add_requirement(
165164
# If we're now installing a constraint, mark the existing
166165
# object for real installation.
167166
existing_req.constraint = False
167+
# If we're now installing a user supplied requirement,
168+
# mark the existing object as such.
169+
if install_req.user_supplied:
170+
existing_req.user_supplied = True
168171
existing_req.extras = tuple(sorted(
169172
set(existing_req.extras) | set(install_req.extras)
170173
))

src/pip/_internal/resolution/legacy/resolver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def _is_upgrade_allowed(self, req):
195195
return True
196196
else:
197197
assert self.upgrade_strategy == "only-if-needed"
198-
return req.is_direct
198+
return req.user_supplied or req.constraint
199199

200200
def _set_req_to_reinstall(self, req):
201201
# type: (InstallRequirement) -> None
@@ -419,7 +419,7 @@ def add_req(subreq, extras_requested):
419419
# 'unnamed' requirements will get added here
420420
# 'unnamed' requirements can only come from being directly
421421
# provided by the user.
422-
assert req_to_install.is_direct
422+
assert req_to_install.user_supplied
423423
requirement_set.add_requirement(
424424
req_to_install, parent_req_name=None,
425425
)

src/pip/_internal/resolution/resolvelib/candidates.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def make_install_req_from_link(link, template):
4848
line = link.url
4949
ireq = install_req_from_line(
5050
line,
51+
user_supplied=template.user_supplied,
5152
comes_from=template.comes_from,
5253
use_pep517=template.use_pep517,
5354
isolated=template.isolated,
@@ -68,6 +69,7 @@ def make_install_req_from_editable(link, template):
6869
assert template.editable, "template not editable"
6970
return install_req_from_editable(
7071
link.url,
72+
user_supplied=template.user_supplied,
7173
comes_from=template.comes_from,
7274
use_pep517=template.use_pep517,
7375
isolated=template.isolated,
@@ -91,6 +93,7 @@ def make_install_req_from_dist(dist, template):
9193
line = "{}=={}".format(project_name, dist.parsed_version)
9294
ireq = install_req_from_line(
9395
line,
96+
user_supplied=template.user_supplied,
9497
comes_from=template.comes_from,
9598
use_pep517=template.use_pep517,
9699
isolated=template.isolated,

src/pip/_internal/resolution/resolvelib/resolver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def resolve(self, root_reqs, check_supported_wheels):
116116
else:
117117
constraints[name] = req.specifier
118118
else:
119-
if req.is_direct and req.name:
119+
if req.user_supplied and req.name:
120120
user_requested.add(canonicalize_name(req.name))
121121
r = self.factory.make_requirement_from_install_req(
122122
req, requested_extras=(),
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
def _assert_requested_present(script, result, name, version):
2+
dist_info = script.site_packages / name + "-" + version + ".dist-info"
3+
requested = dist_info / "REQUESTED"
4+
assert dist_info in result.files_created
5+
assert requested in result.files_created
6+
7+
8+
def _assert_requested_absent(script, result, name, version):
9+
dist_info = script.site_packages / name + "-" + version + ".dist-info"
10+
requested = dist_info / "REQUESTED"
11+
assert dist_info in result.files_created
12+
assert requested not in result.files_created
13+
14+
15+
def test_install_requested_basic(script, data, with_wheel):
16+
result = script.pip(
17+
"install", "--no-index", "-f", data.find_links, "require_simple"
18+
)
19+
_assert_requested_present(script, result, "require_simple", "1.0")
20+
# dependency is not REQUESTED
21+
_assert_requested_absent(script, result, "simple", "3.0")
22+
23+
24+
def test_install_requested_requirements(script, data, with_wheel):
25+
script.scratch_path.joinpath("requirements.txt").write_text(
26+
"require_simple\n"
27+
)
28+
result = script.pip(
29+
"install",
30+
"--no-index",
31+
"-f",
32+
data.find_links,
33+
"-r",
34+
script.scratch_path / "requirements.txt",
35+
)
36+
_assert_requested_present(script, result, "require_simple", "1.0")
37+
_assert_requested_absent(script, result, "simple", "3.0")
38+
39+
40+
def test_install_requested_dep_in_requirements(script, data, with_wheel):
41+
script.scratch_path.joinpath("requirements.txt").write_text(
42+
"require_simple\nsimple<3\n"
43+
)
44+
result = script.pip(
45+
"install",
46+
"--no-index",
47+
"-f",
48+
data.find_links,
49+
"-r",
50+
script.scratch_path / "requirements.txt",
51+
)
52+
_assert_requested_present(script, result, "require_simple", "1.0")
53+
# simple must have REQUESTED because it is in requirements.txt
54+
_assert_requested_present(script, result, "simple", "2.0")
55+
56+
57+
def test_install_requested_reqs_and_constraints(script, data, with_wheel):
58+
script.scratch_path.joinpath("requirements.txt").write_text(
59+
"require_simple\n"
60+
)
61+
script.scratch_path.joinpath("constraints.txt").write_text("simple<3\n")
62+
result = script.pip(
63+
"install",
64+
"--no-index",
65+
"-f",
66+
data.find_links,
67+
"-r",
68+
script.scratch_path / "requirements.txt",
69+
"-c",
70+
script.scratch_path / "constraints.txt",
71+
)
72+
_assert_requested_present(script, result, "require_simple", "1.0")
73+
# simple must not have REQUESTED because it is merely a constraint
74+
_assert_requested_absent(script, result, "simple", "2.0")
75+
76+
77+
def test_install_requested_in_reqs_and_constraints(script, data, with_wheel):
78+
script.scratch_path.joinpath("requirements.txt").write_text(
79+
"require_simple\nsimple\n"
80+
)
81+
script.scratch_path.joinpath("constraints.txt").write_text("simple<3\n")
82+
result = script.pip(
83+
"install",
84+
"--no-index",
85+
"-f",
86+
data.find_links,
87+
"-r",
88+
script.scratch_path / "requirements.txt",
89+
"-c",
90+
script.scratch_path / "constraints.txt",
91+
)
92+
_assert_requested_present(script, result, "require_simple", "1.0")
93+
# simple must have REQUESTED because it is in requirements.txt
94+
_assert_requested_present(script, result, "simple", "2.0")

0 commit comments

Comments
 (0)