Skip to content

Implement PEP 376 REQUESTED #8026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/7811.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Generate PEP 376 REQUESTED metadata for user supplied requirements installed
by pip.
10 changes: 5 additions & 5 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,25 +309,25 @@ def get_requirements(
req_to_add = install_req_from_parsed_requirement(
parsed_req,
isolated=options.isolated_mode,
user_supplied=False,
)
req_to_add.is_direct = True
requirements.append(req_to_add)

for req in args:
req_to_add = install_req_from_line(
req, None, isolated=options.isolated_mode,
use_pep517=options.use_pep517,
user_supplied=True,
)
req_to_add.is_direct = True
requirements.append(req_to_add)

for req in options.editables:
req_to_add = install_req_from_editable(
req,
user_supplied=True,
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
)
req_to_add.is_direct = True
requirements.append(req_to_add)

# NOTE: options.require_hashes may be set if --require-hashes is True
Expand All @@ -338,9 +338,9 @@ def get_requirements(
req_to_add = install_req_from_parsed_requirement(
parsed_req,
isolated=options.isolated_mode,
use_pep517=options.use_pep517
use_pep517=options.use_pep517,
user_supplied=True,
)
req_to_add.is_direct = True
requirements.append(req_to_add)

# If any requirement has hash options, enable hash checking.
Expand Down
10 changes: 10 additions & 0 deletions src/pip/_internal/operations/install/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ def install_unpacked_wheel(
pycompile=True, # type: bool
warn_script_location=True, # type: bool
direct_url=None, # type: Optional[DirectUrl]
requested=False, # type: bool
):
# type: (...) -> None
"""Install a wheel.
Expand Down Expand Up @@ -645,6 +646,13 @@ def _generate_file(path, **kwargs):
direct_url_file.write(direct_url.to_json().encode("utf-8"))
generated.append(direct_url_path)

# Record the REQUESTED file
if requested:
requested_path = os.path.join(dest_info_dir, 'REQUESTED')
with open(requested_path, "w"):
pass
generated.append(requested_path)

# Record details of all files installed
record_path = os.path.join(dest_info_dir, 'RECORD')
with open(record_path, **csv_io_kwargs('r')) as record_file:
Expand All @@ -671,6 +679,7 @@ def install_wheel(
warn_script_location=True, # type: bool
_temp_dir_for_testing=None, # type: Optional[str]
direct_url=None, # type: Optional[DirectUrl]
requested=False, # type: bool
):
# type: (...) -> None
with TempDirectory(
Expand All @@ -686,4 +695,5 @@ def install_wheel(
pycompile=pycompile,
warn_script_location=warn_script_location,
direct_url=direct_url,
requested=requested,
)
20 changes: 16 additions & 4 deletions src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ def install_req_from_editable(
use_pep517=None, # type: Optional[bool]
isolated=False, # type: bool
options=None, # type: Optional[Dict[str, Any]]
constraint=False # type: bool
constraint=False, # type: bool
user_supplied=False, # type: bool
):
# type: (...) -> InstallRequirement

Expand All @@ -228,6 +229,7 @@ def install_req_from_editable(
return InstallRequirement(
parts.requirement,
comes_from=comes_from,
user_supplied=user_supplied,
editable=True,
link=parts.link,
constraint=constraint,
Expand Down Expand Up @@ -382,6 +384,7 @@ def install_req_from_line(
options=None, # type: Optional[Dict[str, Any]]
constraint=False, # type: bool
line_source=None, # type: Optional[str]
user_supplied=False, # type: bool
):
# type: (...) -> InstallRequirement
"""Creates an InstallRequirement from a name, which might be a
Expand All @@ -400,14 +403,16 @@ def install_req_from_line(
hash_options=options.get("hashes", {}) if options else {},
constraint=constraint,
extras=parts.extras,
user_supplied=user_supplied,
)


def install_req_from_req_string(
req_string, # type: str
comes_from=None, # type: Optional[InstallRequirement]
isolated=False, # type: bool
use_pep517=None # type: Optional[bool]
use_pep517=None, # type: Optional[bool]
user_supplied=False, # type: bool
):
# type: (...) -> InstallRequirement
try:
Expand All @@ -429,14 +434,19 @@ def install_req_from_req_string(
)

return InstallRequirement(
req, comes_from, isolated=isolated, use_pep517=use_pep517
req,
comes_from,
isolated=isolated,
use_pep517=use_pep517,
user_supplied=user_supplied,
)


def install_req_from_parsed_requirement(
parsed_req, # type: ParsedRequirement
isolated=False, # type: bool
use_pep517=None # type: Optional[bool]
use_pep517=None, # type: Optional[bool]
user_supplied=False, # type: bool
):
# type: (...) -> InstallRequirement
if parsed_req.is_editable:
Expand All @@ -446,6 +456,7 @@ def install_req_from_parsed_requirement(
use_pep517=use_pep517,
constraint=parsed_req.constraint,
isolated=isolated,
user_supplied=user_supplied,
)

else:
Expand All @@ -457,5 +468,6 @@ def install_req_from_parsed_requirement(
options=parsed_req.options,
constraint=parsed_req.constraint,
line_source=parsed_req.line_source,
user_supplied=user_supplied,
)
return req
9 changes: 7 additions & 2 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ def __init__(
global_options=None, # type: Optional[List[str]]
hash_options=None, # type: Optional[Dict[str, List[str]]]
constraint=False, # type: bool
extras=() # type: Iterable[str]
extras=(), # type: Iterable[str]
user_supplied=False, # type: bool
):
# type: (...) -> None
assert req is None or isinstance(req, Requirement), req
Expand Down Expand Up @@ -171,7 +172,10 @@ def __init__(
self.hash_options = hash_options if hash_options else {}
# Set to True after successful preparation of this requirement
self.prepared = False
self.is_direct = False
# User supplied requirement are explicitly requested for installation
# by the user via CLI arguments or requirements files, as opposed to,
# e.g. dependencies, extras or constraints.
self.user_supplied = user_supplied

# Set by the legacy resolver when the requirement has been downloaded
# TODO: This introduces a strong coupling between the resolver and the
Expand Down Expand Up @@ -809,6 +813,7 @@ def install(
pycompile=pycompile,
warn_script_location=warn_script_location,
direct_url=direct_url,
requested=self.user_supplied,
)
self.install_succeeded = True
return
Expand Down
9 changes: 6 additions & 3 deletions src/pip/_internal/req/req_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,8 @@ def add_requirement(
)

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

# Unnamed requirements are scanned again and the requirement won't be
Expand Down Expand Up @@ -165,6 +164,10 @@ def add_requirement(
# If we're now installing a constraint, mark the existing
# object for real installation.
existing_req.constraint = False
# If we're now installing a user supplied requirement,
# mark the existing object as such.
if install_req.user_supplied:
existing_req.user_supplied = True
existing_req.extras = tuple(sorted(
set(existing_req.extras) | set(install_req.extras)
))
Expand Down
4 changes: 2 additions & 2 deletions src/pip/_internal/resolution/legacy/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def _is_upgrade_allowed(self, req):
return True
else:
assert self.upgrade_strategy == "only-if-needed"
return req.is_direct
return req.user_supplied or req.constraint

def _set_req_to_reinstall(self, req):
# type: (InstallRequirement) -> None
Expand Down Expand Up @@ -419,7 +419,7 @@ def add_req(subreq, extras_requested):
# 'unnamed' requirements will get added here
# 'unnamed' requirements can only come from being directly
# provided by the user.
assert req_to_install.is_direct
assert req_to_install.user_supplied
requirement_set.add_requirement(
req_to_install, parent_req_name=None,
)
Expand Down
3 changes: 3 additions & 0 deletions src/pip/_internal/resolution/resolvelib/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def make_install_req_from_link(link, template):
line = link.url
ireq = install_req_from_line(
line,
user_supplied=template.user_supplied,
comes_from=template.comes_from,
use_pep517=template.use_pep517,
isolated=template.isolated,
Expand All @@ -68,6 +69,7 @@ def make_install_req_from_editable(link, template):
assert template.editable, "template not editable"
return install_req_from_editable(
link.url,
user_supplied=template.user_supplied,
comes_from=template.comes_from,
use_pep517=template.use_pep517,
isolated=template.isolated,
Expand All @@ -91,6 +93,7 @@ def make_install_req_from_dist(dist, template):
line = "{}=={}".format(project_name, dist.parsed_version)
ireq = install_req_from_line(
line,
user_supplied=template.user_supplied,
comes_from=template.comes_from,
use_pep517=template.use_pep517,
isolated=template.isolated,
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_internal/resolution/resolvelib/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def resolve(self, root_reqs, check_supported_wheels):
else:
constraints[name] = req.specifier
else:
if req.is_direct and req.name:
if req.user_supplied and req.name:
user_requested.add(canonicalize_name(req.name))
requirements.append(
self.factory.make_requirement_from_install_req(req)
Expand Down
94 changes: 94 additions & 0 deletions tests/functional/test_install_requested.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
def _assert_requested_present(script, result, name, version):
dist_info = script.site_packages / name + "-" + version + ".dist-info"
requested = dist_info / "REQUESTED"
assert dist_info in result.files_created
assert requested in result.files_created


def _assert_requested_absent(script, result, name, version):
dist_info = script.site_packages / name + "-" + version + ".dist-info"
requested = dist_info / "REQUESTED"
assert dist_info in result.files_created
assert requested not in result.files_created


def test_install_requested_basic(script, data, with_wheel):
result = script.pip(
"install", "--no-index", "-f", data.find_links, "require_simple"
)
_assert_requested_present(script, result, "require_simple", "1.0")
# dependency is not REQUESTED
_assert_requested_absent(script, result, "simple", "3.0")


def test_install_requested_requirements(script, data, with_wheel):
script.scratch_path.joinpath("requirements.txt").write_text(
"require_simple\n"
)
result = script.pip(
"install",
"--no-index",
"-f",
data.find_links,
"-r",
script.scratch_path / "requirements.txt",
)
_assert_requested_present(script, result, "require_simple", "1.0")
_assert_requested_absent(script, result, "simple", "3.0")


def test_install_requested_dep_in_requirements(script, data, with_wheel):
script.scratch_path.joinpath("requirements.txt").write_text(
"require_simple\nsimple<3\n"
)
result = script.pip(
"install",
"--no-index",
"-f",
data.find_links,
"-r",
script.scratch_path / "requirements.txt",
)
_assert_requested_present(script, result, "require_simple", "1.0")
# simple must have REQUESTED because it is in requirements.txt
_assert_requested_present(script, result, "simple", "2.0")


def test_install_requested_reqs_and_constraints(script, data, with_wheel):
script.scratch_path.joinpath("requirements.txt").write_text(
"require_simple\n"
)
script.scratch_path.joinpath("constraints.txt").write_text("simple<3\n")
result = script.pip(
"install",
"--no-index",
"-f",
data.find_links,
"-r",
script.scratch_path / "requirements.txt",
"-c",
script.scratch_path / "constraints.txt",
)
_assert_requested_present(script, result, "require_simple", "1.0")
# simple must not have REQUESTED because it is merely a constraint
_assert_requested_absent(script, result, "simple", "2.0")


def test_install_requested_in_reqs_and_constraints(script, data, with_wheel):
script.scratch_path.joinpath("requirements.txt").write_text(
"require_simple\nsimple\n"
)
script.scratch_path.joinpath("constraints.txt").write_text("simple<3\n")
result = script.pip(
"install",
"--no-index",
"-f",
data.find_links,
"-r",
script.scratch_path / "requirements.txt",
"-c",
script.scratch_path / "constraints.txt",
)
_assert_requested_present(script, result, "require_simple", "1.0")
# simple must have REQUESTED because it is in requirements.txt
_assert_requested_present(script, result, "simple", "2.0")
10 changes: 5 additions & 5 deletions tests/unit/test_req.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def get_processed_req_from_line(line, fname='file', lineno=1):
parsed_req = handle_requirement_line(parsed_line)
assert parsed_req is not None
req = install_req_from_parsed_requirement(parsed_req)
req.is_direct = True
req.user_supplied = True
return req


Expand Down Expand Up @@ -109,7 +109,7 @@ def test_no_reuse_existing_build_dir(self, data):
pass
reqset = RequirementSet()
req = install_req_from_line('simple')
req.is_direct = True
req.user_supplied = True
reqset.add_requirement(req)
finder = make_test_finder(find_links=[data.find_links])
with self._basic_resolver(finder) as resolver:
Expand All @@ -133,7 +133,7 @@ def test_environment_marker_extras(self, data):
req = install_req_from_editable(
data.packages.joinpath("LocalEnvironMarker")
)
req.is_direct = True
req.user_supplied = True
reqset.add_requirement(req)
finder = make_test_finder(find_links=[data.find_links])
with self._basic_resolver(finder) as resolver:
Expand Down Expand Up @@ -626,10 +626,10 @@ def test_exclusive_environment_markers():
"""Make sure RequirementSet accepts several excluding env markers"""
eq36 = install_req_from_line(
"Django>=1.6.10,<1.7 ; python_version == '3.6'")
eq36.is_direct = True
eq36.user_supplied = True
ne36 = install_req_from_line(
"Django>=1.6.10,<1.8 ; python_version != '3.6'")
ne36.is_direct = True
ne36.user_supplied = True

req_set = RequirementSet()
req_set.add_requirement(eq36)
Expand Down
Loading