Exemple #1
0
def upgrade(
    venv_dir: Path,
    package: str,
    package_or_url: str,
    pip_args: List[str],
    verbose: bool,
    *,
    upgrading_all: bool,
    include_dependencies: bool,
    force: bool,
) -> int:
    """Returns nonzero if package was upgraded, 0 if version did not change"""

    if not venv_dir.is_dir():
        raise PipxError(
            f"Package is not installed. Expected to find {str(venv_dir)}, "
            "but it does not exist.")

    venv = Venv(venv_dir, verbose=verbose)

    old_version = venv.get_venv_metadata_for_package(package).package_version

    # Upgrade shared libraries (pip, setuptools and wheel)
    venv.upgrade_packaging_libraries(pip_args)

    venv.upgrade_package(package_or_url, pip_args)
    new_version = venv.get_venv_metadata_for_package(package).package_version

    metadata = venv.get_venv_metadata_for_package(package)
    _expose_apps_globally(constants.LOCAL_BIN_DIR,
                          metadata.app_paths,
                          package,
                          force=force)

    if include_dependencies:
        for _, app_paths in metadata.app_paths_of_dependencies.items():
            _expose_apps_globally(constants.LOCAL_BIN_DIR,
                                  app_paths,
                                  package,
                                  force=force)

    if old_version == new_version:
        if upgrading_all:
            pass
        else:
            print(
                f"{package} is already at latest version {old_version} (location: {str(venv_dir)})"
            )
        return 0
    else:
        print(
            f"upgraded package {package} from {old_version} to {new_version} (location: {str(venv_dir)})"
        )
        return 1
Exemple #2
0
def _get_package_summary(path: Path,
                         *,
                         package: str = None,
                         new_install: bool = False) -> str:
    venv = Venv(path)
    python_path = venv.python_path.resolve()
    if package is None:
        package = path.name
    metadata = venv.get_venv_metadata_for_package(package)

    if metadata.package_version is None:
        not_installed = red("is not installed")
        return f"   package {bold(package)} {not_installed} in the venv {str(path)}"

    apps = metadata.apps + metadata.apps_of_dependencies
    exposed_app_paths = _get_exposed_app_paths_for_package(
        venv.bin_path, apps, constants.LOCAL_BIN_DIR)
    exposed_binary_names = sorted(p.name for p in exposed_app_paths)
    unavailable_binary_names = sorted(
        set(metadata.apps) - set(exposed_binary_names))
    return _get_list_output(
        metadata.python_version,
        python_path,
        metadata.package_version,
        package,
        new_install,
        exposed_binary_names,
        unavailable_binary_names,
    )
Exemple #3
0
def uninstall(venv_dir: Path, package: str, local_bin_dir: Path,
              verbose: bool):
    if not venv_dir.exists():
        print(f"Nothing to uninstall for {package} 😴")
        app = which(package)
        if app:
            print(
                f"{hazard}  Note: '{app}' still exists on your system and is on your PATH"
            )
        return

    venv = Venv(venv_dir, verbose=verbose)

    metadata = venv.get_venv_metadata_for_package(package)
    app_paths = metadata.app_paths
    for dep_paths in metadata.app_paths_of_dependencies.values():
        app_paths += dep_paths
    for file in local_bin_dir.iterdir():
        if WINDOWS:
            for b in app_paths:
                if file.name == b.name:
                    file.unlink()
        else:
            symlink = file
            for b in app_paths:
                if symlink.exists() and b.exists() and symlink.samefile(b):
                    logging.info(f"removing symlink {str(symlink)}")
                    symlink.unlink()

    rmdir(venv_dir)
    print(f"uninstalled {package}! {stars}")
Exemple #4
0
def _get_venv_bin_dir_app_paths(venv: Venv, local_bin_dir: Path) -> Set[Path]:
    bin_dir_app_paths = set()
    if venv.pipx_metadata.main_package.package is not None:
        # Valid metadata for venv
        for package_info in venv.package_metadata.values():
            bin_dir_app_paths |= _get_package_bin_dir_app_paths(
                venv, package_info, local_bin_dir)
    elif venv.python_path.is_file():
        # No metadata from pipx_metadata.json, but valid python interpreter.
        # In pre-metadata-pipx venv.root.name is name of main package
        # In pre-metadata-pipx there is no suffix
        # We make the conservative assumptions: no injected packages,
        # not include_dependencies.  Other PackageInfo fields are irrelevant
        # here.
        venv_metadata = venv.get_venv_metadata_for_package(
            venv.root.name, set())
        main_package_info = _venv_metadata_to_package_info(
            venv_metadata, venv.root.name)
        bin_dir_app_paths = _get_package_bin_dir_app_paths(
            venv, main_package_info, local_bin_dir)
    else:
        # No metadata and no valid python interpreter.
        # We'll take our best guess on what to uninstall here based on symlink
        # location for symlink-capable systems.
        # The heuristic here is any symlink in ~/.local/bin pointing to
        # .local/pipx/venvs/VENV_NAME/{bin,Scripts} should be uninstalled.

        # For non-symlink systems we give up and return and empty set.
        if not can_symlink(local_bin_dir):
            return set()

        bin_dir_app_paths = get_exposed_app_paths_for_package(
            venv.bin_path, local_bin_dir)

    return bin_dir_app_paths
Exemple #5
0
def uninstall(venv_dir: Path, package: str, local_bin_dir: Path,
              verbose: bool):
    """Uninstall entire venv_dir, including main package and all injected
    packages.
    """
    if not venv_dir.exists():
        print(f"Nothing to uninstall for {package} {sleep}")
        app = which(package)
        if app:
            print(
                f"{hazard}  Note: '{app}' still exists on your system and is on your PATH"
            )
        return

    venv = Venv(venv_dir, verbose=verbose)

    if venv.pipx_metadata.main_package is not None:
        app_paths: List[Path] = []
        for viewed_package in venv.package_metadata.values():
            app_paths += viewed_package.app_paths
            for dep_paths in viewed_package.app_paths_of_dependencies.values():
                app_paths += dep_paths
    else:
        # fallback if not metadata from pipx_metadata.json
        if venv.python_path.is_file():
            # has a valid python interpreter and can get metadata about the package
            metadata = venv.get_venv_metadata_for_package(package)
            app_paths = metadata.app_paths
            for dep_paths in metadata.app_paths_of_dependencies.values():
                app_paths += dep_paths
        else:
            # Doesn't have a valid python interpreter. We'll take our best guess on what to uninstall
            # here based on symlink location. pipx doesn't use symlinks on windows, so this is for
            # non-windows only.
            # The heuristic here is any symlink in ~/.local/bin pointing to .local/pipx/venvs/PACKAGE/bin
            # should be uninstalled.
            if WINDOWS:
                app_paths = []
            else:
                apps_linking_to_venv_bin_dir = [
                    f for f in constants.LOCAL_BIN_DIR.iterdir()
                    if str(f.resolve()).startswith(str(venv.bin_path))
                ]
                app_paths = apps_linking_to_venv_bin_dir

    for file in local_bin_dir.iterdir():
        if WINDOWS:
            for b in app_paths:
                if file.name == b.name:
                    file.unlink()
        else:
            symlink = file
            for b in app_paths:
                if symlink.exists() and b.exists() and symlink.samefile(b):
                    logging.info(f"removing symlink {str(symlink)}")
                    symlink.unlink()

    rmdir(venv_dir)
    print(f"uninstalled {package}! {stars}")
Exemple #6
0
def install(
    venv_dir: Path,
    package: str,
    package_or_url: str,
    local_bin_dir: Path,
    python: str,
    pip_args: List[str],
    venv_args: List[str],
    verbose: bool,
    *,
    force: bool,
    include_dependencies: bool,
):
    try:
        exists = venv_dir.exists() and next(venv_dir.iterdir())
    except StopIteration:
        exists = False

    if exists:
        if force:
            print(f"Installing to existing directory {str(venv_dir)!r}")
        else:
            print(f"{package!r} already seems to be installed. "
                  f"Not modifying existing installation in {str(venv_dir)!r}. "
                  "Pass '--force' to force installation.")
            return

    venv = Venv(venv_dir, python=python, verbose=verbose)
    try:
        venv.create_venv(venv_args, pip_args)
        venv.install_package(package_or_url, pip_args)

        if venv.get_venv_metadata_for_package(package).package_version is None:
            venv.remove_venv()
            raise PipxError(
                f"Could not find package {package}. Is the name correct?")

        _run_post_install_actions(venv,
                                  package,
                                  local_bin_dir,
                                  venv_dir,
                                  include_dependencies,
                                  force=force)
    except (Exception, KeyboardInterrupt):
        print("")
        venv.remove_venv()
        raise
Exemple #7
0
def _download_and_run(
    venv_dir: Path,
    package: str,
    app: str,
    binary_args: List[str],
    python: str,
    pip_args: List[str],
    venv_args: List[str],
    verbose: bool,
):
    venv = Venv(venv_dir, python=python, verbose=verbose)
    venv.create_venv(venv_args, pip_args)
    venv.install_package(package, pip_args)

    if not (venv.bin_path / app).exists():
        apps = venv.get_venv_metadata_for_package(package).apps
        raise PipxError(
            f"'{app}' executable script not found in package '{package}'. "
            "Available executable scripts: "
            f"{', '.join(b for b in apps)}")
    return venv.run_app(app, binary_args)
Exemple #8
0
def _run_post_install_actions(
    venv: Venv,
    package: str,
    local_bin_dir: Path,
    venv_dir: Path,
    include_dependencies: bool,
    *,
    force: bool,
):
    metadata = venv.get_venv_metadata_for_package(package)

    if not metadata.app_paths and not include_dependencies:
        # No apps associated with this package and we aren't including dependencies.
        # This package has nothing for pipx to use, so this is an error.
        for dep, dependent_apps in metadata.app_paths_of_dependencies.items():
            print(
                f"Note: Dependent package '{dep}' contains {len(dependent_apps)} apps"
            )
            for app in dependent_apps:
                print(f"  - {app.name}")

        if venv.safe_to_remove():
            venv.remove_venv()

        if len(metadata.app_paths_of_dependencies.keys()):
            raise PipxError(
                f"No apps associated with package {package}. "
                "Try again with '--include-deps' to include apps of dependent packages, "
                "which are listed above. "
                "If you are attempting to install a library, pipx should not be used. "
                "Consider using pip or a similar tool instead.")
        else:
            raise PipxError(
                f"No apps associated with package {package}. "
                "If you are attempting to install a library, pipx should not be used. "
                "Consider using pip or a similar tool instead.")

    if metadata.apps:
        pass
    elif metadata.apps_of_dependencies and include_dependencies:
        pass
    else:
        # No apps associated with this package and we aren't including dependencies.
        # This package has nothing for pipx to use, so this is an error.
        if venv.safe_to_remove():
            venv.remove_venv()
        raise PipxError(
            f"No apps associated with package {package} or its dependencies."
            "If you are attempting to install a library, pipx should not be used. "
            "Consider using pip or a similar tool instead.")

    _expose_apps_globally(local_bin_dir,
                          metadata.app_paths,
                          package,
                          force=force)

    if include_dependencies:
        for _, app_paths in metadata.app_paths_of_dependencies.items():
            _expose_apps_globally(local_bin_dir,
                                  app_paths,
                                  package,
                                  force=force)

    print(_get_package_summary(venv_dir, package=package, new_install=True))
    _warn_if_not_on_path(local_bin_dir)
    print(f"done! {stars}", file=sys.stderr)