decorators.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. """Decorators for running functions with context/sockets.
  2. .. versionadded:: 15.3
  3. Like using Contexts and Sockets as context managers, but with decorator syntax.
  4. Context and sockets are closed at the end of the function.
  5. For example::
  6. from zmq.decorators import context, socket
  7. @context()
  8. @socket(zmq.PUSH)
  9. def work(ctx, push):
  10. ...
  11. """
  12. # Copyright (c) PyZMQ Developers.
  13. # Distributed under the terms of the Modified BSD License.
  14. __all__ = (
  15. 'context',
  16. 'socket',
  17. )
  18. from functools import wraps
  19. import zmq
  20. from zmq.utils.strtypes import basestring
  21. class _Decorator(object):
  22. '''The mini decorator factory'''
  23. def __init__(self, target=None):
  24. self._target = target
  25. def __call__(self, *dec_args, **dec_kwargs):
  26. '''
  27. The main logic of decorator
  28. Here is how those arguments works::
  29. @out_decorator(*dec_args, *dec_kwargs)
  30. def func(*wrap_args, **wrap_kwargs):
  31. ...
  32. And in the ``wrapper``, we simply create ``self.target`` instance via
  33. ``with``::
  34. target = self.get_target(*args, **kwargs)
  35. with target(*dec_args, **dec_kwargs) as obj:
  36. ...
  37. '''
  38. kw_name, dec_args, dec_kwargs = self.process_decorator_args(*dec_args, **dec_kwargs)
  39. def decorator(func):
  40. @wraps(func)
  41. def wrapper(*args, **kwargs):
  42. target = self.get_target(*args, **kwargs)
  43. with target(*dec_args, **dec_kwargs) as obj:
  44. # insert our object into args
  45. if kw_name and kw_name not in kwargs:
  46. kwargs[kw_name] = obj
  47. elif kw_name and kw_name in kwargs:
  48. raise TypeError(
  49. "{0}() got multiple values for"
  50. " argument '{1}'".format(
  51. func.__name__, kw_name))
  52. else:
  53. args = args + (obj,)
  54. return func(*args, **kwargs)
  55. return wrapper
  56. return decorator
  57. def get_target(self, *args, **kwargs):
  58. """Return the target function
  59. Allows modifying args/kwargs to be passed.
  60. """
  61. return self._target
  62. def process_decorator_args(self, *args, **kwargs):
  63. """Process args passed to the decorator.
  64. args not consumed by the decorator will be passed to the target factory
  65. (Context/Socket constructor).
  66. """
  67. kw_name = None
  68. if isinstance(kwargs.get('name'), basestring):
  69. kw_name = kwargs.pop('name')
  70. elif len(args) >= 1 and isinstance(args[0], basestring):
  71. kw_name = args[0]
  72. args = args[1:]
  73. return kw_name, args, kwargs
  74. class _ContextDecorator(_Decorator):
  75. """Decorator subclass for Contexts"""
  76. def __init__(self):
  77. super(_ContextDecorator, self).__init__(zmq.Context)
  78. class _SocketDecorator(_Decorator):
  79. """Decorator subclass for sockets
  80. Gets the context from other args.
  81. """
  82. def process_decorator_args(self, *args, **kwargs):
  83. """Also grab context_name out of kwargs"""
  84. kw_name, args, kwargs = super(_SocketDecorator, self).process_decorator_args(*args, **kwargs)
  85. self.context_name = kwargs.pop('context_name', 'context')
  86. return kw_name, args, kwargs
  87. def get_target(self, *args, **kwargs):
  88. """Get context, based on call-time args"""
  89. context = self._get_context(*args, **kwargs)
  90. return context.socket
  91. def _get_context(self, *args, **kwargs):
  92. '''
  93. Find the ``zmq.Context`` from ``args`` and ``kwargs`` at call time.
  94. First, if there is an keyword argument named ``context`` and it is a
  95. ``zmq.Context`` instance , we will take it.
  96. Second, we check all the ``args``, take the first ``zmq.Context``
  97. instance.
  98. Finally, we will provide default Context -- ``zmq.Context.instance``
  99. :return: a ``zmq.Context`` instance
  100. '''
  101. if self.context_name in kwargs:
  102. ctx = kwargs[self.context_name]
  103. if isinstance(ctx, zmq.Context):
  104. return ctx
  105. for arg in args:
  106. if isinstance(arg, zmq.Context):
  107. return arg
  108. # not specified by any decorator
  109. return zmq.Context.instance()
  110. def context(*args, **kwargs):
  111. '''Decorator for adding a Context to a function.
  112. Usage::
  113. @context()
  114. def foo(ctx):
  115. ...
  116. .. versionadded:: 15.3
  117. :param str name: the keyword argument passed to decorated function
  118. '''
  119. return _ContextDecorator()(*args, **kwargs)
  120. def socket(*args, **kwargs):
  121. '''Decorator for adding a socket to a function.
  122. Usage::
  123. @socket(zmq.PUSH)
  124. def foo(push):
  125. ...
  126. .. versionadded:: 15.3
  127. :param str name: the keyword argument passed to decorated function
  128. :param str context_name: the keyword only argument to identify context
  129. object
  130. '''
  131. return _SocketDecorator()(*args, **kwargs)