Python Basics: Execute a Trusted Script with runpy
A small test harness may need to execute a trusted script and inspect its resulting globals. runpy.run_path does that in a fresh namespace in the current process. This fixture supplies an input value and checks how the script’s name affects its main guard.
Prepare a scratch directory
Tested on Linux with CPython 3.12.14. You need the named runtime, Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. Run Python without -O so the assertions remain enabled. Any generated fixture files live inside an automatically cleaned temporary directory.
Keep using the same terminal. Stop if setup fails, before saving the source:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Run the example
Save this as demonstrate-runpy.py:
from pathlib import Path
from tempfile import TemporaryDirectory
import runpy
with TemporaryDirectory() as directory:
source = Path(directory) / "calculate_report.py"
source.write_text(
'result = amount * 2\n'
'ran_main = (__name__ == "__main__")\n', encoding="utf-8")
inputs = {"amount": 7}
ordinary = runpy.run_path(str(source), init_globals=inputs)
assert ordinary["result"] == 14
assert ordinary["ran_main"] is False
assert "result" not in inputs
print("Result:", ordinary["result"])
print("Default run executed as main:", ordinary["ran_main"])
main = runpy.run_path(str(source), init_globals=inputs, run_name="__main__")
assert main["ran_main"] is True
print("Explicit main name:", main["ran_main"])
print("Input dictionary gained result:", "result" in inputs)Run it:
python3 -B demonstrate-runpy.pyObserved output:
Result: 14
Default run executed as main: False
Explicit main name: True
Input dictionary gained result: FalseRead the result
The input dictionary seeds the new namespace, and the returned dictionary contains the script’s result. run_name controls the special __name__ value, making the usual main-guard condition true in the second run. The original input dictionary is not extended with the script’s new bindings. The source is deliberately tiny and created by this program, so no third-party file is executed.
Apply it carefully
runpy is not a sandbox: executed code can read files, import modules, and change process state. Side effects can outlive the call. The initial globals are not a deep copy of every supplied object, so mutable values can still be shared. Execution temporarily adjusts interpreter state, which requires care in threaded programs. Use ordinary imports for reusable APIs and a subprocess when genuine process isolation is needed.
The official reference describes the API and its options. The checks above cover the specific fixture, not every possible input or environment.
Clean up
Remove the source and then the empty practice directory:
rm -- demonstrate-runpy.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf the directory is not empty, inspect the remaining files rather than deleting unknown contents.
Acknowledgements
This post was created with assistance from AI.