I remember when initially Marcus and I sat down to discuss the design for Pyblish when he started building it (partially based on some funding from our end actually). It’s a long time ago. Initially we prototyped the idea with a node based system - since everything around us was node graphs in our work - but felt that it’d hold back on the simplicity of Pyblish design philosophy. It would have e.g. required at least a UI tool to manage plug-in dependencies since it’s non-trivial to describe node graphs by code.
Pyblish define order by dependencies
By the way, nothing is actually holding you to set the `order plugins based on their dependencies. Consider e.g. doing this:
Say we define plug-ins like this:
class A(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder
def process(self, context):
context.data["A"] = True
print("A")
class B(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder
dependencies = ["A"]
def process(self, context):
assert context.data["A"] is True
context.data["B"] = True
print("B")
class C(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder
dependencies = ["A", "B"]
def process(self, context):
assert context.data["A"] is True
assert context.data["B"] is True
context.data["C"] = True
print("C")
class D(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder
def process(self, context):
context.data["D"] = True
print("D")
And use a sorting algorithm, e.g. this:
from collections import defaultdict, namedtuple
Results = namedtuple('Results', ['sorted', 'cyclic'])
def topological_sort(dependency_pairs):
"""Sort values subject to dependency constraints"""
num_heads = defaultdict(int) # num arrows pointing in
tails = defaultdict(list) # list of arrows going out
heads = [] # unique list of heads in order first seen
for h, t in dependency_pairs:
num_heads[t] += 1
if h in tails:
tails[h].append(t)
else:
tails[h] = [t]
heads.append(h)
ordered = [h for h in heads if h not in num_heads]
for h in ordered:
for t in tails[h]:
num_heads[t] -= 1
if not num_heads[t]:
ordered.append(t)
cyclic = [n for n, heads in num_heads.items() if heads]
return Results(ordered, cyclic)
Then we can register a discovery filter to do some magic.
def shift_order_for_dependencies(plugins):
offset = 0.0001 # amount of offset applied from dependencies
def get_dependencies(plugin):
return getattr(plugin, "dependencies", [])
plugins_by_name = {plugin.__class__.__name__: plugin for plugin in plugins}
dependency_pairs = zip(
name, get_dependencies(plugin)
for name, plugin in plugins_by_name.items()
)
result = topological_sort(dependency_pairs)
assert not result.cyclic, "Not allowed to have cyclic dependencies"
# Apply offsets in sorted order
for plugin_name in result.ordered:
plugin = plugins_by_name[plugin_name]
for dependency_name in get_dependencies(plugin):
dependency_plugin = plugins_by_name[dependency_name]
if dependency_plugin.order > plugin.order:
# todo: we might want to validate it does not move into another order
# e.g. avoid pushing it from Collector to Validator
plugin.order = dependency_plugin.order + offset
return plugins
pyblish.api.register_discovery_filter(shift_order_for_dependencies)
# Now publish away
Note that I didn’t test run this code at all - so consider it quick pseudocode.
Visualizing data during publishing
Having said that - I think the bigger issue really is instead more of a visual debugging problem than anything else. It’s too bad it’s just non-trivial to see how values change over time, by what and what uses what data, etc. - Which actually isn’t even that hard to do - look for example at this old prototype I had quickly put together that shows what data was added and changed by a plugin.
Nothing is holding us from swapping out the actual instance.data
behavior with something that also adds a notification if any data was accessed - so, that also that could be visualized.