PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/celery/utils/__init__.py

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