five.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. # -*- coding: utf-8 -*-
  2. """Python 2/3 compatibility utilities."""
  3. from __future__ import absolute_import, unicode_literals
  4. import operator
  5. import sys
  6. from importlib import import_module
  7. from types import ModuleType
  8. # extends vine.five
  9. from vine import five
  10. from vine.five import * # noqa
  11. from vine.five import __all__ as _all_five
  12. # bloody flake8
  13. items = five.items
  14. bytes_if_py2 = five.bytes_if_py2
  15. string_t = five.string_t
  16. try:
  17. from functools import reduce
  18. except ImportError:
  19. pass
  20. __all__ = [
  21. 'class_property', 'reclassmethod', 'create_module', 'recreate_module',
  22. ]
  23. __all__ += _all_five
  24. # ############# Module Generation ##########################
  25. # Utilities to dynamically
  26. # recreate modules, either for lazy loading or
  27. # to create old modules at runtime instead of
  28. # having them litter the source tree.
  29. # import fails in python 2.5. fallback to reduce in stdlib
  30. MODULE_DEPRECATED = """
  31. The module %s is deprecated and will be removed in a future version.
  32. """
  33. DEFAULT_ATTRS = {'__file__', '__path__', '__doc__', '__all__'}
  34. # im_func is no longer available in Py3.
  35. # instead the unbound method itself can be used.
  36. if sys.version_info[0] == 3: # pragma: no cover
  37. def fun_of_method(method):
  38. return method
  39. else:
  40. def fun_of_method(method): # noqa
  41. return method.im_func
  42. def getappattr(path):
  43. """Gets attribute from the current_app recursively,
  44. (e.g., ``getappattr('amqp.get_task_consumer')``."""
  45. from celery import current_app
  46. return current_app._rgetattr(path)
  47. def _compat_periodic_task_decorator(*args, **kwargs):
  48. from celery.task import periodic_task
  49. return periodic_task(*args, **kwargs)
  50. COMPAT_MODULES = {
  51. 'celery': {
  52. 'execute': {
  53. 'send_task': 'send_task',
  54. },
  55. 'decorators': {
  56. 'task': 'task',
  57. 'periodic_task': _compat_periodic_task_decorator,
  58. },
  59. 'log': {
  60. 'get_default_logger': 'log.get_default_logger',
  61. 'setup_logger': 'log.setup_logger',
  62. 'setup_logging_subsystem': 'log.setup_logging_subsystem',
  63. 'redirect_stdouts_to_logger': 'log.redirect_stdouts_to_logger',
  64. },
  65. 'messaging': {
  66. 'TaskConsumer': 'amqp.TaskConsumer',
  67. 'establish_connection': 'connection',
  68. 'get_consumer_set': 'amqp.TaskConsumer',
  69. },
  70. 'registry': {
  71. 'tasks': 'tasks',
  72. },
  73. },
  74. 'celery.task': {
  75. 'control': {
  76. 'broadcast': 'control.broadcast',
  77. 'rate_limit': 'control.rate_limit',
  78. 'time_limit': 'control.time_limit',
  79. 'ping': 'control.ping',
  80. 'revoke': 'control.revoke',
  81. 'discard_all': 'control.purge',
  82. 'inspect': 'control.inspect',
  83. },
  84. 'schedules': 'celery.schedules',
  85. 'chords': 'celery.canvas',
  86. }
  87. }
  88. class class_property(object):
  89. def __init__(self, getter=None, setter=None):
  90. if getter is not None and not isinstance(getter, classmethod):
  91. getter = classmethod(getter)
  92. if setter is not None and not isinstance(setter, classmethod):
  93. setter = classmethod(setter)
  94. self.__get = getter
  95. self.__set = setter
  96. info = getter.__get__(object) # just need the info attrs.
  97. self.__doc__ = info.__doc__
  98. self.__name__ = info.__name__
  99. self.__module__ = info.__module__
  100. def __get__(self, obj, type=None):
  101. if obj and type is None:
  102. type = obj.__class__
  103. return self.__get.__get__(obj, type)()
  104. def __set__(self, obj, value):
  105. if obj is None:
  106. return self
  107. return self.__set.__get__(obj)(value)
  108. def setter(self, setter):
  109. return self.__class__(self.__get, setter)
  110. def reclassmethod(method):
  111. return classmethod(fun_of_method(method))
  112. class LazyModule(ModuleType):
  113. _compat_modules = ()
  114. _all_by_module = {}
  115. _direct = {}
  116. _object_origins = {}
  117. def __getattr__(self, name):
  118. if name in self._object_origins:
  119. module = __import__(self._object_origins[name], None, None, [name])
  120. for item in self._all_by_module[module.__name__]:
  121. setattr(self, item, getattr(module, item))
  122. return getattr(module, name)
  123. elif name in self._direct: # pragma: no cover
  124. module = __import__(self._direct[name], None, None, [name])
  125. setattr(self, name, module)
  126. return module
  127. return ModuleType.__getattribute__(self, name)
  128. def __dir__(self):
  129. return list(set(self.__all__) | DEFAULT_ATTRS)
  130. def __reduce__(self):
  131. return import_module, (self.__name__,)
  132. def create_module(name, attrs, cls_attrs=None, pkg=None,
  133. base=LazyModule, prepare_attr=None):
  134. fqdn = '.'.join([pkg.__name__, name]) if pkg else name
  135. cls_attrs = {} if cls_attrs is None else cls_attrs
  136. pkg, _, modname = name.rpartition('.')
  137. cls_attrs['__module__'] = pkg
  138. attrs = {
  139. attr_name: (prepare_attr(attr) if prepare_attr else attr)
  140. for attr_name, attr in items(attrs)
  141. }
  142. module = sys.modules[fqdn] = type(
  143. bytes_if_py2(modname), (base,), cls_attrs)(bytes_if_py2(name))
  144. module.__dict__.update(attrs)
  145. return module
  146. def recreate_module(name, compat_modules=(), by_module={}, direct={},
  147. base=LazyModule, **attrs):
  148. old_module = sys.modules[name]
  149. origins = get_origins(by_module)
  150. compat_modules = COMPAT_MODULES.get(name, ())
  151. _all = tuple(set(reduce(
  152. operator.add,
  153. [tuple(v) for v in [compat_modules, origins, direct, attrs]],
  154. )))
  155. if sys.version_info[0] < 3:
  156. _all = [s.encode() for s in _all]
  157. cattrs = dict(
  158. _compat_modules=compat_modules,
  159. _all_by_module=by_module, _direct=direct,
  160. _object_origins=origins,
  161. __all__=_all,
  162. )
  163. new_module = create_module(name, attrs, cls_attrs=cattrs, base=base)
  164. new_module.__dict__.update({
  165. mod: get_compat_module(new_module, mod) for mod in compat_modules
  166. })
  167. return old_module, new_module
  168. def get_compat_module(pkg, name):
  169. from .local import Proxy
  170. def prepare(attr):
  171. if isinstance(attr, string_t):
  172. return Proxy(getappattr, (attr,))
  173. return attr
  174. attrs = COMPAT_MODULES[pkg.__name__][name]
  175. if isinstance(attrs, string_t):
  176. fqdn = '.'.join([pkg.__name__, name])
  177. module = sys.modules[fqdn] = import_module(attrs)
  178. return module
  179. attrs['__all__'] = list(attrs)
  180. return create_module(name, dict(attrs), pkg=pkg, prepare_attr=prepare)
  181. def get_origins(defs):
  182. origins = {}
  183. for module, attrs in items(defs):
  184. origins.update({attr: module for attr in attrs})
  185. return origins