Hot Reload Guide¶
Domovoy automatically reloads apps when their source files change, enabling rapid development without restarts.
How Hot Reload Works¶
File Watcher: Domovoy monitors your app directory for changes
Dependency Tracking: Tracks which modules each app imports
Deep Reload: When a file changes, reloads all affected modules
App Restart: Terminates and reinitializes affected apps
Development Workflow¶
Start Domovoy¶
python domovoy/cli.py --config config.yml
Edit Your Apps¶
Save changes to any app file, and Domovoy will:
Detect the file change
Call
finalize()on affected appsReload the modified modules
Call
initialize()on restarted apps
Watch the Logs¶
INFO: File changed: /apps/lighting_apps.py
INFO: Reloading app: sunset_light
INFO: App sunset_light terminated
INFO: App sunset_light initialized
Making Apps Reload-Safe¶
Idempotent Initialization¶
Your initialize() method may be called multiple times due to reloads:
async def initialize(self) -> None:
# Good: Each reload creates fresh listeners
# Old listeners are automatically cleaned up
self.callbacks.listen_state(
self.config.entity_id,
self.on_change,
)
# Good: ServEnts entities are deduplicated by ID
self.sensor = await self.servents.create_sensor(
servent_id="my_sensor", # Same ID = same entity
name="My Sensor",
)
Cleanup in finalize()¶
Use finalize() to clean up resources that shouldn’t persist:
async def initialize(self) -> None:
self.session = aiohttp.ClientSession()
async def finalize(self) -> None:
# Clean up external resources
if hasattr(self, 'session'):
await self.session.close()
Avoid Global State¶
# Bad: Global state persists across reloads
_global_counter = 0
class MyApp(AppBase[MyAppConfig]):
async def initialize(self) -> None:
global _global_counter
_global_counter += 1 # Keeps incrementing on reload!
# Good: Instance state is reset on reload
class MyApp(AppBase[MyAppConfig]):
async def initialize(self) -> None:
self.counter = 0 # Fresh on each reload
Dependency Tracking¶
Domovoy tracks module dependencies automatically:
apps/
├── lighting_apps.py # App file
├── climate_apps.py # App file
└── utils/
└── helpers.py # Shared module
If helpers.py changes:
Apps that import
helperswill be reloadedApps that don’t import it are unaffected
Reload Triggers¶
Hot reload triggers when:
App file (ending with
_apps.py) is modifiedAny Python module imported by an app is modified
Configuration changes that require a restart
Hot reload does NOT trigger when:
Non-Python files change
Files outside the app directory change
Config file changes (requires manual restart)
Debugging Reloads¶
Check App Status¶
Use the meta plugin to check current status:
status = self.meta.get_status()
self.log.info("App status: {status}", status=status)
Manual Restart¶
Create a reload button for manual restarts:
async def initialize(self) -> None:
await self.servents.enable_reload_button()
Or programmatically:
await self.meta.restart_app()
Logging on Lifecycle Events¶
async def initialize(self) -> None:
self.log.info("App initializing...")
# Setup code
self.log.info("App initialized successfully")
async def finalize(self) -> None:
self.log.info("App finalizing...")
# Cleanup code
self.log.info("App finalized")
Common Issues¶
State Lost on Reload¶
Use ServEnts to persist state in Home Assistant:
async def initialize(self) -> None:
self.counter_sensor = await self.servents.create_sensor(
servent_id="counter",
name="Counter",
default_state=0,
)
# State persists in HA across reloads
current = self.counter_sensor.get_state()
Callbacks Not Firing After Reload¶
Callbacks are automatically cleaned up and recreated. If callbacks aren’t firing:
Check for errors in
initialize()Verify entity IDs are correct
Check Home Assistant connection
Module Import Errors¶
If a syntax error prevents loading:
ERROR: Failed to reload module: my_apps
ERROR: SyntaxError on line 42
Fix the error and save - Domovoy will retry automatically.
Best Practices¶
Keep Apps Small¶
Smaller apps reload faster and are easier to debug:
# Good: One responsibility per app
class LightController(AppBase[LightConfig]):
...
class MotionHandler(AppBase[MotionConfig]):
...
# Avoid: One giant app doing everything
class EverythingApp(AppBase[HugeConfig]):
...
Use Typed Configuration¶
Catch errors at reload time:
@dataclass
class MyAppConfig(AppConfigBase):
entity_id: EntityID # Type checked
threshold: float # Type checked
Test Incrementally¶
Make a small change
Save and watch logs
Verify behavior
Repeat
Production Considerations¶
In production:
Hot reload continues to work
Consider logging reload events
Monitor for reload loops (continuous errors)
Use
finalize()to ensure clean shutdown
async def finalize(self) -> None:
self.log.info("App shutting down - cleaning up resources")
# Ensure all cleanup happens