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 build_dependency_graph(working_set: WorkingSet) -> DirectedGraph: """Build a dependency graph from locked result.""" graph = DirectedGraph() graph.add(None) # sentinel parent of top nodes. node_with_extras = set() def add_package(key, dist): name, extras = strip_extras(key) extras = extras or () reqs = {} if dist: requirements = [ Requirement.from_pkg_requirement(r) for r in dist.requires(extras) ] for req in requirements: reqs[req.identify()] = req version = dist.version else: version = None node = Package(key, version, reqs) if node not in graph: if extras: node_with_extras.add(name) graph.add(node) for k in reqs: child = add_package(k, working_set.get(strip_extras(k)[0])) graph.connect(node, child) return node for k, dist in working_set.items(): add_package(k, dist) for node in graph._vertices.copy(): if node is not None and not list(graph.iter_parents(node)): # Top requirements if node.name in node_with_extras: # Already included in package[extra], no need to keep the top level # non-extra package. graph.remove(node) else: graph.connect(None, node) return graph