Esempio n. 1
0
 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)
Esempio n. 2
0
    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
Esempio n. 5
0
    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