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.
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
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
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
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, )
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
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, )
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." )
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.")
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)
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.")
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
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
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")
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
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)
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, )
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))
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
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
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.")
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
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))
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)
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)
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, )
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
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
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, )
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