duck.contrib.reloader.dependency_graphยถ
Provides a class to build a reverse dependency graph for Python modules, track runtime/dynamic imports lazily, and determine safe reload order.
Intended for use in hot-reload systems, e.g., DuckSightReloader.
Key Concepts:
Reverse Dependency Graph
Maps a module to the set of modules that depend on it.
Example: module_a.py module_b.py -> imports module_a module_c.py -> imports module_b
Reverse graph:
{ "module_a": {"module_b"}, "module_b": {"module_c"} }If module_a changes, we reload module_b and module_c in safe order.
Static Imports
Collected using AST parsing from the source code.
Detects top-level and nested imports (in functions/classes).
Runtime Imports
Captured dynamically by patching Pythonโs built-in import.
Tracks imports that happen inside setup functions or dynamically loaded code.
Safe Reload Workflow
Initialize DependencyGraph before importing your main app.
Optionally pre-build the graph for all files at startup.
On file change:
Lazily build graph for changed file
Merge into global graph
Compute affected modules with get_modules_to_reload()
Reload affected modules using importlib.reload()
Apply SETTINGS.reload() or other mutable object updates
Repeat for subsequent file changes.
Usage Summary:
from duck.contrib.reloader.dependency_graph import DependencyGraph # Initialize BEFORE importing app graph = DependencyGraph(project_root=".") # Optional: pre-build graph for all project files for py_file in Path(".").rglob("*.py"): local_graph, _ = graph.build_graph_for_file(str(py_file)) graph.merge_graph(local_graph) # Import app after graph is ready from duck.app import App app = App() app.run() # On file change: local_graph, changed_module = graph.build_graph_for_file(changed_file) graph.merge_graph(local_graph) affected = graph.get_modules_to_reload(changed_module) for mod_name in affected: importlib.reload(sys.modules[mod_name])Notes:
Always initialize DependencyGraph before any other imports to capture runtime imports.
Supports lazy updates: only rebuild for changed files, not the whole project.
Leaf-first reload order ensures safe reloading of dependencies.
Runtime imports captured automatically; manual additions possible via add_runtime_dependency().
Author: Brian Musakwa
Module Contentsยถ
Classesยถ
Tracks module dependencies (static and runtime) and provides methods to determine safe reload order for affected modules. |
APIยถ
- class duck.contrib.reloader.dependency_graph.DependencyGraph(project_root: str = '.')[source]ยถ
Tracks module dependencies (static and runtime) and provides methods to determine safe reload order for affected modules.
Initialization
Initialize the dependency graph.
- Parameters:
project_root โ Root directory of the project for module path resolution.
- _tracked_import(name, globals=None, locals=None, fromlist=(), level=0)[source]ยถ
Intercepts dynamic imports and updates the dependency graph lazily.
- Parameters:
name โ Name of the module being imported.
- add_runtime_dependency(module_name: str, imported_module: str)[source]ยถ
Manually add a runtime dependency (optional).
- Parameters:
module_name โ The module that imported another.
imported_module โ The module that was imported.
- build_graph_for_file(file_path: str) tuple[dict, str][source]ยถ
Build a reverse dependency graph for a specific file lazily.
- Parameters:
file_path โ File to parse.
- Returns:
Local graph {imported_module: set([current_module])}, Current module name
- Return type:
tuple[dict, str]
- get_modules_to_reload(changed_module: str) list[str][source]ยถ
Return a list of modules affected by a changed module, in safe reload order (leaf-first).
- Parameters:
changed_module โ Module that has changed.
- Returns:
Modules to reload in order.
- Return type:
list[str]
- merge_graph(local_graph: dict)[source]ยถ
Merge a local graph into the global reverse dependency graph.
- Parameters:
local_graph โ {imported_module: set([dependent_modules])}
- module_name_from_path(file_path: str) str[source]ยถ
Convert a file path to a Python module name relative to project root.
- Parameters:
file_path โ Path to the Python file.
- Returns:
Python module name (dot-separated).
- Return type:
str