Metadata-Version: 2.4
Name: explr
Version: 0.1.1
Summary: Trace any Python process and generate a clean call graph diagram
License-Expression: MIT
Project-URL: Homepage, https://github.com/omavashia2005/explr
Project-URL: Repository, https://github.com/omavashia2005/explr
Project-URL: Bug Tracker, https://github.com/omavashia2005/explr/issues
Keywords: tracing,call-graph,debugging,visualization,profiling
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Debuggers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: graphviz>=0.20
Dynamic: license-file

# 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](https://github.com/omavashia2005/explr/blob/main/explr_diagrams/t6_cross_module_diagram.png)


## 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](https://graphviz.org/download/) — make sure `dot` is added to PATH |

### Install explr

```bash
pip install explr
```

## CLI usage

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

### Examples

```bash
# 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) |

```bash
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.

```python
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:

```python
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:
> ```bash
> pip install nest_asyncio
> ```
> ```python
> 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:

```bash
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
```
