decorators.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. from __future__ import annotations
  13. # Copyright (c) PyZMQ Developers.
  14. # Distributed under the terms of the Modified BSD License.
  15. __all__ = (
  16. 'context',
  17. 'socket',
  18. )
  19. from functools import wraps
  20. import zmq
  21. class _Decorator:
  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(
  39. *dec_args, **dec_kwargs
  40. )
  41. def decorator(func):
  42. @wraps(func)
  43. def wrapper(*args, **kwargs):
  44. target = self.get_target(*args, **kwargs)
  45. with target(*dec_args, **dec_kwargs) as obj:
  46. # insert our object into args
  47. if kw_name and kw_name not in kwargs:
  48. kwargs[kw_name] = obj
  49. elif kw_name and kw_name in kwargs:
  50. raise TypeError(
  51. f"{func.__name__}() got multiple values for"
  52. f" argument '{kw_name}'"
  53. )
  54. else:
  55. args = args + (obj,)
  56. return func(*args, **kwargs)
  57. return wrapper
  58. return decorator
  59. def get_target(self, *args, **kwargs):
  60. """Return the target function
  61. Allows modifying args/kwargs to be passed.
  62. """
  63. return self._target
  64. def process_decorator_args(self, *args, **kwargs):
  65. """Process args passed to the decorator.
  66. args not consumed by the decorator will be passed to the target factory
  67. (Context/Socket constructor).
  68. """
  69. kw_name = None
  70. if isinstance(kwargs.get('name'), str):
  71. kw_name = kwargs.pop('name')
  72. elif len(args) >= 1 and isinstance(args[0], str):
  73. kw_name = args[0]
  74. args = args[1:]
  75. return kw_name, args, kwargs
  76. class _ContextDecorator(_Decorator):
  77. """Decorator subclass for Contexts"""
  78. def __init__(self):
  79. super().__init__(zmq.Context)
  80. class _SocketDecorator(_Decorator):
  81. """Decorator subclass for sockets
  82. Gets the context from other args.
  83. """
  84. def process_decorator_args(self, *args, **kwargs):
  85. """Also grab context_name out of kwargs"""
  86. kw_name, args, kwargs = super().process_decorator_args(*args, **kwargs)
  87. self.context_name = kwargs.pop('context_name', 'context')
  88. return kw_name, args, kwargs
  89. def get_target(self, *args, **kwargs):
  90. """Get context, based on call-time args"""
  91. context = self._get_context(*args, **kwargs)
  92. return context.socket
  93. def _get_context(self, *args, **kwargs):
  94. """
  95. Find the ``zmq.Context`` from ``args`` and ``kwargs`` at call time.
  96. First, if there is an keyword argument named ``context`` and it is a
  97. ``zmq.Context`` instance , we will take it.
  98. Second, we check all the ``args``, take the first ``zmq.Context``
  99. instance.
  100. Finally, we will provide default Context -- ``zmq.Context.instance``
  101. :return: a ``zmq.Context`` instance
  102. """
  103. if self.context_name in kwargs:
  104. ctx = kwargs[self.context_name]
  105. if isinstance(ctx, zmq.Context):
  106. return ctx
  107. for arg in args:
  108. if isinstance(arg, zmq.Context):
  109. return arg
  110. # not specified by any decorator
  111. return zmq.Context.instance()
  112. def context(*args, **kwargs):
  113. """Decorator for adding a Context to a function.
  114. Usage::
  115. @context()
  116. def foo(ctx):
  117. ...
  118. .. versionadded:: 15.3
  119. :param str name: the keyword argument passed to decorated function
  120. """
  121. return _ContextDecorator()(*args, **kwargs)
  122. def socket(*args, **kwargs):
  123. """Decorator for adding a socket to a function.
  124. Usage::
  125. @socket(zmq.PUSH)
  126. def foo(push):
  127. ...
  128. .. versionadded:: 15.3
  129. :param str name: the keyword argument passed to decorated function
  130. :param str context_name: the keyword only argument to identify context
  131. object
  132. """
  133. return _SocketDecorator()(*args, **kwargs)