hook.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. I define support for hookable instance methods.
  5. These are methods which you can register pre-call and post-call external
  6. functions to augment their functionality. People familiar with more esoteric
  7. languages may think of these as \"method combinations\".
  8. This could be used to add optional preconditions, user-extensible callbacks
  9. (a-la emacs) or a thread-safety mechanism.
  10. The four exported calls are:
  11. - L{addPre}
  12. - L{addPost}
  13. - L{removePre}
  14. - L{removePost}
  15. All have the signature (class, methodName, callable), and the callable they
  16. take must always have the signature (instance, *args, **kw) unless the
  17. particular signature of the method they hook is known.
  18. Hooks should typically not throw exceptions, however, no effort will be made by
  19. this module to prevent them from doing so. Pre-hooks will always be called,
  20. but post-hooks will only be called if the pre-hooks do not raise any exceptions
  21. (they will still be called if the main method raises an exception). The return
  22. values and exception status of the main method will be propagated (assuming
  23. none of the hooks raise an exception). Hooks will be executed in the order in
  24. which they are added.
  25. """
  26. ### Public Interface
  27. class HookError(Exception):
  28. "An error which will fire when an invariant is violated."
  29. def addPre(klass, name, func):
  30. """hook.addPre(klass, name, func) -> None
  31. Add a function to be called before the method klass.name is invoked.
  32. """
  33. _addHook(klass, name, PRE, func)
  34. def addPost(klass, name, func):
  35. """hook.addPost(klass, name, func) -> None
  36. Add a function to be called after the method klass.name is invoked.
  37. """
  38. _addHook(klass, name, POST, func)
  39. def removePre(klass, name, func):
  40. """hook.removePre(klass, name, func) -> None
  41. Remove a function (previously registered with addPre) so that it
  42. is no longer executed before klass.name.
  43. """
  44. _removeHook(klass, name, PRE, func)
  45. def removePost(klass, name, func):
  46. """hook.removePre(klass, name, func) -> None
  47. Remove a function (previously registered with addPost) so that it
  48. is no longer executed after klass.name.
  49. """
  50. _removeHook(klass, name, POST, func)
  51. ### "Helper" functions.
  52. hooked_func = """
  53. import %(module)s
  54. def %(name)s(*args, **kw):
  55. klazz = %(module)s.%(klass)s
  56. for preMethod in klazz.%(preName)s:
  57. preMethod(*args, **kw)
  58. try:
  59. return klazz.%(originalName)s(*args, **kw)
  60. finally:
  61. for postMethod in klazz.%(postName)s:
  62. postMethod(*args, **kw)
  63. """
  64. _PRE = '__hook_pre_%s_%s_%s__'
  65. _POST = '__hook_post_%s_%s_%s__'
  66. _ORIG = '__hook_orig_%s_%s_%s__'
  67. def _XXX(k,n,s):
  68. """
  69. String manipulation garbage.
  70. """
  71. x = s % (k.__module__.replace('.', '_'), k.__name__, n)
  72. return x
  73. def PRE(k,n):
  74. "(private) munging to turn a method name into a pre-hook-method-name"
  75. return _XXX(k,n,_PRE)
  76. def POST(k,n):
  77. "(private) munging to turn a method name into a post-hook-method-name"
  78. return _XXX(k,n,_POST)
  79. def ORIG(k,n):
  80. "(private) munging to turn a method name into an `original' identifier"
  81. return _XXX(k,n,_ORIG)
  82. def _addHook(klass, name, phase, func):
  83. "(private) adds a hook to a method on a class"
  84. _enhook(klass, name)
  85. if not hasattr(klass, phase(klass, name)):
  86. setattr(klass, phase(klass, name), [])
  87. phaselist = getattr(klass, phase(klass, name))
  88. phaselist.append(func)
  89. def _removeHook(klass, name, phase, func):
  90. "(private) removes a hook from a method on a class"
  91. phaselistname = phase(klass, name)
  92. if not hasattr(klass, ORIG(klass,name)):
  93. raise HookError("no hooks present!")
  94. phaselist = getattr(klass, phaselistname)
  95. try: phaselist.remove(func)
  96. except ValueError:
  97. raise HookError("hook %s not found in removal list for %s"%
  98. (name,klass))
  99. if not getattr(klass, PRE(klass,name)) and not getattr(klass, POST(klass, name)):
  100. _dehook(klass, name)
  101. def _enhook(klass, name):
  102. "(private) causes a certain method name to be hooked on a class"
  103. if hasattr(klass, ORIG(klass, name)):
  104. return
  105. def newfunc(*args, **kw):
  106. for preMethod in getattr(klass, PRE(klass, name)):
  107. preMethod(*args, **kw)
  108. try:
  109. return getattr(klass, ORIG(klass, name))(*args, **kw)
  110. finally:
  111. for postMethod in getattr(klass, POST(klass, name)):
  112. postMethod(*args, **kw)
  113. try:
  114. newfunc.func_name = name
  115. except TypeError:
  116. # Older python's don't let you do this
  117. pass
  118. oldfunc = getattr(klass, name).im_func
  119. setattr(klass, ORIG(klass, name), oldfunc)
  120. setattr(klass, PRE(klass, name), [])
  121. setattr(klass, POST(klass, name), [])
  122. setattr(klass, name, newfunc)
  123. def _dehook(klass, name):
  124. "(private) causes a certain method name no longer to be hooked on a class"
  125. if not hasattr(klass, ORIG(klass, name)):
  126. raise HookError("Cannot unhook!")
  127. setattr(klass, name, getattr(klass, ORIG(klass,name)))
  128. delattr(klass, PRE(klass,name))
  129. delattr(klass, POST(klass,name))
  130. delattr(klass, ORIG(klass,name))