Skip to main content

Trace any Python process and generate a clean call graph diagram

Project description

explr

Trace any Python process and generate a clean call graph diagram.

Best suited for debugging small-to-medium synchronous Python programs (for now).

example diagram

How it works

explr injects Python's sys.settrace at runtime, records every function call, filters out noise (stdlib, dunders, private functions), and renders a flow diagram showing how control moves through your code.

The diagram has a horizontal spine of entry points in execution order, with each node's sub-calls hanging below it:

(S) → run → (E)
       ├── auth.register → db.save_user
       │                 → db.get_user
       ├── auth.login    → db.get_user
       └── report        → db.all_users
  • S / E = start and end of execution
  • Green nodes = entry points (called from top-level code), in the order they ran
  • Blue nodes = sub-calls

Installation

Prerequisites

explr requires Graphviz to render diagrams. Install it for your OS:

OS Command
macOS (Homebrew) brew install graphviz
Ubuntu / Debian sudo apt install graphviz
Fedora / RHEL sudo dnf install graphviz
Windows Download installer — make sure dot is added to PATH

Install explr

pip install -e .

Or with uv:

uv pip install -e .

Note: Use --no-build-isolation if your environment already has setuptools:

pip install -e . --no-build-isolation

CLI usage

explr [options] <target> [target-args ...]

Examples

# Trace a .py file
explr myscript.py

# Trace with the python prefix (same result)
explr python myscript.py
explr python3 myscript.py

# Pass arguments through to your script
explr myscript.py --config dev

# Trace a module-style tool (e.g. pytest, flask)
explr pytest tests/
explr python -m mypackage

Options

Flag Description
--depth N Limit call depth (default: unlimited)
--no-stdlib Skip tracing stdlib frames (faster, same visual result)
--output NAME Override output filename (no extension needed)
explr --depth 5 myscript.py
explr --no-stdlib myscript.py
explr --output my_graph myscript.py

Output

Diagrams are saved to ./explr_diagrams/ in the current working directory:

explr_diagrams/
  myscript_diagram.png

Python API

Trace a specific function from within your own code using explr.trace(). Works with both sync and async functions.

import explr

# Sync function
explr.trace(my_function, args=(1, 2))

# Async function — explr handles the event loop automatically
explr.trace(my_async_function, kwargs={"url": "...", "headers": {}})

# With keyword args
explr.trace(my_function, args=(x,), kwargs={"flag": True})

# All options
explr.trace(
    my_function,
    args=(x,),
    output="my_graph",   # custom output filename (no extension)
    depth=5,             # limit call depth
    no_stdlib=True,      # skip stdlib frames (faster)
)

# Returns the path to the generated PNG (or None if nothing was captured)
path = explr.trace(my_function, args=(x,))

Diagrams are written to ./explr_diagrams/<func_name>_diagram.png (or your output name).

explr.trace() runs entirely in-process using sys.settrace — no subprocess or temp files. Any existing trace hook is saved and restored around the call.

Async functions

For async functions, explr.trace() automatically runs the coroutine via asyncio.run(). Mock out any network/IO calls so the function executes without side effects:

import explr

# Mock the network call
async def fetch(url, headers):
    return b"mock response"

async def my_pipeline(url: str, headers: dict):
    raw = await fetch(url=url, headers=headers)
    result = process(raw)
    return result

explr.trace(
    my_pipeline,
    kwargs={"url": "https://example.com", "headers": {}},
    output="my_pipeline",
    no_stdlib=True,
)

Jupyter / running event loop: asyncio.run() cannot be called from inside an already-running loop. Install nest_asyncio to work around this:

pip install nest_asyncio
import nest_asyncio
nest_asyncio.apply()
explr.trace(my_async_function, kwargs={...})

What gets shown

Included Excluded
User-defined functions stdlib functions
Cross-module calls Dunder methods (__init__, etc.)
Recursive calls (self-loops) Private functions/modules (leading _)
Class methods Synthetic names (<listcomp>, <lambda>, etc.)

If a function has no user-defined sub-calls, it still appears on the spine as S → fn → E.


Test files

The test_files/ directory contains examples covering common patterns:

explr test_files/simple.py             # linear call chain
explr test_files/recursive.py          # recursive functions
explr test_files/classes.py            # class methods
explr test_files/branching.py          # conditional branches
explr test_files/multi_module/main.py  # calls across multiple files
explr test_files/no_calls.py           # no sub-calls (spine only)

Project structure

explr/
  __init__.py   # explr.trace() Python API
  cli.py        # entry point, argument parsing, process detection
  tracer.py     # sys.settrace bootstrap (CLI) and in-process tracer (API)
  renderer.py   # graphviz diagram rendering
  models.py     # CallNode / CallEdge / CallGraph dataclasses
test_files/     # example scripts for testing
pyproject.toml

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

explr-0.1.0.tar.gz (15.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

explr-0.1.0-py3-none-any.whl (14.4 kB view details)

Uploaded Python 3

File details

Details for the file explr-0.1.0.tar.gz.

File metadata

  • Download URL: explr-0.1.0.tar.gz
  • Upload date:
  • Size: 15.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for explr-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c0ee0a17bdf2b39182c0d792e0065b2f08d76828aae2f690158ae5627cebc5a3
MD5 e409c84d67992ae558339cdc9ac72ebf
BLAKE2b-256 9e7c8e0ce0afb899bc6814ff98896917fb4b1c4c7fda1f19c70812bf8f764464

See more details on using hashes here.

File details

Details for the file explr-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: explr-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for explr-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 89a680a0ba3c1fafc74dc55baa100e4ae8c84a61fd341f8c0ebdb169bc64d0be
MD5 37e35534f28adcedb2aed6613cb1bbfb
BLAKE2b-256 c0ea211e2c3cf50bebdcab6577e2791d7b7d5631011ba5887e06263bff894152

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page