Beispiel #1
0
def _find_default_windows_python() -> str:

    if has_venv():
        return sys.executable

    py = shutil.which("py")
    if py:
        return py
    python = shutil.which("python")
    if python is None:
        raise PipxError("no suitable Python found")

    # If the path contains "WindowsApps", it's the store python
    if "WindowsApps" not in python:
        return python

    # Special treatment to detect Windows Store stub.
    # https://twitter.com/zooba/status/1212454929379581952

    proc = subprocess.run(
        [python, "-V"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
    )
    if proc.returncode != 0:
        # Cover the 9009 return code pre-emptively.
        raise PipxError("no suitable Python found")
    if not proc.stdout.strip():
        # A real Python should print version, Windows Store stub won't.
        raise PipxError("no suitable Python found")
    return python  # This executable seems to work.
Beispiel #2
0
    def install_package_no_deps(self, package_or_url: str,
                                pip_args: List[str]) -> str:
        with animate(f"determining package name from {package_or_url!r}",
                     self.do_animation):
            old_package_set = self.list_installed_packages()
            cmd = ["install"] + ["--no-dependencies"
                                 ] + pip_args + [package_or_url]
            pip_process = self._run_pip(cmd)
        subprocess_post_check(pip_process, raise_error=False)
        if pip_process.returncode:
            raise PipxError(f"""
                Cannot determine package name from spec {package_or_url!r}.
                Check package spec for errors.
                """)

        installed_packages = self.list_installed_packages() - old_package_set
        if len(installed_packages) == 1:
            package_name = installed_packages.pop()
            logger.info(f"Determined package name: {package_name}")
        else:
            logger.info(f"old_package_set = {old_package_set}")
            logger.info(f"install_packages = {installed_packages}")
            raise PipxError(f"""
                Cannot determine package name from spec {package_or_url!r}.
                Check package spec for errors.
                """)

        return package_name
Beispiel #3
0
    def install_package_no_deps(self, package_or_url: str,
                                pip_args: List[str]) -> str:
        try:
            with animate(f"determining package name from {package_or_url!r}",
                         self.do_animation):
                old_package_set = self.list_installed_packages()
                cmd = ["install"] + ["--no-dependencies"
                                     ] + pip_args + [package_or_url]
                self._run_pip(cmd)
        except PipxError as e:
            logging.info(e)
            raise PipxError(
                f"Cannot determine package name from spec {package_or_url!r}. "
                f"Check package spec for errors.")

        installed_packages = self.list_installed_packages() - old_package_set
        if len(installed_packages) == 1:
            package = installed_packages.pop()
            logging.info(f"Determined package name: {package}")
        else:
            logging.info(f"old_package_set = {old_package_set}")
            logging.info(f"install_packages = {installed_packages}")
            raise PipxError(
                f"Cannot determine package name from spec {package_or_url!r}. "
                f"Check package spec for errors.")

        return package
Beispiel #4
0
def inject_dep(
    venv_dir: Path,
    package_name: Optional[str],
    package_spec: str,
    pip_args: List[str],
    *,
    verbose: bool,
    include_apps: bool,
    include_dependencies: bool,
    force: bool,
) -> bool:
    if not venv_dir.exists() or not next(venv_dir.iterdir()):
        raise PipxError(f"""
            Can't inject {package_spec!r} into nonexistent Virtual Environment
            {venv_dir.name!r}. Be sure to install the package first with 'pipx
            install {venv_dir.name}' before injecting into it.
            """)

    venv = Venv(venv_dir, verbose=verbose)

    if not venv.package_metadata:
        raise PipxError(f"""
            Can't inject {package_spec!r} into Virtual Environment
            {venv.name!r}. {venv.name!r} has missing internal pipx metadata. It
            was likely installed using a pipx version before 0.15.0.0. Please
            uninstall and install {venv.name!r}, or reinstall-all to fix.
            """)

    # package_spec is anything pip-installable, including package_name, vcs spec,
    #   zip file, or tar.gz file.
    if package_name is None:
        package_name = package_name_from_spec(package_spec,
                                              venv.python,
                                              pip_args=pip_args,
                                              verbose=verbose)

    venv.install_package(
        package=package_name,
        package_or_url=package_spec,
        pip_args=pip_args,
        include_dependencies=include_dependencies,
        include_apps=include_apps,
        is_main_package=False,
    )
    if include_apps:
        run_post_install_actions(
            venv,
            package_name,
            constants.LOCAL_BIN_DIR,
            venv_dir,
            include_dependencies,
            force=force,
        )

    print(
        f"  injected package {bold(package_name)} into venv {bold(venv.name)}")
    print(f"done! {stars}", file=sys.stderr)

    # Any failure to install will raise PipxError, otherwise success
    return True
Beispiel #5
0
    def install_package(
        self,
        package_name: str,
        package_or_url: str,
        pip_args: List[str],
        include_dependencies: bool,
        include_apps: bool,
        is_main_package: bool,
        suffix: str = "",
    ) -> None:
        # package_name in package specifier can mismatch URL due to user error
        package_or_url = fix_package_name(package_or_url, package_name)

        # check syntax and clean up spec and pip_args
        (package_or_url,
         pip_args) = parse_specifier_for_install(package_or_url, pip_args)

        with animate(
                f"installing {full_package_description(package_name, package_or_url)}",
                self.do_animation,
        ):
            # do not use -q with `pip install` so subprocess_post_check_pip_errors
            #   has more information to analyze in case of failure.
            cmd = ([str(self.python_path), "-m", "pip", "install"] + pip_args +
                   [package_or_url])
            # no logging because any errors will be specially logged by
            #   subprocess_post_check_handle_pip_error()
            pip_process = run_subprocess(cmd,
                                         log_stdout=False,
                                         log_stderr=False)
        subprocess_post_check_handle_pip_error(pip_process)
        if pip_process.returncode:
            raise PipxError(
                f"Error installing {full_package_description(package_name, package_or_url)}."
            )

        self._update_package_metadata(
            package_name=package_name,
            package_or_url=package_or_url,
            pip_args=pip_args,
            include_dependencies=include_dependencies,
            include_apps=include_apps,
            is_main_package=is_main_package,
            suffix=suffix,
        )

        # Verify package installed ok
        if self.package_metadata[package_name].package_version is None:
            raise PipxError(
                f"Unable to install "
                f"{full_package_description(package_name, package_or_url)}.\n"
                f"Check the name or spec for errors, and verify that it can "
                f"be installed with pip.",
                wrap_message=False,
            )
Beispiel #6
0
def _upgrade_venv(
    venv_dir: Path,
    pip_args: List[str],
    verbose: bool,
    *,
    include_injected: bool,
    upgrading_all: bool,
    force: bool,
) -> int:
    """Returns number of packages with changed versions."""
    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)

    if not venv.package_metadata:
        raise PipxError(
            f"Not upgrading {red(bold(venv_dir.name))}. It has missing internal pipx metadata.\n"
            f"It was likely installed using a pipx version before 0.15.0.0.\n"
            f"Please uninstall and install this package to fix.",
            wrap_message=False,
        )

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

    versions_updated = 0

    package_name = venv.main_package_name
    versions_updated += _upgrade_package(
        venv,
        package_name,
        pip_args,
        is_main_package=True,
        force=force,
        upgrading_all=upgrading_all,
    )

    if include_injected:
        for package_name in venv.package_metadata:
            if package_name == venv.main_package_name:
                continue
            versions_updated += _upgrade_package(
                venv,
                package_name,
                pip_args,
                is_main_package=False,
                force=force,
                upgrading_all=upgrading_all,
            )

    return versions_updated
Beispiel #7
0
    def install_package(
        self,
        package: str,
        package_or_url: str,
        pip_args: List[str],
        include_dependencies: bool,
        include_apps: bool,
        is_main_package: bool,
        suffix: str = "",
    ) -> None:
        if pip_args is None:
            pip_args = []

        # package name in package specifier can mismatch URL due to user error
        package_or_url = fix_package_name(package_or_url, package)

        # check syntax and clean up spec and pip_args
        (package_or_url, pip_args) = parse_specifier_for_install(
            package_or_url, pip_args
        )

        with animate(
            f"installing {full_package_description(package, package_or_url)}",
            self.do_animation,
        ):
            cmd = ["install"] + pip_args + [package_or_url]
            pip_process = self._run_pip(cmd)
        subprocess_post_check(pip_process, raise_error=False)
        if pip_process.returncode:
            raise PipxError(
                f"Error installing {full_package_description(package, package_or_url)}."
            )

        self._update_package_metadata(
            package=package,
            package_or_url=package_or_url,
            pip_args=pip_args,
            include_dependencies=include_dependencies,
            include_apps=include_apps,
            is_main_package=is_main_package,
            suffix=suffix,
        )

        # Verify package installed ok
        if self.package_metadata[package].package_version is None:
            raise PipxError(
                f"Unable to install "
                f"{full_package_description(package, package_or_url)}.\n"
                f"Check the name or spec for errors, and verify that it can "
                f"be installed with pip.",
                wrap_message=False,
            )
Beispiel #8
0
    def install_package(
        self,
        package: Optional[str],  # if None, will be determined in this function
        package_or_url: str,
        pip_args: List[str],
        include_dependencies: bool,
        include_apps: bool,
        is_main_package: bool,
    ) -> None:

        if pip_args is None:
            pip_args = []
        if package is None:
            # If no package name is supplied, install only main package
            #   first in order to see what its name is
            package = self.install_package_no_deps(package_or_url, pip_args)

        try:
            with animate(
                f"installing {full_package_description(package, package_or_url)}",
                self.do_animation,
            ):
                cmd = ["install"] + pip_args + [package_or_url]
                self._run_pip(cmd)
        except PipxError as e:
            logging.info(e)
            raise PipxError(
                f"Error installing "
                f"{full_package_description(package, package_or_url)}."
            )

        self._update_package_metadata(
            package=package,
            package_or_url=package_or_url,
            pip_args=pip_args,
            include_dependencies=include_dependencies,
            include_apps=include_apps,
            is_main_package=is_main_package,
        )

        # Verify package installed ok
        if self.package_metadata[package].package_version is None:
            raise PipxError(
                f"Unable to install "
                f"{full_package_description(package, package_or_url)}.\n"
                f"Check the name or spec for errors, and verify that it can "
                f"be installed with pip."
            )
Beispiel #9
0
def upgrade_all(venv_container: VenvContainer, verbose: bool, *,
                skip: List[str], force: bool):
    venv_error = False
    venvs_upgraded = 0
    for venv_dir in venv_container.iter_venv_dirs():
        venv = Venv(venv_dir, verbose=verbose)
        if (venv_dir.name in skip
                or "--editable" in venv.pipx_metadata.main_package.pip_args):
            continue
        try:
            venvs_upgraded += upgrade(
                venv_dir,
                venv.pipx_metadata.main_package.pip_args,
                verbose,
                upgrading_all=True,
                force=force,
            )

        except Exception:
            venv_error = True
            logging.error(f"Error encountered when upgrading {venv_dir.name}")

    if venvs_upgraded == 0:
        print(
            f"Versions did not change after running 'pip upgrade' for each package {sleep}"
        )
    if venv_error:
        raise PipxError("Some packages encountered errors during upgrade. "
                        "See specific error messages above.")
Beispiel #10
0
def _download_and_run(
    venv_dir: Path,
    package_or_url: str,
    app: str,
    app_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.pipx_metadata.main_package.package contains package name if it is
    #   pre-existing, otherwise is None to instruct venv.install_package to
    #   determine package name.

    venv.install_package(
        package=venv.pipx_metadata.main_package.package,
        package_or_url=package_or_url,
        pip_args=pip_args,
        include_dependencies=False,
        include_apps=True,
        is_main_package=True,
    )

    if not (venv.bin_path / app).exists():
        apps = venv.pipx_metadata.main_package.apps
        raise PipxError(
            f"'{app}' executable script not found in package '{package_or_url}'. "
            "Available executable scripts: "
            f"{', '.join(b for b in apps)}")
    return venv.run_app(app, app_args)
Beispiel #11
0
 def _validate_before_write(self) -> None:
     if (self.main_package.package is None
             or self.main_package.package_or_url is None
             or not self.main_package.include_apps):
         logging.debug(f"PipxMetadata corrupt:\n{self.to_dict()}")
         raise PipxError(
             "Internal Error: PipxMetadata is corrupt, cannot write.")
Beispiel #12
0
def reinstall_all(
    venv_container: VenvContainer,
    local_bin_dir: Path,
    python: str,
    verbose: bool,
    *,
    skip: List[str],
) -> int:
    """Returns pipx shell exit code"""
    failed: List[str] = []
    for venv_dir in venv_container.iter_venv_dirs():
        if venv_dir.name in skip:
            continue
        try:
            reinstall(
                venv_dir=venv_dir,
                local_bin_dir=local_bin_dir,
                python=python,
                verbose=verbose,
            )
        except PipxError as e:
            print(e, file=sys.stderr)
            failed.append(venv_dir.name)
    if len(failed) > 0:
        raise PipxError(
            f"The following package(s) failed to reinstall: {', '.join(failed)}"
        )
    return 0
Beispiel #13
0
def reinstall(
    *,
    venv_dir: Path,
    local_bin_dir: Path,
    python: str,
    verbose: bool,
) -> int:
    """Returns pipx shell exit code"""
    if not venv_dir.exists():
        print(f"Nothing to reinstall for {venv_dir.name} {sleep}")
        return 1

    venv = Venv(venv_dir, verbose=verbose)

    if venv.pipx_metadata.main_package.package_or_url is not None:
        package_or_url = venv.pipx_metadata.main_package.package_or_url
    else:
        package_or_url = venv.main_package_name

    uninstall(venv_dir, local_bin_dir, verbose)

    # install main package first
    install(
        venv_dir,
        venv.main_package_name,
        package_or_url,
        local_bin_dir,
        python,
        venv.pipx_metadata.main_package.pip_args,
        venv.pipx_metadata.venv_args,
        verbose,
        force=True,
        include_dependencies=venv.pipx_metadata.main_package.
        include_dependencies,
        suffix=venv.pipx_metadata.main_package.suffix,
    )

    # now install injected packages
    for (
            injected_name,
            injected_package,
    ) in venv.pipx_metadata.injected_packages.items():
        if injected_package.package_or_url is None:
            # This should never happen, but package_or_url is type
            #   Optional[str] so mypy thinks it could be None
            raise PipxError(
                f"Internal Error injecting package {injected_package} into {venv_dir.name}"
            )
        inject(
            venv_dir,
            injected_name,
            injected_package.package_or_url,
            injected_package.pip_args,
            verbose=verbose,
            include_apps=injected_package.include_apps,
            include_dependencies=injected_package.include_dependencies,
            force=True,
        )

    return 0
Beispiel #14
0
    def __init__(self,
                 path: Path,
                 *,
                 verbose: bool = False,
                 python: str = DEFAULT_PYTHON) -> None:
        self.root = path
        self._python = python
        self.bin_path, self.python_path = get_venv_paths(self.root)
        self.pipx_metadata = PipxMetadata(venv_dir=path)
        self.verbose = verbose
        self.do_animation = not verbose
        try:
            self._existing = self.root.exists() and next(self.root.iterdir())
        except StopIteration:
            self._existing = False

        if self._existing and self.uses_shared_libs and not shared_libs.is_valid:
            logging.warning(
                f"Shared libraries not found, but are required for package {self.root.name}. "
                "Attempting to install now.")
            shared_libs.create([])
            if shared_libs.is_valid:
                logging.info("Successfully created shared libraries")
            else:
                raise PipxError(
                    f"Error: pipx's shared venv is invalid and "
                    "needs re-installation. To fix this, install or reinstall a "
                    "package. For example,\n"
                    f"  pipx install {self.root.name} --force")
Beispiel #15
0
def _dfs_package_apps(
    dist: metadata.Distribution,
    package_req: Requirement,
    venv_inspect_info: VenvInspectInformation,
    app_paths_of_dependencies: Dict[str, List[Path]],
    dep_visited: Optional[Dict[str, bool]] = None,
) -> Dict[str, List[Path]]:
    if dep_visited is None:
        # Initialize: we have already visited root
        dep_visited = {canonicalize_name(package_req.name): True}

    dependencies = get_package_dependencies(dist, package_req.extras,
                                            venv_inspect_info.env)
    for dep_req in dependencies:
        dep_name = canonicalize_name(dep_req.name)
        if dep_name in dep_visited:
            # avoid infinite recursion, avoid duplicates in info
            continue

        dep_dist = get_dist(dep_req.name, venv_inspect_info.distributions)
        if dep_dist is None:
            raise PipxError(
                "Pipx Internal Error: cannot find package {dep_req.name!r} metadata."
            )
        app_names = get_apps(dep_dist, venv_inspect_info.bin_path)
        if app_names:
            app_paths_of_dependencies[dep_name] = [
                venv_inspect_info.bin_path / app for app in app_names
            ]
        # recursively search for more
        dep_visited[dep_name] = True
        app_paths_of_dependencies = _dfs_package_apps(
            dep_dist, dep_req, venv_inspect_info, app_paths_of_dependencies,
            dep_visited)
    return app_paths_of_dependencies
Beispiel #16
0
def inject(
    venv_dir: Path,
    package: str,
    pip_args: List[str],
    *,
    verbose: bool,
    include_apps: bool,
    include_dependencies: bool,
    force: bool,
):
    if not venv_dir.exists() or not next(venv_dir.iterdir()):
        raise PipxError(
            textwrap.dedent(f"""\
            Can't inject {package!r} into nonexistent Virtual Environment {str(venv_dir)!r}.
            Be sure to install the package first with pipx install {venv_dir.name!r}
            before injecting into it."""))

    venv = Venv(venv_dir, verbose=verbose)
    venv.install_package(package, pip_args)

    if include_apps:
        _run_post_install_actions(
            venv,
            package,
            constants.LOCAL_BIN_DIR,
            venv_dir,
            include_dependencies,
            force=force,
        )

    print(
        f"  injected package {bold(package)} into venv {bold(venv_dir.name)}")
    print(f"done! {stars}", file=sys.stderr)
Beispiel #17
0
    def __init__(self,
                 path: Path,
                 *,
                 verbose: bool = False,
                 python: str = DEFAULT_PYTHON) -> None:
        self.root = path
        self.python = python
        self.bin_path, self.python_path = get_venv_paths(self.root)
        self.pipx_metadata = PipxMetadata(venv_dir=path)
        self.verbose = verbose
        self.do_animation = not verbose
        try:
            self._existing = self.root.exists() and next(self.root.iterdir())
        except StopIteration:
            self._existing = False

        if self._existing and self.uses_shared_libs:
            if shared_libs.is_valid:
                if shared_libs.needs_upgrade:
                    shared_libs.upgrade(verbose=verbose)
            else:
                shared_libs.create(verbose)

            if not shared_libs.is_valid:
                raise PipxError(
                    pipx_wrap(f"""
                        Error: pipx's shared venv {shared_libs.root} is invalid
                        and needs re-installation. To fix this, install or
                        reinstall a package. For example:
                        """) + f"\n  pipx install {self.root.name} --force",
                    wrap_message=False,
                )
Beispiel #18
0
def _http_get_request(url: str):
    try:
        res = urllib.request.urlopen(url)
        charset = res.headers.get_content_charset() or "utf-8"  # type: ignore
        return res.read().decode(charset)
    except Exception as e:
        raise PipxError(str(e))
Beispiel #19
0
def inject(
    venv_dir: Path,
    package_name: Optional[str],
    package_specs: List[str],
    pip_args: List[str],
    *,
    verbose: bool,
    include_apps: bool,
    include_dependencies: bool,
    force: bool,
) -> ExitCode:
    """Returns pipx exit code."""
    if not include_apps and include_dependencies:
        raise PipxError(
            "Cannot pass --include-deps if --include-apps is not passed as well"
        )
    all_success = True
    for dep in package_specs:
        all_success &= inject_dep(
            venv_dir,
            None,
            dep,
            pip_args,
            verbose=verbose,
            include_apps=include_apps,
            include_dependencies=include_dependencies,
            force=force,
        )

    # Any failure to install will raise PipxError, otherwise success
    return EXIT_CODE_OK if all_success else EXIT_CODE_INJECT_ERROR
Beispiel #20
0
def reinstall_all(
    venv_container: VenvContainer,
    local_bin_dir: Path,
    python: str,
    verbose: bool,
    *,
    skip: Sequence[str],
) -> ExitCode:
    """Returns pipx exit code."""
    pipx.shared_libs.shared_libs.upgrade(verbose=verbose)

    failed: List[str] = []
    for venv_dir in venv_container.iter_venv_dirs():
        if venv_dir.name in skip:
            continue
        try:
            package_exit = reinstall(
                venv_dir=venv_dir,
                local_bin_dir=local_bin_dir,
                python=python,
                verbose=verbose,
            )
        except PipxError as e:
            print(e, file=sys.stderr)
            failed.append(venv_dir.name)
        if package_exit != 0:
            failed.append(venv_dir.name)
    if len(failed) > 0:
        raise PipxError(
            f"The following package(s) failed to reinstall: {', '.join(failed)}"
        )
    # Any failure to install will raise PipxError, otherwise success
    return EXIT_CODE_OK
Beispiel #21
0
    def install_package(
        self,
        package: str,
        package_or_url: str,
        pip_args: List[str],
        include_dependencies: bool,
        include_apps: bool,
        is_main_package: bool,
        suffix: str = "",
    ) -> None:
        if pip_args is None:
            pip_args = []

        # check syntax and clean up spec and pip_args
        (package_or_url,
         pip_args) = parse_specifier_for_install(package_or_url, pip_args)

        try:
            with animate(
                    f"installing {full_package_description(package, package_or_url)}",
                    self.do_animation,
            ):
                cmd = ["install"] + pip_args + [package_or_url]
                self._run_pip(cmd)
        except PipxError as e:
            logging.info(e)
            raise PipxError(
                f"Error installing "
                f"{full_package_description(package, package_or_url)}.")

        self._update_package_metadata(
            package=package,
            package_or_url=package_or_url,
            pip_args=pip_args,
            include_dependencies=include_dependencies,
            include_apps=include_apps,
            is_main_package=is_main_package,
            suffix=suffix,
        )

        # Verify package installed ok
        if self.package_metadata[package].package_version is None:
            raise PipxError(
                f"Unable to install "
                f"{full_package_description(package, package_or_url)}.\n"
                f"Check the name or spec for errors, and verify that it can "
                f"be installed with pip.")
Beispiel #22
0
def reinstall(
    *, venv_dir: Path, local_bin_dir: Path, python: str, verbose: bool
) -> ExitCode:
    """Returns pipx exit code."""
    if not venv_dir.exists():
        print(f"Nothing to reinstall for {venv_dir.name} {sleep}")
        return EXIT_CODE_REINSTALL_VENV_NONEXISTENT

    venv = Venv(venv_dir, verbose=verbose)

    if venv.pipx_metadata.main_package.package_or_url is not None:
        package_or_url = venv.pipx_metadata.main_package.package_or_url
    else:
        package_or_url = venv.main_package_name

    uninstall(venv_dir, local_bin_dir, verbose)

    # in case legacy original dir name
    venv_dir = venv_dir.with_name(canonicalize_name(venv_dir.name))

    # install main package first
    install(
        venv_dir,
        venv.main_package_name,
        package_or_url,
        local_bin_dir,
        python,
        venv.pipx_metadata.main_package.pip_args,
        venv.pipx_metadata.venv_args,
        verbose,
        force=True,
        include_dependencies=venv.pipx_metadata.main_package.include_dependencies,
        suffix=venv.pipx_metadata.main_package.suffix,
    )

    # now install injected packages
    for (
        injected_name,
        injected_package,
    ) in venv.pipx_metadata.injected_packages.items():
        if injected_package.package_or_url is None:
            # This should never happen, but package_or_url is type
            #   Optional[str] so mypy thinks it could be None
            raise PipxError(
                f"Internal Error injecting package {injected_package} into {venv.name}"
            )
        inject_dep(
            venv_dir,
            injected_name,
            injected_package.package_or_url,
            injected_package.pip_args,
            verbose=verbose,
            include_apps=injected_package.include_apps,
            include_dependencies=injected_package.include_dependencies,
            force=True,
        )

    # Any failure to install will raise PipxError, otherwise success
    return EXIT_CODE_OK
Beispiel #23
0
def _http_get_request(url: str) -> str:
    try:
        res = urllib.request.urlopen(url)
        charset = res.headers.get_content_charset() or "utf-8"
        return res.read().decode(charset)
    except Exception as e:
        logger.debug("Uncaught Exception:", exc_info=True)
        raise PipxError(str(e))
Beispiel #24
0
def run_pip(package: str, venv_dir: Path, pip_args: List[str], verbose: bool):
    venv = Venv(venv_dir, verbose=verbose)
    if not venv.python_path.exists():
        raise PipxError(
            f"venv for {package!r} was not found. Was {package!r} installed with pipx?"
        )
    venv.verbose = True
    venv._run_pip(pip_args)
Beispiel #25
0
def inject(
    venv_dir: Path,
    package: str,
    package_or_url: str,
    pip_args: List[str],
    *,
    verbose: bool,
    include_apps: bool,
    include_dependencies: bool,
    force: bool,
):
    if not venv_dir.exists() or not next(venv_dir.iterdir()):
        raise PipxError(
            textwrap.dedent(f"""\
            Can't inject {package!r} into nonexistent Virtual Environment {str(venv_dir)!r}.
            Be sure to install the package first with pipx install {venv_dir.name!r}
            before injecting into it."""))

    venv = Venv(venv_dir, verbose=verbose)
    try:
        venv.install_package(
            package=package,
            package_or_url=package_or_url,
            pip_args=pip_args,
            include_dependencies=include_dependencies,
            include_apps=include_apps,
            is_main_package=False,
        )
    except PackageInstallFailureError:
        raise PipxError(
            f"Could not inject package {package}. Is the name or spec correct?"
        )
    if include_apps:
        _run_post_install_actions(
            venv,
            package,
            constants.LOCAL_BIN_DIR,
            venv_dir,
            include_dependencies,
            force=force,
        )

    print(
        f"  injected package {bold(package)} into venv {bold(venv_dir.name)}")
    print(f"done! {stars}", file=sys.stderr)
Beispiel #26
0
def reinstall_all(
    venv_container: VenvContainer,
    local_bin_dir: Path,
    python: str,
    verbose: bool,
    *,
    skip: List[str],
):
    for venv_dir in venv_container.iter_venv_dirs():
        package = venv_dir.name
        if package in skip:
            continue

        venv = Venv(venv_dir, verbose=verbose)

        if venv.pipx_metadata.main_package.package_or_url is not None:
            package_or_url = venv.pipx_metadata.main_package.package_or_url
        else:
            package_or_url = package

        uninstall(venv_dir, package, local_bin_dir, verbose)

        # install main package first
        install(
            venv_dir,
            package,
            package_or_url,
            local_bin_dir,
            python,
            venv.pipx_metadata.main_package.pip_args,
            venv.pipx_metadata.venv_args,
            verbose,
            force=True,
            include_dependencies=venv.pipx_metadata.main_package.
            include_dependencies,
        )

        # now install injected packages
        for (
                injected_name,
                injected_package,
        ) in venv.pipx_metadata.injected_packages.items():
            if injected_package.package_or_url is None:
                # This should never happen, but package_or_url is type
                #   Optional[str] so mypy thinks it could be None
                raise PipxError("Internal Error injecting package")
            inject(
                venv_dir,
                injected_name,
                injected_package.package_or_url,
                injected_package.pip_args,
                verbose=verbose,
                include_apps=injected_package.include_apps,
                include_dependencies=injected_package.include_dependencies,
                force=True,
            )
Beispiel #27
0
def inspect_venv(
    root_package_name: str,
    root_package_extras: Set[str],
    venv_bin_path: Path,
    venv_python_path: Path,
) -> VenvMetadata:
    app_paths_of_dependencies: Dict[str, List[Path]] = {}
    apps_of_dependencies: List[str] = []

    root_req = Requirement(root_package_name)
    root_req.extras = root_package_extras

    (venv_sys_path, venv_env, venv_python_version) = fetch_info_in_venv(
        venv_python_path
    )

    venv_inspect_info = VenvInspectInformation(
        bin_path=venv_bin_path,
        env=venv_env,
        distributions=list(metadata.distributions(path=venv_sys_path)),
    )

    root_dist = get_dist(root_req.name, venv_inspect_info.distributions)
    if root_dist is None:
        raise PipxError(
            "Pipx Internal Error: cannot find package {root_req.name!r} metadata."
        )
    app_paths_of_dependencies = _dfs_package_apps(
        root_dist, root_req, venv_inspect_info, app_paths_of_dependencies
    )

    apps = get_apps(root_dist, venv_bin_path)
    app_paths = [venv_bin_path / app for app in apps]
    if WINDOWS:
        app_paths = _windows_extra_app_paths(app_paths)

    for dep in app_paths_of_dependencies:
        apps_of_dependencies += [
            dep_path.name for dep_path in app_paths_of_dependencies[dep]
        ]
        if WINDOWS:
            app_paths_of_dependencies[dep] = _windows_extra_app_paths(
                app_paths_of_dependencies[dep]
            )

    venv_metadata = VenvMetadata(
        apps=apps,
        app_paths=app_paths,
        apps_of_dependencies=apps_of_dependencies,
        app_paths_of_dependencies=app_paths_of_dependencies,
        package_version=root_dist.version,
        python_version=venv_python_version,
    )

    return venv_metadata
Beispiel #28
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)
        try:
            venv.install_package(
                package=package,
                package_or_url=package_or_url,
                pip_args=pip_args,
                include_dependencies=include_dependencies,
                include_apps=True,
                is_main_package=True,
            )
        except PackageInstallFailureError:
            venv.remove_venv()
            raise PipxError(
                f"Could not install package {package}. Is the name or spec correct?"
            )

        _run_post_install_actions(venv,
                                  package,
                                  local_bin_dir,
                                  venv_dir,
                                  include_dependencies,
                                  force=force)
    except (Exception, KeyboardInterrupt):
        print("")
        venv.remove_venv()
        raise
Beispiel #29
0
def _parse_specifier(package_spec: str) -> ParsedPackage:
    """Parse package_spec as would be given to pipx
    """
    # If package_spec is valid pypi name, pip will always treat it as a
    #       pypi package, not checking for local path.
    #       We replicate pypi precedence here (only non-valid-pypi names
    #       initiate check for local path, e.g. './package-name')
    valid_pep508 = None
    valid_url = None
    valid_local_path = None

    try:
        package_req = Requirement(package_spec)
    except InvalidRequirement:
        # not a valid PEP508 package specification
        pass
    else:
        # valid PEP508 package specification
        valid_pep508 = package_req

    # packaging currently (2020-07-19) only does basic syntax checks on URL.
    #   Some examples of what it will not catch:
    #       - invalid RCS string (e.g. "gat+https://...")
    #       - non-existent scheme (e.g. "zzzzz://...")
    if not valid_pep508:
        try:
            package_req = Requirement("notapackagename @ " + package_spec)
        except InvalidRequirement:
            # not a valid url
            pass
        else:
            valid_url = package_spec

    if not valid_pep508 and not valid_url:
        (package_path_str,
         package_extras_str) = _split_path_extras(package_spec)

        package_path = Path(package_path_str)
        try:
            package_path_exists = package_path.exists()
        except OSError:
            package_path_exists = False

        if package_path_exists:
            valid_local_path = str(package_path.resolve()) + package_extras_str

    if not valid_pep508 and not valid_url and not valid_local_path:
        raise PipxError(f"Unable to parse package spec: {package_spec}")

    return ParsedPackage(
        valid_pep508=valid_pep508,
        valid_url=valid_url,
        valid_local_path=valid_local_path,
    )
Beispiel #30
0
def _run(cmd: Sequence[Union[str, Path]], check=True) -> int:
    """Run arbitrary command as subprocess"""

    cmd_str = " ".join(str(c) for c in cmd)
    logging.info(f"running {cmd_str}")
    # windows cannot take Path objects, only strings
    cmd_str_list = [str(c) for c in cmd]
    returncode = subprocess.run(cmd_str_list).returncode
    if check and returncode:
        raise PipxError(f"{cmd_str!r} failed")
    return returncode