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 setup(args: argparse.Namespace) -> None: if "version" in args and args.version: print_version() sys.exit(0) setup_logging("verbose" in args and args.verbose) logger.debug(f"{time.strftime('%Y-%m-%d %H:%M:%S')}") logger.debug(f"{' '.join(sys.argv)}") logger.info(f"pipx version is {__version__}") logger.info(f"Default python interpreter is {repr(DEFAULT_PYTHON)}") mkdir(constants.PIPX_LOCAL_VENVS) mkdir(constants.LOCAL_BIN_DIR) mkdir(constants.PIPX_VENV_CACHEDIR) rmdir(constants.PIPX_TRASH_DIR, False) old_pipx_venv_location = constants.PIPX_LOCAL_VENVS / "pipx-app" if old_pipx_venv_location.exists(): logger.warning( pipx_wrap( f""" {hazard} A virtual environment for pipx was detected at {str(old_pipx_venv_location)}. The 'pipx-app' package has been renamed back to 'pipx' (https://github.com/pypa/pipx/issues/82). """, subsequent_indent=" " * 4, ))
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 _symlink_package_apps(local_bin_dir: Path, app_paths: List[Path], package: str, *, force: bool): for app_path in app_paths: app_name = app_path.name symlink_path = Path(local_bin_dir / app_name) if not symlink_path.parent.is_dir(): mkdir(symlink_path.parent) if force: logging.info(f"Force is true. Removing {str(symlink_path)}.") try: symlink_path.unlink() except FileNotFoundError: pass except IsADirectoryError: rmdir(symlink_path) if symlink_path.exists(): if symlink_path.samefile(app_path): logging.info( f"Same path {str(symlink_path)} and {str(app_path)}") else: logging.warning( f"{hazard} File exists at {str(symlink_path)} and points " f"to {symlink_path.resolve()}, not {str(app_path)}. Not modifying." ) continue existing_executable_on_path = which(app_name) symlink_path.symlink_to(app_path) if existing_executable_on_path: logging.warning( f"{hazard} Note: {app_name} was already on your PATH at " f"{existing_executable_on_path}")
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 remove_venv(self) -> None: if self.safe_to_remove(): rmdir(self.root) else: logging.warning( f"Not removing existing venv {self.root} because " "it was not created in this session" )
def _symlink_package_apps( local_bin_dir: Path, app_paths: List[Path], *, force: bool, suffix: str = "" ) -> None: for app_path in app_paths: app_name = app_path.name app_name_suffixed = add_suffix(app_name, suffix) symlink_path = Path(local_bin_dir / app_name_suffixed) if not symlink_path.parent.is_dir(): mkdir(symlink_path.parent) if force: logger.info(f"Force is true. Removing {str(symlink_path)}.") try: symlink_path.unlink() except FileNotFoundError: pass except IsADirectoryError: rmdir(symlink_path) exists = symlink_path.exists() is_symlink = symlink_path.is_symlink() if exists: if symlink_path.samefile(app_path): logger.info(f"Same path {str(symlink_path)} and {str(app_path)}") else: logger.warning( pipx_wrap( f""" {hazard} File exists at {str(symlink_path)} and points to {symlink_path.resolve()}, not {str(app_path)}. Not modifying. """, subsequent_indent=" " * 4, ) ) continue if is_symlink and not exists: logger.info( f"Removing existing symlink {str(symlink_path)} since it " "pointed non-existent location" ) symlink_path.unlink() existing_executable_on_path = which(app_name_suffixed) symlink_path.symlink_to(app_path) if existing_executable_on_path: logger.warning( pipx_wrap( f""" {hazard} Note: {app_name_suffixed} was already on your PATH at {existing_executable_on_path} """, subsequent_indent=" " * 4, ) )
def remove_venv(self) -> None: if self.safe_to_remove(): rmdir(self.root) else: logger.warning( pipx_wrap( f""" {hazard} Not removing existing venv {self.root} because it was not created in this session """, subsequent_indent=" " * 4, ))
def remove_venv(self) -> None: rmdir(self.root)
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"): raise PipxError( "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: return subprocess.run([str(python), "-c", content]).returncode except KeyboardInterrupt: return 1 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 _remove_all_expired_venvs(): for venv_dir in Path(constants.PIPX_VENV_CACHEDIR).iterdir(): if _is_temporary_venv_expired(venv_dir): logging.info(f"Removing expired venv {str(venv_dir)}") rmdir(venv_dir)
def _prepare_venv_cache(venv: Venv, bin_path: Path, use_cache: bool): venv_dir = venv.root if not use_cache and bin_path.exists(): logging.info(f"Removing cached venv {str(venv_dir)}") rmdir(venv_dir) _remove_all_expired_venvs()