'Decorator: Make method `hookable` (i.e. extensible by another app).
Note: If each hooked method returns a value (dict), then all returns are
collated in one dict and returned. Ideally, don\'t return values in hookable
methods, set properties in the document.'

@staticmethod
def hook(f):

    def add_to_return_value(self, new_return_value):
        if isinstance(new_return_value, dict):
            if (not self.get(u'_return_value')):
                self._return_value = {}
            self._return_value.update(new_return_value)
        else:
            self._return_value = (new_return_value or self.get(u'_return_value'))
    def compose(fn, *hooks):
        def runner(self, method, *args, **kwargs):
            add_to_return_value(self, fn(self, *args, **kwargs))
            for f in hooks:
                add_to_return_value(self, f(self, method, *args, **kwargs))
            return self._return_value
        return runner
    def composer(self, *args, **kwargs):
        hooks = []
        method = f.__name__
        doc_events = frappe.get_doc_hooks()
        for handler in (doc_events.get(self.doctype, {}).get(method, []) + doc_events.get(u'*', {}).get(method, [])):
            hooks.append(frappe.get_attr(handler))
        composed = compose(f, *hooks)
        return composed(self, method, *args, **kwargs)
    return composer
