Exploring doit graphs with ipywidgetsΒΆ

[ ]:
import ipywidgets as W, traitlets as T, doit, graphviz, jinja2, textwrap, re; ui = W.VBox([W.HTML('restart and run all to see something here')]); ui
[ ]:
class Doit(W.Widget):
    doit_ = T.Instance(doit.doit_cmd.DoitMain)
    tasks = W.trait_types.TypedTuple(trait=T.Instance(doit.task.Task))

    @T.observe("doit_")
    def on_doit_change(self, change):
        cmds = self.doit_.get_cmds()
        tasks, _ = self.doit_.task_loader.load_tasks(cmds['list'], None, None)
        tc = doit.control.TaskControl(tasks)
        self.tasks = tuple(tc.tasks.values())
[ ]:
class FileDoit(Doit):
    path = T.Unicode("dodo.py").tag(sync=True)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.on_path_change(T.Bunch(new=self.path))

    @T.observe("path")
    def _on_path_change(self, change):
        self.on_path_change(change)

    def on_path_change(self, change):
        pass
[ ]:
class PyFileDoit(FileDoit):
    def on_path_change(self, change=None):
        self.doit_ = doit.doit_cmd.DoitMain(doit.cmd_base.ModuleTaskLoader(doit.loader.get_module(self.path)))
[ ]:
it = PyFileDoit(path = "dodo.py")
[ ]:
class TaskGraph(W.HTML):
    tasks = W.trait_types.TypedTuple(trait=T.Instance(doit.task.Task))
    graph = T.Instance(graphviz.Digraph)
    node_template = T.Unicode("""<
    <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"><TR><TD><b>{{t.name}}</b></TD></TR><TR><TD>{{wrap(t.doc)}}</TD></TR></TABLE>
    >""")
    filter_text = T.Unicode()
    filter_kind = T.Unicode()
    rankdir = T.Unicode("RL")
    wrap = T.Int(24)

    @T.observe("tasks", "node_template", "filter_text", "filter_kind", "wrap", "rankdir")
    def on_tasks(self, change):
        tmpl = jinja2.Template(self.node_template)
        wrap = lambda x: "<br/>".join(textwrap.wrap(x, self.wrap))
        self.graph = G = graphviz.Digraph(format="svg", node_attr=dict(fontname="sans-serif"))
        G.attr(rankdir=self.rankdir)
        tasks = self.filtered()
        task_names = [t.name for t in tasks]
        for t in tasks:
            G.node(t.name, tmpl.render(t=t, wrap=wrap), shape="none")
            for tdep in t.task_dep:
                if tdep in task_names:
                    G.edge(tdep, t.name)

        self.value = re.sub(r'<svg (.*)viewBox', '<svg viewBox', G._repr_svg_(), flags=re.M | re.DOTALL)

    def filtered(self):
        tasks = self.tasks or []
        kind = self.filter_kind
        if not kind:
            return tasks

        if kind in ["upstream", "downstream"]:
            filtered = {t.name: t for t in tasks if re.findall(self.filter_text, t.name)}
        elif kind == "file":
            filtered = {t.name: t for t in tasks if [fd for fd in t.file_dep if re.findall(self.filter_text, fd)]}

        candidates = {t.name: t for t in tasks if t.name not in filtered}

        while True:
            found = False
            f_round = list(filtered.values())
            for t in f_round:
                if kind == "upstream":
                    for d in t.task_dep:
                        if d in candidates:
                            filtered[d] = candidates.pop(d)
                            found = True
                elif kind in ["downstream", "file"]:
                    d_round = list(candidates.values())
                    for d in d_round:
                        if t.name in d.task_dep:
                            filtered[d.name] = candidates.pop(d.name)
                            found = True
            if not found:
                break

        return filtered.values()
[ ]:
tg = TaskGraph()
wrap = W.IntSlider(24, min=5, max=45, description="wrap")
filter_text = W.Text(".*", description="goal")
filter_kind = W.SelectionSlider(options=["upstream", "downstream", "file", ""], description="filter by")
rankdir = W.SelectionSlider(options=["LR", "TB"], description="direction")

T.link((it, "tasks"), (tg, "tasks"))
T.link((filter_text, "value"), (tg, "filter_text"))
T.link((wrap, "value"), (tg, "wrap"))
T.link((filter_kind, "value"), (tg, "filter_kind"))
T.link((rankdir, "value"), (tg, "rankdir"))

ui.children = [W.HBox([filter_text, filter_kind, rankdir, wrap]), tg]