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.stepsis an ordered list of step records. Each record has astep_id, timestamp, filename, function name, line number, and frozen local snapshot.trace.resultis the in-memory return value for a successful execution.trace.exceptionisNonefor a successful run or an exception record for a failed traced run.trace.at(step_id)returns aStepViewfor 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
AsyncFunctionTracerin 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
- basic_usage.py - v0.1 basic example
- v2_showcase.py - v2 features demo
- v2_comprehensive_showcase.py - Complete v2 feature showcase
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)
| File | Size | Uploaded | |
|---|---|---|---|
| pyrewind_dev-0.2.0a1.tar.gz | 54.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|