diff --git a/CHANGES.txt b/CHANGES.txt index 90e400f..3ea584c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,6 +3,10 @@ - Drop support of Python 2.4 / 2.5 / Jython. +- Add ``lift`` and ``onlyliftedfrom`` class decorators to allow for inheritance + of venusian decorators attached to superclass methods. See the API + documentation for more information. + 1.0a8 (2013-04-15) ------------------ diff --git a/docs/api.rst b/docs/api.rst index 284d2b3..c8a74e0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -9,5 +9,8 @@ API Documentation for Venusian .. autoclass:: AttachInfo - .. autofunction:: attach(wrapped, callback, category=None) + .. autofunction:: attach(wrapped, callback, category=None, name=None) + .. autoclass:: lift + + .. auutoclass:: onlyliftedfrom diff --git a/venusian/__init__.py b/venusian/__init__.py index 08c000c..3c3e6dd 100644 --- a/venusian/__init__.py +++ b/venusian/__init__.py @@ -270,7 +270,15 @@ def attached_to(self, obj): def attach(wrapped, callback, category=None, depth=1, name=None): """ Attach a callback to the wrapped object. It will be found later during a scan. This function returns an instance of the - :class:`venusian.AttachInfo` class.""" + :class:`venusian.AttachInfo` class. + + ``category`` should be ``None`` or a string representing a decorator + category name. + + ``name`` should be ``None`` or a string representing a subcategory within + the category. This will be used by the ``lift`` class decorator to + determine if decorations of a method should be inherited or overridden. + """ frame = sys._getframe(depth+1) scope, module, f_locals, f_globals, codeinfo = getFrameInfo(frame) @@ -373,7 +381,69 @@ def seen(p, m={}): else: yield importer, name, ispkg + class lift(object): + """ + A class decorator which 'lifts' superclass venusian configuration + decorations into subclasses. For example:: + + from venusian import lift + from somepackage import venusian_decorator + + class Super(object): + @venusian_decorator() + def boo(self): pass + + @venusian_decorator() + def hiss(self): pass + + @venusian_decorator() + def jump(self): pass + + @lift() + class Sub(Super): + def boo(self): pass + + def hiss(self): pass + + @venusian_decorator() + def smack(self): pass + + The above configuration will cause the callbacks of seven venusian + decorators. The ones attached to Super.boo, Super.hiss, and Super.jump + *plus* ones attached to Sub.boo, Sub.hiss, Sub.hump and Sub.smack. + + If a subclass overrides a decorator on a method, its superclass decorators + will be ignored for the subclass. That means that in this configuration: + + from venusian import lift + from somepackage import venusian_decorator + + class Super(object): + @venusian_decorator() + def boo(self): pass + + @venusian_decorator() + def hiss(self): pass + + @lift() + class Sub(Super): + + def boo(self): pass + + @venusian_decorator() + def hiss(self): pass + + Only four, not five decorator callbacks will be run: the ones attached to + Super.boo and Super.hiss, the inherited one of Sub.boo and the + non-inherited one of Sub.hiss. The inherited decorator on Super.hiss will + be ignored for the subclass. + + The ``lift`` decorator takes a single argument named 'categories'. If + supplied, it should be a tuple of category names. Only decorators + in this category will be lifted if it is suppled. + + """ def __init__(self, categories=None): self.categories = categories @@ -417,6 +487,36 @@ def __call__(self, wrapped): return wrapped class onlyliftedfrom(object): + """ + A class decorator which marks a class as 'only lifted from'. Decorations + made on methods of the class won't have their callbacks called directly, + but classes which inherit from only-lifted-from classes which also use the + ``lift`` class decorator will use the superclass decoration callbacks. + + For example:: + + from venusian import lift, onlyliftedfrom + from somepackage import venusian_decorator + + @onlyliftedfrom() + class Super(object): + @venusian_decorator() + def boo(self): pass + + @venusian_decorator() + def hiss(self): pass + + @lift() + class Sub(Super): + + def boo(self): pass + + def hiss(self): pass + + Only two decorator callbacks will be run: the ones attached to Sub.boo and + Sub.hiss. The inherited decorators on Super.boo and Super.hiss will be + not be registered. + """ def __call__(self, wrapped): if not isclass(wrapped): raise RuntimeError(