From 891209d92590f48b7cd96ed6df4f60b2586f3d71 Mon Sep 17 00:00:00 2001 From: bendikro Date: Mon, 7 Mar 2016 20:48:13 +0100 Subject: [PATCH] [Common] Add overrides function decorator --- deluge/decorators.py | 90 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/deluge/decorators.py b/deluge/decorators.py index f2ea593a5..002d465de 100644 --- a/deluge/decorators.py +++ b/deluge/decorators.py @@ -7,6 +7,8 @@ # See LICENSE for more details. # +import inspect +import re from functools import wraps @@ -24,3 +26,91 @@ def proxy(proxy_func): return proxy_func(func, *args, **kwargs) return wrapper return decorator + + +def overrides(*args): + """ + Decorater function to specify when class methods override + super class methods. + + When used as + @overrides + def funcname + + the argument will be the funcname function. + + When used as + @overrides(BaseClass) + def funcname + + the argument will be the BaseClass + + """ + stack = inspect.stack() + if inspect.isfunction(args[0]): + return _overrides(stack, args[0]) + else: + # One or more classes are specifed, so return a function that will be + # called with the real function as argument + def ret_func(func, **kwargs): + return _overrides(stack, func, explicit_base_classes=args) + return ret_func + + +def _overrides(stack, method, explicit_base_classes=None): + # stack[0]=overrides, stack[1]=inside class def'n, stack[2]=outside class def'n + classes = {} + derived_class_locals = stack[2][0].f_locals + + # Find all super classes + m = re.search(r'class\s(.+)\((.+)\)\s*\:', stack[2][4][0]) + class_name = m.group(1) + base_classes = m.group(2) + + # Handle multiple inheritance + base_classes = [s.strip() for s in base_classes.split(',')] + check_classes = base_classes + + if not base_classes: + raise ValueError('overrides decorator: unable to determine base class of class "%s"' % class_name) + + def get_class(cls_name): + if '.' not in cls_name: + return derived_class_locals[cls_name] + else: + components = cls_name.split('.') + # obj is either a module or a class + obj = derived_class_locals[components[0]] + for c in components[1:]: + assert inspect.ismodule(obj) or inspect.isclass(obj) + obj = getattr(obj, c) + return obj + + if explicit_base_classes: + # One or more base classes are explicitly given, check only those classes + override_classes = re.search(r'\s*@overrides\((.+)\)\s*', stack[1][4][0]).group(1) + override_classes = [c.strip() for c in override_classes.split(",")] + check_classes = override_classes + + for c in base_classes + check_classes: + classes[c] = get_class(c) + + # Verify that the excplicit override class is one of base classes + if explicit_base_classes: + from itertools import product + for bc, cc in product(base_classes, check_classes): + if issubclass(classes[bc], classes[cc]): + break + else: + raise Exception("Excplicit override class '%s' is not a super class of '%s'" + % (explicit_base_classes, class_name)) + if not all(hasattr(classes[cls], method.__name__) for cls in check_classes): + for cls in check_classes: + if not hasattr(classes[cls], method.__name__): + raise Exception("Function override '%s' not found in superclass: '%s'\n%s" + % (method.__name__, cls, "File: %s:%s" % (stack[1][1], stack[1][2]))) + + if not any(hasattr(classes[cls], method.__name__) for cls in check_classes): + raise Exception("Function override '%s' not found in any superclass: '%s'\n%s" + % (method.__name__, check_classes, "File: %s:%s" % (stack[1][1], stack[1][2]))) + return method