Beispiel #1
0
    def add_package(key: str, dist: Distribution) -> Package:
        name, extras = strip_extras(key)
        extras = extras or ()
        reqs = {}
        if dist:
            requirements = [
                Requirement.from_pkg_requirement(r) for r in dist.requires(extras)
            ]
            for req in requirements:
                reqs[req.identify()] = req
            version = dist.version
        else:
            version = None

        node = Package(key, version, reqs)
        if node not in graph:
            if extras:
                node_with_extras.add(name)
            graph.add(node)

            for k in reqs:
                child = add_package(k, working_set.get(strip_extras(k)[0]))
                graph.connect(node, child)

        return node
Beispiel #2
0
def uninstallation_paths(dist: Distribution) -> Iterator[str]:
    """
    Yield all the uninstallation paths for dist based on RECORD-without-.py[co]

    Yield paths to all the files in RECORD. For each .py file in RECORD, add
    the .pyc and .pyo in the same directory.

    UninstallPathSet.add() takes care of the __pycache__ .py[co].

    If RECORD is not found, raises UninstallationError,
    with possible information from the INSTALLER file.

    https://packaging.python.org/specifications/recording-installed-packages/
    """
    try:
        r = csv.reader(dist.get_metadata_lines('RECORD'))
    except FileNotFoundError as missing_record_exception:
        msg = 'Cannot uninstall {dist}, RECORD file not found.'.format(
            dist=dist)
        try:
            installer = next(dist.get_metadata_lines('INSTALLER'))
            if not installer or installer == 'pip':
                raise ValueError()
        except (OSError, StopIteration, ValueError):
            dep = '{}=={}'.format(dist.project_name, dist.version)
            msg += (
                " You might be able to recover from this via: "
                "'pip install --force-reinstall --no-deps {}'.".format(dep))
        else:
            msg += ' Hint: The package was installed by {}.'.format(installer)
        raise UninstallationError(msg) from missing_record_exception
    for row in r:
        path = os.path.join(dist.location, row[0])
        yield path
        if path.endswith('.py'):
            dn, fn = os.path.split(path)
            base = fn[:-3]
            path = os.path.join(dn, base + '.pyc')
            yield path
            path = os.path.join(dn, base + '.pyo')
            yield path
Beispiel #3
0
class TestPackaging:
    mock_installed = [Distribution(project_name='pipsy-test', version='0.1')]
    mock_pkg_json = json.dumps({
        "info": {
            "version": "0.1",
            "name": "pipsy-test"
        },
        "releases": {
            "0.3": [{}],
            "0.2": [{}],
            "0.1": [{}]
        }
    }).encode('utf-8')

    @patch('pip.get_installed_distributions')
    def test_pip_installed(self, pip_mock):
        pip_mock.return_value = self.mock_installed
        packages = pip.get_installed_distributions()
        assert len(packages) == 1

    @patch('urllib.request.urlopen')
    def test_get_pkg_info(self, info_mock):
        req_mock = Mock()
        req_mock.readall.return_value = self.mock_pkg_json
        info_mock.return_value = req_mock

        pkg = get_pkg_info(self.mock_installed[0])
        versions = get_versions(pkg)
        assert len(versions) == 3

    @patch('urllib.request.urlopen')
    def test_get_version_range(self, info_mock):
        req_mock = Mock()
        req_mock.readall.return_value = self.mock_pkg_json
        info_mock.return_value = req_mock

        pkg = get_pkg_info(self.mock_installed[0])
        version_range = get_version_range(pkg, '0.1')
        assert len(version_range) == 2

    @patch('urllib.request.urlopen')
    def test_get_latest_version(self, info_mock):
        req_mock = Mock()
        req_mock.readall.return_value = self.mock_pkg_json
        info_mock.return_value = req_mock

        pkg = get_pkg_info(self.mock_installed[0])
        latest_version = get_latest_version(pkg)
        assert str(latest_version) == '0.3'
def get_metadata(dist: Distribution) -> Message:
    """
    :raises NoneMetadataError: if the distribution reports `has_metadata()`
        True but `get_metadata()` returns None.
    """
    metadata_name = "METADATA"
    if isinstance(dist, pkg_resources.DistInfoDistribution
                  ) and dist.has_metadata(metadata_name):
        metadata = dist.get_metadata(metadata_name)
    elif dist.has_metadata("PKG-INFO"):
        metadata_name = "PKG-INFO"
        metadata = dist.get_metadata(metadata_name)
    else:
        logger.warning("No metadata found in %s", display_path(dist.location))
        metadata = ""

    if metadata is None:
        raise NoneMetadataError(dist, metadata_name)

    feed_parser = FeedParser()
    # The following line errors out if with a "NoneType" TypeError if
    # passed metadata=None.
    feed_parser.feed(metadata)
    return feed_parser.close()
Beispiel #5
0
    def from_dist(cls, dist: Distribution) -> "UninstallPathSet":
        dist_path = normalize_path(dist.location)
        if not dist_is_local(dist):
            logger.info(
                "Not uninstalling %s at %s, outside environment %s",
                dist.key,
                dist_path,
                sys.prefix,
            )
            return cls(dist)

        if dist_path in {
                p
                for p in
            {sysconfig.get_path("stdlib"),
             sysconfig.get_path("platstdlib")} if p
        }:
            logger.info(
                "Not uninstalling %s at %s, as it is in the standard library.",
                dist.key,
                dist_path,
            )
            return cls(dist)

        paths_to_remove = cls(dist)
        develop_egg_link = egg_link_path(dist)
        develop_egg_link_egg_info = '{}.egg-info'.format(
            pkg_resources.to_filename(dist.project_name))
        egg_info_exists = dist.egg_info and os.path.exists(dist.egg_info)
        # Special case for distutils installed package
        distutils_egg_info = getattr(dist._provider, 'path', None)

        # Uninstall cases order do matter as in the case of 2 installs of the
        # same package, pip needs to uninstall the currently detected version
        if (egg_info_exists and dist.egg_info.endswith('.egg-info')
                and not dist.egg_info.endswith(develop_egg_link_egg_info)):
            # if dist.egg_info.endswith(develop_egg_link_egg_info), we
            # are in fact in the develop_egg_link case
            paths_to_remove.add(dist.egg_info)
            if dist.has_metadata('installed-files.txt'):
                for installed_file in dist.get_metadata(
                        'installed-files.txt').splitlines():
                    path = os.path.normpath(
                        os.path.join(dist.egg_info, installed_file))
                    paths_to_remove.add(path)
            # FIXME: need a test for this elif block
            # occurs with --single-version-externally-managed/--record outside
            # of pip
            elif dist.has_metadata('top_level.txt'):
                if dist.has_metadata('namespace_packages.txt'):
                    namespaces = dist.get_metadata('namespace_packages.txt')
                else:
                    namespaces = []
                for top_level_pkg in [
                        p for p in dist.get_metadata(
                            'top_level.txt').splitlines()
                        if p and p not in namespaces
                ]:
                    path = os.path.join(dist.location, top_level_pkg)
                    paths_to_remove.add(path)
                    paths_to_remove.add(path + '.py')
                    paths_to_remove.add(path + '.pyc')
                    paths_to_remove.add(path + '.pyo')

        elif distutils_egg_info:
            raise UninstallationError(
                "Cannot uninstall {!r}. It is a distutils installed project "
                "and thus we cannot accurately determine which files belong "
                "to it which would lead to only a partial uninstall.".format(
                    dist.project_name, ))

        elif dist.location.endswith('.egg'):
            # package installed by easy_install
            # We cannot match on dist.egg_name because it can slightly vary
            # i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
            paths_to_remove.add(dist.location)
            easy_install_egg = os.path.split(dist.location)[1]
            easy_install_pth = os.path.join(os.path.dirname(dist.location),
                                            'easy-install.pth')
            paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg)

        elif egg_info_exists and dist.egg_info.endswith('.dist-info'):
            for path in uninstallation_paths(dist):
                paths_to_remove.add(path)

        elif develop_egg_link:
            # develop egg
            with open(develop_egg_link) as fh:
                link_pointer = os.path.normcase(fh.readline().strip())
            assert (link_pointer == dist.location), (
                'Egg-link {} does not match installed location of {} '
                '(at {})'.format(link_pointer, dist.project_name,
                                 dist.location))
            paths_to_remove.add(develop_egg_link)
            easy_install_pth = os.path.join(os.path.dirname(develop_egg_link),
                                            'easy-install.pth')
            paths_to_remove.add_pth(easy_install_pth, dist.location)

        else:
            logger.debug(
                'Not sure how to uninstall: %s - Check: %s',
                dist,
                dist.location,
            )

        # find distutils scripts= scripts
        if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'):
            for script in dist.metadata_listdir('scripts'):
                if dist_in_usersite(dist):
                    bin_dir = get_bin_user()
                else:
                    bin_dir = get_bin_prefix()
                paths_to_remove.add(os.path.join(bin_dir, script))
                if WINDOWS:
                    paths_to_remove.add(os.path.join(bin_dir, script) + '.bat')

        # find console_scripts
        _scripts_to_remove = []
        console_scripts = dist.get_entry_map(group='console_scripts')
        for name in console_scripts.keys():
            _scripts_to_remove.extend(_script_names(dist, name, False))
        # find gui_scripts
        gui_scripts = dist.get_entry_map(group='gui_scripts')
        for name in gui_scripts.keys():
            _scripts_to_remove.extend(_script_names(dist, name, True))

        for s in _scripts_to_remove:
            paths_to_remove.add(s)

        return paths_to_remove
def get_installer(dist: Distribution) -> str:
    if dist.has_metadata("INSTALLER"):
        for line in dist.get_metadata_lines("INSTALLER"):
            if line.strip():
                return line.strip()
    return ""