Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

base.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.app.base
  4. ~~~~~~~~~~~~~~~
  5. Actual App instance implementation.
  6. """
  7. from __future__ import absolute_import
  8. import os
  9. import threading
  10. import warnings
  11. from collections import defaultdict, deque
  12. from copy import deepcopy
  13. from operator import attrgetter
  14. from amqp import promise
  15. from billiard.util import register_after_fork
  16. from kombu.clocks import LamportClock
  17. from kombu.common import oid_from
  18. from kombu.utils import cached_property, uuid
  19. from celery import platforms
  20. from celery import signals
  21. from celery._state import (
  22. _task_stack, get_current_app, _set_current_app, set_default_app,
  23. _register_app, get_current_worker_task, connect_on_app_finalize,
  24. _announce_app_finalized,
  25. )
  26. from celery.exceptions import AlwaysEagerIgnored, ImproperlyConfigured
  27. from celery.five import values
  28. from celery.loaders import get_loader_cls
  29. from celery.local import PromiseProxy, maybe_evaluate
  30. from celery.utils.functional import first, maybe_list
  31. from celery.utils.imports import instantiate, symbol_by_name
  32. from celery.utils.objects import FallbackContext, mro_lookup
  33. from .annotations import prepare as prepare_annotations
  34. from .defaults import DEFAULTS, find_deprecated_settings
  35. from .registry import TaskRegistry
  36. from .utils import (
  37. AppPickler, Settings, bugreport, _unpickle_app, _unpickle_app_v2, appstr,
  38. )
  39. # Load all builtin tasks
  40. from . import builtins # noqa
  41. __all__ = ['Celery']
  42. _EXECV = os.environ.get('FORKED_BY_MULTIPROCESSING')
  43. BUILTIN_FIXUPS = frozenset([
  44. 'celery.fixups.django:fixup',
  45. ])
  46. ERR_ENVVAR_NOT_SET = """\
  47. The environment variable {0!r} is not set,
  48. and as such the configuration could not be loaded.
  49. Please set this variable and make it point to
  50. a configuration module."""
  51. _after_fork_registered = False
  52. def app_has_custom(app, attr):
  53. return mro_lookup(app.__class__, attr, stop=(Celery, object),
  54. monkey_patched=[__name__])
  55. def _unpickle_appattr(reverse_name, args):
  56. """Given an attribute name and a list of args, gets
  57. the attribute from the current app and calls it."""
  58. return get_current_app()._rgetattr(reverse_name)(*args)
  59. def _global_after_fork(obj):
  60. # Previously every app would call:
  61. # `register_after_fork(app, app._after_fork)`
  62. # but this created a leak as `register_after_fork` stores concrete object
  63. # references and once registered an object cannot be removed without
  64. # touching and iterating over the private afterfork registry list.
  65. #
  66. # See Issue #1949
  67. from celery import _state
  68. from multiprocessing import util as mputil
  69. for app in _state._apps:
  70. try:
  71. app._after_fork(obj)
  72. except Exception as exc:
  73. if mputil._logger:
  74. mputil._logger.info(
  75. 'after forker raised exception: %r', exc, exc_info=1)
  76. def _ensure_after_fork():
  77. global _after_fork_registered
  78. _after_fork_registered = True
  79. register_after_fork(_global_after_fork, _global_after_fork)
  80. class Celery(object):
  81. #: This is deprecated, use :meth:`reduce_keys` instead
  82. Pickler = AppPickler
  83. SYSTEM = platforms.SYSTEM
  84. IS_OSX, IS_WINDOWS = platforms.IS_OSX, platforms.IS_WINDOWS
  85. amqp_cls = 'celery.app.amqp:AMQP'
  86. backend_cls = None
  87. events_cls = 'celery.events:Events'
  88. loader_cls = 'celery.loaders.app:AppLoader'
  89. log_cls = 'celery.app.log:Logging'
  90. control_cls = 'celery.app.control:Control'
  91. task_cls = 'celery.app.task:Task'
  92. registry_cls = TaskRegistry
  93. _fixups = None
  94. _pool = None
  95. builtin_fixups = BUILTIN_FIXUPS
  96. def __init__(self, main=None, loader=None, backend=None,
  97. amqp=None, events=None, log=None, control=None,
  98. set_as_current=True, accept_magic_kwargs=False,
  99. tasks=None, broker=None, include=None, changes=None,
  100. config_source=None, fixups=None, task_cls=None,
  101. autofinalize=True, **kwargs):
  102. self.clock = LamportClock()
  103. self.main = main
  104. self.amqp_cls = amqp or self.amqp_cls
  105. self.events_cls = events or self.events_cls
  106. self.loader_cls = loader or self.loader_cls
  107. self.log_cls = log or self.log_cls
  108. self.control_cls = control or self.control_cls
  109. self.task_cls = task_cls or self.task_cls
  110. self.set_as_current = set_as_current
  111. self.registry_cls = symbol_by_name(self.registry_cls)
  112. self.accept_magic_kwargs = accept_magic_kwargs
  113. self.user_options = defaultdict(set)
  114. self.steps = defaultdict(set)
  115. self.autofinalize = autofinalize
  116. self.configured = False
  117. self._config_source = config_source
  118. self._pending_defaults = deque()
  119. self.finalized = False
  120. self._finalize_mutex = threading.Lock()
  121. self._pending = deque()
  122. self._tasks = tasks
  123. if not isinstance(self._tasks, TaskRegistry):
  124. self._tasks = TaskRegistry(self._tasks or {})
  125. # If the class defines a custom __reduce_args__ we need to use
  126. # the old way of pickling apps, which is pickling a list of
  127. # args instead of the new way that pickles a dict of keywords.
  128. self._using_v1_reduce = app_has_custom(self, '__reduce_args__')
  129. # these options are moved to the config to
  130. # simplify pickling of the app object.
  131. self._preconf = changes or {}
  132. if broker:
  133. self._preconf['BROKER_URL'] = broker
  134. if backend:
  135. self._preconf['CELERY_RESULT_BACKEND'] = backend
  136. if include:
  137. self._preconf['CELERY_IMPORTS'] = include
  138. # - Apply fixups.
  139. self.fixups = set(self.builtin_fixups) if fixups is None else fixups
  140. # ...store fixup instances in _fixups to keep weakrefs alive.
  141. self._fixups = [symbol_by_name(fixup)(self) for fixup in self.fixups]
  142. if self.set_as_current:
  143. self.set_current()
  144. self.on_init()
  145. _register_app(self)
  146. def set_current(self):
  147. _set_current_app(self)
  148. def set_default(self):
  149. set_default_app(self)
  150. def __enter__(self):
  151. return self
  152. def __exit__(self, *exc_info):
  153. self.close()
  154. def close(self):
  155. self._maybe_close_pool()
  156. def on_init(self):
  157. """Optional callback called at init."""
  158. pass
  159. def start(self, argv=None):
  160. return instantiate(
  161. 'celery.bin.celery:CeleryCommand',
  162. app=self).execute_from_commandline(argv)
  163. def worker_main(self, argv=None):
  164. return instantiate(
  165. 'celery.bin.worker:worker',
  166. app=self).execute_from_commandline(argv)
  167. def task(self, *args, **opts):
  168. """Creates new task class from any callable."""
  169. if _EXECV and not opts.get('_force_evaluate'):
  170. # When using execv the task in the original module will point to a
  171. # different app, so doing things like 'add.request' will point to
  172. # a differnt task instance. This makes sure it will always use
  173. # the task instance from the current app.
  174. # Really need a better solution for this :(
  175. from . import shared_task
  176. return shared_task(*args, _force_evaluate=True, **opts)
  177. def inner_create_task_cls(shared=True, filter=None, **opts):
  178. _filt = filter # stupid 2to3
  179. def _create_task_cls(fun):
  180. if shared:
  181. def cons(app):
  182. return app._task_from_fun(fun, **opts)
  183. cons.__name__ = fun.__name__
  184. connect_on_app_finalize(cons)
  185. if self.accept_magic_kwargs: # compat mode
  186. task = self._task_from_fun(fun, **opts)
  187. if filter:
  188. task = filter(task)
  189. return task
  190. if self.finalized or opts.get('_force_evaluate'):
  191. ret = self._task_from_fun(fun, **opts)
  192. else:
  193. # return a proxy object that evaluates on first use
  194. ret = PromiseProxy(self._task_from_fun, (fun, ), opts,
  195. __doc__=fun.__doc__)
  196. self._pending.append(ret)
  197. if _filt:
  198. return _filt(ret)
  199. return ret
  200. return _create_task_cls
  201. if len(args) == 1:
  202. if callable(args[0]):
  203. return inner_create_task_cls(**opts)(*args)
  204. raise TypeError('argument 1 to @task() must be a callable')
  205. if args:
  206. raise TypeError(
  207. '@task() takes exactly 1 argument ({0} given)'.format(
  208. sum([len(args), len(opts)])))
  209. return inner_create_task_cls(**opts)
  210. def _task_from_fun(self, fun, **options):
  211. if not self.finalized and not self.autofinalize:
  212. raise RuntimeError('Contract breach: app not finalized')
  213. base = options.pop('base', None) or self.Task
  214. bind = options.pop('bind', False)
  215. T = type(fun.__name__, (base, ), dict({
  216. 'app': self,
  217. 'accept_magic_kwargs': False,
  218. 'run': fun if bind else staticmethod(fun),
  219. '_decorated': True,
  220. '__doc__': fun.__doc__,
  221. '__module__': fun.__module__,
  222. '__wrapped__': fun}, **options))()
  223. task = self._tasks[T.name] # return global instance.
  224. return task
  225. def finalize(self, auto=False):
  226. with self._finalize_mutex:
  227. if not self.finalized:
  228. if auto and not self.autofinalize:
  229. raise RuntimeError('Contract breach: app not finalized')
  230. self.finalized = True
  231. _announce_app_finalized(self)
  232. pending = self._pending
  233. while pending:
  234. maybe_evaluate(pending.popleft())
  235. for task in values(self._tasks):
  236. task.bind(self)
  237. def add_defaults(self, fun):
  238. if not callable(fun):
  239. d, fun = fun, lambda: d
  240. if self.configured:
  241. return self.conf.add_defaults(fun())
  242. self._pending_defaults.append(fun)
  243. def config_from_object(self, obj, silent=False, force=False):
  244. self._config_source = obj
  245. if force or self.configured:
  246. del(self.conf)
  247. return self.loader.config_from_object(obj, silent=silent)
  248. def config_from_envvar(self, variable_name, silent=False, force=False):
  249. module_name = os.environ.get(variable_name)
  250. if not module_name:
  251. if silent:
  252. return False
  253. raise ImproperlyConfigured(
  254. ERR_ENVVAR_NOT_SET.format(variable_name))
  255. return self.config_from_object(module_name, silent=silent, force=force)
  256. def config_from_cmdline(self, argv, namespace='celery'):
  257. self.conf.update(self.loader.cmdline_config_parser(argv, namespace))
  258. def setup_security(self, allowed_serializers=None, key=None, cert=None,
  259. store=None, digest='sha1', serializer='json'):
  260. from celery.security import setup_security
  261. return setup_security(allowed_serializers, key, cert,
  262. store, digest, serializer, app=self)
  263. def autodiscover_tasks(self, packages, related_name='tasks', force=False):
  264. if force:
  265. return self._autodiscover_tasks(packages, related_name)
  266. signals.import_modules.connect(promise(
  267. self._autodiscover_tasks, (packages, related_name),
  268. ), weak=False, sender=self)
  269. def _autodiscover_tasks(self, packages, related_name='tasks', **kwargs):
  270. # argument may be lazy
  271. packages = packages() if callable(packages) else packages
  272. self.loader.autodiscover_tasks(packages, related_name)
  273. def send_task(self, name, args=None, kwargs=None, countdown=None,
  274. eta=None, task_id=None, producer=None, connection=None,
  275. router=None, result_cls=None, expires=None,
  276. publisher=None, link=None, link_error=None,
  277. add_to_parent=True, reply_to=None, **options):
  278. task_id = task_id or uuid()
  279. producer = producer or publisher # XXX compat
  280. router = router or self.amqp.router
  281. conf = self.conf
  282. if conf.CELERY_ALWAYS_EAGER: # pragma: no cover
  283. warnings.warn(AlwaysEagerIgnored(
  284. 'CELERY_ALWAYS_EAGER has no effect on send_task',
  285. ), stacklevel=2)
  286. options = router.route(options, name, args, kwargs)
  287. if connection:
  288. producer = self.amqp.TaskProducer(connection)
  289. with self.producer_or_acquire(producer) as P:
  290. self.backend.on_task_call(P, task_id)
  291. task_id = P.publish_task(
  292. name, args, kwargs, countdown=countdown, eta=eta,
  293. task_id=task_id, expires=expires,
  294. callbacks=maybe_list(link), errbacks=maybe_list(link_error),
  295. reply_to=reply_to or self.oid, **options
  296. )
  297. result = (result_cls or self.AsyncResult)(task_id)
  298. if add_to_parent:
  299. parent = get_current_worker_task()
  300. if parent:
  301. parent.add_trail(result)
  302. return result
  303. def connection(self, hostname=None, userid=None, password=None,
  304. virtual_host=None, port=None, ssl=None,
  305. connect_timeout=None, transport=None,
  306. transport_options=None, heartbeat=None,
  307. login_method=None, failover_strategy=None, **kwargs):
  308. conf = self.conf
  309. return self.amqp.Connection(
  310. hostname or conf.BROKER_URL,
  311. userid or conf.BROKER_USER,
  312. password or conf.BROKER_PASSWORD,
  313. virtual_host or conf.BROKER_VHOST,
  314. port or conf.BROKER_PORT,
  315. transport=transport or conf.BROKER_TRANSPORT,
  316. ssl=self.either('BROKER_USE_SSL', ssl),
  317. heartbeat=heartbeat,
  318. login_method=login_method or conf.BROKER_LOGIN_METHOD,
  319. failover_strategy=(
  320. failover_strategy or conf.BROKER_FAILOVER_STRATEGY
  321. ),
  322. transport_options=dict(
  323. conf.BROKER_TRANSPORT_OPTIONS, **transport_options or {}
  324. ),
  325. connect_timeout=self.either(
  326. 'BROKER_CONNECTION_TIMEOUT', connect_timeout
  327. ),
  328. )
  329. broker_connection = connection
  330. def _acquire_connection(self, pool=True):
  331. """Helper for :meth:`connection_or_acquire`."""
  332. if pool:
  333. return self.pool.acquire(block=True)
  334. return self.connection()
  335. def connection_or_acquire(self, connection=None, pool=True, *_, **__):
  336. return FallbackContext(connection, self._acquire_connection, pool=pool)
  337. default_connection = connection_or_acquire # XXX compat
  338. def producer_or_acquire(self, producer=None):
  339. return FallbackContext(
  340. producer, self.amqp.producer_pool.acquire, block=True,
  341. )
  342. default_producer = producer_or_acquire # XXX compat
  343. def prepare_config(self, c):
  344. """Prepare configuration before it is merged with the defaults."""
  345. return find_deprecated_settings(c)
  346. def now(self):
  347. return self.loader.now(utc=self.conf.CELERY_ENABLE_UTC)
  348. def mail_admins(self, subject, body, fail_silently=False):
  349. if self.conf.ADMINS:
  350. to = [admin_email for _, admin_email in self.conf.ADMINS]
  351. return self.loader.mail_admins(
  352. subject, body, fail_silently, to=to,
  353. sender=self.conf.SERVER_EMAIL,
  354. host=self.conf.EMAIL_HOST,
  355. port=self.conf.EMAIL_PORT,
  356. user=self.conf.EMAIL_HOST_USER,
  357. password=self.conf.EMAIL_HOST_PASSWORD,
  358. timeout=self.conf.EMAIL_TIMEOUT,
  359. use_ssl=self.conf.EMAIL_USE_SSL,
  360. use_tls=self.conf.EMAIL_USE_TLS,
  361. )
  362. def select_queues(self, queues=None):
  363. return self.amqp.queues.select(queues)
  364. def either(self, default_key, *values):
  365. """Fallback to the value of a configuration key if none of the
  366. `*values` are true."""
  367. return first(None, values) or self.conf.get(default_key)
  368. def bugreport(self):
  369. return bugreport(self)
  370. def _get_backend(self):
  371. from celery.backends import get_backend_by_url
  372. backend, url = get_backend_by_url(
  373. self.backend_cls or self.conf.CELERY_RESULT_BACKEND,
  374. self.loader)
  375. return backend(app=self, url=url)
  376. def on_configure(self):
  377. """Callback calld when the app loads configuration"""
  378. pass
  379. def _get_config(self):
  380. self.on_configure()
  381. if self._config_source:
  382. self.loader.config_from_object(self._config_source)
  383. self.configured = True
  384. s = Settings({}, [self.prepare_config(self.loader.conf),
  385. deepcopy(DEFAULTS)])
  386. # load lazy config dict initializers.
  387. pending = self._pending_defaults
  388. while pending:
  389. s.add_defaults(maybe_evaluate(pending.popleft()()))
  390. # preconf options must be explicitly set in the conf, and not
  391. # as defaults or they will not be pickled with the app instance.
  392. # This will cause errors when `CELERYD_FORCE_EXECV=True` as
  393. # the workers will not have a BROKER_URL, CELERY_RESULT_BACKEND,
  394. # or CELERY_IMPORTS set in the config.
  395. if self._preconf:
  396. s.update(self._preconf)
  397. return s
  398. def _after_fork(self, obj_):
  399. self._maybe_close_pool()
  400. def _maybe_close_pool(self):
  401. pool, self._pool = self._pool, None
  402. if pool is not None:
  403. pool.force_close_all()
  404. amqp = self.__dict__.get('amqp')
  405. if amqp is not None:
  406. producer_pool, amqp._producer_pool = amqp._producer_pool, None
  407. if producer_pool is not None:
  408. producer_pool.force_close_all()
  409. def signature(self, *args, **kwargs):
  410. kwargs['app'] = self
  411. return self.canvas.signature(*args, **kwargs)
  412. def create_task_cls(self):
  413. """Creates a base task class using default configuration
  414. taken from this app."""
  415. return self.subclass_with_self(
  416. self.task_cls, name='Task', attribute='_app',
  417. keep_reduce=True, abstract=True,
  418. )
  419. def subclass_with_self(self, Class, name=None, attribute='app',
  420. reverse=None, keep_reduce=False, **kw):
  421. """Subclass an app-compatible class by setting its app attribute
  422. to be this app instance.
  423. App-compatible means that the class has a class attribute that
  424. provides the default app it should use, e.g.
  425. ``class Foo: app = None``.
  426. :param Class: The app-compatible class to subclass.
  427. :keyword name: Custom name for the target class.
  428. :keyword attribute: Name of the attribute holding the app,
  429. default is 'app'.
  430. """
  431. Class = symbol_by_name(Class)
  432. reverse = reverse if reverse else Class.__name__
  433. def __reduce__(self):
  434. return _unpickle_appattr, (reverse, self.__reduce_args__())
  435. attrs = dict({attribute: self}, __module__=Class.__module__,
  436. __doc__=Class.__doc__, **kw)
  437. if not keep_reduce:
  438. attrs['__reduce__'] = __reduce__
  439. return type(name or Class.__name__, (Class, ), attrs)
  440. def _rgetattr(self, path):
  441. return attrgetter(path)(self)
  442. def __repr__(self):
  443. return '<{0} {1}>'.format(type(self).__name__, appstr(self))
  444. def __reduce__(self):
  445. if self._using_v1_reduce:
  446. return self.__reduce_v1__()
  447. return (_unpickle_app_v2, (self.__class__, self.__reduce_keys__()))
  448. def __reduce_v1__(self):
  449. # Reduce only pickles the configuration changes,
  450. # so the default configuration doesn't have to be passed
  451. # between processes.
  452. return (
  453. _unpickle_app,
  454. (self.__class__, self.Pickler) + self.__reduce_args__(),
  455. )
  456. def __reduce_keys__(self):
  457. """Return keyword arguments used to reconstruct the object
  458. when unpickling."""
  459. return {
  460. 'main': self.main,
  461. 'changes': self.conf.changes if self.configured else self._preconf,
  462. 'loader': self.loader_cls,
  463. 'backend': self.backend_cls,
  464. 'amqp': self.amqp_cls,
  465. 'events': self.events_cls,
  466. 'log': self.log_cls,
  467. 'control': self.control_cls,
  468. 'accept_magic_kwargs': self.accept_magic_kwargs,
  469. 'fixups': self.fixups,
  470. 'config_source': self._config_source,
  471. 'task_cls': self.task_cls,
  472. }
  473. def __reduce_args__(self):
  474. """Deprecated method, please use :meth:`__reduce_keys__` instead."""
  475. return (self.main, self.conf.changes,
  476. self.loader_cls, self.backend_cls, self.amqp_cls,
  477. self.events_cls, self.log_cls, self.control_cls,
  478. self.accept_magic_kwargs, self._config_source)
  479. @cached_property
  480. def Worker(self):
  481. return self.subclass_with_self('celery.apps.worker:Worker')
  482. @cached_property
  483. def WorkController(self, **kwargs):
  484. return self.subclass_with_self('celery.worker:WorkController')
  485. @cached_property
  486. def Beat(self, **kwargs):
  487. return self.subclass_with_self('celery.apps.beat:Beat')
  488. @cached_property
  489. def Task(self):
  490. return self.create_task_cls()
  491. @cached_property
  492. def annotations(self):
  493. return prepare_annotations(self.conf.CELERY_ANNOTATIONS)
  494. @cached_property
  495. def AsyncResult(self):
  496. return self.subclass_with_self('celery.result:AsyncResult')
  497. @cached_property
  498. def ResultSet(self):
  499. return self.subclass_with_self('celery.result:ResultSet')
  500. @cached_property
  501. def GroupResult(self):
  502. return self.subclass_with_self('celery.result:GroupResult')
  503. @cached_property
  504. def TaskSet(self): # XXX compat
  505. """Deprecated! Please use :class:`celery.group` instead."""
  506. return self.subclass_with_self('celery.task.sets:TaskSet')
  507. @cached_property
  508. def TaskSetResult(self): # XXX compat
  509. """Deprecated! Please use :attr:`GroupResult` instead."""
  510. return self.subclass_with_self('celery.result:TaskSetResult')
  511. @property
  512. def pool(self):
  513. if self._pool is None:
  514. _ensure_after_fork()
  515. limit = self.conf.BROKER_POOL_LIMIT
  516. self._pool = self.connection().Pool(limit=limit)
  517. return self._pool
  518. @property
  519. def current_task(self):
  520. return _task_stack.top
  521. @cached_property
  522. def oid(self):
  523. return oid_from(self)
  524. @cached_property
  525. def amqp(self):
  526. return instantiate(self.amqp_cls, app=self)
  527. @cached_property
  528. def backend(self):
  529. return self._get_backend()
  530. @cached_property
  531. def conf(self):
  532. return self._get_config()
  533. @cached_property
  534. def control(self):
  535. return instantiate(self.control_cls, app=self)
  536. @cached_property
  537. def events(self):
  538. return instantiate(self.events_cls, app=self)
  539. @cached_property
  540. def loader(self):
  541. return get_loader_cls(self.loader_cls)(app=self)
  542. @cached_property
  543. def log(self):
  544. return instantiate(self.log_cls, app=self)
  545. @cached_property
  546. def canvas(self):
  547. from celery import canvas
  548. return canvas
  549. @cached_property
  550. def tasks(self):
  551. self.finalize(auto=True)
  552. return self._tasks
  553. @cached_property
  554. def timezone(self):
  555. from celery.utils.timeutils import timezone
  556. conf = self.conf
  557. tz = conf.CELERY_TIMEZONE
  558. if not tz:
  559. return (timezone.get_timezone('UTC') if conf.CELERY_ENABLE_UTC
  560. else timezone.local)
  561. return timezone.get_timezone(self.conf.CELERY_TIMEZONE)
  562. App = Celery # compat