PageRenderTime 60ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/celery/utils/__init__.py

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