class PluginInvocation(): _plugins = {} def __init__(self, name, data): super().__init__() if name not in self._plugins: raise KeyError(f'No such plugin function: {name}') self._name = name self._data = data def __call__(self): return self._plugins[self._name](**self._data) @classmethod def register_plugin(cls, name, fn): cls._plugins[name] = fn def plugin_constructor(loader, node): data = loader.construct_mapping(node) return PluginInvocation(node.tag[1:], data) def render_traverse(obj): """ Walk through a complex, JSON-serializable data structure, and invoke plugins if for custom tags. :param obj: The object to traverse. """ if isinstance(obj, list): # list -> recurse into each item return [render_traverse(x) for x in obj] elif isinstance(obj, dict): # dict -> recurse into the value of each (key, value) return {k: render_traverse(v) for k, v in obj.items()} elif isinstance(obj, PluginInvocation): # PluginTag -> invoke the plugin with the stored arguments return obj() else: # anything else -> return as-is return obj