reloader.py 3.7 KB

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