rope.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. """:mod:`wirerope.rope` --- Wire access dispatcher for descriptor type.
  2. =======================================================================
  3. """
  4. import six
  5. from .callable import Callable
  6. from .wire import descriptor_bind
  7. from ._compat import functools
  8. __all__ = 'WireRope', 'RopeCore'
  9. class RopeCore(object):
  10. """The base rope object.
  11. To change rope behavior, create a subclass or compatible class
  12. and pass it to `core_class` argument of :class wirerope.rope.WireRope`.
  13. """
  14. def __init__(self, callable, rope):
  15. super(RopeCore, self).__init__()
  16. self.callable = callable
  17. self.rope = rope
  18. @property
  19. def wire_class(self):
  20. return self.rope.wire_class
  21. class MethodRopeMixin(object):
  22. def __init__(self, *args, **kwargs):
  23. super(MethodRopeMixin, self).__init__(*args, **kwargs)
  24. assert not self.callable.is_barefunction
  25. def __set_name__(self, owner, name):
  26. # Use a non-identifier character as separator to prevent name clash.
  27. self.wire_name = '|'.join(['__wire', owner.__name__, name])
  28. def __get__(self, obj, type=None):
  29. cw = self.callable
  30. co = cw.wrapped_object
  31. owner, _ = descriptor_bind(co, obj, type)
  32. if owner is None: # invalid binding but still wire it
  33. owner = obj if obj is not None else type
  34. if hasattr(self, 'wire_name'):
  35. wire_name = self.wire_name
  36. # Lookup in `__dict__` instead of using `getattr`, because
  37. # `getattr` falls back to class attributes.
  38. wire = owner.__dict__.get(wire_name)
  39. else:
  40. wire_name_parts = ['__wire_', cw.wrapped_callable.__name__]
  41. if owner is type:
  42. wire_name_parts.extend(('_', type.__name__))
  43. wire_name = ''.join(wire_name_parts)
  44. wire = getattr(owner, wire_name, None)
  45. if wire is None:
  46. wire = self.wire_class(self, owner, (obj, type))
  47. setattr(owner, wire_name, wire)
  48. assert callable(wire.__func__)
  49. return wire
  50. class PropertyRopeMixin(object):
  51. def __init__(self, *args, **kwargs):
  52. super(PropertyRopeMixin, self).__init__(*args, **kwargs)
  53. assert not self.callable.is_barefunction
  54. def __set_name__(self, owner, name):
  55. # Use a non-identifier character as separator to prevent name clash.
  56. self.wire_name = '|'.join(['__wire', owner.__name__, name])
  57. def __get__(self, obj, type=None):
  58. cw = self.callable
  59. co = cw.wrapped_object
  60. owner, _ = descriptor_bind(co, obj, type)
  61. if owner is None: # invalid binding but still wire it
  62. owner = obj if obj is not None else type
  63. if hasattr(self, 'wire_name'):
  64. wire_name = self.wire_name
  65. # Lookup in `__dict__` instead of using `getattr`, because
  66. # `getattr` falls back to class attributes.
  67. wire = owner.__dict__.get(wire_name)
  68. else:
  69. wire_name_parts = ['__wire_', cw.wrapped_callable.__name__]
  70. if owner is type:
  71. wire_name_parts.extend(('_', type.__name__))
  72. wire_name = ''.join(wire_name_parts)
  73. wire = getattr(owner, wire_name, None)
  74. if wire is None:
  75. wire = self.wire_class(self, owner, (obj, type))
  76. setattr(owner, wire_name, wire)
  77. return wire._on_property() # requires property path
  78. class FunctionRopeMixin(object):
  79. def __init__(self, *args, **kwargs):
  80. super(FunctionRopeMixin, self).__init__(*args, **kwargs)
  81. assert self.callable.is_barefunction or self.callable.is_boundmethod
  82. self._wire = self.wire_class(self, None, None)
  83. def __getattr__(self, name):
  84. try:
  85. return self.__getattribute__(name)
  86. except AttributeError:
  87. pass
  88. return getattr(self._wire, name)
  89. class CallableRopeMixin(object):
  90. def __init__(self, *args, **kwargs):
  91. super(CallableRopeMixin, self).__init__(*args, **kwargs)
  92. self.__call__ = functools.wraps(self.callable.wrapped_object)(self)
  93. def __call__(self, *args, **kwargs):
  94. return self._wire(*args, **kwargs)
  95. class WireRope(object):
  96. """The end-user wrapper for callables.
  97. Any callable can be wrapped by this class regardless of its concept -
  98. free function, method, property or even more weird one.
  99. The calling type is decided by each call and redirected to proper
  100. RopeMixin.
  101. The rope will detect method or property owner by needs. It also will return
  102. or call their associated `wirerope.wire.Wire` object - which are the user
  103. defined behavior.
  104. """
  105. def __init__(
  106. self, wire_class, core_class=RopeCore,
  107. wraps=False, rope_args=None):
  108. self.wire_class = wire_class
  109. self.method_rope = type(
  110. '_MethodRope', (MethodRopeMixin, core_class), {})
  111. self.property_rope = type(
  112. '_PropertyRope', (PropertyRopeMixin, core_class), {})
  113. self.function_rope = type(
  114. '_FunctionRope', (FunctionRopeMixin, core_class), {})
  115. self.callable_function_rope = type(
  116. '_CallableFunctionRope',
  117. (CallableRopeMixin, FunctionRopeMixin, core_class), {})
  118. for rope in (self, self.method_rope, self.property_rope,
  119. self.function_rope, self.callable_function_rope):
  120. rope._wrapped = wraps
  121. rope._args = rope_args
  122. def __call__(self, function):
  123. """Wrap a function/method definition.
  124. :return: Rope object. The return type is up to given callable is
  125. function, method or property.
  126. """
  127. cw = Callable(function)
  128. if cw.is_barefunction or cw.is_boundmethod:
  129. rope_class = self.callable_function_rope
  130. wire_class_call = self.wire_class.__call__
  131. if six.PY3:
  132. if wire_class_call.__qualname__ == 'type.__call__':
  133. rope_class = self.function_rope
  134. else:
  135. # method-wrapper test for CPython2.7
  136. # im_class == type test for PyPy2.7
  137. if type(wire_class_call).__name__ == 'method-wrapper' or \
  138. wire_class_call.im_class == type:
  139. rope_class = self.function_rope
  140. elif cw.is_property:
  141. rope_class = self.property_rope
  142. else:
  143. rope_class = self.method_rope
  144. rope = rope_class(cw, rope=self)
  145. if rope._wrapped:
  146. rope = functools.wraps(cw.wrapped_callable)(rope)
  147. return rope