Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

pyrewind

pyrewind records the line-by-line execution of a regular Python function and returns an inspectable trace alongside its result. It is intended for local debugging, teaching, and lightweight execution forensics: run a function, inspect the values that were in scope at each recorded line, save the trace, and make an assisted rerun with changed keyword arguments.

Version 0.2.0a0 (v2) includes major new features: trace analysis tools, CLI integration, advanced filtering, tagging system, and plugin architecture—all while maintaining 100% backwards compatibility with v0.1.

Install

pyrewind has no required runtime dependencies and supports Python 3.10+.

python -m pip install pyrewind

For a checkout of this repository:

python -m pip install -e .

60-second quickstart

from pyrewind import rewindable


@rewindable
def apply_discount(price, percent):
    discount = price * percent / 100
    return price - discount


# Ordinary calls retain the function's normal behavior.
assert apply_discount(100, 20) == 80

# Use .run() when a trace is wanted.
result, trace = apply_discount.run(100, 20)
print(result)                         # 80.0
print(len(trace.steps))               # one or more line snapshots
print(trace.at(0).locals())           # locals at the first recorded step

trace.to_file("discount.pyrewind.json")

See examples/basic_usage.py for a complete runnable example.

v2 Features: Trace Analysis & Advanced Tools

Analyze Execution with TraceInspector

from pyrewind import rewindable, TraceInspector

@rewindable
def calculate(x, y):
    result = x + y
    return result

result, trace = calculate.run(10, 20)
inspector = TraceInspector(trace)

# Timing analysis
print(f"Total time: {inspector.execution_time_ms():.2f} ms")
print(f"Avg per step: {inspector.average_time_per_step_us():.2f} µs")

# Comprehensive summary
summary = inspector.summary()
print(f"Steps: {summary['total_steps']}")
print(f"Mean time: {summary['timing']['mean_us']:.2f} µs")

# Find slowest parts
hotspots = inspector.hotspots()

Advanced Filtering & Slicing

from pyrewind import AdvancedTraceFilter, TraceSlice

# Chainable filtering
filtered = (
    AdvancedTraceFilter(trace)
    .by_function("calculate")
    .by_line_range(5, 15)
    .by_local_name("result")
    .apply()
)

# Slice and merge traces
first_half = TraceSlice.first_n_steps(trace, len(trace.steps) // 2)
merged = TraceSlice.merge_traces([trace1, trace2])

Metadata & Tagging

from pyrewind import TraceTagger

tagger = TraceTagger()
tagger.add_tag("performance-test")
tagger.annotate_step(0, "Entry point")

# Serialize for persistence
data = tagger.to_dict()

API reference

@rewindable

from pyrewind import rewindable


@rewindable
def function(...):
    ...

The decorated callable preserves its usual call behavior: function(*args, **kwargs) simply executes the original function and returns its result.

Call function.run(*args, **kwargs) to execute it under pyrewind's tracer. On success it returns (result, trace). Step IDs are zero-based, contiguous, and identify the corresponding entry in trace.steps.

The decorator also accepts these optional keyword arguments:

@rewindable(max_depth=3, capture_exceptions=True)
def function(...):
    ...

max_depth bounds recursive freezing of container values in local snapshots. With the default capture_exceptions=True, .run() still re-raises the original exception, preserving normal Python error handling. The finalized trace is then available as function.last_trace (it is None before the first traced run). Its exception field contains the exception type, message, and representation.

Trace

Trace is the object returned by .run().

  • trace.steps is an ordered list of step records. Each record has a step_id, timestamp, filename, function name, line number, and frozen local snapshot.
  • trace.result is the in-memory return value for a successful execution.
  • trace.exception is None for a successful run or an exception record for a failed traced run.
  • trace.at(step_id) returns a StepView for that recorded step.
  • trace.to_file(path) writes a JSON trace artifact.
  • Trace.from_file(path) loads a JSON trace artifact.

A StepView provides:

view = trace.at(0)
view.locals()    # dict
view.line_no()   # int
view.function()  # str
view.filename()  # str

Trace artifacts use the .pyrewind.json suffix by convention. They contain a versioned, JSON-safe representation of the trace. The original runtime return object is intentionally in-memory only; use result_repr when inspecting a trace loaded from disk.

replay(trace)

from pyrewind import replay

new_result, new_trace = replay(trace).run(quantity=4)
new_result, new_trace = replay(trace).from_step(2).run(quantity=4)

Replay is an assisted rerun, not time travel. In the same Python process, pyrewind can reuse captured call inputs and patch the keyword arguments given to .run(). A persisted trace cannot safely reconstruct arbitrary values from their repr; supply the function's required keyword arguments yourself when rerunning one. Functions that require positional-only inputs are therefore not portable replay targets in v0.1.

from_step(step_id) records a checkpoint selection for the replay workflow, but in v0.1 it is metadata-only: the function is always re-executed from its start. It does not resume Python execution from that line.

Determinism notes

pyrewind captures Python line events for the decorated function only. Step order and source locations follow that function's execution, but timestamps, randomness, I/O, clocks, threads, and external services are outside its control. Replaying such a function reruns those effects; pyrewind does not stub or reverse them.

Local snapshots are frozen into JSON-safe values as they are captured. Primitive values are retained; standard containers are copied recursively up to max_depth; unsupported or deeply nested values are represented with their type and repr. This makes earlier snapshots stable even when a mutable local is changed later, but it is not a deep object debugger.

Limitations and non-goals

  • Regular synchronous Python functions are the primary target. Async/await is supported via AsyncFunctionTracer in v2.
  • Nested calls are not expanded into the parent trace; only the decorated function's own line events are recorded.
  • Replay is a rerun with supplied or same-process inputs, not checkpoint restoration or deterministic execution.
  • Saved traces are portable JSON diagnostics, not a serialization format for arbitrary Python objects or executable state.

v2 Architecture

PyRewind v2 is organized into several modules providing extensible capabilities:

  • core/: Plugin system, storage backends, event dispatching, serialization strategies
  • analysis/: Trace inspection and analysis tools (TraceInspector)
  • trace/: Advanced filtering and trace slicing (AdvancedTraceFilter, TraceSlice)
  • cli/: Command-line interface for trace inspection and export
  • metadata/: Tagging and annotation system (TraceTagger)
  • plugins/: Example plugin implementations and extensibility points

Backwards Compatibility

All v0.1 functionality is preserved and fully compatible. Existing code continues to work without modification. New features are additive.

Examples

Testing

Run the test suite:

python -m pytest tests/ -v          # All tests
python -m pytest tests/test_v2*.py  # v2 feature tests only

All 24 tests pass (11 v0.1 backwards compatibility + 13 v2 features)

Performance and privacy

Line tracing and freezing locals add noticeable overhead, so use pyrewind for debugging and focused investigations rather than hot production paths.

Traces can contain values from local variables, including credentials, tokens, customer data, and file paths. Treat trace files as sensitive artifacts: avoid committing or sharing them, review them before export, and redact sensitive values in application code.

Metadata

Release files for pyrewind-dev 0.2.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyrewind-dev 0.2.0a1
File Size Uploaded
pyrewind_dev-0.2.0a1.tar.gz 54.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyrewind-dev 0.2.0a1
File Interpreter ABI Platform
pyrewind_dev-0.2.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 108.8 kB

Release files / pyrewind_dev-0.2.0a1.tar.gz

Download URL pyrewind_dev-0.2.0a1.tar.gz
Size 54.1 kB
Tags Source
SHA-256 checksum
How to use checksums
af61602ae1cb267c43e4801bde88ccc67fa924e847e94b3e8e2b22b52df3c926
BLAKE2b-256 checksum
How to use checksums
57238e02a833fc1332fb599990a0197ebc08c28f520b83b3add790c4ff144189
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / pyrewind_dev-0.2.0a1-py3-none-any.whl

Download URL pyrewind_dev-0.2.0a1-py3-none-any.whl
Size 54.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
424c3a508ae425b69a3ace4c6054129fd62b48505d4bd03ae0fa3c798bd950e2
BLAKE2b-256 checksum
How to use checksums
d39f57fd00424ea183d9f51fad8d06f59ef43a2e7d60c84edfdceb46dcf54531
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page