Skip to main content

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 — shallow copy.copy per name where possible; otherwise repr(...).
  • globals — filtered shallow snapshot (no dunders, callables, or modules).
  • timestamptime.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 steps only 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 with lives in that frame); you cannot narrow to “only lines inside the with” 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.setprofile mode for lower granularity / different trade-offs.
  • Serialization of RewindSession for CI artifacts.

Development

python -m pytest rewindr/tests -q
python rewindr/examples/basic_usage.py

License

MIT (see 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

rewindr-0.1.0.tar.gz (19.4 kB view details)

Uploaded Source

Built Distribution

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

rewindr-0.1.0-py3-none-any.whl (24.1 kB view details)

Uploaded Python 3

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

Hashes for rewindr-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1eeb41b60971d6f1e50bf43131709e8db3cd3ba44e7c3a0b86f65e9ce4569e73
MD5 b761f23c3d120c9b13305a57a1f54016
BLAKE2b-256 2ed08b4e0e76878bb73cd469389ad74a01457791d3de2d1bc39db1efce4bbb08

See more details on using hashes here.

Provenance

The following attestation bundles were made for rewindr-0.1.0.tar.gz:

Publisher: workflow.yml on abhidotnet/RewindR

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for rewindr-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8782edb53dce4b3047ddb81a1951927193e8cab33571fdbb6184a46593996cfc
MD5 9c145918f53c7a83a0588cb469cbdc7a
BLAKE2b-256 67733a2c9195d3a9524fe8d2ff48a26a5a5036d4208149673d9122bc8bd21d18

See more details on using hashes here.

Provenance

The following attestation bundles were made for rewindr-0.1.0-py3-none-any.whl:

Publisher: workflow.yml on abhidotnet/RewindR

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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