Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hhhrrrttt222111
GitHub Repository: hhhrrrttt222111/Dorkify
Path: blob/master/venv/Lib/site-packages/pip/_internal/operations/prepare.py
811 views
1
"""Prepares a distribution for installation
2
"""
3
4
# The following comment should be removed at some point in the future.
5
# mypy: strict-optional=False
6
7
import logging
8
import mimetypes
9
import os
10
import shutil
11
12
from pip._vendor import requests
13
from pip._vendor.six import PY2
14
15
from pip._internal.distributions import (
16
make_distribution_for_install_requirement,
17
)
18
from pip._internal.distributions.installed import InstalledDistribution
19
from pip._internal.exceptions import (
20
DirectoryUrlHashUnsupported,
21
HashMismatch,
22
HashUnpinned,
23
InstallationError,
24
PreviousBuildDirError,
25
VcsHashUnsupported,
26
)
27
from pip._internal.utils.filesystem import copy2_fixed
28
from pip._internal.utils.hashes import MissingHashes
29
from pip._internal.utils.logging import indent_log
30
from pip._internal.utils.misc import (
31
display_path,
32
hide_url,
33
path_to_display,
34
rmtree,
35
)
36
from pip._internal.utils.temp_dir import TempDirectory
37
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
38
from pip._internal.utils.unpacking import unpack_file
39
from pip._internal.vcs import vcs
40
41
if MYPY_CHECK_RUNNING:
42
from typing import (
43
Callable, List, Optional, Tuple,
44
)
45
46
from mypy_extensions import TypedDict
47
48
from pip._internal.distributions import AbstractDistribution
49
from pip._internal.index.package_finder import PackageFinder
50
from pip._internal.models.link import Link
51
from pip._internal.network.download import Downloader
52
from pip._internal.req.req_install import InstallRequirement
53
from pip._internal.req.req_tracker import RequirementTracker
54
from pip._internal.utils.hashes import Hashes
55
56
if PY2:
57
CopytreeKwargs = TypedDict(
58
'CopytreeKwargs',
59
{
60
'ignore': Callable[[str, List[str]], List[str]],
61
'symlinks': bool,
62
},
63
total=False,
64
)
65
else:
66
CopytreeKwargs = TypedDict(
67
'CopytreeKwargs',
68
{
69
'copy_function': Callable[[str, str], None],
70
'ignore': Callable[[str, List[str]], List[str]],
71
'ignore_dangling_symlinks': bool,
72
'symlinks': bool,
73
},
74
total=False,
75
)
76
77
logger = logging.getLogger(__name__)
78
79
80
def _get_prepared_distribution(
81
req, # type: InstallRequirement
82
req_tracker, # type: RequirementTracker
83
finder, # type: PackageFinder
84
build_isolation # type: bool
85
):
86
# type: (...) -> AbstractDistribution
87
"""Prepare a distribution for installation.
88
"""
89
abstract_dist = make_distribution_for_install_requirement(req)
90
with req_tracker.track(req):
91
abstract_dist.prepare_distribution_metadata(finder, build_isolation)
92
return abstract_dist
93
94
95
def unpack_vcs_link(link, location):
96
# type: (Link, str) -> None
97
vcs_backend = vcs.get_backend_for_scheme(link.scheme)
98
assert vcs_backend is not None
99
vcs_backend.unpack(location, url=hide_url(link.url))
100
101
102
class File(object):
103
def __init__(self, path, content_type):
104
# type: (str, str) -> None
105
self.path = path
106
self.content_type = content_type
107
108
109
def get_http_url(
110
link, # type: Link
111
downloader, # type: Downloader
112
download_dir=None, # type: Optional[str]
113
hashes=None, # type: Optional[Hashes]
114
):
115
# type: (...) -> File
116
temp_dir = TempDirectory(kind="unpack", globally_managed=True)
117
# If a download dir is specified, is the file already downloaded there?
118
already_downloaded_path = None
119
if download_dir:
120
already_downloaded_path = _check_download_dir(
121
link, download_dir, hashes
122
)
123
124
if already_downloaded_path:
125
from_path = already_downloaded_path
126
content_type = mimetypes.guess_type(from_path)[0]
127
else:
128
# let's download to a tmp dir
129
from_path, content_type = _download_http_url(
130
link, downloader, temp_dir.path, hashes
131
)
132
133
return File(from_path, content_type)
134
135
136
def _copy2_ignoring_special_files(src, dest):
137
# type: (str, str) -> None
138
"""Copying special files is not supported, but as a convenience to users
139
we skip errors copying them. This supports tools that may create e.g.
140
socket files in the project source directory.
141
"""
142
try:
143
copy2_fixed(src, dest)
144
except shutil.SpecialFileError as e:
145
# SpecialFileError may be raised due to either the source or
146
# destination. If the destination was the cause then we would actually
147
# care, but since the destination directory is deleted prior to
148
# copy we ignore all of them assuming it is caused by the source.
149
logger.warning(
150
"Ignoring special file error '%s' encountered copying %s to %s.",
151
str(e),
152
path_to_display(src),
153
path_to_display(dest),
154
)
155
156
157
def _copy_source_tree(source, target):
158
# type: (str, str) -> None
159
target_abspath = os.path.abspath(target)
160
target_basename = os.path.basename(target_abspath)
161
target_dirname = os.path.dirname(target_abspath)
162
163
def ignore(d, names):
164
# type: (str, List[str]) -> List[str]
165
skipped = [] # type: List[str]
166
if d == source:
167
# Pulling in those directories can potentially be very slow,
168
# exclude the following directories if they appear in the top
169
# level dir (and only it).
170
# See discussion at https://github.com/pypa/pip/pull/6770
171
skipped += ['.tox', '.nox']
172
if os.path.abspath(d) == target_dirname:
173
# Prevent an infinite recursion if the target is in source.
174
# This can happen when TMPDIR is set to ${PWD}/...
175
# and we copy PWD to TMPDIR.
176
skipped += [target_basename]
177
return skipped
178
179
kwargs = dict(ignore=ignore, symlinks=True) # type: CopytreeKwargs
180
181
if not PY2:
182
# Python 2 does not support copy_function, so we only ignore
183
# errors on special file copy in Python 3.
184
kwargs['copy_function'] = _copy2_ignoring_special_files
185
186
shutil.copytree(source, target, **kwargs)
187
188
189
def get_file_url(
190
link, # type: Link
191
download_dir=None, # type: Optional[str]
192
hashes=None # type: Optional[Hashes]
193
):
194
# type: (...) -> File
195
"""Get file and optionally check its hash.
196
"""
197
# If a download dir is specified, is the file already there and valid?
198
already_downloaded_path = None
199
if download_dir:
200
already_downloaded_path = _check_download_dir(
201
link, download_dir, hashes
202
)
203
204
if already_downloaded_path:
205
from_path = already_downloaded_path
206
else:
207
from_path = link.file_path
208
209
# If --require-hashes is off, `hashes` is either empty, the
210
# link's embedded hash, or MissingHashes; it is required to
211
# match. If --require-hashes is on, we are satisfied by any
212
# hash in `hashes` matching: a URL-based or an option-based
213
# one; no internet-sourced hash will be in `hashes`.
214
if hashes:
215
hashes.check_against_path(from_path)
216
217
content_type = mimetypes.guess_type(from_path)[0]
218
219
return File(from_path, content_type)
220
221
222
def unpack_url(
223
link, # type: Link
224
location, # type: str
225
downloader, # type: Downloader
226
download_dir=None, # type: Optional[str]
227
hashes=None, # type: Optional[Hashes]
228
):
229
# type: (...) -> Optional[File]
230
"""Unpack link into location, downloading if required.
231
232
:param hashes: A Hashes object, one of whose embedded hashes must match,
233
or HashMismatch will be raised. If the Hashes is empty, no matches are
234
required, and unhashable types of requirements (like VCS ones, which
235
would ordinarily raise HashUnsupported) are allowed.
236
"""
237
# non-editable vcs urls
238
if link.is_vcs:
239
unpack_vcs_link(link, location)
240
return None
241
242
# If it's a url to a local directory
243
if link.is_existing_dir():
244
if os.path.isdir(location):
245
rmtree(location)
246
_copy_source_tree(link.file_path, location)
247
return None
248
249
# file urls
250
if link.is_file:
251
file = get_file_url(link, download_dir, hashes=hashes)
252
253
# http urls
254
else:
255
file = get_http_url(
256
link,
257
downloader,
258
download_dir,
259
hashes=hashes,
260
)
261
262
# unpack the archive to the build dir location. even when only downloading
263
# archives, they have to be unpacked to parse dependencies
264
unpack_file(file.path, location, file.content_type)
265
266
return file
267
268
269
def _download_http_url(
270
link, # type: Link
271
downloader, # type: Downloader
272
temp_dir, # type: str
273
hashes, # type: Optional[Hashes]
274
):
275
# type: (...) -> Tuple[str, str]
276
"""Download link url into temp_dir using provided session"""
277
download = downloader(link)
278
279
file_path = os.path.join(temp_dir, download.filename)
280
with open(file_path, 'wb') as content_file:
281
for chunk in download.chunks:
282
content_file.write(chunk)
283
284
if hashes:
285
hashes.check_against_path(file_path)
286
287
return file_path, download.response.headers.get('content-type', '')
288
289
290
def _check_download_dir(link, download_dir, hashes):
291
# type: (Link, str, Optional[Hashes]) -> Optional[str]
292
""" Check download_dir for previously downloaded file with correct hash
293
If a correct file is found return its path else None
294
"""
295
download_path = os.path.join(download_dir, link.filename)
296
297
if not os.path.exists(download_path):
298
return None
299
300
# If already downloaded, does its hash match?
301
logger.info('File was already downloaded %s', download_path)
302
if hashes:
303
try:
304
hashes.check_against_path(download_path)
305
except HashMismatch:
306
logger.warning(
307
'Previously-downloaded file %s has bad hash. '
308
'Re-downloading.',
309
download_path
310
)
311
os.unlink(download_path)
312
return None
313
return download_path
314
315
316
class RequirementPreparer(object):
317
"""Prepares a Requirement
318
"""
319
320
def __init__(
321
self,
322
build_dir, # type: str
323
download_dir, # type: Optional[str]
324
src_dir, # type: str
325
wheel_download_dir, # type: Optional[str]
326
build_isolation, # type: bool
327
req_tracker, # type: RequirementTracker
328
downloader, # type: Downloader
329
finder, # type: PackageFinder
330
require_hashes, # type: bool
331
use_user_site, # type: bool
332
):
333
# type: (...) -> None
334
super(RequirementPreparer, self).__init__()
335
336
self.src_dir = src_dir
337
self.build_dir = build_dir
338
self.req_tracker = req_tracker
339
self.downloader = downloader
340
self.finder = finder
341
342
# Where still-packed archives should be written to. If None, they are
343
# not saved, and are deleted immediately after unpacking.
344
self.download_dir = download_dir
345
346
# Where still-packed .whl files should be written to. If None, they are
347
# written to the download_dir parameter. Separate to download_dir to
348
# permit only keeping wheel archives for pip wheel.
349
self.wheel_download_dir = wheel_download_dir
350
351
# NOTE
352
# download_dir and wheel_download_dir overlap semantically and may
353
# be combined if we're willing to have non-wheel archives present in
354
# the wheelhouse output by 'pip wheel'.
355
356
# Is build isolation allowed?
357
self.build_isolation = build_isolation
358
359
# Should hash-checking be required?
360
self.require_hashes = require_hashes
361
362
# Should install in user site-packages?
363
self.use_user_site = use_user_site
364
365
@property
366
def _download_should_save(self):
367
# type: () -> bool
368
if not self.download_dir:
369
return False
370
371
if os.path.exists(self.download_dir):
372
return True
373
374
logger.critical('Could not find download directory')
375
raise InstallationError(
376
"Could not find or access download directory '{}'"
377
.format(self.download_dir))
378
379
def prepare_linked_requirement(
380
self,
381
req, # type: InstallRequirement
382
):
383
# type: (...) -> AbstractDistribution
384
"""Prepare a requirement that would be obtained from req.link
385
"""
386
assert req.link
387
link = req.link
388
389
# TODO: Breakup into smaller functions
390
if link.scheme == 'file':
391
path = link.file_path
392
logger.info('Processing %s', display_path(path))
393
else:
394
logger.info('Collecting %s', req.req or req)
395
396
download_dir = self.download_dir
397
if link.is_wheel and self.wheel_download_dir:
398
# when doing 'pip wheel` we download wheels to a
399
# dedicated dir.
400
download_dir = self.wheel_download_dir
401
402
if link.is_wheel:
403
if download_dir:
404
# When downloading, we only unpack wheels to get
405
# metadata.
406
autodelete_unpacked = True
407
else:
408
# When installing a wheel, we use the unpacked
409
# wheel.
410
autodelete_unpacked = False
411
else:
412
# We always delete unpacked sdists after pip runs.
413
autodelete_unpacked = True
414
415
with indent_log():
416
# Since source_dir is only set for editable requirements.
417
assert req.source_dir is None
418
req.ensure_has_source_dir(self.build_dir, autodelete_unpacked)
419
# If a checkout exists, it's unwise to keep going. version
420
# inconsistencies are logged later, but do not fail the
421
# installation.
422
# FIXME: this won't upgrade when there's an existing
423
# package unpacked in `req.source_dir`
424
if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
425
raise PreviousBuildDirError(
426
"pip can't proceed with requirements '{}' due to a"
427
" pre-existing build directory ({}). This is "
428
"likely due to a previous installation that failed"
429
". pip is being responsible and not assuming it "
430
"can delete this. Please delete it and try again."
431
.format(req, req.source_dir)
432
)
433
434
# Now that we have the real link, we can tell what kind of
435
# requirements we have and raise some more informative errors
436
# than otherwise. (For example, we can raise VcsHashUnsupported
437
# for a VCS URL rather than HashMissing.)
438
if self.require_hashes:
439
# We could check these first 2 conditions inside
440
# unpack_url and save repetition of conditions, but then
441
# we would report less-useful error messages for
442
# unhashable requirements, complaining that there's no
443
# hash provided.
444
if link.is_vcs:
445
raise VcsHashUnsupported()
446
elif link.is_existing_dir():
447
raise DirectoryUrlHashUnsupported()
448
if not req.original_link and not req.is_pinned:
449
# Unpinned packages are asking for trouble when a new
450
# version is uploaded. This isn't a security check, but
451
# it saves users a surprising hash mismatch in the
452
# future.
453
#
454
# file:/// URLs aren't pinnable, so don't complain
455
# about them not being pinned.
456
raise HashUnpinned()
457
458
hashes = req.hashes(trust_internet=not self.require_hashes)
459
if self.require_hashes and not hashes:
460
# Known-good hashes are missing for this requirement, so
461
# shim it with a facade object that will provoke hash
462
# computation and then raise a HashMissing exception
463
# showing the user what the hash should be.
464
hashes = MissingHashes()
465
466
try:
467
local_file = unpack_url(
468
link, req.source_dir, self.downloader, download_dir,
469
hashes=hashes,
470
)
471
except requests.HTTPError as exc:
472
logger.critical(
473
'Could not install requirement %s because of error %s',
474
req,
475
exc,
476
)
477
raise InstallationError(
478
'Could not install requirement {} because of HTTP '
479
'error {} for URL {}'.format(req, exc, link)
480
)
481
482
# For use in later processing, preserve the file path on the
483
# requirement.
484
if local_file:
485
req.local_file_path = local_file.path
486
487
abstract_dist = _get_prepared_distribution(
488
req, self.req_tracker, self.finder, self.build_isolation,
489
)
490
491
if download_dir:
492
if link.is_existing_dir():
493
logger.info('Link is a directory, ignoring download_dir')
494
elif local_file:
495
download_location = os.path.join(
496
download_dir, link.filename
497
)
498
if not os.path.exists(download_location):
499
shutil.copy(local_file.path, download_location)
500
logger.info(
501
'Saved %s', display_path(download_location)
502
)
503
504
if self._download_should_save:
505
# Make a .zip of the source_dir we already created.
506
if link.is_vcs:
507
req.archive(self.download_dir)
508
return abstract_dist
509
510
def prepare_editable_requirement(
511
self,
512
req, # type: InstallRequirement
513
):
514
# type: (...) -> AbstractDistribution
515
"""Prepare an editable requirement
516
"""
517
assert req.editable, "cannot prepare a non-editable req as editable"
518
519
logger.info('Obtaining %s', req)
520
521
with indent_log():
522
if self.require_hashes:
523
raise InstallationError(
524
'The editable requirement {} cannot be installed when '
525
'requiring hashes, because there is no single file to '
526
'hash.'.format(req)
527
)
528
req.ensure_has_source_dir(self.src_dir)
529
req.update_editable(not self._download_should_save)
530
531
abstract_dist = _get_prepared_distribution(
532
req, self.req_tracker, self.finder, self.build_isolation,
533
)
534
535
if self._download_should_save:
536
req.archive(self.download_dir)
537
req.check_if_exists(self.use_user_site)
538
539
return abstract_dist
540
541
def prepare_installed_requirement(
542
self,
543
req, # type: InstallRequirement
544
skip_reason # type: str
545
):
546
# type: (...) -> AbstractDistribution
547
"""Prepare an already-installed requirement
548
"""
549
assert req.satisfied_by, "req should have been satisfied but isn't"
550
assert skip_reason is not None, (
551
"did not get skip reason skipped but req.satisfied_by "
552
"is set to {}".format(req.satisfied_by)
553
)
554
logger.info(
555
'Requirement %s: %s (%s)',
556
skip_reason, req, req.satisfied_by.version
557
)
558
with indent_log():
559
if self.require_hashes:
560
logger.debug(
561
'Since it is already installed, we are trusting this '
562
'package without checking its hash. To ensure a '
563
'completely repeatable environment, install into an '
564
'empty virtualenv.'
565
)
566
abstract_dist = InstalledDistribution(req)
567
568
return abstract_dist
569
570