Skip to content

Commit f7dd273

Browse files
clean up duplicated code
1 parent 9ba5e9f commit f7dd273

File tree

3 files changed

+69
-67
lines changed

3 files changed

+69
-67
lines changed

src/pip/_internal/operations/prepare.py

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import mimetypes
88
import os
99
import shutil
10-
from typing import Dict, Iterable, List, Optional, Set
10+
from pathlib import Path
11+
from typing import Dict, Iterable, List, Optional
1112

1213
from pip._vendor.packaging.utils import canonicalize_name
1314

@@ -20,7 +21,6 @@
2021
InstallationError,
2122
MetadataInconsistent,
2223
NetworkConnectionError,
23-
PreviousBuildDirError,
2424
VcsHashUnsupported,
2525
)
2626
from pip._internal.index.package_finder import PackageFinder
@@ -47,7 +47,6 @@
4747
display_path,
4848
hash_file,
4949
hide_url,
50-
is_installable_dir,
5150
)
5251
from pip._internal.utils.temp_dir import TempDirectory
5352
from pip._internal.utils.unpacking import unpack_file
@@ -319,21 +318,7 @@ def _ensure_link_req_src_dir(
319318
autodelete=True,
320319
parallel_builds=parallel_builds,
321320
)
322-
323-
# If a checkout exists, it's unwise to keep going. version
324-
# inconsistencies are logged later, but do not fail the
325-
# installation.
326-
# FIXME: this won't upgrade when there's an existing
327-
# package unpacked in `req.source_dir`
328-
# TODO: this check is now probably dead code
329-
if is_installable_dir(req.source_dir):
330-
raise PreviousBuildDirError(
331-
"pip can't proceed with requirements '{}' due to a"
332-
"pre-existing build directory ({}). This is likely "
333-
"due to a previous installation that failed . pip is "
334-
"being responsible and not assuming it can delete this. "
335-
"Please delete it and try again.".format(req, req.source_dir)
336-
)
321+
req.ensure_pristine_source_checkout()
337322

338323
def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes:
339324
# By the time this is called, the requirement's link should have
@@ -474,8 +459,6 @@ def _complete_partial_requirements(
474459
assert req.link
475460
links_to_fully_download[req.link] = req
476461

477-
reqs_with_newly_unpacked_source_dirs: Set[Link] = set()
478-
479462
batch_download = self._batch_download(
480463
links_to_fully_download.keys(),
481464
temp_dir,
@@ -490,28 +473,17 @@ def _complete_partial_requirements(
490473
# _prepare_linked_requirement().
491474
self._downloaded[req.link.url] = filepath
492475

493-
# If this is an sdist, we need to unpack it and set the .source_dir
494-
# immediately after downloading, as _prepare_linked_requirement() assumes
495-
# the req is either not downloaded at all, or both downloaded and
496-
# unpacked. The downloading and unpacking is is typically done with
497-
# unpack_url(), but we separate the downloading and unpacking steps here in
498-
# order to use the BatchDownloader.
476+
# If this is an sdist, we need to unpack it after downloading, but the
477+
# .source_dir won't be set up until we are in _prepare_linked_requirement().
478+
# Add the downloaded archive to the install requirement to unpack after
479+
# preparing the source dir.
499480
if not req.is_wheel:
500-
hashes = self._get_linked_req_hashes(req)
501-
assert filepath == _check_download_dir(req.link, temp_dir, hashes)
502-
self._ensure_link_req_src_dir(req, parallel_builds)
503-
unpack_file(filepath, req.source_dir)
504-
reqs_with_newly_unpacked_source_dirs.add(req.link)
481+
req.needs_unpacked_archive(Path(filepath))
505482

506483
# This step is necessary to ensure all lazy wheels are processed
507484
# successfully by the 'download', 'wheel', and 'install' commands.
508485
for req in partially_downloaded_reqs:
509-
self._prepare_linked_requirement(
510-
req,
511-
parallel_builds,
512-
source_dir_exists_already=req.link
513-
in reqs_with_newly_unpacked_source_dirs,
514-
)
486+
self._prepare_linked_requirement(req, parallel_builds)
515487

516488
def prepare_linked_requirement(
517489
self, req: InstallRequirement, parallel_builds: bool = False
@@ -582,10 +554,7 @@ def prepare_linked_requirements_more(
582554
)
583555

584556
def _prepare_linked_requirement(
585-
self,
586-
req: InstallRequirement,
587-
parallel_builds: bool,
588-
source_dir_exists_already: bool = False,
557+
self, req: InstallRequirement, parallel_builds: bool
589558
) -> BaseDistribution:
590559
assert req.link
591560
link = req.link
@@ -617,8 +586,7 @@ def _prepare_linked_requirement(
617586
req.link = req.cached_wheel_source_link
618587
link = req.link
619588

620-
if not source_dir_exists_already:
621-
self._ensure_link_req_src_dir(req, parallel_builds)
589+
self._ensure_link_req_src_dir(req, parallel_builds)
622590

623591
if link.is_existing_dir():
624592
local_file = None

src/pip/_internal/req/req_install.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import uuid
77
import zipfile
88
from optparse import Values
9+
from pathlib import Path
910
from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
1011

1112
from pip._vendor.packaging.markers import Marker
@@ -17,7 +18,7 @@
1718
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
1819

1920
from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
20-
from pip._internal.exceptions import InstallationError
21+
from pip._internal.exceptions import InstallationError, PreviousBuildDirError
2122
from pip._internal.locations import get_scheme
2223
from pip._internal.metadata import (
2324
BaseDistribution,
@@ -47,11 +48,13 @@
4748
backup_dir,
4849
display_path,
4950
hide_url,
51+
is_installable_dir,
5052
redact_auth_from_url,
5153
)
5254
from pip._internal.utils.packaging import safe_extra
5355
from pip._internal.utils.subprocess import runner_with_spinner_message
5456
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
57+
from pip._internal.utils.unpacking import unpack_file
5558
from pip._internal.utils.virtualenv import running_under_virtualenv
5659
from pip._internal.vcs import vcs
5760

@@ -180,6 +183,9 @@ def __init__(
180183
# This requirement needs more preparation before it can be built
181184
self.needs_more_preparation = False
182185

186+
# This requirement needs to be unpacked before it can be installed.
187+
self._archive_source: Optional[Path] = None
188+
183189
def __str__(self) -> str:
184190
if self.req:
185191
s = str(self.req)
@@ -645,6 +651,28 @@ def ensure_has_source_dir(
645651
parallel_builds=parallel_builds,
646652
)
647653

654+
def needs_unpacked_archive(self, archive_source: Path) -> None:
655+
assert self._archive_source is None
656+
self._archive_source = archive_source
657+
658+
def ensure_pristine_source_checkout(self) -> None:
659+
"""Ensure the source directory has not yet been built in."""
660+
assert self.source_dir is not None
661+
if self._archive_source is not None:
662+
unpack_file(str(self._archive_source), self.source_dir)
663+
664+
# If a checkout exists, it's unwise to keep going.
665+
# version inconsistencies are logged later, but do not fail
666+
# the installation.
667+
if is_installable_dir(self.source_dir):
668+
raise PreviousBuildDirError(
669+
"pip can't proceed with requirements '{}' due to a "
670+
"pre-existing build directory ({}). This is likely "
671+
"due to a previous installation that failed . pip is "
672+
"being responsible and not assuming it can delete this. "
673+
"Please delete it and try again.".format(self, self.source_dir)
674+
)
675+
648676
# For editable installations
649677
def update_editable(self) -> None:
650678
if not self.link:

tests/conftest.py

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -935,17 +935,21 @@ def html_index_for_packages(
935935
pkg_links = "\n".join(
936936
f' <a href="{pkg}/index.html">{pkg}</a>' for pkg in fake_packages.keys()
937937
)
938-
index_html = f"""\
939-
<!DOCTYPE html>
940-
<html>
941-
<head>
942-
<meta name="pypi:repository-version" content="1.0">
943-
<title>Simple index</title>
944-
</head>
945-
<body>
946-
{pkg_links}
947-
</body>
948-
</html>"""
938+
# Output won't be nicely indented because dedent() acts after f-string
939+
# arg insertion.
940+
index_html = dedent(
941+
f"""\
942+
<!DOCTYPE html>
943+
<html>
944+
<head>
945+
<meta name="pypi:repository-version" content="1.0">
946+
<title>Simple index</title>
947+
</head>
948+
<body>
949+
{pkg_links}
950+
</body>
951+
</html>"""
952+
)
949953
# (2) Generate the index.html in a new subdirectory of the temp directory.
950954
(html_dir / "index.html").write_text(index_html)
951955

@@ -976,18 +980,20 @@ def html_index_for_packages(
976980
# write an index.html with the generated download links for each
977981
# copied file for this specific package name.
978982
download_links_str = "\n".join(download_links)
979-
pkg_index_content = f"""\
980-
<!DOCTYPE html>
981-
<html>
982-
<head>
983-
<meta name="pypi:repository-version" content="1.0">
984-
<title>Links for {pkg}</title>
985-
</head>
986-
<body>
987-
<h1>Links for {pkg}</h1>
988-
{download_links_str}
989-
</body>
990-
</html>"""
983+
pkg_index_content = dedent(
984+
f"""\
985+
<!DOCTYPE html>
986+
<html>
987+
<head>
988+
<meta name="pypi:repository-version" content="1.0">
989+
<title>Links for {pkg}</title>
990+
</head>
991+
<body>
992+
<h1>Links for {pkg}</h1>
993+
{download_links_str}
994+
</body>
995+
</html>"""
996+
)
991997
with open(pkg_subdir / "index.html", "w") as f:
992998
f.write(pkg_index_content)
993999

0 commit comments

Comments
 (0)