reloader.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # -*- coding: utf-8 -
  2. #
  3. # This file is part of gunicorn released under the MIT license.
  4. # See the NOTICE for more information.
  5. import os
  6. import os.path
  7. import re
  8. import sys
  9. import time
  10. import threading
  11. class Reloader(threading.Thread):
  12. def __init__(self, extra_files=None, interval=1, callback=None):
  13. super(Reloader, self).__init__()
  14. self.setDaemon(True)
  15. self._extra_files = set(extra_files or ())
  16. self._extra_files_lock = threading.RLock()
  17. self._interval = interval
  18. self._callback = callback
  19. def add_extra_file(self, filename):
  20. with self._extra_files_lock:
  21. self._extra_files.add(filename)
  22. def get_files(self):
  23. fnames = [
  24. re.sub('py[co]$', 'py', module.__file__)
  25. for module in list(sys.modules.values())
  26. if hasattr(module, '__file__')
  27. ]
  28. with self._extra_files_lock:
  29. fnames.extend(self._extra_files)
  30. return fnames
  31. def run(self):
  32. mtimes = {}
  33. while True:
  34. for filename in self.get_files():
  35. try:
  36. mtime = os.stat(filename).st_mtime
  37. except OSError:
  38. continue
  39. old_time = mtimes.get(filename)
  40. if old_time is None:
  41. mtimes[filename] = mtime
  42. continue
  43. elif mtime > old_time:
  44. if self._callback:
  45. self._callback(filename)
  46. time.sleep(self._interval)
  47. try:
  48. from inotify.adapters import Inotify
  49. import inotify.constants
  50. has_inotify = True
  51. except ImportError:
  52. has_inotify = False
  53. class InotifyReloader():
  54. def __init__(self, callback=None):
  55. raise ImportError('You must have the inotify module installed to use '
  56. 'the inotify reloader')
  57. if has_inotify:
  58. class InotifyReloader(threading.Thread):
  59. event_mask = (inotify.constants.IN_CREATE | inotify.constants.IN_DELETE
  60. | inotify.constants.IN_DELETE_SELF | inotify.constants.IN_MODIFY
  61. | inotify.constants.IN_MOVE_SELF | inotify.constants.IN_MOVED_FROM
  62. | inotify.constants.IN_MOVED_TO)
  63. def __init__(self, extra_files=None, callback=None):
  64. super(InotifyReloader, self).__init__()
  65. self.setDaemon(True)
  66. self._callback = callback
  67. self._dirs = set()
  68. self._watcher = Inotify()
  69. def add_extra_file(self, filename):
  70. dirname = os.path.dirname(filename)
  71. if dirname in self._dirs:
  72. return
  73. self._watcher.add_watch(dirname, mask=self.event_mask)
  74. self._dirs.add(dirname)
  75. def get_dirs(self):
  76. fnames = [
  77. os.path.dirname(re.sub('py[co]$', 'py', module.__file__))
  78. for module in list(sys.modules.values())
  79. if hasattr(module, '__file__')
  80. ]
  81. return set(fnames)
  82. def run(self):
  83. self._dirs = self.get_dirs()
  84. for dirname in self._dirs:
  85. self._watcher.add_watch(dirname, mask=self.event_mask)
  86. for event in self._watcher.event_gen():
  87. if event is None:
  88. continue
  89. filename = event[3]
  90. self._callback(filename)
  91. preferred_reloader = InotifyReloader if has_inotify else Reloader
  92. reloader_engines = {
  93. 'auto': preferred_reloader,
  94. 'poll': Reloader,
  95. 'inotify': InotifyReloader,
  96. }