def test_link_sorting_raises_when_wheel_unsupported(self): links = [ InstallationCandidate( "simple", '1.0', Link('simple-1.0-py2.py3-none-TEST.whl'), ), ] finder = PackageFinder([], [], use_wheel=True, session=PipSession()) with pytest.raises(InstallationError): finder._sort_versions(links)
def test_link_sorting(self): """ Test link sorting """ links = [ InstallationCandidate("simple", "2.0", Link(Inf)), InstallationCandidate("simple", "2.0", Link('simple-2.0.tar.gz')), InstallationCandidate( "simple", "1.0", Link('simple-1.0-pyT-none-TEST.whl'), ), InstallationCandidate( "simple", '1.0', Link('simple-1.0-pyT-TEST-any.whl'), ), InstallationCandidate( "simple", '1.0', Link('simple-1.0-pyT-none-any.whl'), ), InstallationCandidate( "simple", '1.0', Link('simple-1.0.tar.gz'), ), ] finder = PackageFinder([], [], session=PipSession()) finder.use_wheel = True results = finder._sort_versions(links) results2 = finder._sort_versions(reversed(links)) assert links == results == results2, results2
def _link_package_versions(self, link, search_name): """ Return an iterable of triples (pkg_resources_version_key, link, python_version) that can be extracted from the given link. Meant to be overridden by subclasses, not called by clients. """ platform = get_platform() version = None if link.egg_fragment: egg_info = link.egg_fragment else: egg_info, ext = link.splitext() if not ext: if link not in self.logged_links: logger.debug('Skipping link %s; not a file', link) self.logged_links.add(link) return if egg_info.endswith('.tar'): # Special double-extension case: egg_info = egg_info[:-4] ext = '.tar' + ext if ext not in self._known_extensions(): if link not in self.logged_links: logger.debug( 'Skipping link %s; unknown archive format: %s', link, ext, ) self.logged_links.add(link) return if "macosx10" in link.path and ext == '.zip': if link not in self.logged_links: logger.debug('Skipping link %s; macosx10 one', link) self.logged_links.add(link) return if ext == wheel_ext: try: wheel = Wheel(link.filename) except InvalidWheelFilename: logger.debug( 'Skipping %s because the wheel filename is invalid', link) return if (pkg_resources.safe_name(wheel.name).lower() != pkg_resources.safe_name(search_name).lower()): logger.debug( 'Skipping link %s; wrong project name (not %s)', link, search_name, ) return if not wheel.supported(): logger.debug( 'Skipping %s because it is not compatible with this ' 'Python', link, ) return # This is a dirty hack to prevent installing Binary Wheels from # PyPI unless it is a Windows or Mac Binary Wheel. This is # paired with a change to PyPI disabling uploads for the # same. Once we have a mechanism for enabling support for # binary wheels on linux that deals with the inherent problems # of binary distribution this can be removed. comes_from = getattr(link, "comes_from", None) if ((not platform.startswith('win') and not platform.startswith('macosx') and not platform == 'cli') and comes_from is not None and urllib_parse.urlparse( comes_from.url).netloc.endswith(PyPI.netloc)): if not wheel.supported(tags=supported_tags_noarch): logger.debug( "Skipping %s because it is a pypi-hosted binary " "Wheel on an unsupported platform", link, ) return version = wheel.version if not version: version = self._egg_info_matches(egg_info, search_name, link) if version is None: logger.debug( 'Skipping link %s; wrong project name (not %s)', link, search_name, ) return if (link.internal is not None and not link.internal and not normalize_name(search_name).lower() in self.allow_external and not self.allow_all_external): # We have a link that we are sure is external, so we should skip # it unless we are allowing externals logger.debug("Skipping %s because it is externally hosted.", link) self.need_warn_external = True return if (link.verifiable is not None and not link.verifiable and not (normalize_name(search_name).lower() in self.allow_unverified)): # We have a link that we are sure we cannot verify its integrity, # so we should skip it unless we are allowing unsafe installs # for this requirement. logger.debug( "Skipping %s because it is an insecure and unverifiable file.", link, ) self.need_warn_unverified = True return match = self._py_version_re.search(version) if match: version = version[:match.start()] py_version = match.group(1) if py_version != sys.version[:3]: logger.debug('Skipping %s because Python version is incorrect', link) return logger.debug('Found link %s, version: %s', link, version) return InstallationCandidate(search_name, version, link)
def find_requirement(self, req, upgrade): def mkurl_pypi_url(url): loc = posixpath.join(url, url_name) # For maximum compatibility with easy_install, ensure the path # ends in a trailing slash. Although this isn't in the spec # (and PyPI can handle it without the slash) some other index # implementations might break if they relied on easy_install's # behavior. if not loc.endswith('/'): loc = loc + '/' return loc url_name = req.url_name # Only check main index if index URL is given: main_index_url = None if self.index_urls: # Check that we have the url_name correctly spelled: main_index_url = Link( mkurl_pypi_url(self.index_urls[0]), trusted=True, ) page = self._get_page(main_index_url, req) if page is None and PyPI.netloc not in str(main_index_url): warnings.warn( "Failed to find %r at %s. It is suggested to upgrade " "your index to support normalized names as the name in " "/simple/{name}." % (req.name, main_index_url), RemovedInPip8Warning, ) url_name = self._find_url_name( Link(self.index_urls[0], trusted=True), url_name, req) or req.url_name if url_name is not None: locations = [mkurl_pypi_url(url) for url in self.index_urls] + self.find_links else: locations = list(self.find_links) file_locations, url_locations = self._sort_locations(locations) _flocations, _ulocations = self._sort_locations(self.dependency_links) file_locations.extend(_flocations) # We trust every url that the user has given us whether it was given # via --index-url or --find-links locations = [Link(url, trusted=True) for url in url_locations] # We explicitly do not trust links that came from dependency_links locations.extend([Link(url) for url in _ulocations]) logger.debug('URLs to search for versions for %s:', req) for location in locations: logger.debug('* %s', location) self._validate_secure_origin(logger, location) found_versions = [] found_versions.extend( self._package_versions( # We trust every directly linked archive in find_links [Link(url, '-f', trusted=True) for url in self.find_links], req.name.lower())) page_versions = [] for page in self._get_pages(locations, req): logger.debug('Analyzing links from page %s', page.url) with indent_log(): page_versions.extend( self._package_versions(page.links, req.name.lower())) dependency_versions = list( self._package_versions( [Link(url) for url in self.dependency_links], req.name.lower())) if dependency_versions: logger.debug( 'dependency_links found: %s', ', '.join( [link.url for p, link, version in dependency_versions])) file_versions = list( self._package_versions([Link(url) for url in file_locations], req.name.lower())) if (not found_versions and not page_versions and not dependency_versions and not file_versions): logger.critical( 'Could not find any downloads that satisfy the requirement %s', req, ) if self.need_warn_external: logger.warning( "Some externally hosted files were ignored as access to " "them may be unreliable (use --allow-external %s to " "allow).", req.name, ) if self.need_warn_unverified: logger.warning( "Some insecure and unverifiable files were ignored" " (use --allow-unverified %s to allow).", req.name, ) raise DistributionNotFound('No distributions at all found for %s' % req) installed_version = [] if req.satisfied_by is not None: installed_version = [ InstallationCandidate( req.name, req.satisfied_by.version, INSTALLED_VERSION, ), ] if file_versions: file_versions.sort(reverse=True) logger.debug( 'Local files found: %s', ', '.join([ url_to_path(candidate.location.url) for candidate in file_versions ])) # This is an intentional priority ordering all_versions = (file_versions + found_versions + page_versions + dependency_versions) # Filter out anything which doesn't match our specifier _versions = set( req.specifier.filter( [x.version for x in all_versions], prereleases=(self.allow_all_prereleases if self.allow_all_prereleases else None), )) all_versions = [x for x in all_versions if x.version in _versions] # Finally add our existing versions to the front of our versions. applicable_versions = installed_version + all_versions applicable_versions = self._sort_versions(applicable_versions) existing_applicable = any(i.location is INSTALLED_VERSION for i in applicable_versions) if not upgrade and existing_applicable: if applicable_versions[0].location is INSTALLED_VERSION: logger.debug( 'Existing installed version (%s) is most up-to-date and ' 'satisfies requirement', req.satisfied_by.version, ) else: logger.debug( 'Existing installed version (%s) satisfies requirement ' '(most up-to-date version is %s)', req.satisfied_by.version, applicable_versions[0][2], ) return None if not applicable_versions: logger.critical( 'Could not find a version that satisfies the requirement %s ' '(from versions: %s)', req, ', '.join( sorted( set(str(i.version) for i in all_versions), key=parse_version, ))) if self.need_warn_external: logger.warning( "Some externally hosted files were ignored as access to " "them may be unreliable (use --allow-external to allow).") if self.need_warn_unverified: logger.warning( "Some insecure and unverifiable files were ignored" " (use --allow-unverified %s to allow).", req.name, ) raise DistributionNotFound( 'No distributions matching the version for %s' % req) if applicable_versions[0].location is INSTALLED_VERSION: # We have an existing version, and its the best version logger.debug( 'Installed version (%s) is most up-to-date (past versions: ', '%s)', req.satisfied_by.version, ', '.join(str(i.version) for i in applicable_versions[1:]) or "none", ) raise BestVersionAlreadyInstalled if len(applicable_versions) > 1: logger.debug( 'Using version %s (newest of versions: %s)', applicable_versions[0].version, ', '.join(str(i.version) for i in applicable_versions)) selected_version = applicable_versions[0].location if (selected_version.verifiable is not None and not selected_version.verifiable): logger.warning( "%s is potentially insecure and unverifiable.", req.name, ) if selected_version._deprecated_regex: warnings.warn( "%s discovered using a deprecated method of parsing, in the " "future it will no longer be discovered." % req.name, RemovedInPip7Warning, ) return selected_version
def find_requirement(self, req, upgrade): """Try to find an InstallationCandidate for req Expects req, an InstallRequirement and upgrade, a boolean Returns an InstallationCandidate or None May raise DistributionNotFound or BestVersionAlreadyInstalled """ all_versions = self._find_all_versions(req.name) # Filter out anything which doesn't match our specifier _versions = set( req.specifier.filter( [x.version for x in all_versions], prereleases=(self.allow_all_prereleases if self.allow_all_prereleases else None), )) applicable_versions = [ x for x in all_versions if x.version in _versions ] if req.satisfied_by is not None: # Finally add our existing versions to the front of our versions. applicable_versions.insert( 0, InstallationCandidate( req.name, req.satisfied_by.version, INSTALLED_VERSION, )) existing_applicable = True else: existing_applicable = False applicable_versions = self._sort_versions(applicable_versions) if not upgrade and existing_applicable: if applicable_versions[0].location is INSTALLED_VERSION: logger.debug( 'Existing installed version (%s) is most up-to-date and ' 'satisfies requirement', req.satisfied_by.version, ) else: logger.debug( 'Existing installed version (%s) satisfies requirement ' '(most up-to-date version is %s)', req.satisfied_by.version, applicable_versions[0][2], ) return None if not applicable_versions: logger.critical( 'Could not find a version that satisfies the requirement %s ' '(from versions: %s)', req, ', '.join( sorted( set(str(i.version) for i in all_versions), key=parse_version, ))) if self.need_warn_external: logger.warning( "Some externally hosted files were ignored as access to " "them may be unreliable (use --allow-external %s to " "allow).", req.name, ) if self.need_warn_unverified: logger.warning( "Some insecure and unverifiable files were ignored" " (use --allow-unverified %s to allow).", req.name, ) raise DistributionNotFound( 'No matching distribution found for %s' % req) if applicable_versions[0].location is INSTALLED_VERSION: # We have an existing version, and its the best version logger.debug( 'Installed version (%s) is most up-to-date (past versions: ' '%s)', req.satisfied_by.version, ', '.join(str(i.version) for i in applicable_versions[1:]) or "none", ) raise BestVersionAlreadyInstalled if len(applicable_versions) > 1: logger.debug( 'Using version %s (newest of versions: %s)', applicable_versions[0].version, ', '.join(str(i.version) for i in applicable_versions)) selected_version = applicable_versions[0].location if (selected_version.verifiable is not None and not selected_version.verifiable): logger.warning( "%s is potentially insecure and unverifiable.", req.name, ) if selected_version._deprecated_regex: warnings.warn( "%s discovered using a deprecated method of parsing, in the " "future it will no longer be discovered." % req.name, RemovedInPip7Warning, ) return selected_version