def format_package( graph: DirectedGraph, package: Package, required: str = "", prefix: str = "", visited: Optional[Set[str]] = None, ) -> str: """Format one package. :param graph: the dependency graph :param package: the package instance :param required: the version required by its parent :param prefix: prefix text for children :param visited: the visited package collection """ if visited is None: visited = set() result = [] version = ( termui.red("[ not installed ]") if not package.version else termui.red(package.version) if required and required not in ("Any", "This project") and not SpecifierSet(required).contains(package.version) else termui.yellow(package.version) ) if package.name in visited: version = termui.red("[circular]") required = f"[ required: {required} ]" if required else "[ Not required ]" result.append(f"{termui.green(package.name, bold=True)} {version} {required}\n") if package.name in visited: return "".join(result) visited.add(package.name) children = sorted(graph.iter_children(package), key=lambda p: p.name) for i, child in enumerate(children): is_last = i == len(children) - 1 head = LAST_CHILD if is_last else NON_LAST_CHILD cur_prefix = LAST_PREFIX if is_last else NON_LAST_PREFIX required = str(package.requirements[child.name].specifier or "Any") result.append( prefix + head + format_package( graph, child, required, prefix + cur_prefix, visited.copy() ) ) return "".join(result)
def print_pep582_command(ui: termui.UI, shell: str = "AUTO"): """Print the export PYTHONPATH line to be evaluated by the shell.""" import shellingham if os.name == "nt": try: set_env_in_reg("PYTHONPATH", PEP582_PATH) except PermissionError: ui.echo( termui.red( "Permission denied, please run the terminal as administrator." ), err=True, ) ui.echo( termui.green("The environment variable has been saved, " "please restart the session to take effect.")) return lib_path = PEP582_PATH.replace("'", "\\'") if shell == "AUTO": shell = shellingham.detect_shell()[0] shell = shell.lower() if shell in ("zsh", "bash"): result = f"export PYTHONPATH='{lib_path}':$PYTHONPATH" elif shell == "fish": result = f"set -x PYTHONPATH '{lib_path}' $PYTHONPATH" elif shell in ("tcsh", "csh"): result = f"setenv PYTHONPATH '{lib_path}':$PYTHONPATH" else: raise PdmUsageError( f"Unsupported shell: {shell}, please specify another shell " "via `--pep582 <SHELL>`") ui.echo(result)
def handle(self, project: Project, options: argparse.Namespace) -> None: if not options.force: project.core.ui.echo( termui.red("The following Virtualenvs will be purged:") ) for i, venv in enumerate(get_all_venvs(project)): project.core.ui.echo(f"{i}. {termui.green(venv[0])}") if not options.interactive and ( options.force or click.confirm(termui.yellow("continue? ")) ): self.del_all_venvs(project) if options.interactive: selection = click.prompt( "Please select", type=click.Choice( [str(i) for i in range(len(list(get_all_venvs(project))))] + ["all", "none"] ), default="none", show_choices=False, ) if selection == "all": self.del_all_venvs(project) elif selection != "none": for i, venv in enumerate(get_all_venvs(project)): if i == int(selection): shutil.rmtree(venv[1]) project.core.ui.echo("Purged successfully!")
def format_reverse_package( graph: DirectedGraph, package: Package, child: Optional[Package] = None, requires: str = "", prefix: str = "", visited: Optional[Set[str]] = None, ): """Format one package for output reverse dependency graph.""" if visited is None: visited = set() result = [] version = ( termui.red("[ not installed ]") if not package.version else termui.yellow(package.version) ) if package.name in visited: version = termui.red("[circular]") requires = ( f"[ requires: {termui.red(requires)} ]" if requires not in ("Any", "") and child and child.version and not SpecifierSet(requires).contains(child.version) else "" if not requires else f"[ requires: {requires} ]" ) result.append(f"{termui.green(package.name, bold=True)} {version} {requires}\n") if package.name in visited: return "".join(result) visited.add(package.name) parents = sorted(filter(None, graph.iter_parents(package)), key=lambda p: p.name) for i, parent in enumerate(parents): is_last = i == len(parents) - 1 head = LAST_CHILD if is_last else NON_LAST_CHILD cur_prefix = LAST_PREFIX if is_last else NON_LAST_PREFIX requires = str(parent.requirements[package.name].specifier or "Any") result.append( prefix + head + format_reverse_package( graph, parent, package, requires, prefix + cur_prefix, visited.copy() ) ) return "".join(result)
def _show_config(self, config: Config, ui: termui.UI) -> None: for key in sorted(config): config_item = config._config_map[key] deprecated = "" if config_item.replace and config_item.replace in config._data: deprecated = termui.red( f"(deprecating: {config_item.replace})") ui.echo( termui.yellow("# " + config_item.description), verbosity=termui.DETAIL, ) ui.echo(f"{termui.cyan(key)}{deprecated} = {config[key]}")
def synchronize(self) -> None: """Synchronize the working set with pinned candidates.""" to_add, to_update, to_remove = self.compare_with_working_set() to_do = {"remove": to_remove, "update": to_update, "add": to_add} if self.dry_run: self._show_summary(to_do) return self._show_headline(to_do) handlers = { "add": self.install_candidate, "update": self.update_candidate, "remove": self.remove_distribution, } sequential_jobs = [] parallel_jobs = [] for kind in to_do: for key in to_do[kind]: if key in self.SEQUENTIAL_PACKAGES: sequential_jobs.append((kind, key)) elif key in self.candidates and self.candidates[ key].req.editable: # Editable packages are installed sequentially. sequential_jobs.append((kind, key)) else: parallel_jobs.append((kind, key)) errors: List[str] = [] failed_jobs: List[Tuple[str, str]] = [] def update_progress(future: Union[Future, DummyFuture], kind: str, key: str) -> None: error = future.exception() if error: exc_info = (type(error), error, error.__traceback__) termui.logger.exception("Error occurs: ", exc_info=exc_info) failed_jobs.append((kind, key)) errors.extend([f"{kind} {termui.green(key)} failed:\n"] + traceback.format_exception(*exc_info)) with self.ui.logging("install"): with self.ui.indent(" "): for job in sequential_jobs: kind, key = job handlers[kind](key) for i in range(self.retry_times + 1): with self.create_executor() as executor: for job in parallel_jobs: kind, key = job future = executor.submit(handlers[kind], key) future.add_done_callback( functools.partial(update_progress, kind=kind, key=key)) if not failed_jobs or i == self.retry_times: break parallel_jobs, failed_jobs = failed_jobs, [] errors.clear() self.ui.echo("Retry failed jobs") if errors: self.ui.echo(termui.red("\nERRORS:")) self.ui.echo("".join(errors), err=True) raise InstallationError( "Some package operations are not complete yet") if self.install_self: self_candidate = self.environment.project.make_self_candidate( not self.no_editable) self_key = self.self_key assert self_key self.candidates[self_key] = self_candidate word = "a" if self.no_editable else "an editable" self.ui.echo(f"Installing the project as {word} package...") with self.ui.indent(" "): if self_key in self.working_set: self.update_candidate(self_key) else: self.install_candidate(self_key) self.ui.echo(f"\n{termui.Emoji.SUCC} All complete!")
def synchronize(self, clean: bool = True, dry_run: bool = False) -> None: """Synchronize the working set with pinned candidates. :param clean: Whether to remove unneeded packages, defaults to True. :param dry_run: If set to True, only prints actions without actually do them. """ to_add, to_update, to_remove = self.compare_with_working_set() if not clean: to_remove = [] to_do = {"remove": to_remove, "update": to_update, "add": to_add} self._show_headline(to_do) if dry_run: self._show_summary(to_do) return handlers = { "add": self.install_candidate, "update": self.update_candidate, "remove": self.remove_distribution, } sequential_jobs = [] parallel_jobs = [] # Self package will be installed after all other dependencies are installed. self_action = None self_key = self.self_key if self_key in self.candidates: self_action = "update" if self_key in self.working_set else "add" for kind in to_do: for key in to_do[kind]: if key in self.SEQUENTIAL_PACKAGES: sequential_jobs.append((kind, key)) elif key in self.candidates and self.candidates[ key].req.editable: # Editable packages are installed sequentially. sequential_jobs.append((kind, key)) else: parallel_jobs.append((kind, key)) errors: List[str] = [] failed_jobs: List[Tuple[str, str]] = [] def update_progress(future, kind, key): if future.exception(): failed_jobs.append((kind, key)) error = future.exception() errors.extend([f"{kind} {termui.green(key)} failed:\n"] + traceback.format_exception( type(error), error, error.__traceback__)) with self.ui.logging("install"), self.environment.activate(): with self.ui.indent(" "): for job in sequential_jobs: kind, key = job handlers[kind](key) for i in range(self.retry_times + 1): with self.create_executor() as executor: for job in parallel_jobs: kind, key = job future = executor.submit(handlers[kind], key) future.add_done_callback( functools.partial(update_progress, kind=kind, key=key)) if not failed_jobs or i == self.retry_times: break parallel_jobs, failed_jobs = failed_jobs, [] errors.clear() self.ui.echo("Retry failed jobs") if errors: self.ui.echo(termui.red("\nERRORS:")) self.ui.echo("".join(errors), err=True) raise InstallationError( "Some package operations are not complete yet") if self_action: self.ui.echo( "Installing the project as an editable package...") with self.ui.indent(" "): handlers[self_action](self_key) self.ui.echo(f"\n{termui.Emoji.SUCC} All complete!")