Lightweight time-travel debugging via sys.settrace and ring-buffered snapshots.
Project description
rewindR
rewindR (import rewindr) is a small, pure-Python helper for lightweight time-travel debugging: it records shallow snapshots of local (and filtered global) state as your code runs, keeps only the most recent N steps in a ring buffer, and hands you a RewindSession after the call finishes or after an exception—so you can inspect earlier states without re-running the function.
Why time-travel debugging?
When something fails deep inside a loop or a long function, the stack trace shows where you crashed, not how you got there. A short history of variable assignments (line number + locals() + timestamp) answers questions like: “What was i two iterations ago?”, “When did this list become empty?”, or “What changed between the last two executed lines?”—without sprinkling print everywhere or restarting under a heavy debugger.
Installation
From the repository root (editable install):
pip install -e ".[dev]"
Or once published:
pip install rewindR
Requires Python 3.10+. There are no C extensions—everything uses the interpreter’s tracing hooks.
Quickstart
from rewindr import rewindr
@rewindr(steps=100)
def risky(n: int) -> float:
acc = 0.0
for i in range(n):
acc += 1.0 / (n - i) # ZeroDivisionError on last iteration when n>0
return acc
session = risky(3)
print(session.summary())
print(session.rewind(1).locals) # one step before the last snapshot
print(session.diff(-2, -1)) # what changed between the last two steps
Decorator usage
@rewindr— uses the default ring buffer size (50).@rewindr(steps=200)— keep the last 200 snapshots.
The wrapped function is invoked for side effects; the return value of the wrapper is always a RewindSession, not the original function’s return value. On a clean run, session.exception is None. If the wrapped function raises, the exception is swallowed and the same RewindSession is returned so you can assign session = my_func(...) without a try/except block. The exception object is still attached on rewind_session for compatibility with tools that catch by type.
from rewindr import rewindr, get_session_from_exception
@rewindr(steps=50)
def boom() -> None:
x = 1
y = 0
_ = x / y
session = boom()
assert isinstance(session.exception, ZeroDivisionError)
exc = session.exception
assert get_session_from_exception(exc) is session
print(session.rewind(0).locals)
Context manager usage
You can trace an arbitrary block in the caller function with either API:
from rewindr import rewind_context, rewindr
with rewind_context(steps=50) as ref:
a = 1
b = a + 1
session = ref.session
with rewindr(steps=50) as ref2:
c = b + 1
session2 = ref2.session
After the block, ref.session is populated. If the block raises, the session is still stored on ref.session, and the same object is attached to the exception under rewind_session.
Inspecting snapshots
Each Snapshot includes:
line_no— current line in the traced frame.locals— shallowcopy.copyper name where possible; otherwiserepr(...).globals— filtered shallow snapshot (no dunders, callables, or modules).timestamp—time.monotonic()at capture (relative timing, not wall clock).event—"line","call","return", or"exception".
RewindSession.history is ordered oldest → newest.
Diffing snapshots
delta = session.diff(0, -1) # supports negative indices like list indexing
for name, change in delta.items():
print(name, change["before"], "->", change["after"])
Only bindings that differ (by ==, with a safe fallback) appear. Missing names show as "<undefined>".
Performance notes
- Tracing uses
sys.settrace, which has significant overhead—suitable for debugging hotspots, tests, or narrow code paths—not production servers. - Snapshots use shallow copies of locals to balance fidelity and cost; deeply nested structures are not deep-copied.
- The ring buffer caps memory; increase
stepsonly as needed.
Limitations
- Per-thread tracing (
sys.settrace); multi-threaded code is only traced on the thread where the recorder runs. - No async (
async/await) story yet; tracing async coroutines is subtle and not a goal of v0.1. - Filtered globals and best-effort locals (
copy.copy/repr) mean some objects may be incomplete or expensive. - Context manager scope is the containing function’s code object (the
withlives in that frame); you cannot narrow to “only lines inside thewith” without AST rewriting. - Security: snapshots may contain secrets from locals—treat sessions like logs.
Roadmap
- Optional deep-copy strategies and size budgets per snapshot.
- Richer diff (recursive dict/list diff, custom comparators).
- Async and thread helpers.
- Optional
sys.setprofilemode for lower granularity / different trade-offs. - Serialization of
RewindSessionfor CI artifacts.
Development
python -m pytest rewindr/tests -q
python rewindr/examples/basic_usage.py
License
MIT (see pyproject.toml).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rewindr-0.1.0.tar.gz.
File metadata
- Download URL: rewindr-0.1.0.tar.gz
- Upload date:
- Size: 19.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1eeb41b60971d6f1e50bf43131709e8db3cd3ba44e7c3a0b86f65e9ce4569e73
|
|
| MD5 |
b761f23c3d120c9b13305a57a1f54016
|
|
| BLAKE2b-256 |
2ed08b4e0e76878bb73cd469389ad74a01457791d3de2d1bc39db1efce4bbb08
|
Provenance
The following attestation bundles were made for rewindr-0.1.0.tar.gz:
Publisher:
workflow.yml on abhidotnet/RewindR
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rewindr-0.1.0.tar.gz -
Subject digest:
1eeb41b60971d6f1e50bf43131709e8db3cd3ba44e7c3a0b86f65e9ce4569e73 - Sigstore transparency entry: 1524668368
- Sigstore integration time:
-
Permalink:
abhidotnet/RewindR@600e2cebb346b31abc08c0b865426d2a61c69c39 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/abhidotnet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@600e2cebb346b31abc08c0b865426d2a61c69c39 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file rewindr-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rewindr-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8782edb53dce4b3047ddb81a1951927193e8cab33571fdbb6184a46593996cfc
|
|
| MD5 |
9c145918f53c7a83a0588cb469cbdc7a
|
|
| BLAKE2b-256 |
67733a2c9195d3a9524fe8d2ff48a26a5a5036d4208149673d9122bc8bd21d18
|
Provenance
The following attestation bundles were made for rewindr-0.1.0-py3-none-any.whl:
Publisher:
workflow.yml on abhidotnet/RewindR
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rewindr-0.1.0-py3-none-any.whl -
Subject digest:
8782edb53dce4b3047ddb81a1951927193e8cab33571fdbb6184a46593996cfc - Sigstore transparency entry: 1524668409
- Sigstore integration time:
-
Permalink:
abhidotnet/RewindR@600e2cebb346b31abc08c0b865426d2a61c69c39 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/abhidotnet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@600e2cebb346b31abc08c0b865426d2a61c69c39 -
Trigger Event:
workflow_dispatch
-
Statement type: