This release is a pre-release and may not be stable for production use.
ipython-freshrun
Run Python scripts and/or use REPL in a genuinely fresh, interactive IPython session.
ipython-freshrun provides the %freshrun IPython magic. It starts a new blank-slate IPython child session or launches a script in a completely new IPython child process. When running a script, it leaves that child process interactive after the script finishes, and provides convenient shortcuts for repeating or replacing the current session, as well as user-friendly mechanisms to inspect the variables created during the execution, whether successful (return) or not (an exception was raised).
The main motivation is to make it easy and reliable to repeatedly run a script from an absolutely clean Python process, while still being able to inspect its resulting state interactively using all the bells and whistles of IPython.
freshrun is currently maintained by Yann Ziegler. The proof-of-concept and initial version were designed by Yann Ziegler and written by ChatGPT (due to a lack of knowledge on IPython low-level mechanisms and strong time constraints, if you ask). This project was inspired by Jeanne's creative way of using IPython.
Features
- Run a Python script in a completely fresh IPython process every time with
%freshrun my_script.py - ...or simply start a fresh interactive child without running a script with
%freshrun. - Invoke a
%freshruncommand directly from inside a child without having to go back to the parent IPython session (automatic child replacement). - Keep the child IPython session interactive after the script finishes.
- Repeat the most recent
%freshruncommand with Alt+Enter. - Recall the most recent
%freshruncommand with Page Up or Alt+Up. - Preserve normal IPython
%runargument handling. - Support IPython's
-d/--pdbdebugging mode. - When running a script (with or without
-d), capture the final locals frommain()and make them available in the current namespace. - Capture locals from the Python call chain when an exception occurs (including
KeyboardInterrupt) and store them in a dedicated_exception_localsdictionary for in-depth inspection. - After an exception has occurred, pull locals from specific functions or methods directly into the current namespace for easy inspection with
%pull. - Similarly, inject all entries from any dictionary into the current namespace with
%inject. - All of this, while keeping the parent IPython session intact and only replacing the child sessions.
ipython-freshrun is an IPython extension. It is intended for terminal IPython use and does not provide Jupyter-specific functionality.
Why use a fresh process?
A normal %run leaves the Python process itself untouched.
That means state created by previous executions can remain in the process:
- imported modules remain in
sys.modules; - global state can survive;
- singleton objects can remain alive;
- library state can persist;
- background threads or other resources may remain;
- module-level initialization may not happen again in the same way.
%freshrun starts a new Python interpreter instead.
Conceptually:
Parent IPython
│
│ %freshrun example.py
▼
Fresh Python process
│
├── Fresh IPython
├── Run example.py
└── Remain interactive
When you exit that child, control returns to the original parent IPython session.
Interactive child sessions
The child session uses a custom prompt showing the script that was launched:
[example.py] In [1]:
After the script finishes, the child is a normal interactive IPython session.
You can inspect variables, call functions, import modules, and continue working normally.
For example, given:
# example.py
def main():
x = 10
y = 20
result = x + y
if __name__ == "__main__":
main()
running:
%freshrun example.py
leaves the child with the values from main() available:
In [1]: x
Out[1]: 10
In [2]: y
Out[2]: 20
In [3]: result
Out[3]: 30
This makes %freshrun particularly useful for developing conventional scripts while retaining an interactive environment for inspecting their state.
When %freshrun is used without a script, the child displays:
[fresh] In [1]:
No script is executed in this case, but the child otherwise behaves like a normal fresh IPython session.
Setup
Installation
PyPI
Install from PyPI with:
python -m pip install ipython-freshrun
Or install the development version from a local checkout:
python -m pip install -e .
The package requires a recent Python and IPython installation.
Conda
Install from conda-forge:
conda install -c conda-forge ipython-freshrun
Loading the extension
Start IPython and load the extension with:
%load_ext freshrun
After loading it, the %freshrun magic becomes available:
%freshrun my_script.py
You can verify that the extension is loaded with:
%lsmagic
and look for freshrun in the list of line magics.
Loading automatically
If you use freshrun regularly, you can configure IPython to load it every time IPython starts.
First, create the default IPython profile if you do not already have one:
ipython profile create
This normally creates:
~/.ipython/profile_default/
Edit:
~/.ipython/profile_default/ipython_config.py
and add:
c.InteractiveShellApp.extensions.append("freshrun")
If the file already contains an extensions setting, append to the existing list rather than replacing it. For example:
c.InteractiveShellApp.extensions = [
"some_other_extension",
"freshrun",
]
After restarting IPython, %freshrun should be available automatically.
You can find the profile being used by IPython with:
ipython locate profile default
If you use a different profile, put the setting in that profile's ipython_config.py instead.
Documentation
%freshrun
The basic syntax is:
%freshrun
%freshrun script.py [args...]
%freshrun --help
With no arguments, %freshrun starts a completely fresh, interactive IPython child without running any script:
%freshrun
The child is a normal interactive IPython session. It uses a generic prompt:
[fresh] In [1]:
You can use this mode when you simply want a clean Python/IPython process without initially running a script.
To display the command documentation:
%freshrun --help
When a script is specified, it is executed in a newly created IPython process.
For example:
%freshrun example.py
or:
%freshrun example.py first second
The script is executed in a newly created IPython process.
When the script terminates normally, the child IPython session remains open and interactive.
This is different from simply executing:
%run example.py
because %run executes inside the current IPython process, whereas %freshrun deliberately creates a new Python/IPython process.
%inject and %pull
The %inject magic copies all entries from a dictionary into the current IPython namespace:
%inject my_dict
or
%inject {'a': 1, 'b': 2}
For example, if my_dict contains {"x": 10, "y": 20}, both x and y become directly available in the session.
The %pull magic populates the current namespace using an entry from the _exception_locals dictionary (see below for exceptions handling). For a given function or method name as listed in _exception_locals.keys(), an entry corresponds to all the locals of that function or method when the exception occurred. For example,
%pull main.some_module.my_func
makes all the locals from main.some_module.my_func (before the exception was raised) directly available in the current IPython child session.
Note the strict equivalence between:
%pull main.some_module.my_func
and
%inject _exception_locals['main.some_module.my_func']
Thus, %pull may be seen as a handy alias for %inject when the intent is to populate the namespace with locals from a given function or method once an exception has been raised.
Both magics are available in the parent IPython session and fresh child sessions, but only %inject may be useful in the parent (of course, because _exception_locals is only set after an exception occurs in a child).
main() locals
When a conventional script contains:
def main():
x = 123
y = "hello"
if __name__ == "__main__":
main()
%freshrun captures the final local variables from main().
Those variables are copied into the child IPython namespace after main() returns.
The complete captured dictionary is also available as:
_main_locals
For example:
In [1]: _main_locals
Out[1]: {'x': 123, 'y': 'hello'}
The capture is specifically concerned with the script being executed. It does not attempt to serialize arbitrary Python objects between processes; the objects remain in the child process where they were created.
Exception locals
When the executed script raises an exception, %freshrun captures locals from the relevant exception call chain.
The resulting dictionary is available as:
_exception_locals
For example, a script might contain:
def deep():
z = 42
raise RuntimeError("something went wrong")
def inner():
y = 10
deep()
def main():
x = 456
inner()
if __name__ == "__main__":
main()
After the exception, _exception_locals can contain entries corresponding to the functions in the call chain:
{
"deep": {
"z": 42,
},
"inner": {
"y": 10,
},
"main": {
"x": 456,
},
}
The exact contents naturally depend on the state of the frames at the time of the exception.
A short notification is printed when exception locals have been captured:
[freshrun: exception locals() captured in _exception_locals]
deep → inner → main
Debugging with -d
%freshrun supports IPython's debug form:
%freshrun -d example.py
The arguments are passed through to IPython's %run machinery.
This allows the normal IPython debugger (ipdb) workflow to be used inside the fresh child.
For example:
%freshrun -d example.py
The fresh child is still a separate IPython process; the debugger operates inside that child.
The debug path intentionally leaves tracing under IPython/IPDB's control rather than installing the normal sys.settrace() capture mechanism at the same time.
Passing script arguments
Arguments can be passed just as they can with %run:
%freshrun example.py foo bar
Quoted arguments are supported:
%freshrun example.py "hello world" "another argument"
The complete %freshrun argument list is retained and passed through to IPython's %run implementation.
%run options
%freshrun accepts the relevant %run-style arguments used by the extension.
For example:
%freshrun -d example.py
The script itself is identified from the argument list so that %freshrun knows which file's frames should be monitored.
Module execution with:
%freshrun -m some_module
is intentionally not supported.
The command requires a physical script file because the locals-capture mechanism identifies frames by the script filename.
Keyboard shortcuts
freshrun provides several terminal IPython shortcuts.
Alt+Enter
In the parent IPython session, Alt+Enter immediately executes the most recent %freshrun command again.
For example:
%freshrun example.py
After the child exits, pressing Alt+Enter starts another fresh child using the same command.
In a fresh child, Alt+Enter immediately repeats the current fresh run, replacing the current child with another fresh child.
This is useful when repeatedly testing a script from a clean process.
If the child was started with a bare %freshrun, Alt+Enter starts another fresh child without running a script.
Page Up / Alt+Up
In the parent session:
- Page Up recalls the most recent
%freshruncommand from history. - Alt+Up also searches for the most recent
%freshruncommand.
The command is placed into the input buffer but is not executed.
Inside a fresh child, Page Up and Alt+Up place the current %freshrun command into the input buffer.
This provides a convenient way to edit the command before executing it.
Exiting the child
The child is an ordinary interactive IPython process.
You can leave it with:
exit
or:
quit
You can also use the normal terminal EOF shortcut:
Ctrl+D
Depending on the terminal/IPython EOF behavior, Ctrl+D may need to be pressed twice.
After the child terminates, %freshrun returns to the original parent IPython session.
The parent session is not replaced.
Restarting from inside a child
A child can launch another fresh child directly, either with or without a script:
%freshrun
or
%freshrun other.py
The first form replaces the current child with another completely fresh child without running a script. The second replaces it with a fresh child running other.py.
The current child does not spawn a nested permanent hierarchy.
Instead, it writes a restart request and terminates with a dedicated exit status. The parent %freshrun process detects the request and starts another fresh IPython child.
Conceptually:
Parent IPython
│
└── Child A
│
│ %freshrun other.py
▼
Child A exits
│
▼
Child B
This keeps the process tree manageable while still allowing the currently running fresh session to request a completely new one.
Freshness model
The important distinction is between the parent and child processes.
Suppose you start:
IPython A
and execute:
%freshrun example.py
The extension starts:
IPython B
The script executes in B.
Anything created by the script belongs to B, not A.
When B exits, A is still the original process it was before %freshrun was invoked.
This is the core reason for using a separate process rather than attempting to emulate a fresh interpreter by clearing variables or unloading modules.
How it works
The extension consists of three logical components.
Parent extension
The parent extension registers the %freshrun magic with IPython.
It:
- Parses the
%freshruncommand. - Determines the script being executed.
- Creates a temporary working area.
- Creates a restart-request file.
- Starts a new IPython subprocess.
- Passes the required state through environment variables.
- Waits for the child.
- Detects the special restart exit status.
- Reads any restart request.
- Starts the next fresh child when requested.
The parent process remains untouched throughout.
Child startup
The child startup code configures the new IPython instance.
It:
- installs the custom prompt;
- installs the child keyboard shortcuts;
- registers the child version of
%freshrun.
The child %freshrun magic communicates a new request to the parent and terminates the child with the special restart status.
Child capture
The capture code runs the requested script through IPython's %run implementation.
In normal mode it temporarily installs a sys.settrace() function to:
- identify frames belonging to the target script;
- capture
main()locals whenmain()returns; - capture locals from the exception call chain.
In debug mode it allows IPython's debugger to control tracing and temporarily wraps the IPython traceback handler so that the final exception and traceback can be recovered afterward.
Limitations
%run -m is not supported
The extension currently requires a physical script file:
%freshrun script.py
rather than:
%freshrun -m package.module
The locals-capture mechanism relies on identifying frames belonging to a particular script file.
Only the target script is monitored
Frame capture is restricted to frames whose filename matches the script being executed.
This prevents unrelated library frames from being included in _exception_locals.
Locals are copied, not serialized
The child and parent are separate processes.
The captured main() locals are copied within the child IPython namespace; they are not sent back to the parent process.
Consequently, the objects remain owned by the fresh child process and are lost after you leave the child.
Terminal IPython
The extension is designed around terminal IPython and its prompt-toolkit interface.
It is not intended to provide a Jupyter Notebook interface.
Typical workflow
Running a script
A common development workflow looks like this:
%freshrun my_program.py
The program runs in a clean interpreter.
After it finishes:
x
result
can be inspected interactively.
If you change the source code, press:
Alt+Enter
to run it again from a new process.
If you need to switch to another script:
%freshrun another_program.py --some-option
If the program fails, inspect:
_exception_locals
to examine the locals captured from the exception call chain.
For debugging:
%freshrun -d my_program.py
When finished, exit the child and return to the original IPython session.
Starting a blank-slate session
You can also start with a completely clean interactive child:
%freshrun
This is useful when you want a fresh interpreter but do not want to run a script immediately. You can then work interactively as with any usual IPython REPL. This approach is very similar to leaving IPython entirely and launching it again, with the only but major difference being that your main session always remains alive.
Once you are done, you can leave the child session and come back to your parent session or start another blank-slate session from inside the child with Alt+Enter (or using %freshrun again), or even directly replace the current child session with any other %freshrun command, as described in the previous subsection.
Development
Clone the repository and install it in editable mode:
git clone https://github.com/yannziegler/ipython-freshrun
cd ipython-freshrun
python -m pip install -e ".[dev]"
Run the test suite with:
pytest
The tests should focus primarily on observable behavior, particularly process isolation, command parsing, argument forwarding, restart behavior, locals capture, and debugger behavior.
Project structure
The implementation deliberately keeps the parent and child programs separate:
src/freshrun/
├── __init__.py
├── freshrun.py
├── child_startup.py
└── child_capture.py
└── tools.py
freshrun.py
Contains the parent IPython extension and %freshrun implementation.
child_startup.py
Contains the code executed when a new fresh IPython process starts.
It is kept as an ordinary .py file so that the child IPython behavior can be edited and maintained independently.
child_capture.py
Contains the script execution and locals-capture logic.
It is also a normal .py file rather than an embedded Python string.
The two child files are packaged as resources and copied to the temporary execution area when a fresh child is started.
tools.py
Contains the magic commands %inject and %pull.
Tools used
- Coding: Emacs with Spacemacs config
- ChatGPT: initial code writing
Licence
This software is licensed under the EUPL.
Acknowledgements
Many thanks to all the current and future beta-testers!
Release files for ipython-freshrun 0.1.0b0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ipython_freshrun-0.1.0b0.tar.gz | 20.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ipython_freshrun-0.1.0b0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.7 kB
Release files / ipython_freshrun-0.1.0b0.tar.gz
| Download URL | ipython_freshrun-0.1.0b0.tar.gz |
|---|---|
| Size | 20.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
34036207fcf581f7cc17c8ea54a99b14699d1c49799c14abf6d4a9f151fa5cc2
|
|
BLAKE2b-256 checksum How to use checksums |
911f0d8de810113b32f862b881253634ec567fdfe03fb103c79e526b2f3d35bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / ipython_freshrun-0.1.0b0-py3-none-any.whl
| Download URL | ipython_freshrun-0.1.0b0-py3-none-any.whl |
|---|---|
| Size | 17.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
98e89212fab5ed6d44a412084738e7cef871ddecb49613693f43ea079da90445
|
|
BLAKE2b-256 checksum How to use checksums |
1ffc6294d3a0b80c65727c6f625a8a8cfab082323e463170277edafa39ea2718
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log