Metadata-Version: 2.4
Name: pyfollow
Version: 0.1.2
Summary: Lightweight Python tracing utility — trace function calls, variables, expressions, and code blocks via console and webhook
Author-email: Rayan Rav <ravelonirinarayan@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/RayanBO/trace-module-python
Project-URL: Repository, https://github.com/RayanBO/trace-module-python
Project-URL: Issues, https://github.com/RayanBO/trace-module-python/issues
Project-URL: Author, https://rayan-rav.web.app
Keywords: tracing,debugging,debug,profiler,webhook,instrumentation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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 :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

﻿# pyfollow

[![PyPI version](https://img.shields.io/pypi/v/pyfollow?color=blue)](https://pypi.org/project/pyfollow/)
[![Python](https://img.shields.io/pypi/pyversions/pyfollow)](https://pypi.org/project/pyfollow/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RayanBO/trace-module-python/blob/main/LICENSE)

Lightweight Python tracing utility. Trace function calls, variables, expressions, and code blocks — output to console, webhook, or both.

Zero dependencies. Pure stdlib.

---

- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Configuration](#configuration)
- [Webhook](#webhook)
- [Project Structure](#project-structure)
- [Tests](#tests)
- [Contributing](#contributing)
- [License](#license)

---

## Installation

```bash
pip install pyfollow
```

Or copy the `pyfollow/` directory into your project.

```python
from pyfollow import follow
```

## Quick Start

### Console tracing

```python
from pyfollow import follow

t = follow.init()

@t.function
def add(a, b):
    return a + b

x = 1
t.var(x)

x = x + 5
r = add(3, 7)

t.flush()
```

Output:

```
[pyfollow.function] add:
[pyfollow.function] call add(a=3, b=7)
[pyfollow.var] x = 1
[pyfollow.var] x change: 6 [changed from 1]
[pyfollow.function] add -> 10
```

### Webhook tracing

```python
from pyfollow import follow

t = follow.init({"webhook_url": "http://localhost:8000/webhook"})

@t.function(usecode="batch1")
def compute(n):
    return n * 2

compute(5)
t.sync("batch1")
t.flush()
```

### Variable watching

```python
from pyfollow import follow

t = follow.init()

x = 10
t.var(x)          # trace init: x = 10

x = 20            # auto-detect: x changed from 10 to 20

print(repr(x))    # auto-detect: x read

t.unvar("x")      # stop watching
```

### Block tracing

```python
from pyfollow import follow

t = follow.init()

with t:
    a = 42
    b = a + 1
    print(b)
```

### Expression tracing

```python
from pyfollow import follow

t = follow.init()

t(1 + 2)          # [pyfollow] t(1 + 2) -> 3
t(sorted([3,1,2])) # [pyfollow] t(sorted([3,1,2])) -> [1, 2, 3]
```

---

## API Reference

### `follow.init(config=None)`

Create a new `Follow` instance.

```python
t = follow.init()                                        # default
t = follow.init({"webhook_url": "http://...", "uselogs": False})
```

### `t.config(**kwargs)`

Update configuration at runtime.

```python
t.config(uselogs=False)
t.config(webhook_url="http://localhost:8000/webhook")
```

### `@t.function`

Decorator that traces function calls and returns.

```python
@t.function
def my_func(x):
    return x + 1
```

### `@t.function(usecode="tag")`

Decorator with deferred sync — events are buffered under a tag until `t.sync("tag")` is called.

```python
@t.function(usecode="batch1")
def compute(n):
    return n * 2

compute(5)         # buffered, not sent
t.sync("batch1")   # flush only batch1 events
```

### `t.var(value, name=None)`

Watch a variable. Detects init, changes, and reads (via `repr()`/`str()`).

```python
x = 1
t.var(x)    # log: x = 1
x = 2       # log: x changed from 1 to 2
```

### `t.unvar(name)`

Stop watching a variable.

```python
t.unvar("x")
```

### `t(expr)`

Trace an expression — logs the source code line and its result.

```python
t(1 + 2)    # log: t(1 + 2) -> 3
```

### `with t:`

Context manager that traces every line inside the block.

```python
with t:
    x = 1
    y = x + 1
```

### `t.sync(code=None)`

Flush buffered events to webhook.

- `t.sync()` — flush all untagged events
- `t.sync("tag")` — flush only events with matching tag

### `t.flush()`

Block until all pending webhook sends complete.

---

## Configuration

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `webhook_url` | `str` | `None` | URL for HTTP POST event delivery |
| `uselogs` | `bool` | `True` | Print trace events to console |

```python
# Console only (default)
t = follow.init()

# Webhook only
t = follow.init({"webhook_url": "http://localhost:8000/webhook", "uselogs": False})

# Both
t = follow.init({"webhook_url": "http://localhost:8000/webhook"})
```

---

## Webhook

### Event types

| `type` | Trigger |
|--------|---------|
| `var_init` | `t.var(x)` called |
| `var` (change) | Variable reassigned |
| `var` (read) | Variable accessed via `repr()`/`str()` |
| `function_call` | Traced function called |
| `function_return` | Traced function returned |
| `block_start` | `with t:` entered |
| `block_line` | Line executed inside block |
| `block_end` | `with t:` exited |
| `expression` | `t(expr)` evaluated |

### Event payload example

```json
{
  "type": "function_call",
  "name": "add",
  "args": "a=2, b=3",
  "source": "def add(a, b):\n    return a + b"
}
```

### Server example (FastAPI)

```bash
cd examples/server
uvicorn main:app --reload
```

```python
# examples/server/main.py
from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhook")
async def webhook(request: Request):
    events = await request.json()
    for event in events:
        print(event)
    return {"status": "ok"}
```

### Client example

```bash
python examples/webhook_client.py
```

---

## Project Structure

```
trace-module-python/
├── pyproject.toml              # Build config
├── README.md                   # Documentation
├── LICENSE                     # MIT License
├── TODO.md                     # Task tracking
├── pyfollow/                   # Package
│   ├── __init__.py             # Public API + metadata
│   ├── follow.py               # Follow class (core orchestrator)
│   ├── watcher.py              # _FollowedVar proxy + VarWatcher (bytecode)
│   └── sender.py               # WebhookSender (async queue + retry)
├── tests/
│   └── test_trace.py           # 17 pytest tests
└── examples/
    ├── simple.py               # Console demo
    ├── webhook_client.py       # Webhook demo
    └── server/
        └── main.py             # FastAPI webhook receiver
```

---

## Tests

```bash
pip install pytest
python -m pytest tests/ -v
```

---

## How It Works

`pyfollow` hooks into Python's `sys.settrace` machinery to intercept line execution events. For variable watching, it uses `dis` (bytecode disassembly) to detect `LOAD_FAST`/`LOAD_GLOBAL` instructions that read watched variables.

The `_FollowedVar` proxy wraps values transparently — all dunder methods are forwarded to the original object, making the wrapper invisible to user code. Reads are detected when `repr()` or `str()` is called on the proxy.

Webhook delivery runs on a background daemon thread with a queue, retrying failed requests up to 2 times with 1-second backoff.

---

## Contributing

Contributions welcome. Open an issue or submit a PR on [GitHub](https://github.com/RayanBO/trace-module-python).

---

## Author

**Rayan Rav** — [GitHub](https://github.com/RayanBO) · [Website](https://rayan-rav.web.app/)

---

## License

[MIT](LICENSE) © 2026 Rayan Rav
