Skip to content

Commit 657cf25

Browse files
authored
Merge pull request #7929 from McSinyx/tmp-file
Use better temporary files mechanism
2 parents 43f82c2 + 209c74f commit 657cf25

File tree

4 files changed

+40
-34
lines changed

4 files changed

+40
-34
lines changed

news/7699.bugfix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Use better mechanism for handling temporary files, when recording metadata
2+
about installed files (RECORD) and the installer (INSTALLER).

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

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from pip._internal.exceptions import InstallationError
2929
from pip._internal.locations import get_major_minor_version
30+
from pip._internal.utils.filesystem import adjacent_tmp_file, replace
3031
from pip._internal.utils.misc import captured_stdout, ensure_dir, hash_file
3132
from pip._internal.utils.temp_dir import TempDirectory
3233
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -36,7 +37,7 @@
3637
if MYPY_CHECK_RUNNING:
3738
from email.message import Message
3839
from typing import (
39-
Dict, List, Optional, Sequence, Tuple, IO, Text, Any,
40+
Dict, List, Optional, Sequence, Tuple, Any,
4041
Iterable, Callable, Set,
4142
)
4243

@@ -64,15 +65,15 @@ def rehash(path, blocksize=1 << 20):
6465
return (digest, str(length)) # type: ignore
6566

6667

67-
def open_for_csv(name, mode):
68-
# type: (str, Text) -> IO[Any]
69-
if sys.version_info[0] < 3:
70-
nl = {} # type: Dict[str, Any]
71-
bin = 'b'
68+
def csv_io_kwargs(mode):
69+
# type: (str) -> Dict[str, Any]
70+
"""Return keyword arguments to properly open a CSV file
71+
in the given mode.
72+
"""
73+
if sys.version_info.major < 3:
74+
return {'mode': '{}b'.format(mode)}
7275
else:
73-
nl = {'newline': ''} # type: Dict[str, Any]
74-
bin = ''
75-
return open(name, mode + bin, **nl)
76+
return {'mode': mode, 'newline': ''}
7677

7778

7879
def fix_script(path):
@@ -563,28 +564,25 @@ def is_entrypoint_wrapper(name):
563564
logger.warning(msg)
564565

565566
# Record pip as the installer
566-
installer = os.path.join(dest_info_dir, 'INSTALLER')
567-
temp_installer = os.path.join(dest_info_dir, 'INSTALLER.pip')
568-
with open(temp_installer, 'wb') as installer_file:
567+
installer_path = os.path.join(dest_info_dir, 'INSTALLER')
568+
with adjacent_tmp_file(installer_path) as installer_file:
569569
installer_file.write(b'pip\n')
570-
shutil.move(temp_installer, installer)
571-
generated.append(installer)
570+
replace(installer_file.name, installer_path)
571+
generated.append(installer_path)
572572

573573
# Record details of all files installed
574-
record = os.path.join(dest_info_dir, 'RECORD')
575-
temp_record = os.path.join(dest_info_dir, 'RECORD.pip')
576-
with open_for_csv(record, 'r') as record_in:
577-
with open_for_csv(temp_record, 'w+') as record_out:
578-
reader = csv.reader(record_in)
579-
outrows = get_csv_rows_for_installed(
580-
reader, installed=installed, changed=changed,
581-
generated=generated, lib_dir=lib_dir,
582-
)
583-
writer = csv.writer(record_out)
584-
# Sort to simplify testing.
585-
for row in sorted_outrows(outrows):
586-
writer.writerow(row)
587-
shutil.move(temp_record, record)
574+
record_path = os.path.join(dest_info_dir, 'RECORD')
575+
with open(record_path, **csv_io_kwargs('r')) as record_file:
576+
rows = get_csv_rows_for_installed(
577+
csv.reader(record_file),
578+
installed=installed,
579+
changed=changed,
580+
generated=generated,
581+
lib_dir=lib_dir)
582+
with adjacent_tmp_file(record_path, **csv_io_kwargs('w')) as record_file:
583+
writer = csv.writer(record_file)
584+
writer.writerows(sorted_outrows(rows)) # sort to simplify testing
585+
replace(record_file.name, record_path)
588586

589587

590588
def install_wheel(

src/pip/_internal/utils/filesystem.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from pip._internal.utils.typing import MYPY_CHECK_RUNNING, cast
1818

1919
if MYPY_CHECK_RUNNING:
20-
from typing import BinaryIO, Iterator
20+
from typing import Any, BinaryIO, Iterator
2121

2222
class NamedTemporaryFileResult(BinaryIO):
2323
@property
@@ -85,16 +85,22 @@ def is_socket(path):
8585

8686

8787
@contextmanager
88-
def adjacent_tmp_file(path):
89-
# type: (str) -> Iterator[NamedTemporaryFileResult]
90-
"""Given a path to a file, open a temp file next to it securely and ensure
91-
it is written to disk after the context reaches its end.
88+
def adjacent_tmp_file(path, **kwargs):
89+
# type: (str, **Any) -> Iterator[NamedTemporaryFileResult]
90+
"""Return a file-like object pointing to a tmp file next to path.
91+
92+
The file is created securely and is ensured to be written to disk
93+
after the context reaches its end.
94+
95+
kwargs will be passed to tempfile.NamedTemporaryFile to control
96+
the way the temporary file will be opened.
9297
"""
9398
with NamedTemporaryFile(
9499
delete=False,
95100
dir=os.path.dirname(path),
96101
prefix=os.path.basename(path),
97102
suffix='.tmp',
103+
**kwargs
98104
) as f:
99105
result = cast('NamedTemporaryFileResult', f)
100106
try:

tests/unit/test_wheel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def call_get_csv_rows_for_installed(tmpdir, text):
141141
generated = []
142142
lib_dir = '/lib/dir'
143143

144-
with wheel.open_for_csv(path, 'r') as f:
144+
with open(path, **wheel.csv_io_kwargs('r')) as f:
145145
reader = csv.reader(f)
146146
outrows = wheel.get_csv_rows_for_installed(
147147
reader, installed=installed, changed=changed,

0 commit comments

Comments
 (0)