#!/usr/bin/env python3 """ Watch for code changes and restart a target container. This script watches one or more directories (set via the `WATCH_PATHS` environment variable, comma-separated) and restarts the container named by `TARGET_CONTAINER` (defaults to `ur_gia`) when a filesystem change is detected. Typical usage in this repo (examples are provided in `docker-compose.yml`): - To restart the runtime router (UR) when core code changes set: WATCH_PATHS=/code/core TARGET_CONTAINER=ur_gia - To restart the scheduling command when app code changes set: WATCH_PATHS=/code/app TARGET_CONTAINER=scheduling_gia If you need to force a restart manually (for example to refresh a running management command), you can "touch" any file under the watched path: docker compose exec sh -c 'touch /code/core/__restart__' The watcher ignores `__pycache__`, `.pyc` files and `.git` paths. """ import os import sys import time import subprocess from pathlib import Path from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class ChangeHandler(FileSystemEventHandler): def __init__(self): self.last_restart = 0 self.restart_debounce = 2 # seconds def on_modified(self, event): self._check_and_restart(event.src_path) def on_created(self, event): self._check_and_restart(event.src_path) def on_deleted(self, event): self._check_and_restart(event.src_path) def _check_and_restart(self, path): # Ignore pycache and compiled files if '__pycache__' in path or '.pyc' in path or '.git' in path: return now = time.time() if now - self.last_restart < self.restart_debounce: return self.last_restart = now print(f'[{time.strftime("%H:%M:%S")}] Change detected: {path}', flush=True) time.sleep(1) self._restart_ur() def _restart_ur(self): # Determine target container from environment (default `ur_gia`) target = os.environ.get('TARGET_CONTAINER', 'ur_gia') print(f'[{time.strftime("%H:%M:%S")}] Restarting {target}...', flush=True) # Try podman first (preferred in this setup), then docker cmd = f"podman restart {target} 2>/dev/null || docker restart {target} 2>/dev/null" result = subprocess.run(cmd, shell=True, capture_output=True) if result.returncode == 0: print(f'[{time.strftime("%H:%M:%S")}] {target} restarted successfully', flush=True) else: print(f'[{time.strftime("%H:%M:%S")}] {target} restart failed', flush=True) time.sleep(1) def main(): handler = ChangeHandler() observer = Observer() # Allow overriding watched paths via environment variable `WATCH_PATHS`. # Default is `/code/core,/code/app` but you can set e.g. `WATCH_PATHS=/code/core` watch_paths_env = os.environ.get('WATCH_PATHS', '/code/core,/code/app') watch_paths = [p.strip() for p in watch_paths_env.split(',') if p.strip()] for path in watch_paths: if os.path.exists(path): observer.schedule(handler, path, recursive=True) print(f'Watching: {path}', flush=True) else: print(f'Not found (will not watch): {path}', flush=True) observer.start() print(f'[{time.strftime("%H:%M:%S")}] File watcher started. Monitoring for changes...', flush=True) try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() if __name__ == '__main__': main()