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]