def format_package( graph: DirectedGraph, package: Package, required: str = "", prefix: str = "", visited=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 = ( stream.red("[ not installed ]") if not package.version else stream.red(package.version) if required and required != "Any" and not SpecifierSet(required).contains(package.version) else stream.yellow(package.version) ) if package.name in visited: version = stream.red("[circular]") required = f"[ required: {required} ]" if required else "" result.append(f"{stream.green(package.name, bold=True)} {version} {required}\n") if package.name in visited: return "".join(result) visited.add(package.name) try: *children, last = sorted(graph.iter_children(package), key=lambda p: p.name) except ValueError: # No children nodes pass else: for child in children: required = str(package.requirements[child.name].specifier or "Any") result.append( prefix + NON_LAST_CHILD + format_package( graph, child, required, prefix + NON_LAST_PREFIX, visited.copy() ) ) required = str(package.requirements[last.name].specifier or "Any") result.append( prefix + LAST_CHILD + format_package( graph, last, required, prefix + LAST_PREFIX, visited.copy() ) ) return "".join(result)
def set_env_in_reg(env_name: str, value: str) -> None: """Manipulate the WinReg, and add value to the environment variable if exists or create new. """ import winreg value = os.path.normcase(value) with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root: with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as env_key: try: old_value, type_ = winreg.QueryValueEx(env_key, env_name) if value in [ os.path.normcase(item) for item in old_value.split(os.pathsep) ]: return except FileNotFoundError: old_value, type_ = "", winreg.REG_EXPAND_SZ new_value = ";".join(old_value, value) if old_value else value try: winreg.SetValueEx(env_key, env_name, 0, type_, new_value) except PermissionError: stream.echo( stream.red( "Permission denied, please run the terminal as administrator." ), err=True, ) sys.exit(1) stream.echo( stream.green("The environment variable has been saved, " "please restart the session to take effect."))
def deprecate_global_option(value) -> Project: if value: stream.echo( stream.red( "DEPRECATION: -g/--global with argument is deprecated and will be " "removed in v1.5.0, please use '-gp <PROJECT_PATH>' instead."), err=True, ) return Project.create_global(value)
def format_package( graph: DirectedGraph, package: Package, required: str = "", prefix: str = "", visited=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 = (stream.red("[ not installed ]") if not package.version else stream.red(package.version) if required and required not in ("Any", "This project") and not SpecifierSet(required).contains(package.version) else stream.yellow(package.version)) if package.name in visited: version = stream.red("[circular]") required = f"[ required: {required} ]" if required else "[ Not required ]" result.append( f"{stream.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 format_reverse_package( graph: DirectedGraph, package: Package, child: Optional[Package] = None, requires: str = "", prefix: str = "", visited=None, ): """Format one package for output reverse dependency graph.""" if visited is None: visited = set() result = [] version = (stream.red("[ not installed ]") if not package.version else stream.yellow(package.version)) if package.name in visited: version = stream.red("[circular]") requires = (f"[ requires: {stream.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"{stream.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 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 = [] if not any([to_add, to_update, to_remove]): stream.echo( stream.yellow( "All packages are synced to date, nothing to do.")) if not dry_run: with stream.logging("install"): self.update_project_egg_info() return 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. install_self = None for kind in to_do: for key in to_do[kind]: if (key == self.environment.project.meta.name and self.environment.project.meta.project_name.lower()): install_self = (kind, key) elif 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} {stream.green(key)} failed:\n"] + traceback.format_exception( type(error), error, error.__traceback__)) with stream.logging("install"), self.environment.activate(): with stream.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() stream.echo("Retry failed jobs") if errors: stream.echo(stream.red("\nERRORS:")) stream.echo("".join(errors), err=True) raise InstallationError( "Some package operations are not complete yet") if install_self: stream.echo("Installing the project as an editable package...") with stream.indent(" "): handlers[install_self[0]](install_self[1]) else: self.update_project_egg_info() stream.echo(f"\n{CELE} 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 = [] lists_to_check = [to_add, to_update, to_remove] if not any(lists_to_check): stream.echo("All packages are synced to date, nothing to do.") return if dry_run: result = dict( add=[self.candidates[key] for key in to_add], update=[ (self.working_set[key], self.candidates[key]) for key in to_update ], remove=[self.working_set[key] for key in to_remove], ) self.summarize(result, dry_run) return handlers = { "add": self.install_candidate, "update": self.update_candidate, "remove": self.remove_distribution, } result = defaultdict(list) failed = defaultdict(list) to_do = {"add": to_add, "update": to_update, "remove": to_remove} # Keep track of exceptions errors = [] def update_progress(future, section, key, bar): if future.exception(): failed[section].append(key) errors.append(future.exception()) else: result[section].append(future.result()) bar.update(1) with self.progressbar( "Synchronizing:", sum(len(l) for l in to_do.values()) ) as (bar, pool): for section in to_do: for key in to_do[section]: future = pool.submit(handlers[section], key) future.add_done_callback( functools.partial( update_progress, section=section, key=key, bar=bar ) ) # Retry for failed items for i in range(self.RETRY_TIMES): if not any(failed.values()): break to_do = failed failed = defaultdict(list) errors.clear() with self.progressbar( f"Retrying ({i + 1}/{self.RETRY_TIMES}):", sum(len(l) for l in to_do.values()), ) as (bar, pool): for section in to_do: for key in to_do[section]: future = pool.submit(handlers[section], key) future.add_done_callback( functools.partial( update_progress, section=section, key=key, bar=bar ) ) stream.echo() self.summarize(result) if not any(failed.values()): return stream.echo(stream.red("[ERROR]", bold=True)) if failed["add"] + failed["update"]: stream.echo( f"Installation failed: {', '.join(failed['add'] + failed['update'])}" ) if failed["remove"]: stream.echo(f"Removal failed: {', '.join(failed['remove'])}") for error in errors: stream.echo( "".join( traceback.format_exception(type(error), error, error.__traceback__) ), verbosity=stream.DEBUG, )