| | import argparse |
| | import subprocess |
| | import sys |
| | import time |
| | from pathlib import Path |
| | from watchdog.events import FileSystemEventHandler |
| | from watchdog.observers import Observer |
| |
|
| |
|
| | class RestartOnChange(FileSystemEventHandler): |
| | """Restart a subprocess when watched files change.""" |
| |
|
| | def __init__(self, command: list[str], watch_paths: list[str]) -> None: |
| | self.command = command |
| | self.watch_paths = [Path(p).resolve() for p in watch_paths] |
| | self.process: subprocess.Popen | None = None |
| | self.restart() |
| |
|
| | def restart(self) -> None: |
| | if self.process and self.process.poll() is None: |
| | self.process.terminate() |
| | try: |
| | self.process.wait(timeout=5) |
| | except subprocess.TimeoutExpired: |
| | self.process.kill() |
| | self.process.wait() |
| | self.process = subprocess.Popen(self.command) |
| |
|
| | def on_any_event(self, event) -> None: |
| | if event.is_directory: |
| | return |
| | path = Path(event.src_path) |
| | if path.suffix != ".py": |
| | return |
| | if any(str(path).startswith(str(p)) for p in self.watch_paths): |
| | print(f"[watcher] {path} changed, running tests...") |
| | subprocess.run([sys.executable, "-m", "pytest", "-q"]) |
| | print("[watcher] restarting process...") |
| | self.restart() |
| |
|
| |
|
| | def main() -> None: |
| | parser = argparse.ArgumentParser( |
| | description="Watch files and restart a command on changes", |
| | ) |
| | parser.add_argument( |
| | "--command", |
| | nargs="+", |
| | default=[sys.executable, "mcp_server.py"], |
| | help="Command to run", |
| | ) |
| | parser.add_argument( |
| | "--paths", |
| | nargs="+", |
| | default=["bit_transformer", "mcp_server.py"], |
| | help="Paths to watch for changes", |
| | ) |
| | args = parser.parse_args() |
| |
|
| | observer = Observer() |
| | handler = RestartOnChange(args.command, args.paths) |
| | for p in args.paths: |
| | observer.schedule(handler, p, recursive=True) |
| | observer.start() |
| | try: |
| | while True: |
| | time.sleep(1) |
| | except KeyboardInterrupt: |
| | pass |
| | finally: |
| | observer.stop() |
| | handler.restart() |
| | if handler.process and handler.process.poll() is None: |
| | handler.process.terminate() |
| | handler.process.wait() |
| | observer.join() |
| |
|
| |
|
| | if __name__ == "__main__": |
| | main() |
| |
|