PageRenderTime 43ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/celery/utils/__init__.py

https://github.com/mumrah/celery
Python | 436 lines | 430 code | 6 blank | 0 comment | 0 complexity | f2beadb22169370c53b9ff6d64b35e42 MD5 | raw file
  1. from __future__ import absolute_import, with_statement
  2. import os
  3. import sys
  4. import operator
  5. import imp as _imp
  6. import importlib
  7. import logging
  8. import threading
  9. import traceback
  10. import warnings
  11. from contextlib import contextmanager
  12. from functools import partial, wraps
  13. from inspect import getargspec
  14. from itertools import islice
  15. from pprint import pprint
  16. from kombu.utils import cached_property, gen_unique_id # noqa
  17. from celery.utils.compat import StringIO
  18. LOG_LEVELS = dict(logging._levelNames)
  19. LOG_LEVELS["FATAL"] = logging.FATAL
  20. LOG_LEVELS[logging.FATAL] = "FATAL"
  21. PENDING_DEPRECATION_FMT = """
  22. %(description)s is scheduled for deprecation in \
  23. version %(deprecation)s and removal in version v%(removal)s. \
  24. %(alternative)s
  25. """
  26. DEPRECATION_FMT = """
  27. %(description)s is deprecated and scheduled for removal in
  28. version %(removal)s. %(alternative)s
  29. """
  30. def deprecated(description=None, deprecation=None, removal=None,
  31. alternative=None):
  32. def _inner(fun):
  33. @wraps(fun)
  34. def __inner(*args, **kwargs):
  35. ctx = {"description": description or get_full_cls_name(fun),
  36. "deprecation": deprecation, "removal": removal,
  37. "alternative": alternative}
  38. if deprecation is not None:
  39. w = PendingDeprecationWarning(PENDING_DEPRECATION_FMT % ctx)
  40. else:
  41. w = DeprecationWarning(DEPRECATION_FMT % ctx)
  42. warnings.warn(w)
  43. return fun(*args, **kwargs)
  44. return __inner
  45. return _inner
  46. def lpmerge(L, R):
  47. """Left precedent dictionary merge. Keeps values from `l`, if the value
  48. in `r` is :const:`None`."""
  49. return dict(L, **dict((k, v) for k, v in R.iteritems() if v is not None))
  50. class promise(object):
  51. """A promise.
  52. Evaluated when called or if the :meth:`evaluate` method is called.
  53. The function is evaluated on every access, so the value is not
  54. memoized (see :class:`mpromise`).
  55. Overloaded operations that will evaluate the promise:
  56. :meth:`__str__`, :meth:`__repr__`, :meth:`__cmp__`.
  57. """
  58. def __init__(self, fun, *args, **kwargs):
  59. self._fun = fun
  60. self._args = args
  61. self._kwargs = kwargs
  62. def __call__(self):
  63. return self.evaluate()
  64. def evaluate(self):
  65. return self._fun(*self._args, **self._kwargs)
  66. def __str__(self):
  67. return str(self())
  68. def __repr__(self):
  69. return repr(self())
  70. def __cmp__(self, rhs):
  71. if isinstance(rhs, self.__class__):
  72. return -cmp(rhs, self())
  73. return cmp(self(), rhs)
  74. def __eq__(self, rhs):
  75. return self() == rhs
  76. def __deepcopy__(self, memo):
  77. memo[id(self)] = self
  78. return self
  79. def __reduce__(self):
  80. return (self.__class__, (self._fun, ), {"_args": self._args,
  81. "_kwargs": self._kwargs})
  82. class mpromise(promise):
  83. """Memoized promise.
  84. The function is only evaluated once, every subsequent access
  85. will return the same value.
  86. .. attribute:: evaluated
  87. Set to to :const:`True` after the promise has been evaluated.
  88. """
  89. evaluated = False
  90. _value = None
  91. def evaluate(self):
  92. if not self.evaluated:
  93. self._value = super(mpromise, self).evaluate()
  94. self.evaluated = True
  95. return self._value
  96. def maybe_promise(value):
  97. """Evaluates if the value is a promise."""
  98. if isinstance(value, promise):
  99. return value.evaluate()
  100. return value
  101. def noop(*args, **kwargs):
  102. """No operation.
  103. Takes any arguments/keyword arguments and does nothing.
  104. """
  105. pass
  106. def kwdict(kwargs):
  107. """Make sure keyword arguments are not in unicode.
  108. This should be fixed in newer Python versions,
  109. see: http://bugs.python.org/issue4978.
  110. """
  111. return dict((key.encode("utf-8"), value)
  112. for key, value in kwargs.items())
  113. def first(predicate, iterable):
  114. """Returns the first element in `iterable` that `predicate` returns a
  115. :const:`True` value for."""
  116. for item in iterable:
  117. if predicate(item):
  118. return item
  119. def firstmethod(method):
  120. """Returns a functions that with a list of instances,
  121. finds the first instance that returns a value for the given method.
  122. The list can also contain promises (:class:`promise`.)
  123. """
  124. def _matcher(seq, *args, **kwargs):
  125. for cls in seq:
  126. try:
  127. answer = getattr(maybe_promise(cls), method)(*args, **kwargs)
  128. if answer is not None:
  129. return answer
  130. except AttributeError:
  131. pass
  132. return _matcher
  133. def chunks(it, n):
  134. """Split an iterator into chunks with `n` elements each.
  135. Examples
  136. # n == 2
  137. >>> x = chunks(iter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 2)
  138. >>> list(x)
  139. [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10]]
  140. # n == 3
  141. >>> x = chunks(iter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 3)
  142. >>> list(x)
  143. [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
  144. """
  145. for first in it:
  146. yield [first] + list(islice(it, n - 1))
  147. def padlist(container, size, default=None):
  148. """Pad list with default elements.
  149. Examples:
  150. >>> first, last, city = padlist(["George", "Costanza", "NYC"], 3)
  151. ("George", "Costanza", "NYC")
  152. >>> first, last, city = padlist(["George", "Costanza"], 3)
  153. ("George", "Costanza", None)
  154. >>> first, last, city, planet = padlist(["George", "Costanza",
  155. "NYC"], 4, default="Earth")
  156. ("George", "Costanza", "NYC", "Earth")
  157. """
  158. return list(container)[:size] + [default] * (size - len(container))
  159. def is_iterable(obj):
  160. try:
  161. iter(obj)
  162. except TypeError:
  163. return False
  164. return True
  165. def mattrgetter(*attrs):
  166. """Like :func:`operator.itemgetter` but returns :const:`None` on missing
  167. attributes instead of raising :exc:`AttributeError`."""
  168. return lambda obj: dict((attr, getattr(obj, attr, None))
  169. for attr in attrs)
  170. def get_full_cls_name(cls):
  171. """With a class, get its full module and class name."""
  172. return ".".join([cls.__module__,
  173. cls.__name__])
  174. def fun_takes_kwargs(fun, kwlist=[]):
  175. """With a function, and a list of keyword arguments, returns arguments
  176. in the list which the function takes.
  177. If the object has an `argspec` attribute that is used instead
  178. of using the :meth:`inspect.getargspec` introspection.
  179. :param fun: The function to inspect arguments of.
  180. :param kwlist: The list of keyword arguments.
  181. Examples
  182. >>> def foo(self, x, y, logfile=None, loglevel=None):
  183. ... return x * y
  184. >>> fun_takes_kwargs(foo, ["logfile", "loglevel", "task_id"])
  185. ["logfile", "loglevel"]
  186. >>> def foo(self, x, y, **kwargs):
  187. >>> fun_takes_kwargs(foo, ["logfile", "loglevel", "task_id"])
  188. ["logfile", "loglevel", "task_id"]
  189. """
  190. argspec = getattr(fun, "argspec", getargspec(fun))
  191. args, _varargs, keywords, _defaults = argspec
  192. if keywords != None:
  193. return kwlist
  194. return filter(partial(operator.contains, args), kwlist)
  195. def get_cls_by_name(name, aliases={}, imp=None):
  196. """Get class by name.
  197. The name should be the full dot-separated path to the class::
  198. modulename.ClassName
  199. Example::
  200. celery.concurrency.processes.TaskPool
  201. ^- class name
  202. If `aliases` is provided, a dict containing short name/long name
  203. mappings, the name is looked up in the aliases first.
  204. Examples:
  205. >>> get_cls_by_name("celery.concurrency.processes.TaskPool")
  206. <class 'celery.concurrency.processes.TaskPool'>
  207. >>> get_cls_by_name("default", {
  208. ... "default": "celery.concurrency.processes.TaskPool"})
  209. <class 'celery.concurrency.processes.TaskPool'>
  210. # Does not try to look up non-string names.
  211. >>> from celery.concurrency.processes import TaskPool
  212. >>> get_cls_by_name(TaskPool) is TaskPool
  213. True
  214. """
  215. if imp is None:
  216. imp = importlib.import_module
  217. if not isinstance(name, basestring):
  218. return name # already a class
  219. name = aliases.get(name) or name
  220. module_name, _, cls_name = name.rpartition(".")
  221. try:
  222. module = imp(module_name)
  223. except ValueError, exc:
  224. raise ValueError("Couldn't import %r: %s" % (name, exc))
  225. return getattr(module, cls_name)
  226. get_symbol_by_name = get_cls_by_name
  227. def instantiate(name, *args, **kwargs):
  228. """Instantiate class by name.
  229. See :func:`get_cls_by_name`.
  230. """
  231. return get_cls_by_name(name)(*args, **kwargs)
  232. def truncate_text(text, maxlen=128, suffix="..."):
  233. """Truncates text to a maximum number of characters."""
  234. if len(text) >= maxlen:
  235. return text[:maxlen].rsplit(" ", 1)[0] + suffix
  236. return text
  237. def abbr(S, max, ellipsis="..."):
  238. if S is None:
  239. return "???"
  240. if len(S) > max:
  241. return ellipsis and (S[:max - len(ellipsis)] + ellipsis) or S[:max]
  242. return S
  243. def abbrtask(S, max):
  244. if S is None:
  245. return "???"
  246. if len(S) > max:
  247. module, _, cls = S.rpartition(".")
  248. module = abbr(module, max - len(cls) - 3, False)
  249. return module + "[.]" + cls
  250. return S
  251. def isatty(fh):
  252. # Fixes bug with mod_wsgi:
  253. # mod_wsgi.Log object has no attribute isatty.
  254. return getattr(fh, "isatty", None) and fh.isatty()
  255. def textindent(t, indent=0):
  256. """Indent text."""
  257. return "\n".join(" " * indent + p for p in t.split("\n"))
  258. @contextmanager
  259. def cwd_in_path():
  260. cwd = os.getcwd()
  261. if cwd in sys.path:
  262. yield
  263. else:
  264. sys.path.insert(0, cwd)
  265. try:
  266. yield cwd
  267. finally:
  268. try:
  269. sys.path.remove(cwd)
  270. except ValueError:
  271. pass
  272. def find_module(module, path=None, imp=None):
  273. """Version of :func:`imp.find_module` supporting dots."""
  274. if imp is None:
  275. imp = importlib.import_module
  276. with cwd_in_path():
  277. if "." in module:
  278. last = None
  279. parts = module.split(".")
  280. for i, part in enumerate(parts[:-1]):
  281. path = imp(".".join(parts[:i + 1])).__path__
  282. last = _imp.find_module(parts[i + 1], path)
  283. return last
  284. return _imp.find_module(module)
  285. def import_from_cwd(module, imp=None):
  286. """Import module, but make sure it finds modules
  287. located in the current directory.
  288. Modules located in the current directory has
  289. precedence over modules located in `sys.path`.
  290. """
  291. if imp is None:
  292. imp = importlib.import_module
  293. with cwd_in_path():
  294. return imp(module)
  295. def cry():
  296. """Return stacktrace of all active threads.
  297. From https://gist.github.com/737056
  298. """
  299. tmap = {}
  300. main_thread = None
  301. # get a map of threads by their ID so we can print their names
  302. # during the traceback dump
  303. for t in threading.enumerate():
  304. if getattr(t, "ident", None):
  305. tmap[t.ident] = t
  306. else:
  307. main_thread = t
  308. out = StringIO()
  309. for tid, frame in sys._current_frames().iteritems():
  310. thread = tmap.get(tid, main_thread)
  311. out.write("%s\n" % (thread.getName(), ))
  312. out.write("=================================================\n")
  313. traceback.print_stack(frame, file=out)
  314. out.write("=================================================\n")
  315. out.write("LOCAL VARIABLES\n")
  316. out.write("=================================================\n")
  317. pprint(frame.f_locals, stream=out)
  318. out.write("\n\n")
  319. return out.getvalue()