functional.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.utils.functional
  4. ~~~~~~~~~~~~~~~~~~~~~~~
  5. Utilities for functions.
  6. """
  7. from __future__ import absolute_import
  8. import operator
  9. import threading
  10. from functools import partial, wraps
  11. from itertools import islice
  12. from kombu.utils import cached_property
  13. from kombu.utils.functional import promise, maybe_promise
  14. from kombu.utils.compat import OrderedDict
  15. from celery.five import UserDict, UserList, items, keys, string_t
  16. KEYWORD_MARK = object()
  17. is_not_None = partial(operator.is_not, None)
  18. class LRUCache(UserDict):
  19. """LRU Cache implementation using a doubly linked list to track access.
  20. :keyword limit: The maximum number of keys to keep in the cache.
  21. When a new key is inserted and the limit has been exceeded,
  22. the *Least Recently Used* key will be discarded from the
  23. cache.
  24. """
  25. def __init__(self, limit=None):
  26. self.limit = limit
  27. self.mutex = threading.RLock()
  28. self.data = OrderedDict()
  29. def __getitem__(self, key):
  30. with self.mutex:
  31. value = self[key] = self.data.pop(key)
  32. return value
  33. def keys(self):
  34. # userdict.keys in py3k calls __getitem__
  35. return keys(self.data)
  36. def values(self):
  37. return list(self._iterate_values())
  38. def items(self):
  39. return list(self._iterate_items())
  40. def __setitem__(self, key, value):
  41. # remove least recently used key.
  42. with self.mutex:
  43. if self.limit and len(self.data) >= self.limit:
  44. self.data.pop(next(iter(self.data)))
  45. self.data[key] = value
  46. def __iter__(self):
  47. return iter(self.data)
  48. def _iterate_items(self):
  49. for k in self:
  50. try:
  51. yield (k, self.data[k])
  52. except KeyError: # pragma: no cover
  53. pass
  54. iteritems = _iterate_items
  55. def _iterate_values(self):
  56. for k in self:
  57. try:
  58. yield self.data[k]
  59. except KeyError: # pragma: no cover
  60. pass
  61. itervalues = _iterate_values
  62. def incr(self, key, delta=1):
  63. with self.mutex:
  64. # this acts as memcached does- store as a string, but return a
  65. # integer as long as it exists and we can cast it
  66. newval = int(self.data.pop(key)) + delta
  67. self[key] = str(newval)
  68. return newval
  69. def __getstate__(self):
  70. d = dict(vars(self))
  71. d.pop('mutex')
  72. return d
  73. def __setstate__(self, state):
  74. self.__dict__ = state
  75. self.mutex = threading.RLock()
  76. def is_list(l):
  77. """Returns true if object is list-like, but not a dict or string."""
  78. return hasattr(l, '__iter__') and not isinstance(l, (dict, string_t))
  79. def maybe_list(l):
  80. """Returns list of one element if ``l`` is a scalar."""
  81. return l if l is None or is_list(l) else [l]
  82. def memoize(maxsize=None, Cache=LRUCache):
  83. def _memoize(fun):
  84. mutex = threading.Lock()
  85. cache = Cache(limit=maxsize)
  86. @wraps(fun)
  87. def _M(*args, **kwargs):
  88. key = args + (KEYWORD_MARK, ) + tuple(sorted(kwargs.items()))
  89. try:
  90. with mutex:
  91. value = cache[key]
  92. except KeyError:
  93. value = fun(*args, **kwargs)
  94. _M.misses += 1
  95. with mutex:
  96. cache[key] = value
  97. else:
  98. _M.hits += 1
  99. return value
  100. def clear():
  101. """Clear the cache and reset cache statistics."""
  102. cache.clear()
  103. _M.hits = _M.misses = 0
  104. _M.hits = _M.misses = 0
  105. _M.clear = clear
  106. _M.original_func = fun
  107. return _M
  108. return _memoize
  109. class mpromise(promise):
  110. """Memoized promise.
  111. The function is only evaluated once, every subsequent access
  112. will return the same value.
  113. .. attribute:: evaluated
  114. Set to to :const:`True` after the promise has been evaluated.
  115. """
  116. evaluated = False
  117. _value = None
  118. def evaluate(self):
  119. if not self.evaluated:
  120. self._value = super(mpromise, self).evaluate()
  121. self.evaluated = True
  122. return self._value
  123. def noop(*args, **kwargs):
  124. """No operation.
  125. Takes any arguments/keyword arguments and does nothing.
  126. """
  127. pass
  128. def first(predicate, iterable):
  129. """Returns the first element in `iterable` that `predicate` returns a
  130. :const:`True` value for."""
  131. predicate = predicate or is_not_None
  132. for item in iterable:
  133. if predicate(item):
  134. return item
  135. def firstmethod(method):
  136. """Returns a function that with a list of instances,
  137. finds the first instance that returns a value for the given method.
  138. The list can also contain promises (:class:`promise`.)
  139. """
  140. def _matcher(it, *args, **kwargs):
  141. for obj in it:
  142. try:
  143. answer = getattr(maybe_promise(obj), method)(*args, **kwargs)
  144. except AttributeError:
  145. pass
  146. else:
  147. if answer is not None:
  148. return answer
  149. return _matcher
  150. def chunks(it, n):
  151. """Split an iterator into chunks with `n` elements each.
  152. Examples
  153. # n == 2
  154. >>> x = chunks(iter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 2)
  155. >>> list(x)
  156. [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10]]
  157. # n == 3
  158. >>> x = chunks(iter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 3)
  159. >>> list(x)
  160. [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
  161. """
  162. # XXX This function is not used anymore, at least not by Celery itself.
  163. for first in it:
  164. yield [first] + list(islice(it, n - 1))
  165. def padlist(container, size, default=None):
  166. """Pad list with default elements.
  167. Examples:
  168. >>> first, last, city = padlist(['George', 'Costanza', 'NYC'], 3)
  169. ('George', 'Costanza', 'NYC')
  170. >>> first, last, city = padlist(['George', 'Costanza'], 3)
  171. ('George', 'Costanza', None)
  172. >>> first, last, city, planet = padlist(['George', 'Costanza',
  173. 'NYC'], 4, default='Earth')
  174. ('George', 'Costanza', 'NYC', 'Earth')
  175. """
  176. return list(container)[:size] + [default] * (size - len(container))
  177. def mattrgetter(*attrs):
  178. """Like :func:`operator.itemgetter` but returns :const:`None` on missing
  179. attributes instead of raising :exc:`AttributeError`."""
  180. return lambda obj: dict((attr, getattr(obj, attr, None))
  181. for attr in attrs)
  182. def uniq(it):
  183. """Returns all unique elements in ``it``, preserving order."""
  184. seen = set()
  185. return (seen.add(obj) or obj for obj in it if obj not in seen)
  186. def regen(it):
  187. """Regen takes any iterable, and if the object is an
  188. generator it will cache the evaluated list on first access,
  189. so that the generator can be "consumed" multiple times."""
  190. if isinstance(it, (list, tuple)):
  191. return it
  192. return _regen(it)
  193. class _regen(UserList, list):
  194. # must be subclass of list so that json can encode.
  195. def __init__(self, it):
  196. self.__it = it
  197. @cached_property
  198. def data(self):
  199. return list(self.__it)
  200. def dictfilter(d, **filterkeys):
  201. d = dict(d, **filterkeys) if filterkeys else d
  202. return dict((k, v) for k, v in items(d) if v is not None)