comparison venv/lib/python2.7/site-packages/pip/req/req_set.py @ 0:d67268158946 draft

planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author bcclaywell
date Mon, 12 Oct 2015 17:43:33 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d67268158946
1 from __future__ import absolute_import
2
3 from collections import defaultdict
4 import functools
5 import itertools
6 import logging
7 import os
8
9 from pip._vendor import pkg_resources
10 from pip._vendor import requests
11
12 from pip.download import (url_to_path, unpack_url)
13 from pip.exceptions import (InstallationError, BestVersionAlreadyInstalled,
14 DistributionNotFound, PreviousBuildDirError)
15 from pip.locations import (PIP_DELETE_MARKER_FILENAME, build_prefix)
16 from pip.req.req_install import InstallRequirement
17 from pip.utils import (display_path, rmtree, dist_in_usersite, normalize_path)
18 from pip.utils.logging import indent_log
19 from pip.vcs import vcs
20
21
22 logger = logging.getLogger(__name__)
23
24
25 class Requirements(object):
26
27 def __init__(self):
28 self._keys = []
29 self._dict = {}
30
31 def keys(self):
32 return self._keys
33
34 def values(self):
35 return [self._dict[key] for key in self._keys]
36
37 def __contains__(self, item):
38 return item in self._keys
39
40 def __setitem__(self, key, value):
41 if key not in self._keys:
42 self._keys.append(key)
43 self._dict[key] = value
44
45 def __getitem__(self, key):
46 return self._dict[key]
47
48 def __repr__(self):
49 values = ['%s: %s' % (repr(k), repr(self[k])) for k in self.keys()]
50 return 'Requirements({%s})' % ', '.join(values)
51
52
53 class DistAbstraction(object):
54 """Abstracts out the wheel vs non-wheel prepare_files logic.
55
56 The requirements for anything installable are as follows:
57 - we must be able to determine the requirement name
58 (or we can't correctly handle the non-upgrade case).
59 - we must be able to generate a list of run-time dependencies
60 without installing any additional packages (or we would
61 have to either burn time by doing temporary isolated installs
62 or alternatively violate pips 'don't start installing unless
63 all requirements are available' rule - neither of which are
64 desirable).
65 - for packages with setup requirements, we must also be able
66 to determine their requirements without installing additional
67 packages (for the same reason as run-time dependencies)
68 - we must be able to create a Distribution object exposing the
69 above metadata.
70 """
71
72 def __init__(self, req_to_install):
73 self.req_to_install = req_to_install
74
75 def dist(self, finder):
76 """Return a setuptools Dist object."""
77 raise NotImplementedError(self.dist)
78
79 def prep_for_dist(self):
80 """Ensure that we can get a Dist for this requirement."""
81 raise NotImplementedError(self.dist)
82
83
84 def make_abstract_dist(req_to_install):
85 """Factory to make an abstract dist object.
86
87 Preconditions: Either an editable req with a source_dir, or satisfied_by or
88 a wheel link, or a non-editable req with a source_dir.
89
90 :return: A concrete DistAbstraction.
91 """
92 if req_to_install.editable:
93 return IsSDist(req_to_install)
94 elif req_to_install.link and req_to_install.link.is_wheel:
95 return IsWheel(req_to_install)
96 else:
97 return IsSDist(req_to_install)
98
99
100 class IsWheel(DistAbstraction):
101
102 def dist(self, finder):
103 return list(pkg_resources.find_distributions(
104 self.req_to_install.source_dir))[0]
105
106 def prep_for_dist(self):
107 # FIXME:https://github.com/pypa/pip/issues/1112
108 pass
109
110
111 class IsSDist(DistAbstraction):
112
113 def dist(self, finder):
114 dist = self.req_to_install.get_dist()
115 # FIXME: shouldn't be globally added:
116 if dist.has_metadata('dependency_links.txt'):
117 finder.add_dependency_links(
118 dist.get_metadata_lines('dependency_links.txt')
119 )
120 return dist
121
122 def prep_for_dist(self):
123 self.req_to_install.run_egg_info()
124 self.req_to_install.assert_source_matches_version()
125
126
127 class Installed(DistAbstraction):
128
129 def dist(self, finder):
130 return self.req_to_install.satisfied_by
131
132 def prep_for_dist(self):
133 pass
134
135
136 class RequirementSet(object):
137
138 def __init__(self, build_dir, src_dir, download_dir, upgrade=False,
139 ignore_installed=False, as_egg=False, target_dir=None,
140 ignore_dependencies=False, force_reinstall=False,
141 use_user_site=False, session=None, pycompile=True,
142 isolated=False, wheel_download_dir=None):
143 if session is None:
144 raise TypeError(
145 "RequirementSet() missing 1 required keyword argument: "
146 "'session'"
147 )
148
149 self.build_dir = build_dir
150 self.src_dir = src_dir
151 # XXX: download_dir and wheel_download_dir overlap semantically and may
152 # be combinable.
153 self.download_dir = download_dir
154 self.upgrade = upgrade
155 self.ignore_installed = ignore_installed
156 self.force_reinstall = force_reinstall
157 self.requirements = Requirements()
158 # Mapping of alias: real_name
159 self.requirement_aliases = {}
160 self.unnamed_requirements = []
161 self.ignore_dependencies = ignore_dependencies
162 self.successfully_downloaded = []
163 self.successfully_installed = []
164 self.reqs_to_cleanup = []
165 self.as_egg = as_egg
166 self.use_user_site = use_user_site
167 self.target_dir = target_dir # set from --target option
168 self.session = session
169 self.pycompile = pycompile
170 self.isolated = isolated
171 if wheel_download_dir:
172 wheel_download_dir = normalize_path(wheel_download_dir)
173 self.wheel_download_dir = wheel_download_dir
174 # Maps from install_req -> dependencies_of_install_req
175 self._dependencies = defaultdict(list)
176
177 def __str__(self):
178 reqs = [req for req in self.requirements.values()
179 if not req.comes_from]
180 reqs.sort(key=lambda req: req.name.lower())
181 return ' '.join([str(req.req) for req in reqs])
182
183 def __repr__(self):
184 reqs = [req for req in self.requirements.values()]
185 reqs.sort(key=lambda req: req.name.lower())
186 reqs_str = ', '.join([str(req.req) for req in reqs])
187 return ('<%s object; %d requirement(s): %s>'
188 % (self.__class__.__name__, len(reqs), reqs_str))
189
190 def add_requirement(self, install_req, parent_req_name=None):
191 """Add install_req as a requirement to install.
192
193 :param parent_req_name: The name of the requirement that needed this
194 added. The name is used because when multiple unnamed requirements
195 resolve to the same name, we could otherwise end up with dependency
196 links that point outside the Requirements set. parent_req must
197 already be added. Note that None implies that this is a user
198 supplied requirement, vs an inferred one.
199 :return: Additional requirements to scan. That is either [] if
200 the requirement is not applicable, or [install_req] if the
201 requirement is applicable and has just been added.
202 """
203 name = install_req.name
204 if ((not name or not self.has_requirement(name)) and not
205 install_req.match_markers()):
206 # Only log if we haven't already got install_req from somewhere.
207 logger.debug("Ignore %s: markers %r don't match",
208 install_req.name, install_req.markers)
209 return []
210
211 install_req.as_egg = self.as_egg
212 install_req.use_user_site = self.use_user_site
213 install_req.target_dir = self.target_dir
214 install_req.pycompile = self.pycompile
215 if not name:
216 # url or path requirement w/o an egg fragment
217 self.unnamed_requirements.append(install_req)
218 return [install_req]
219 else:
220 if parent_req_name is None and self.has_requirement(name):
221 raise InstallationError(
222 'Double requirement given: %s (already in %s, name=%r)'
223 % (install_req, self.get_requirement(name), name))
224 if not self.has_requirement(name):
225 # Add requirement
226 self.requirements[name] = install_req
227 # FIXME: what about other normalizations? E.g., _ vs. -?
228 if name.lower() != name:
229 self.requirement_aliases[name.lower()] = name
230 result = [install_req]
231 else:
232 # Canonicalise to the already-added object
233 install_req = self.get_requirement(name)
234 # No need to scan, this is a duplicate requirement.
235 result = []
236 if parent_req_name:
237 parent_req = self.get_requirement(parent_req_name)
238 self._dependencies[parent_req].append(install_req)
239 return result
240
241 def has_requirement(self, project_name):
242 for name in project_name, project_name.lower():
243 if name in self.requirements or name in self.requirement_aliases:
244 return True
245 return False
246
247 @property
248 def has_requirements(self):
249 return list(self.requirements.values()) or self.unnamed_requirements
250
251 @property
252 def is_download(self):
253 if self.download_dir:
254 self.download_dir = os.path.expanduser(self.download_dir)
255 if os.path.exists(self.download_dir):
256 return True
257 else:
258 logger.critical('Could not find download directory')
259 raise InstallationError(
260 "Could not find or access download directory '%s'"
261 % display_path(self.download_dir))
262 return False
263
264 def get_requirement(self, project_name):
265 for name in project_name, project_name.lower():
266 if name in self.requirements:
267 return self.requirements[name]
268 if name in self.requirement_aliases:
269 return self.requirements[self.requirement_aliases[name]]
270 raise KeyError("No project with the name %r" % project_name)
271
272 def uninstall(self, auto_confirm=False):
273 for req in self.requirements.values():
274 req.uninstall(auto_confirm=auto_confirm)
275 req.commit_uninstall()
276
277 def _walk_req_to_install(self, handler):
278 """Call handler for all pending reqs.
279
280 :param handler: Handle a single requirement. Should take a requirement
281 to install. Can optionally return an iterable of additional
282 InstallRequirements to cover.
283 """
284 # The list() here is to avoid potential mutate-while-iterating bugs.
285 discovered_reqs = []
286 reqs = itertools.chain(
287 list(self.unnamed_requirements), list(self.requirements.values()),
288 discovered_reqs)
289 for req_to_install in reqs:
290 more_reqs = handler(req_to_install)
291 if more_reqs:
292 discovered_reqs.extend(more_reqs)
293
294 def locate_files(self):
295 """Remove in 7.0: used by --no-download"""
296 self._walk_req_to_install(self._locate_file)
297
298 def _locate_file(self, req_to_install):
299 install_needed = True
300 if not self.ignore_installed and not req_to_install.editable:
301 req_to_install.check_if_exists()
302 if req_to_install.satisfied_by:
303 if self.upgrade:
304 # don't uninstall conflict if user install and
305 # conflict is not user install
306 if not (self.use_user_site and
307 not dist_in_usersite(
308 req_to_install.satisfied_by
309 )):
310 req_to_install.conflicts_with = \
311 req_to_install.satisfied_by
312 req_to_install.satisfied_by = None
313 else:
314 install_needed = False
315 logger.info(
316 'Requirement already satisfied (use --upgrade to '
317 'upgrade): %s',
318 req_to_install,
319 )
320
321 if req_to_install.editable:
322 if req_to_install.source_dir is None:
323 req_to_install.source_dir = req_to_install.build_location(
324 self.src_dir
325 )
326 elif install_needed:
327 req_to_install.source_dir = req_to_install.build_location(
328 self.build_dir,
329 )
330
331 if (req_to_install.source_dir is not None and not
332 os.path.isdir(req_to_install.source_dir)):
333 raise InstallationError(
334 'Could not install requirement %s because source folder %s'
335 ' does not exist (perhaps --no-download was used without '
336 'first running an equivalent install with --no-install?)' %
337 (req_to_install, req_to_install.source_dir)
338 )
339
340 def prepare_files(self, finder):
341 """
342 Prepare process. Create temp directories, download and/or unpack files.
343 """
344 self._walk_req_to_install(
345 functools.partial(self._prepare_file, finder))
346
347 def _check_skip_installed(self, req_to_install, finder):
348 """Check if req_to_install should be skipped.
349
350 This will check if the req is installed, and whether we should upgrade
351 or reinstall it, taking into account all the relevant user options.
352
353 After calling this req_to_install will only have satisfied_by set to
354 None if the req_to_install is to be upgraded/reinstalled etc. Any
355 other value will be a dist recording the current thing installed that
356 satisfies the requirement.
357
358 Note that for vcs urls and the like we can't assess skipping in this
359 routine - we simply identify that we need to pull the thing down,
360 then later on it is pulled down and introspected to assess upgrade/
361 reinstalls etc.
362
363 :return: A text reason for why it was skipped, or None.
364 """
365 # Check whether to upgrade/reinstall this req or not.
366 req_to_install.check_if_exists()
367 if req_to_install.satisfied_by:
368 skip_reason = 'satisfied (use --upgrade to upgrade)'
369 if self.upgrade:
370 best_installed = False
371 # For link based requirements we have to pull the
372 # tree down and inspect to assess the version #, so
373 # its handled way down.
374 if not (self.force_reinstall or req_to_install.link):
375 try:
376 finder.find_requirement(req_to_install, self.upgrade)
377 except BestVersionAlreadyInstalled:
378 skip_reason = 'up-to-date'
379 best_installed = True
380 except DistributionNotFound:
381 # No distribution found, so we squash the
382 # error - it will be raised later when we
383 # re-try later to do the install.
384 # Why don't we just raise here?
385 pass
386
387 if not best_installed:
388 # don't uninstall conflict if user install and
389 # conflict is not user install
390 if not (self.use_user_site and not
391 dist_in_usersite(req_to_install.satisfied_by)):
392 req_to_install.conflicts_with = \
393 req_to_install.satisfied_by
394 req_to_install.satisfied_by = None
395 return skip_reason
396 else:
397 return None
398
399 def _prepare_file(self, finder, req_to_install):
400 """Prepare a single requirements files.
401
402 :return: A list of addition InstallRequirements to also install.
403 """
404 # Tell user what we are doing for this requirement:
405 # obtain (editable), skipping, processing (local url), collecting
406 # (remote url or package name)
407 if req_to_install.editable:
408 logger.info('Obtaining %s', req_to_install)
409 else:
410 # satisfied_by is only evaluated by calling _check_skip_installed,
411 # so it must be None here.
412 assert req_to_install.satisfied_by is None
413 if not self.ignore_installed:
414 skip_reason = self._check_skip_installed(
415 req_to_install, finder)
416
417 if req_to_install.satisfied_by:
418 assert skip_reason is not None, (
419 '_check_skip_installed returned None but '
420 'req_to_install.satisfied_by is set to %r'
421 % (req_to_install.satisfied_by,))
422 logger.info(
423 'Requirement already %s: %s', skip_reason,
424 req_to_install)
425 else:
426 if (req_to_install.link and
427 req_to_install.link.scheme == 'file'):
428 path = url_to_path(req_to_install.link.url)
429 logger.info('Processing %s', display_path(path))
430 else:
431 logger.info('Collecting %s', req_to_install)
432
433 with indent_log():
434 # ################################ #
435 # # vcs update or unpack archive # #
436 # ################################ #
437 if req_to_install.editable:
438 req_to_install.ensure_has_source_dir(self.src_dir)
439 req_to_install.update_editable(not self.is_download)
440 abstract_dist = make_abstract_dist(req_to_install)
441 abstract_dist.prep_for_dist()
442 if self.is_download:
443 req_to_install.archive(self.download_dir)
444 elif req_to_install.satisfied_by:
445 abstract_dist = Installed(req_to_install)
446 else:
447 # @@ if filesystem packages are not marked
448 # editable in a req, a non deterministic error
449 # occurs when the script attempts to unpack the
450 # build directory
451 req_to_install.ensure_has_source_dir(self.build_dir)
452 # If a checkout exists, it's unwise to keep going. version
453 # inconsistencies are logged later, but do not fail the
454 # installation.
455 # FIXME: this won't upgrade when there's an existing
456 # package unpacked in `req_to_install.source_dir`
457 if os.path.exists(
458 os.path.join(req_to_install.source_dir, 'setup.py')):
459 raise PreviousBuildDirError(
460 "pip can't proceed with requirements '%s' due to a"
461 " pre-existing build directory (%s). This is "
462 "likely due to a previous installation that failed"
463 ". pip is being responsible and not assuming it "
464 "can delete this. Please delete it and try again."
465 % (req_to_install, req_to_install.source_dir)
466 )
467 req_to_install.populate_link(finder, self.upgrade)
468 # We can't hit this spot and have populate_link return None.
469 # req_to_install.satisfied_by is None here (because we're
470 # guarded) and upgrade has no impact except when satisfied_by
471 # is not None.
472 # Then inside find_requirement existing_applicable -> False
473 # If no new versions are found, DistributionNotFound is raised,
474 # otherwise a result is guaranteed.
475 assert req_to_install.link
476 try:
477 if req_to_install.link.is_wheel and \
478 self.wheel_download_dir:
479 # when doing 'pip wheel`
480 download_dir = self.wheel_download_dir
481 do_download = True
482 else:
483 download_dir = self.download_dir
484 do_download = self.is_download
485 unpack_url(
486 req_to_install.link, req_to_install.source_dir,
487 download_dir, do_download, session=self.session,
488 )
489 except requests.HTTPError as exc:
490 logger.critical(
491 'Could not install requirement %s because '
492 'of error %s',
493 req_to_install,
494 exc,
495 )
496 raise InstallationError(
497 'Could not install requirement %s because '
498 'of HTTP error %s for URL %s' %
499 (req_to_install, exc, req_to_install.link)
500 )
501 abstract_dist = make_abstract_dist(req_to_install)
502 abstract_dist.prep_for_dist()
503 if self.is_download:
504 # Make a .zip of the source_dir we already created.
505 if req_to_install.link.scheme in vcs.all_schemes:
506 req_to_install.archive(self.download_dir)
507 # req_to_install.req is only avail after unpack for URL
508 # pkgs repeat check_if_exists to uninstall-on-upgrade
509 # (#14)
510 if not self.ignore_installed:
511 req_to_install.check_if_exists()
512 if req_to_install.satisfied_by:
513 if self.upgrade or self.ignore_installed:
514 # don't uninstall conflict if user install and
515 # conflict is not user install
516 if not (self.use_user_site and not
517 dist_in_usersite(
518 req_to_install.satisfied_by)):
519 req_to_install.conflicts_with = \
520 req_to_install.satisfied_by
521 req_to_install.satisfied_by = None
522 else:
523 logger.info(
524 'Requirement already satisfied (use '
525 '--upgrade to upgrade): %s',
526 req_to_install,
527 )
528
529 # ###################### #
530 # # parse dependencies # #
531 # ###################### #
532 dist = abstract_dist.dist(finder)
533 more_reqs = []
534
535 def add_req(subreq):
536 sub_install_req = InstallRequirement(
537 str(subreq),
538 req_to_install,
539 isolated=self.isolated,
540 )
541 more_reqs.extend(self.add_requirement(
542 sub_install_req, req_to_install.name))
543
544 # We add req_to_install before its dependencies, so that we
545 # can refer to it when adding dependencies.
546 if not self.has_requirement(req_to_install.name):
547 # 'unnamed' requirements will get added here
548 self.add_requirement(req_to_install, None)
549
550 if not self.ignore_dependencies:
551 if (req_to_install.extras):
552 logger.debug(
553 "Installing extra requirements: %r",
554 ','.join(req_to_install.extras),
555 )
556 missing_requested = sorted(
557 set(req_to_install.extras) - set(dist.extras)
558 )
559 for missing in missing_requested:
560 logger.warning(
561 '%s does not provide the extra \'%s\'',
562 dist, missing
563 )
564
565 available_requested = sorted(
566 set(dist.extras) & set(req_to_install.extras)
567 )
568 for subreq in dist.requires(available_requested):
569 add_req(subreq)
570
571 # cleanup tmp src
572 self.reqs_to_cleanup.append(req_to_install)
573
574 if not req_to_install.editable and not req_to_install.satisfied_by:
575 # XXX: --no-install leads this to report 'Successfully
576 # downloaded' for only non-editable reqs, even though we took
577 # action on them.
578 self.successfully_downloaded.append(req_to_install)
579
580 return more_reqs
581
582 def cleanup_files(self):
583 """Clean up files, remove builds."""
584 logger.debug('Cleaning up...')
585 with indent_log():
586 for req in self.reqs_to_cleanup:
587 req.remove_temporary_source()
588
589 if self._pip_has_created_build_dir():
590 logger.debug('Removing temporary dir %s...', self.build_dir)
591 rmtree(self.build_dir)
592
593 def _pip_has_created_build_dir(self):
594 return (
595 self.build_dir == build_prefix and
596 os.path.exists(
597 os.path.join(self.build_dir, PIP_DELETE_MARKER_FILENAME)
598 )
599 )
600
601 def _to_install(self):
602 """Create the installation order.
603
604 The installation order is topological - requirements are installed
605 before the requiring thing. We break cycles at an arbitrary point,
606 and make no other guarantees.
607 """
608 # The current implementation, which we may change at any point
609 # installs the user specified things in the order given, except when
610 # dependencies must come earlier to achieve topological order.
611 order = []
612 ordered_reqs = set()
613
614 def schedule(req):
615 if req.satisfied_by or req in ordered_reqs:
616 return
617 ordered_reqs.add(req)
618 for dep in self._dependencies[req]:
619 schedule(dep)
620 order.append(req)
621 for install_req in self.requirements.values():
622 schedule(install_req)
623 return order
624
625 def install(self, install_options, global_options=(), *args, **kwargs):
626 """
627 Install everything in this set (after having downloaded and unpacked
628 the packages)
629 """
630 to_install = self._to_install()
631
632 # DISTRIBUTE TO SETUPTOOLS UPGRADE HACK (1 of 3 parts)
633 # move the distribute-0.7.X wrapper to the end because it does not
634 # install a setuptools package. by moving it to the end, we ensure it's
635 # setuptools dependency is handled first, which will provide the
636 # setuptools package
637 # TODO: take this out later
638 distribute_req = pkg_resources.Requirement.parse("distribute>=0.7")
639 for req in to_install:
640 if (req.name == 'distribute' and
641 req.installed_version is not None and
642 req.installed_version in distribute_req):
643 to_install.remove(req)
644 to_install.append(req)
645
646 if to_install:
647 logger.info(
648 'Installing collected packages: %s',
649 ', '.join([req.name for req in to_install]),
650 )
651
652 with indent_log():
653 for requirement in to_install:
654
655 # DISTRIBUTE TO SETUPTOOLS UPGRADE HACK (1 of 3 parts)
656 # when upgrading from distribute-0.6.X to the new merged
657 # setuptools in py2, we need to force setuptools to uninstall
658 # distribute. In py3, which is always using distribute, this
659 # conversion is already happening in distribute's
660 # pkg_resources. It's ok *not* to check if setuptools>=0.7
661 # because if someone were actually trying to ugrade from
662 # distribute to setuptools 0.6.X, then all this could do is
663 # actually help, although that upgade path was certainly never
664 # "supported"
665 # TODO: remove this later
666 if requirement.name == 'setuptools':
667 try:
668 # only uninstall distribute<0.7. For >=0.7, setuptools
669 # will also be present, and that's what we need to
670 # uninstall
671 distribute_requirement = \
672 pkg_resources.Requirement.parse("distribute<0.7")
673 existing_distribute = \
674 pkg_resources.get_distribution("distribute")
675 if existing_distribute in distribute_requirement:
676 requirement.conflicts_with = existing_distribute
677 except pkg_resources.DistributionNotFound:
678 # distribute wasn't installed, so nothing to do
679 pass
680
681 if requirement.conflicts_with:
682 logger.info(
683 'Found existing installation: %s',
684 requirement.conflicts_with,
685 )
686 with indent_log():
687 requirement.uninstall(auto_confirm=True)
688 try:
689 requirement.install(
690 install_options,
691 global_options,
692 *args,
693 **kwargs
694 )
695 except:
696 # if install did not succeed, rollback previous uninstall
697 if (requirement.conflicts_with and not
698 requirement.install_succeeded):
699 requirement.rollback_uninstall()
700 raise
701 else:
702 if (requirement.conflicts_with and
703 requirement.install_succeeded):
704 requirement.commit_uninstall()
705 requirement.remove_temporary_source()
706
707 self.successfully_installed = to_install