| # This code lifted from the mod_wsgi docs. |
| import os |
| from pathlib import Path |
| from typing import Sequence |
| import sys |
| import signal |
| import threading |
| import atexit |
| import queue |
| |
| _interval = 1.0 |
| _times = {} |
| _files = [] # type: Sequence[Path] |
| |
| _running = False |
| _queue = queue.Queue() # type: queue.Queue |
| _lock = threading.Lock() |
| |
| |
| def _restart(path): |
| _queue.put(True) |
| prefix = 'monitor (pid=%d):' % os.getpid() |
| print('%s Change detected to \'%s\'.' % (prefix, path), file=sys.stderr) |
| print('%s Triggering process restart.' % prefix, file=sys.stderr) |
| os.kill(os.getpid(), signal.SIGINT) |
| |
| |
| def _modified(path): |
| try: |
| # If path doesn't denote a file and were previously |
| # tracking it, then it has been removed or the file type |
| # has changed so force a restart. If not previously |
| # tracking the file then we can ignore it as probably |
| # pseudo reference such as when file extracted from a |
| # collection of modules contained in a zip file. |
| |
| if not os.path.isfile(path): |
| return path in _times |
| |
| # Check for when file last modified. |
| |
| mtime = os.stat(path).st_mtime |
| if path not in _times: |
| _times[path] = mtime |
| |
| # Force restart when modification time has changed, even |
| # if time now older, as that could indicate older file |
| # has been restored. |
| |
| if mtime != _times[path]: |
| return True |
| except Exception: |
| # If any exception occured, likely that file has been |
| # been removed just before stat(), so force a restart. |
| |
| return True |
| |
| return False |
| |
| |
| def _monitor(): |
| while True: |
| # Check modification times on all files in sys.modules. |
| |
| for module in sys.modules.values(): |
| if not hasattr(module, '__file__'): |
| continue |
| path = getattr(module, '__file__') |
| if not path: |
| continue |
| if os.path.splitext(path)[1] in ['.pyc', '.pyo', '.pyd']: |
| path = path[:-1] |
| |
| if _modified(path): |
| return _restart(path) |
| |
| # Check modification times on files which have |
| # specifically been registered for monitoring. |
| |
| for path in _files: |
| if _modified(path): |
| return _restart(path) |
| |
| # Go to sleep for specified interval. |
| |
| try: |
| return _queue.get(timeout=_interval) |
| except Exception: |
| pass |
| |
| |
| _thread = threading.Thread(target=_monitor) |
| _thread.setDaemon(True) |
| |
| |
| def _exiting(): |
| try: |
| _queue.put(True) |
| except Exception: |
| pass |
| _thread.join() |
| |
| |
| atexit.register(_exiting) |
| |
| |
| def track(path): |
| if path not in _files: |
| _files.append(path) |
| |
| |
| def start(interval=1.0): |
| global _interval |
| if interval < _interval: |
| _interval = interval |
| |
| global _running |
| _lock.acquire() |
| if not _running: |
| prefix = 'monitor (pid=%d):' % os.getpid() |
| print('%s Starting change monitor.' % prefix, file=sys.stderr) |
| _running = True |
| _thread.start() |