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.

django.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. from __future__ import absolute_import
  2. import os
  3. import sys
  4. import warnings
  5. from kombu.utils import cached_property, symbol_by_name
  6. from datetime import datetime
  7. from importlib import import_module
  8. from celery import signals
  9. from celery.exceptions import FixupWarning
  10. if sys.version_info[0] < 3 and not hasattr(sys, 'pypy_version_info'):
  11. from StringIO import StringIO
  12. else:
  13. from io import StringIO
  14. __all__ = ['DjangoFixup', 'fixup']
  15. ERR_NOT_INSTALLED = """\
  16. Environment variable DJANGO_SETTINGS_MODULE is defined
  17. but Django is not installed. Will not apply Django fixups!
  18. """
  19. def _maybe_close_fd(fh):
  20. try:
  21. os.close(fh.fileno())
  22. except (AttributeError, OSError, TypeError):
  23. # TypeError added for celery#962
  24. pass
  25. def fixup(app, env='DJANGO_SETTINGS_MODULE'):
  26. SETTINGS_MODULE = os.environ.get(env)
  27. if SETTINGS_MODULE and 'django' not in app.loader_cls.lower():
  28. try:
  29. import django # noqa
  30. except ImportError:
  31. warnings.warn(FixupWarning(ERR_NOT_INSTALLED))
  32. else:
  33. return DjangoFixup(app).install()
  34. class DjangoFixup(object):
  35. def __init__(self, app):
  36. self.app = app
  37. self.app.set_default()
  38. self._worker_fixup = None
  39. def install(self):
  40. # Need to add project directory to path
  41. sys.path.append(os.getcwd())
  42. self.app.loader.now = self.now
  43. self.app.loader.mail_admins = self.mail_admins
  44. signals.import_modules.connect(self.on_import_modules)
  45. signals.worker_init.connect(self.on_worker_init)
  46. return self
  47. @cached_property
  48. def worker_fixup(self):
  49. if self._worker_fixup is None:
  50. self._worker_fixup = DjangoWorkerFixup(self.app)
  51. return self._worker_fixup
  52. def on_import_modules(self, **kwargs):
  53. # call django.setup() before task modules are imported
  54. self.worker_fixup.validate_models()
  55. def on_worker_init(self, **kwargs):
  56. self.worker_fixup.install()
  57. def now(self, utc=False):
  58. return datetime.utcnow() if utc else self._now()
  59. def mail_admins(self, subject, body, fail_silently=False, **kwargs):
  60. return self._mail_admins(subject, body, fail_silently=fail_silently)
  61. @cached_property
  62. def _mail_admins(self):
  63. return symbol_by_name('django.core.mail:mail_admins')
  64. @cached_property
  65. def _now(self):
  66. try:
  67. return symbol_by_name('django.utils.timezone:now')
  68. except (AttributeError, ImportError): # pre django-1.4
  69. return datetime.now
  70. class DjangoWorkerFixup(object):
  71. _db_recycles = 0
  72. def __init__(self, app):
  73. self.app = app
  74. self.db_reuse_max = self.app.conf.get('CELERY_DB_REUSE_MAX', None)
  75. self._db = import_module('django.db')
  76. self._cache = import_module('django.core.cache')
  77. self._settings = symbol_by_name('django.conf:settings')
  78. # Database-related exceptions.
  79. DatabaseError = symbol_by_name('django.db:DatabaseError')
  80. try:
  81. import MySQLdb as mysql
  82. _my_database_errors = (mysql.DatabaseError,
  83. mysql.InterfaceError,
  84. mysql.OperationalError)
  85. except ImportError:
  86. _my_database_errors = () # noqa
  87. try:
  88. import psycopg2 as pg
  89. _pg_database_errors = (pg.DatabaseError,
  90. pg.InterfaceError,
  91. pg.OperationalError)
  92. except ImportError:
  93. _pg_database_errors = () # noqa
  94. try:
  95. import sqlite3
  96. _lite_database_errors = (sqlite3.DatabaseError,
  97. sqlite3.InterfaceError,
  98. sqlite3.OperationalError)
  99. except ImportError:
  100. _lite_database_errors = () # noqa
  101. try:
  102. import cx_Oracle as oracle
  103. _oracle_database_errors = (oracle.DatabaseError,
  104. oracle.InterfaceError,
  105. oracle.OperationalError)
  106. except ImportError:
  107. _oracle_database_errors = () # noqa
  108. try:
  109. self._close_old_connections = symbol_by_name(
  110. 'django.db:close_old_connections',
  111. )
  112. except (ImportError, AttributeError):
  113. self._close_old_connections = None
  114. self.database_errors = (
  115. (DatabaseError, ) +
  116. _my_database_errors +
  117. _pg_database_errors +
  118. _lite_database_errors +
  119. _oracle_database_errors
  120. )
  121. def validate_models(self):
  122. import django
  123. try:
  124. django_setup = django.setup
  125. except AttributeError:
  126. pass
  127. else:
  128. django_setup()
  129. s = StringIO()
  130. try:
  131. from django.core.management.validation import get_validation_errors
  132. except ImportError:
  133. from django.core.management.base import BaseCommand
  134. cmd = BaseCommand()
  135. try:
  136. # since django 1.5
  137. from django.core.management.base import OutputWrapper
  138. cmd.stdout = OutputWrapper(sys.stdout)
  139. cmd.stderr = OutputWrapper(sys.stderr)
  140. except ImportError:
  141. cmd.stdout, cmd.stderr = sys.stdout, sys.stderr
  142. cmd.check()
  143. else:
  144. num_errors = get_validation_errors(s, None)
  145. if num_errors:
  146. raise RuntimeError(
  147. 'One or more Django models did not validate:\n{0}'.format(
  148. s.getvalue()))
  149. def install(self):
  150. signals.beat_embedded_init.connect(self.close_database)
  151. signals.worker_ready.connect(self.on_worker_ready)
  152. signals.task_prerun.connect(self.on_task_prerun)
  153. signals.task_postrun.connect(self.on_task_postrun)
  154. signals.worker_process_init.connect(self.on_worker_process_init)
  155. self.close_database()
  156. self.close_cache()
  157. return self
  158. def on_worker_process_init(self, **kwargs):
  159. # Child process must validate models again if on Windows,
  160. # or if they were started using execv.
  161. if os.environ.get('FORKED_BY_MULTIPROCESSING'):
  162. self.validate_models()
  163. # close connections:
  164. # the parent process may have established these,
  165. # so need to close them.
  166. # calling db.close() on some DB connections will cause
  167. # the inherited DB conn to also get broken in the parent
  168. # process so we need to remove it without triggering any
  169. # network IO that close() might cause.
  170. try:
  171. for c in self._db.connections.all():
  172. if c and c.connection:
  173. _maybe_close_fd(c.connection)
  174. except AttributeError:
  175. if self._db.connection and self._db.connection.connection:
  176. _maybe_close_fd(self._db.connection.connection)
  177. # use the _ version to avoid DB_REUSE preventing the conn.close() call
  178. self._close_database()
  179. self.close_cache()
  180. def on_task_prerun(self, sender, **kwargs):
  181. """Called before every task."""
  182. if not getattr(sender.request, 'is_eager', False):
  183. self.close_database()
  184. def on_task_postrun(self, sender, **kwargs):
  185. # See http://groups.google.com/group/django-users/
  186. # browse_thread/thread/78200863d0c07c6d/
  187. if not getattr(sender.request, 'is_eager', False):
  188. self.close_database()
  189. self.close_cache()
  190. def close_database(self, **kwargs):
  191. if self._close_old_connections:
  192. return self._close_old_connections() # Django 1.6
  193. if not self.db_reuse_max:
  194. return self._close_database()
  195. if self._db_recycles >= self.db_reuse_max * 2:
  196. self._db_recycles = 0
  197. self._close_database()
  198. self._db_recycles += 1
  199. def _close_database(self):
  200. try:
  201. funs = [conn.close for conn in self._db.connections.all()]
  202. except AttributeError:
  203. if hasattr(self._db, 'close_old_connections'): # django 1.6
  204. funs = [self._db.close_old_connections]
  205. else:
  206. # pre multidb, pending deprication in django 1.6
  207. funs = [self._db.close_connection]
  208. for close in funs:
  209. try:
  210. close()
  211. except self.database_errors as exc:
  212. str_exc = str(exc)
  213. if 'closed' not in str_exc and 'not connected' not in str_exc:
  214. raise
  215. def close_cache(self):
  216. try:
  217. self._cache.cache.close()
  218. except (TypeError, AttributeError):
  219. pass
  220. def on_worker_ready(self, **kwargs):
  221. if self._settings.DEBUG:
  222. warnings.warn('Using settings.DEBUG leads to a memory leak, never '
  223. 'use this setting in production environments!')