[docs]defeach_step(graph:Graph)->Iterable[tuple[Step,list[Step]]]:"""Yield each step and it's direct dependencies. Args: graph: Graph to iterate over. """steps=graph.topological_sort()steps.reverse()forstepinsteps:deps=graph.downstream(step.name)yieldstep,deps
[docs]defdot_format(out:TextIO,graph:Graph,name:str="digraph")->None:"""Output a graph using the graphviz "dot" format. Args: out: Where output will be written. graph: Graph to be output. name: Name of the graph. """out.write(f"digraph {name}{{\n")forstep,depsineach_step(graph):fordepindeps:out.write(f' "{step}" -> "{dep}";\n')out.write("}\n")
[docs]defjson_format(out:TextIO,graph:Graph)->None:"""Output the graph in a machine readable JSON format. Args: out: Where output will be written. graph: Graph to be output. """steps={step.name:{"deps":[dep.namefordepindeps]}forstep,depsineach_step(graph)}json.dump({"steps":steps},out,indent=4)out.write("\n")
FORMATTERS={"dot":dot_format,"json":json_format,}
[docs]classAction(BaseAction):"""Responsible for outputting a graph for the current CFNgin config."""DESCRIPTION="Print graph"NAME="graph"@propertydef_stack_action(self)->Any:"""Run against a step."""returnNone
[docs]defrun(self,*,concurrency:int=0,# noqa: ARG002dump:bool|str=False,# noqa: ARG002force:bool=False,# noqa: ARG002outline:bool=False,# noqa: ARG002tail:bool=False,# noqa: ARG002upload_disabled:bool=False,# noqa: ARG002**kwargs:Any,)->None:"""Generate the underlying graph and prints it."""graph=self._generate_plan(require_unlocked=False,include_persistent_graph=True).graphifself.context.persistent_graph:graph=merge_graphs(self.context.persistent_graph,graph)ifkwargs.get("reduce"):# This will perform a a transitive reduction on the underlying# graph, producing less edges. Mostly useful for the "dot" format,# when converting to PNG, so it creates a prettier/cleaner# dependency graph.graph.transitive_reduction()fn=FORMATTERS[str(kwargs.get("format","json"))]fn(sys.stdout,graph)sys.stdout.flush()