123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- I define support for hookable instance methods.
- These are methods which you can register pre-call and post-call external
- functions to augment their functionality. People familiar with more esoteric
- languages may think of these as \"method combinations\".
- This could be used to add optional preconditions, user-extensible callbacks
- (a-la emacs) or a thread-safety mechanism.
- The four exported calls are:
- - L{addPre}
- - L{addPost}
- - L{removePre}
- - L{removePost}
- All have the signature (class, methodName, callable), and the callable they
- take must always have the signature (instance, *args, **kw) unless the
- particular signature of the method they hook is known.
- Hooks should typically not throw exceptions, however, no effort will be made by
- this module to prevent them from doing so. Pre-hooks will always be called,
- but post-hooks will only be called if the pre-hooks do not raise any exceptions
- (they will still be called if the main method raises an exception). The return
- values and exception status of the main method will be propagated (assuming
- none of the hooks raise an exception). Hooks will be executed in the order in
- which they are added.
- """
- ### Public Interface
- class HookError(Exception):
- "An error which will fire when an invariant is violated."
- def addPre(klass, name, func):
- """hook.addPre(klass, name, func) -> None
- Add a function to be called before the method klass.name is invoked.
- """
- _addHook(klass, name, PRE, func)
- def addPost(klass, name, func):
- """hook.addPost(klass, name, func) -> None
- Add a function to be called after the method klass.name is invoked.
- """
- _addHook(klass, name, POST, func)
- def removePre(klass, name, func):
- """hook.removePre(klass, name, func) -> None
- Remove a function (previously registered with addPre) so that it
- is no longer executed before klass.name.
- """
- _removeHook(klass, name, PRE, func)
- def removePost(klass, name, func):
- """hook.removePre(klass, name, func) -> None
- Remove a function (previously registered with addPost) so that it
- is no longer executed after klass.name.
- """
- _removeHook(klass, name, POST, func)
- ### "Helper" functions.
- hooked_func = """
- import %(module)s
- def %(name)s(*args, **kw):
- klazz = %(module)s.%(klass)s
- for preMethod in klazz.%(preName)s:
- preMethod(*args, **kw)
- try:
- return klazz.%(originalName)s(*args, **kw)
- finally:
- for postMethod in klazz.%(postName)s:
- postMethod(*args, **kw)
- """
- _PRE = '__hook_pre_%s_%s_%s__'
- _POST = '__hook_post_%s_%s_%s__'
- _ORIG = '__hook_orig_%s_%s_%s__'
- def _XXX(k,n,s):
- """
- String manipulation garbage.
- """
- x = s % (k.__module__.replace('.', '_'), k.__name__, n)
- return x
- def PRE(k,n):
- "(private) munging to turn a method name into a pre-hook-method-name"
- return _XXX(k,n,_PRE)
- def POST(k,n):
- "(private) munging to turn a method name into a post-hook-method-name"
- return _XXX(k,n,_POST)
- def ORIG(k,n):
- "(private) munging to turn a method name into an `original' identifier"
- return _XXX(k,n,_ORIG)
- def _addHook(klass, name, phase, func):
- "(private) adds a hook to a method on a class"
- _enhook(klass, name)
- if not hasattr(klass, phase(klass, name)):
- setattr(klass, phase(klass, name), [])
- phaselist = getattr(klass, phase(klass, name))
- phaselist.append(func)
- def _removeHook(klass, name, phase, func):
- "(private) removes a hook from a method on a class"
- phaselistname = phase(klass, name)
- if not hasattr(klass, ORIG(klass,name)):
- raise HookError("no hooks present!")
- phaselist = getattr(klass, phaselistname)
- try: phaselist.remove(func)
- except ValueError:
- raise HookError("hook %s not found in removal list for %s"%
- (name,klass))
- if not getattr(klass, PRE(klass,name)) and not getattr(klass, POST(klass, name)):
- _dehook(klass, name)
- def _enhook(klass, name):
- "(private) causes a certain method name to be hooked on a class"
- if hasattr(klass, ORIG(klass, name)):
- return
- def newfunc(*args, **kw):
- for preMethod in getattr(klass, PRE(klass, name)):
- preMethod(*args, **kw)
- try:
- return getattr(klass, ORIG(klass, name))(*args, **kw)
- finally:
- for postMethod in getattr(klass, POST(klass, name)):
- postMethod(*args, **kw)
- try:
- newfunc.func_name = name
- except TypeError:
- # Older python's don't let you do this
- pass
- oldfunc = getattr(klass, name).im_func
- setattr(klass, ORIG(klass, name), oldfunc)
- setattr(klass, PRE(klass, name), [])
- setattr(klass, POST(klass, name), [])
- setattr(klass, name, newfunc)
- def _dehook(klass, name):
- "(private) causes a certain method name no longer to be hooked on a class"
- if not hasattr(klass, ORIG(klass, name)):
- raise HookError("Cannot unhook!")
- setattr(klass, name, getattr(klass, ORIG(klass,name)))
- delattr(klass, PRE(klass,name))
- delattr(klass, POST(klass,name))
- delattr(klass, ORIG(klass,name))
|