utils.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. """
  2. Utility functions
  3. """
  4. import time
  5. import operator
  6. try:
  7. import ctypes
  8. except ImportError:
  9. ctypes = None
  10. from uuid import UUID, uuid4
  11. try:
  12. from uuid import _uuid_generate_random
  13. except ImportError:
  14. _uuid_generate_random = None
  15. from inspect import getargspec
  16. from itertools import repeat
  17. from billiard.utils.functional import curry
  18. noop = lambda *args, **kwargs: None
  19. def chunks(it, n):
  20. """Split an iterator into chunks with ``n`` elements each.
  21. Examples
  22. # n == 2
  23. >>> x = chunks(iter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 2)
  24. >>> list(x)
  25. [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10]]
  26. # n == 3
  27. >>> x = chunks(iter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 3)
  28. >>> list(x)
  29. [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
  30. """
  31. acc = []
  32. for i, item in enumerate(it):
  33. if i and not i % n:
  34. yield acc
  35. acc = []
  36. acc.append(item)
  37. yield acc
  38. def gen_unique_id():
  39. """Generate a unique id, having - hopefully - a very small chance of
  40. collission.
  41. For now this is provided by :func:`uuid.uuid4`.
  42. """
  43. # Workaround for http://bugs.python.org/issue4607
  44. if ctypes and _uuid_generate_random:
  45. buffer = ctypes.create_string_buffer(16)
  46. _uuid_generate_random(buffer)
  47. return str(UUID(bytes=buffer.raw))
  48. return str(uuid4())
  49. def mitemgetter(*items):
  50. """Like :func:`operator.itemgetter` but returns ``None`` on missing items
  51. instead of raising :exc:`KeyError`."""
  52. return lambda container: map(container.get, items)
  53. def mattrgetter(*attrs):
  54. """Like :func:`operator.itemgetter` but returns ``None`` on missing
  55. attributes instead of raising :exc:`AttributeError`."""
  56. return lambda obj: dict((attr, getattr(obj, attr, None))
  57. for attr in attrs)
  58. def get_full_cls_name(cls):
  59. """With a class, get its full module and class name."""
  60. return ".".join([cls.__module__,
  61. cls.__name__])
  62. def repeatlast(it):
  63. """Iterate over all elements in the iterator, and when its exhausted
  64. yield the last value infinitely."""
  65. for item in it:
  66. yield item
  67. for item in repeat(item):
  68. yield item
  69. def retry_over_time(fun, catch, args=[], kwargs={}, errback=noop,
  70. max_retries=None, interval_start=2, interval_step=2, interval_max=30):
  71. """Retry the function over and over until max retries is exceeded.
  72. For each retry we sleep a for a while before we try again, this interval
  73. is increased for every retry until the max seconds is reached.
  74. :param fun: The function to try
  75. :param catch: Exceptions to catch, can be either tuple or a single
  76. exception class.
  77. :keyword args: Positional arguments passed on to the function.
  78. :keyword kwargs: Keyword arguments passed on to the function.
  79. :keyword errback: Callback for when an exception in ``catch`` is raised.
  80. The callback must take two arguments: ``exc`` and ``interval``, where
  81. ``exc`` is the exception instance, and ``interval`` is the time in
  82. seconds to sleep next..
  83. :keyword max_retries: Maximum number of retries before we give up.
  84. If this is not set, we will retry forever.
  85. :keyword interval_start: How long (in seconds) we start sleeping between
  86. retries.
  87. :keyword interval_step: By how much the interval is increased for each
  88. retry.
  89. :keyword interval_max: Maximum number of seconds to sleep between retries.
  90. """
  91. retries = 0
  92. interval_range = xrange(interval_start,
  93. interval_max + interval_start,
  94. interval_step)
  95. for interval in repeatlast(interval_range):
  96. try:
  97. retval = fun(*args, **kwargs)
  98. except catch, exc:
  99. if max_retries and retries > max_retries:
  100. raise
  101. errback(exc, interval)
  102. retries += 1
  103. time.sleep(interval)
  104. else:
  105. return retval
  106. def fun_takes_kwargs(fun, kwlist=[]):
  107. """With a function, and a list of keyword arguments, returns arguments
  108. in the list which the function takes.
  109. If the object has an ``argspec`` attribute that is used instead
  110. of using the :meth:`inspect.getargspec`` introspection.
  111. :param fun: The function to inspect arguments of.
  112. :param kwlist: The list of keyword arguments.
  113. Examples
  114. >>> def foo(self, x, y, logfile=None, loglevel=None):
  115. ... return x * y
  116. >>> fun_takes_kwargs(foo, ["logfile", "loglevel", "task_id"])
  117. ["logfile", "loglevel"]
  118. >>> def foo(self, x, y, **kwargs):
  119. >>> fun_takes_kwargs(foo, ["logfile", "loglevel", "task_id"])
  120. ["logfile", "loglevel", "task_id"]
  121. """
  122. argspec = getattr(fun, "argspec", getargspec(fun))
  123. args, _varargs, keywords, _defaults = argspec
  124. if keywords != None:
  125. return kwlist
  126. return filter(curry(operator.contains, args), kwlist)
  127. try:
  128. from collections import defaultdict
  129. except ImportError:
  130. # Written by Jason Kirtland, taken from Python Cookbook:
  131. # <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/523034>
  132. class defaultdict(dict):
  133. def __init__(self, default_factory=None, *args, **kwargs):
  134. dict.__init__(self, *args, **kwargs)
  135. self.default_factory = default_factory
  136. def __getitem__(self, key):
  137. try:
  138. return dict.__getitem__(self, key)
  139. except KeyError:
  140. return self.__missing__(key)
  141. def __missing__(self, key):
  142. if self.default_factory is None:
  143. raise KeyError(key)
  144. self[key] = value = self.default_factory()
  145. return value
  146. def __reduce__(self):
  147. f = self.default_factory
  148. args = f is None and tuple() or f
  149. return type(self), args, None, None, self.iteritems()
  150. def copy(self):
  151. return self.__copy__()
  152. def __copy__(self):
  153. return type(self)(self.default_factory, self)
  154. def __deepcopy__(self):
  155. import copy
  156. return type(self)(self.default_factory,
  157. copy.deepcopy(self.items()))
  158. def __repr__(self):
  159. return "defaultdict(%s, %s)" % (self.default_factory,
  160. dict.__repr__(self))
  161. import collections
  162. collections.defaultdict = defaultdict # Pickle needs this.
  163. try:
  164. all([True])
  165. all = all
  166. except NameError:
  167. def all(iterable):
  168. for item in iterable:
  169. if not item:
  170. return False
  171. return True
  172. try:
  173. any([True])
  174. any = any
  175. except NameError:
  176. def any(iterable):
  177. for item in iterable:
  178. if item:
  179. return True
  180. return False