Skip to content

Hot reload

Hot-reload comes in two cooperating pieces: a host-side file watcher that pushes changed .py files to the device, and a device-side module reloader that swaps the new code in and re-renders the active page. Both are wired up automatically by pn run --hot-reload.

Hot-reload support for PythonNative development.

Two cooperating pieces:

  • Host-side: FileWatcher polls the developer's app/ directory for .py changes and triggers a callback (typically adb push on Android or a simctl file copy on iOS).
  • Device-side: ModuleReloader reloads changed Python modules using importlib.reload and asks the page host to re-render the current tree.
Example

Integrated into pn run --hot-reload:

from pythonnative.hot_reload import FileWatcher

def push(changed):
    for path in changed:
        print("changed:", path)

watcher = FileWatcher("app/", on_change=push)
watcher.start()

Classes:

Name Description
FileWatcher

Watch a directory tree for .py file changes.

ModuleReloader

Reload changed Python modules on device and trigger a re-render.

FileWatcher

FileWatcher(watch_dir: str, on_change: Callable[[List[str]], None], interval: float = 1.0)

Watch a directory tree for .py file changes.

Uses simple os.path.getmtime polling rather than a native inotify/FSEvents binding so the watcher works on every platform where Python runs without extra dependencies.

Parameters:

Name Type Description Default
watch_dir str

Directory to watch (recursively).

required
on_change Callable[[List[str]], None]

Called with a list of changed file paths when modifications are detected.

required
interval float

Polling interval, in seconds.

1.0

Attributes:

Name Type Description
watch_dir

Directory being watched.

on_change

Change callback.

interval

Polling interval.

Methods:

Name Description
start

Begin watching in a background daemon thread.

stop

Stop the watcher and join the background thread.

start

start() -> None

Begin watching in a background daemon thread.

Performs an initial scan to seed mtimes so the first notification reflects subsequent edits, not pre-existing files.

stop

stop() -> None

Stop the watcher and join the background thread.

ModuleReloader

Reload changed Python modules on device and trigger a re-render.

Designed to be invoked from device-side glue when a hot-reload push completes. The class itself holds no state; all methods are static.

Methods:

Name Description
reload_module

Reload a single module by its dotted name.

file_to_module

Convert a file path to a dotted module name.

reload_page

Force a page re-render after a module reload.

reload_module staticmethod

reload_module(module_name: str) -> bool

Reload a single module by its dotted name.

Parameters:

Name Type Description Default
module_name str

Dotted module name (e.g., "app.main_page").

required

Returns:

Type Description
bool

True if the module was found in sys.modules and

bool

reloaded without raising; False otherwise.

file_to_module staticmethod

file_to_module(file_path: str, base_dir: str = '') -> Optional[str]

Convert a file path to a dotted module name.

Parameters:

Name Type Description Default
file_path str

Path to a .py file (absolute or relative).

required
base_dir str

Base directory that names should be relative to. If empty, file_path is treated as already relative.

''

Returns:

Type Description
Optional[str]

The dotted module name (e.g., "app.pages.home"), or

Optional[str]

None for an empty path.

reload_page staticmethod

reload_page(page_instance: Any) -> None

Force a page re-render after a module reload.

Parameters:

Name Type Description Default
page_instance Any

An _AppHost instance (or duck-typed equivalent) that exposes a _reconciler attribute.

required

Next steps