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 package_name_from_spec(package_spec: str, python: str, *, pip_args: List[str], verbose: bool) -> str: start_time = time.time() # shortcut if valid PyPI name pypi_name = valid_pypi_name(package_spec) if pypi_name is not None: # NOTE: if pypi name and installed package name differ, this means pipx # will use the pypi name package_name = pypi_name logging.info(f"Determined package name: {package_name}") logging.info( f"Package name determined in {time.time()-start_time:.1f}s") return package_name # check syntax and clean up spec and pip_args (package_spec, pip_args) = parse_specifier_for_install(package_spec, pip_args) with tempfile.TemporaryDirectory() as temp_venv_dir: venv = Venv(Path(temp_venv_dir), python=python, verbose=verbose) venv.create_venv(venv_args=[], pip_args=[]) package_name = venv.install_package_no_deps( package_or_url=package_spec, pip_args=pip_args) logging.info(f"Package name determined in {time.time()-start_time:.1f}s") return package_name
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 _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, )
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}")
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}")
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 _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( 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 run_pip( package: str, venv_dir: Path, pip_args: List[str], verbose: bool ) -> ExitCode: """Returns pipx exit code.""" 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 return venv.run_pip_get_exit_code(pip_args)
def inject( venv_dir: Path, package_name: Optional[str], package_spec: 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_spec!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) # 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_dir.name)}") print(f"done! {stars}", file=sys.stderr)
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
def get_package_summary( path: Path, *, package: str = None, new_install: bool = False, include_injected: bool = False, ) -> str: venv = Venv(path) python_path = venv.python_path.resolve() package_metadata = venv.get_package_metadata(package) package = package_metadata.package or venv.root.name if not python_path.is_file(): return f" package {red(bold(package))} has invalid interpreter {str(python_path)}" if not venv.package_metadata: return ( f" package {red(bold(package))} 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, or reinstall-all to fix." ) if package_metadata.package_version is None: not_installed = red("is not installed") return f" package {bold(package)} {not_installed} in the venv {str(path)}" apps = package_metadata.apps + package_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({ add_suffix(name, package_metadata.suffix) for name in package_metadata.apps } - set(exposed_binary_names)) # The following is to satisfy mypy that python_version is str and not # Optional[str] python_version = (venv.pipx_metadata.python_version if venv.pipx_metadata.python_version is not None else "") return _get_list_output( python_version, python_path, package_metadata.package_version, package, new_install, exposed_binary_names, unavailable_binary_names, venv.pipx_metadata.injected_packages if include_injected else None, suffix=package_metadata.suffix, )
def upgrade_all(venv_container: VenvContainer, verbose: bool, *, skip: List[str], force: bool): packages_upgraded = 0 num_packages = 0 for venv_dir in venv_container.iter_venv_dirs(): num_packages += 1 package = venv_dir.name venv = Venv(venv_dir, verbose=verbose) if package in skip or "--editable" in venv.pipx_metadata.main_package.pip_args: continue try: packages_upgraded += upgrade( venv_dir, package, venv.pipx_metadata.main_package.pip_args, verbose, upgrading_all=True, force=force, ) except Exception: logging.error(f"Error encountered when upgrading {package}") if packages_upgraded == 0: print( f"Versions did not change after running 'pip upgrade' for each package {sleep}" )
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 _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
def uninstall(venv_dir: Path, local_bin_dir: Path, verbose: bool) -> ExitCode: """Uninstall entire venv_dir, including main package and all injected packages. Returns pipx exit code. """ if not venv_dir.exists(): print(f"Nothing to uninstall for {venv_dir.name} {sleep}") app = which(venv_dir.name) if app: print( f"{hazard} Note: '{app}' still exists on your system and is on your PATH" ) return EXIT_CODE_UNINSTALL_VENV_NONEXISTENT venv = Venv(venv_dir, verbose=verbose) bin_dir_app_paths = _get_venv_bin_dir_app_paths(venv, local_bin_dir) for bin_dir_app_path in bin_dir_app_paths: try: safe_unlink(bin_dir_app_path) except FileNotFoundError: logger.info( f"tried to remove but couldn't find {bin_dir_app_path}") else: logger.info(f"removed file {bin_dir_app_path}") rmdir(venv_dir) print(f"uninstalled {venv.name}! {stars}") return EXIT_CODE_OK
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 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 get_venv_metadata_summary(venv_dir: Path) -> Tuple[PipxMetadata, VenvProblems, str]: venv = Venv(venv_dir) (venv_problems, warning_message) = venv_health_check(venv) if venv_problems.any_(): return (PipxMetadata(venv_dir, read=False), venv_problems, warning_message) return (venv.pipx_metadata, venv_problems, "")
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 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 package_name_from_spec(package_spec: str, python: str, *, pip_args: List[str], verbose: bool) -> str: start_time = time.time() # shortcut if valid PyPI name and not a local path if valid_pypi_name(package_spec) and not Path(package_spec).exists(): package_name = package_spec logging.info(f"Determined package name: {package_name}") logging.info( f"Package name determined in {time.time()-start_time:.1f}s") return package_name with tempfile.TemporaryDirectory() as temp_venv_dir: venv = Venv(Path(temp_venv_dir), python=python, verbose=verbose) venv.create_venv(venv_args=[], pip_args=[]) package_name = venv.install_package_no_deps( package_or_url=package_spec, pip_args=pip_args) logging.info(f"Package name determined in {time.time()-start_time:.1f}s") return package_name
def _download_and_run( venv_dir: Path, package_or_url: str, app: str, app_filename: str, app_args: List[str], python: str, pip_args: List[str], venv_args: List[str], use_cache: bool, verbose: bool, ) -> NoReturn: venv = Venv(venv_dir, python=python, verbose=verbose) venv.create_venv(venv_args, pip_args) if venv.pipx_metadata.main_package.package is not None: package_name = venv.pipx_metadata.main_package.package else: package_name = package_name_from_spec(package_or_url, python, pip_args=pip_args, verbose=verbose) venv.install_package( package_name=package_name, package_or_url=package_or_url, pip_args=pip_args, include_dependencies=False, include_apps=True, is_main_package=True, ) if not venv.has_app(app, app_filename): apps = venv.pipx_metadata.main_package.apps raise PipxError(f""" '{app}' executable script not found in package '{package_or_url}'. Available executable scripts: {', '.join(b for b in apps)} """) if not use_cache: # Let future _remove_all_expired_venvs know to remove this (venv_dir / VENV_EXPIRED_FILENAME).touch() venv.run_app(app, app_filename, app_args)
def get_venv_summary( venv_dir: Path, *, package_name: Optional[str] = None, new_install: bool = False, include_injected: bool = False, ) -> Tuple[str, VenvProblems]: venv = Venv(venv_dir) if package_name is None: package_name = venv.main_package_name (venv_problems, warning_message) = venv_health_check(venv, package_name) if venv_problems.any_(): return (warning_message, venv_problems) package_metadata = venv.package_metadata[package_name] apps = package_metadata.apps if package_metadata.include_dependencies: apps += package_metadata.apps_of_dependencies exposed_app_paths = get_exposed_app_paths_for_package( venv.bin_path, constants.LOCAL_BIN_DIR, [add_suffix(app, package_metadata.suffix) for app in apps], ) exposed_binary_names = sorted(p.name for p in exposed_app_paths) unavailable_binary_names = sorted( {add_suffix(name, package_metadata.suffix) for name in package_metadata.apps} - set(exposed_binary_names) ) # The following is to satisfy mypy that python_version is str and not # Optional[str] python_version = ( venv.pipx_metadata.python_version if venv.pipx_metadata.python_version is not None else "" ) return ( _get_list_output( python_version, package_metadata.package_version, package_name, new_install, exposed_binary_names, unavailable_binary_names, venv.pipx_metadata.injected_packages if include_injected else None, suffix=package_metadata.suffix, ), venv_problems, )
def upgrade_all( venv_container: VenvContainer, verbose: bool, *, include_injected: bool, skip: Sequence[str], force: bool, ) -> ExitCode: """Returns pipx exit code.""" 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( venv_dir, venv.pipx_metadata.main_package.pip_args, verbose, include_injected=include_injected, upgrading_all=True, force=force, ) except PipxError as e: venv_error = True logger.error(f"Error encountered when upgrading {venv_dir.name}:") logger.error(f"{e}\n") if venvs_upgraded == 0: print( f"Versions did not change after running 'pip upgrade' for each package {sleep}" ) if venv_error: raise PipxError( "\nSome packages encountered errors during upgrade.\n" " See specific error messages above." ) return EXIT_CODE_OK
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)
def get_package_summary( path: Path, *, package: str = None, new_install: bool = False, include_injected: bool = False, ) -> str: venv = Venv(path) python_path = venv.python_path.resolve() if package is None: package = path.name if not python_path.is_file(): return f" package {red(bold(package))} has invalid interpreter {str(python_path)}" package_metadata = venv.package_metadata[package] if package_metadata.package_version is None: not_installed = red("is not installed") return f" package {bold(package)} {not_installed} in the venv {str(path)}" apps = package_metadata.apps + package_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(package_metadata.apps) - set(exposed_binary_names)) # The following is to satisfy mypy that python_version is str and not # Optional[str] python_version = (venv.pipx_metadata.python_version if venv.pipx_metadata.python_version is not None else "") return _get_list_output( python_version, python_path, package_metadata.package_version, package, new_install, exposed_binary_names, unavailable_binary_names, venv.pipx_metadata.injected_packages.keys() if include_injected else None, )
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) if venv.pipx_metadata.main_package.package is not None: package = venv.pipx_metadata.main_package.package else: package = package_name_from_spec(package_or_url, python, pip_args=pip_args, verbose=verbose) venv.install_package( 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)