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, 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 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, 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 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 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_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(LOCAL_BIN_DIR, metadata.app_paths, package, force=force) if include_dependencies: for _, app_paths in metadata.app_paths_of_dependencies.items(): _expose_apps_globally(LOCAL_BIN_DIR, app_paths, package, force=force) 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 _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 run( app: str, package_or_url: str, binary_args: List[str], python: str, pip_args: List[str], venv_args: List[str], pypackages: bool, verbose: bool, use_cache: bool, ): """Installs venv to temporary dir (or reuses cache), then runs app from package """ if urllib.parse.urlparse(app).scheme: if not app.endswith(".py"): exit( "pipx will only execute apps from the internet directly if " "they end with '.py'. To run from an SVN, try pipx --spec URL BINARY" ) logging.info( "Detected url. Downloading and executing as a Python file.") content = _http_get_request(app) try: exit(subprocess.run([str(python), "-c", content]).returncode) except KeyboardInterrupt: pass exit(0) elif which(app): logging.warning( f"{hazard} {app} is already on your PATH and installed at " f"{which(app)}. Downloading and " "running anyway.") if WINDOWS and not app.endswith(".exe"): app = f"{app}.exe" logging.warning(f"Assuming app is {app!r} (Windows only)") pypackage_bin_path = get_pypackage_bin_path(app) if pypackage_bin_path.exists(): logging.info( f"Using app in local __pypackages__ directory at {str(pypackage_bin_path)}" ) return run_pypackage_bin(pypackage_bin_path, binary_args) if pypackages: raise PipxError( f"'--pypackages' flag was passed, but {str(pypackage_bin_path)!r} was " "not found. See https://github.com/cs01/pythonloc to learn how to " "install here, or omit the flag.") venv_dir = _get_temporary_venv_path(package_or_url, python, pip_args, venv_args) venv = Venv(venv_dir) bin_path = venv.bin_path / app _prepare_venv_cache(venv, bin_path, use_cache) if bin_path.exists(): logging.info(f"Reusing cached venv {venv_dir}") retval = venv.run_app(app, binary_args) else: logging.info(f"venv location is {venv_dir}") retval = _download_and_run( Path(venv_dir), package_or_url, app, binary_args, python, pip_args, venv_args, verbose, ) if not use_cache: rmdir(venv_dir) return retval
def _run_post_install_actions( venv: Venv, package: str, local_bin_dir: Path, venv_dir: Path, include_dependencies: bool, *, force: bool, ): metadata = venv.get_venv_metadata_for_package(package) if not metadata.app_paths and not include_dependencies: # No apps associated with this package and we aren't including dependencies. # This package has nothing for pipx to use, so this is an error. for dep, dependent_apps in metadata.app_paths_of_dependencies.items(): print( f"Note: Dependent package '{dep}' contains {len(dependent_apps)} apps" ) for app in dependent_apps: print(f" - {app.name}") if venv.safe_to_remove(): venv.remove_venv() if len(metadata.app_paths_of_dependencies.keys()): raise PipxError( f"No apps associated with package {package}. " "Try again with '--include-deps' to include apps of dependent packages, " "which are listed above. " "If you are attempting to install a library, pipx should not be used. " "Consider using pip or a similar tool instead.") else: raise PipxError( f"No apps associated with package {package}." "If you are attempting to install a library, pipx should not be used. " "Consider using pip or a similar tool instead.") if metadata.apps: pass elif metadata.apps_of_dependencies and include_dependencies: pass else: # No apps associated with this package and we aren't including dependencies. # This package has nothing for pipx to use, so this is an error. if venv.safe_to_remove(): venv.remove_venv() raise PipxError( f"No apps associated with package {package} or its dependencies." "If you are attempting to install a library, pipx should not be used. " "Consider using pip or a similar tool instead.") _expose_apps_globally(local_bin_dir, metadata.app_paths, package, force=force) if include_dependencies: for _, app_paths in metadata.app_paths_of_dependencies.items(): _expose_apps_globally(local_bin_dir, app_paths, package, force=force) print(_get_package_summary(venv_dir, package=package, new_install=True)) _warn_if_not_on_path(local_bin_dir) print(f"done! {stars}", file=sys.stderr)
def install( venv_dir: Path, package: str, package_or_url: str, local_bin_dir: Path, python: str, pip_args: List[str], venv_args: List[str], verbose: bool, *, force: bool, include_dependencies: bool, ): try: exists = venv_dir.exists() and next(venv_dir.iterdir()) except StopIteration: exists = False if exists: if force: print(f"Installing to existing directory {str(venv_dir)!r}") else: print(f"{package!r} already seems to be installed. " f"Not modifying existing installation in {str(venv_dir)!r}. " "Pass '--force' to force installation.") return venv = Venv(venv_dir, python=python, verbose=verbose) try: venv.create_venv(venv_args, pip_args) venv.install_package(package_or_url, pip_args) if venv.get_venv_metadata_for_package(package).package_version is None: venv.remove_venv() raise PipxError( f"Could not find package {package}. Is the name correct?") _run_post_install_actions(venv, package, local_bin_dir, venv_dir, include_dependencies, force=force) except (Exception, KeyboardInterrupt): print("") venv.remove_venv() raise