diff --git a/util/aop.py b/util/aop.py new file mode 100644 index 0000000..65f80d4 --- /dev/null +++ b/util/aop.py @@ -0,0 +1,76 @@ +# +# Copyright (c) 2007 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" + + +$Id$ +""" + + +def getNotifier(method): + if isinstance(method, Notifier): + return method + return Notifier(method) + + +class Notifier(object): + + def __init__(self, method): + self.method = method + obj = method.im_self or method.im_class + name = method.im_func.func_name + setattr(obj, name, self) + self.beforeSubs = [] + self.afterSubs = [] + + def __get__(self, instance, cls=None): + return BoundNotifier(self, instance) + + def __call__(self, *args, **kw): + for sub, sargs, skw in self.beforeSubs: + sub(None, *sargs, **skw) + result = self.method(*args, **kw) + for sub, aargs, akw in self.afterSubs: + sub(result, *aargs, **akw) + return result + + def subscribe(self, before, after, *args, **kw): + if before is not None: + self.beforeSubs.append((before, args, kw)) + if after is not None: + self.afterSubs.append((after, args, kw)) + + +class BoundNotifier(object): + + def __init__(self, context, instance): + self.context = context + self.instance = instance + + def __call__(self, *args, **kw): + for sub, sargs, skw in self.context.beforeSubs: + sub(None, *sargs, **skw) + result = self.context.method(self.instance, *args, **kw) + for sub, aargs, akw in self.context.afterSubs: + sub(result, *aargs, **akw) + return result + + def subscribe(self, before, after, *args, **kw): + self.context.subscribe(before, after, *args, **kw) + diff --git a/util/aop.txt b/util/aop.txt new file mode 100644 index 0000000..b96d0a0 --- /dev/null +++ b/util/aop.txt @@ -0,0 +1,64 @@ +======================================= +Aspect-oriented Programming for Dummies +======================================= + +$Id$ + + >>> from cybertools.util.aop import getNotifier + + >>> class Demo(object): + ... def foo(self, num): + ... return 38 + num + + >>> demo = Demo() + >>> demo.foo(4) + 42 + + >>> wrappedFoo = getNotifier(demo.foo) + >>> demo.foo is wrappedFoo + True + +Repeated calls of ``getNotifier()`` return the same wrapped object. + + >>> getNotifier(demo.foo) is wrappedFoo + True + +Calling the wrapped method still works. + + >>> demo.foo(8) + 46 + +We can now get information about the returning of the method by +subscribing to it using some log function. + + >>> def log(result, msg): + ... print 'logging: %s, result=%s' % (msg, str(result)) + + >>> demo.foo.subscribe(log, None, 'before foo') + + >>> demo.foo(4) + logging: before foo, result=None + 42 + + >>> demo.foo.subscribe(log, log, "that's foo") + >>> demo.foo(4) + logging: before foo, result=None + logging: that's foo, result=None + logging: that's foo, result=42 + 42 + + +Wrapping methods on class level +=============================== + + >>> demo = Demo() + >>> demo.foo(4) + 42 + + >>> getNotifier(Demo.foo) + <...Notifier object ...> + >>> Demo.foo.subscribe(None, log, 'after foo') + >>> demo.foo(4) + logging: after foo, result=42 + 42 + diff --git a/util/tests.py b/util/tests.py index 8a42664..cf12bc1 100755 --- a/util/tests.py +++ b/util/tests.py @@ -19,6 +19,7 @@ def test_suite(): #unittest.makeSuite(Test), # we don't need this #doctest.DocTestSuite(cybertools.util.property, optionflags=flags), doctest.DocFileSuite('adapter.txt', optionflags=flags), + doctest.DocFileSuite('aop.txt', optionflags=flags), doctest.DocFileSuite('defer.txt', optionflags=flags), doctest.DocFileSuite('format.txt', optionflags=flags), doctest.DocFileSuite('property.txt', optionflags=flags),