{"id":2537,"library":"hupper","title":"hupper","description":"hupper is an integrated process monitor for your Python process, designed for development. It automatically tracks changes to imported Python files and custom paths, restarting the process when files are modified. Reloads can also be triggered manually from code. It is currently at version 1.12.1 and maintains an active release cadence.","status":"active","version":"1.12.1","language":"en","source_language":"en","source_url":"https://github.com/Pylons/hupper","tags":["development","reloader","process-monitor","hot-reloading","daemon"],"install":[{"cmd":"pip install hupper","lang":"bash","label":"Install stable release"}],"dependencies":[{"reason":"Optional dependency for more efficient file monitoring (inotify-style filesystem events) instead of polling.","package":"watchdog","optional":true}],"imports":[{"symbol":"start_reloader","correct":"from hupper import start_reloader"},{"symbol":"is_active","correct":"from hupper import is_active"},{"symbol":"get_reloader","correct":"from hupper import get_reloader"}],"quickstart":{"code":"import hupper\nimport os\nimport datetime\nimport time\n\ndef worker_func():\n    \"\"\"This function contains the main application logic, re-executed on reload.\"\"\"\n    now = datetime.datetime.now()\n    print(f\"Worker process active at {now}. Edit 'my_app.py' to trigger reload.\")\n    # In a real application, this would be your main application loop\n    # (e.g., a web server, a background task). For this example, we just\n    # sleep briefly to demonstrate the worker's lifecycle.\n    try:\n        while True:\n            time.sleep(1)\n    except KeyboardInterrupt:\n        print(\"Worker received KeyboardInterrupt, shutting down.\")\n\ndef main():\n    if os.environ.get('HUPPER_RELOADER') != 'true':\n        # This code runs in the *monitor* process\n        print(\"Monitor: Starting reloader for 'my_app.worker_func'...\")\n        # The first call to start_reloader from the main process\n        # forks a worker, starts monitoring, and never returns in this process.\n        reloader = hupper.start_reloader('my_app.worker_func')\n        # Optional: Watch additional files/directories\n        # reloader.watch_files(['./config.ini'])\n        print(\"Monitor: Reloader stopped.\") # This line is usually not reached\n    else:\n        # This code runs in the *worker* process, after being forked by the monitor\n        print(\"Worker: This is a reloaded worker process.\")\n        worker_func()\n\nif __name__ == '__main__':\n    main()\n","lang":"python","description":"To use hupper, define your application's entry point as a callable function. Call `hupper.start_reloader()` from your main script, passing the dotted path to this entry point. `hupper` will then fork a worker process to run your application and monitor for file changes. If changes are detected, the worker process will be restarted. The `HUPPER_RELOADER` environment variable distinguishes between the monitor and worker processes internally. Running the script and then modifying `my_app.py` will demonstrate the reload functionality."},"warnings":[{"fix":"Upgrade your Python environment to 3.7+ before upgrading hupper to 1.11 or later.","message":"Version 1.11 dropped support for Python 2.7, 3.4, 3.5, and 3.6. Ensure your environment is Python 3.7 or newer.","severity":"breaking","affected_versions":"<1.11"},{"fix":"Always ensure `reload_interval` is a positive integer or omit it to use the default.","message":"The `reload_interval` parameter, which controls how often the filesystem is scanned, must be set to a value greater than 0. Setting it to 0 or less could cause needless CPU spinning and is explicitly disallowed since version 1.11. The default is 1 second.","severity":"gotcha","affected_versions":">=1.11"},{"fix":"Upgrade to hupper 1.10 or later to benefit from improved SIGTERM handling, which gracefully forwards the signal to the child process.","message":"Prior to version 1.10, hupper's handling of SIGTERM signals could be problematic, especially in containerized environments like Docker. It would immediately shut down, potentially stranding the worker process.","severity":"gotcha","affected_versions":"<1.10"},{"fix":"Design your application's entry point to account for this dual behavior, typically by checking `os.environ.get('HUPPER_RELOADER')` or by structuring your `main` function as shown in the quickstart.","message":"The `hupper.start_reloader()` function behaves differently based on whether it's called from the parent (monitor) process or a child (worker) process. When called initially from the parent, it forks a new worker, starts the monitor, and *never returns* in the parent process. When called from a worker process, it returns an `IReloaderProxy` instance to communicate with the monitor.","severity":"gotcha","affected_versions":"All"},{"fix":"Set the `HUPPER_DEFAULT_MONITOR` environment variable (e.g., `export HUPPER_DEFAULT_MONITOR=hupper.polling.PollingFileMonitor`) before running your hupper-monitored application if you need to force a specific monitoring mechanism.","message":"You can override hupper's default file monitor (which auto-selects between `watchdog` and `polling`) by setting the `HUPPER_DEFAULT_MONITOR` environment variable to a dotted Python path of an `IFileMonitorFactory` implementation (e.g., `hupper.polling.PollingFileMonitor`).","severity":"gotcha","affected_versions":"All"}],"env_vars":null,"last_verified":"2026-04-10T00:00:00.000Z","next_check":"2026-07-09T00:00:00.000Z"}