User Guide
Overview
LiveImport eliminates the need to restart a notebook’s Python kernel to
reimport code under development in external files. Suppose you are maintaining
symbolic math code in symcode.py, LaTeX formatting utilities in
printmath.py, and a simulator in simulator.py. In the first cell of a
notebook, you might write
import liveimport import numpy as np import matplotlib.pyplot as plot
and in the second
%%liveimport import symcode from printmath import print_math, print_equations as print_eq from simulator import *
When the %%liveimport cell is run, LiveImport executes and registers the
import statements. Thereafter, whenever you run cells, LiveImport will reload
any of symcode, printmath, and simulator that have changed since
registration or their last reload. LiveImport deems a module changed when its
source file modification time changes.
LiveImport also updates imported module names. For example, if you modify
printmath.py, LiveImport will reload printmath and bind print_math
and print_eq in the global namespace to the new definitions. Similarly, if
you modify simulator.py, LiveImport will create or update bindings for
every public name in simulator (where public means in __all__ if
present, and not starting with _ otherwise.)
Importantly, LiveImport only updates names in the same way the original
import statements would. If your notebook and symcode both happened to
define a variable gamma, reloading symcode would not overwrite your
notebook’s value of gamma. Though it isn’t implemented this way, you can
think of LiveImport as re-executing the registered import statements when a
module reloads.
Tracked Modules
Modules that are monitored by LiveImport for modification are called tracked modules. The process of bringing registered imports up to date by reloading tracked modules and updating names is called syncing.
There are two ways a module becomes tracked. First, it can be referenced by a
registered import statement. In the Overview example, symcode,
printmath, and simulator are all tracked modules. Second, a module in
LiveImport’s workspace (described below) that is referenced by a top-level
import in a tracked module is itself tracked.
For example, suppose there is a module timeutils in the workspace that is
imported by simulator. Then, even if timeutils is not mentioned in any
way in the notebook, LiveImport will track timeutils. If you edit
timeutils.py, LiveImport will reload timeutils, then reload
simulator, then rebind the names imported from simulator in the
notebook.
The second tracked module determination rule (being referenced by a tracked
module’s top-level import statement) is transitive. If timeutils imports
units, and units is also in the workspace, then LiveImport will track
units.
LiveImport’s workspace is a set of directories. A module is in the workspace
if its Python source file is under any of those directories. The default
workspace is a single directory, the current working directory when LiveImport
is first imported. Unless you change it, that is the directory containing your
notebook. So, by default, any Python source files alongside your notebook are
in LiveImport’s workspace. You can change the workspace by calling
workspace().
The workspace determines only which modules are candidates for tracking because of top-level imports from other tracked modules. Modules referenced by registered import statements are always tracked, regardless of the workspace.
Dependency Analysis
LiveImport analyzes top-level import dependencies between tracked modules to ensure reloading is consistent with those dependencies and to prevent stale references between modules.
Suppose simulator imports x from symcode where x is defined in
symcode.py as
x = sympy.Symbol("x", positive=True)
If you realize the assumption about symbolic variable x being positive is
unwarranted and change the line to
x = sympy.Symbol("x")
simulator must be reloaded — and reloaded after symcode — otherwise it
would still refer to the old definition of x with the assumption of
positivity. simulator’s evaluations involving x would be incorrect.
Fortunately, LiveImport’s dependency aware reloading does exactly that. When
you modify symcode.py, LiveImport reloads simulator after it reloads
symcode even if simulator.py has not changed. If you do modify both,
LiveImport takes care to reload symcode first. No stale references are
created.
Registration Details
While %%liveimport cells often contain only import statements, they can
actually contain any valid Python code. For example,
#_%%liveimport from models import ConvNet from orchestrate import * # Includes default_learning_rate print(f"Default learning rate is {default_learning_rate}")
When a %%liveimport cell is run, LiveImport first executes the Python
source. If execution completes without an exception, LiveImport extracts from
the cell the top-level import statements. It then registers those statements
and begins tracking the related modules, if they are not already tracked.
LiveImport imposes no restrictions on the registered import statements: the statements can overlap, they can repeat, they can be any kind of import statement that is legal to use in a notebook. For example, the following cell is perfectly fine.
#_%%liveimport from symcode import x, hermite_poly from symcode import x, lagrange_poly as lp from symcode import * import symcode as sc, simulator from utils.latex_support import (QUAD, REAL_NUMBERS as R)
Normally, %%liveimport cells are additive and idempotent. Top-level import
statements are registered by merging them with prior registrations. The
%%liveimport cell magic’s one optional argument --clear (or the short
form, -c) changes that — it causes LiveImport to discard all prior import
registrations when the cell is run.
Best Practice
The recommended practice is for notebooks to have one %%liveimport cell,
and that cell should include option --clear. In the case of the
Overview example, if you are hiding cell magic, that one cell would be
#_%%liveimport --clear import symcode from printmath import print_math, print_equations as print_eq from simulator import *
This way, if you want to stop tracking modifications to symcode.py, you can
simply delete import symcode and rerun the containing cell. If you do have
multiple %%liveimport cells, then it’s best if the first (and only the
first) uses option --clear.
Reload Reports
By default, LiveImport displays Markdown console blocks to report when it automatically reloads modules in a notebook, something like
Reloaded symcode modified 18 seconds ago Reloaded simulator because symcode reloaded
You can disable these reports by calling
auto_sync(report=False).
Import Statement Order
LiveImport name rebinding is consistent with import statement execution order. Consider a notebook with cell
#_%%liveimport --clear from highlight import * from formatutil import *
where both highlight and formatutil both define a name red.
Whenever that cell runs, the from formatutil import * statement executes
second, so Python binds red in the global namespace to formatutil.red.
To match that behavior, if you modify highlight.py, LiveImport reloads
highlight, but does not rebind red to highlight.red — it remains
bound to formatutil.red. If you modify formatutil.py, LiveImport
reloads formatutil and rebinds red to formatutil’s possibly new
value.
Overall execution order is what counts. Suppose the cell above is split into
two and the --clear option is removed:
#_%%liveimport from highlight import *
and
#_%%liveimport from formatutil import *
If you run the notebook top down, LiveImport will again preserve
formatutil’s value for red when it reloads highlight. But if you
re-run just the first cell, Python binds red to highlight.red, and
LiveImport will now rebind red to highlight.red when it reloads
highlight.
Top-Level Imports
A top-level import is any import ... or from ... import ... statement
in Python source that is not nested within another Python construct such as an
if or try statement.
import colors if _need_debug: from debug import * def report(data): from formatutil import layout generate_table(layout,data) from common import epilogue
In the code above, import colors and from common import epilogue are
the only two top-level import statements. LiveImport only processes top-level
imports in Python source. That affects both registration through
%%liveimport cells and module dependency tracking.
If it’s essential for your application, you can use the API to implement conditional imports that are registered. There is an example in the next section.
Programmatic Registration
As an alternative to %%liveimport cell magic, you can register import
statements by calling register(). Unlike %%liveimport
cell magic, register() does not execute the import statements it
registers, so you must do so before calling register(). The cell below
is equivalent to the Overview example.
import liveimport import numpy as np import matplotlib.pyplot as plot import symcode from printmath import print_math, print_equations as print_eq from simulator import * liveimport.register(globals(),""" import symcode from printmath import print_math, print_equations as print_eq from simulator import * """, clear=True)
While cell magic is far more convenient, programmatic registration might be useful if your notebook conditionally imports modules you wish to automatically reload, something like
from packaging.version import Version if Version(sympy.__version__) < Version("1.13"): from sympyshim import groups_count liveimport.register(globals(),"from sympyshim import groups_count") else: from sympy.combinatorics.group_numbers import groups_count
Managing Synchronization
In order to avoid reloading modules between cell executions of a multi-cell run
(such as when running all cells), LiveImport suppresses module modification
checks for a grace period after each cell execution. The default grace period
is one second. You can change the grace period by calling
auto_sync(grace=...).
You can also disable automatic syncing altogether by calling
auto_sync(enabled=False) and rely on explicit syncing
through calls to sync() instead.
Outside of Notebooks
You can use LiveImport outside of notebook environments, but in that case, you
must use programmatic registration and explicitly sync via
register() and sync() respectively.