reloader.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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. # pylint: disable=no-else-continue
  6. import os
  7. import os.path
  8. import re
  9. import sys
  10. import time
  11. import threading
  12. COMPILED_EXT_RE = re.compile(r'py[co]$')
  13. class Reloader(threading.Thread):
  14. def __init__(self, extra_files=None, interval=1, callback=None):
  15. super().__init__()
  16. self.setDaemon(True)
  17. self._extra_files = set(extra_files or ())
  18. self._interval = interval
  19. self._callback = callback
  20. def add_extra_file(self, filename):
  21. self._extra_files.add(filename)
  22. def get_files(self):
  23. fnames = [
  24. COMPILED_EXT_RE.sub('py', module.__file__)
  25. for module in tuple(sys.modules.values())
  26. if getattr(module, '__file__', None)
  27. ]
  28. fnames.extend(self._extra_files)
  29. return fnames
  30. def run(self):
  31. mtimes = {}
  32. while True:
  33. for filename in self.get_files():
  34. try:
  35. mtime = os.stat(filename).st_mtime
  36. except OSError:
  37. continue
  38. old_time = mtimes.get(filename)
  39. if old_time is None:
  40. mtimes[filename] = mtime
  41. continue
  42. elif mtime > old_time:
  43. if self._callback:
  44. self._callback(filename)
  45. time.sleep(self._interval)
  46. has_inotify = False
  47. if sys.platform.startswith('linux'):
  48. try:
  49. from inotify.adapters import Inotify
  50. import inotify.constants
  51. has_inotify = True
  52. except ImportError:
  53. pass
  54. if has_inotify:
  55. class InotifyReloader(threading.Thread):
  56. event_mask = (inotify.constants.IN_CREATE | inotify.constants.IN_DELETE
  57. | inotify.constants.IN_DELETE_SELF | inotify.constants.IN_MODIFY
  58. | inotify.constants.IN_MOVE_SELF | inotify.constants.IN_MOVED_FROM
  59. | inotify.constants.IN_MOVED_TO)
  60. def __init__(self, extra_files=None, callback=None):
  61. super().__init__()
  62. self.setDaemon(True)
  63. self._callback = callback
  64. self._dirs = set()
  65. self._watcher = Inotify()
  66. for extra_file in extra_files:
  67. self.add_extra_file(extra_file)
  68. def add_extra_file(self, filename):
  69. dirname = os.path.dirname(filename)
  70. if dirname in self._dirs:
  71. return
  72. self._watcher.add_watch(dirname, mask=self.event_mask)
  73. self._dirs.add(dirname)
  74. def get_dirs(self):
  75. fnames = [
  76. os.path.dirname(os.path.abspath(COMPILED_EXT_RE.sub('py', module.__file__)))
  77. for module in tuple(sys.modules.values())
  78. if getattr(module, '__file__', None)
  79. ]
  80. return set(fnames)
  81. def run(self):
  82. self._dirs = self.get_dirs()
  83. for dirname in self._dirs:
  84. if os.path.isdir(dirname):
  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. else:
  92. class InotifyReloader(object):
  93. def __init__(self, callback=None):
  94. raise ImportError('You must have the inotify module installed to '
  95. 'use the inotify reloader')
  96. preferred_reloader = InotifyReloader if has_inotify else Reloader
  97. reloader_engines = {
  98. 'auto': preferred_reloader,
  99. 'poll': Reloader,
  100. 'inotify': InotifyReloader,
  101. }