Skip to main content
Pre-release

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

wrapture

Wrap anything, capture everything, change nothing.

Tests Documentation

wrapture (wrapt + capture) is a Python library for attaching bindings to arbitrary call sites, without modifying the code being observed, and doing something useful with what flows through them.

It is a sibling project to wrapt and autowrapt, building on the safe monkey-patching machinery wrapt provides.

Status: alpha, ahead of 1.0.0. Pre-releases are published to PyPI, and until 1.0.0 is final a plain pip install wrapture picks up the latest pre-release automatically, so there is no need to pin a specific version. The existing API is not foreseen to break: the alpha series has reached the point of mainly evaluating performance overheads and tuning the code behind the API, so code written against it today is expected to carry forward to 1.0.0.

Installation

wrapture is on PyPI:

$ pip install wrapture

or with uv:

$ uv add wrapture

Documentation

Full documentation is at wrapture.readthedocs.io. Start with the getting started page: everything on it can be pasted into a Python interpreter. Coming from unittest.mock? There is a comparison page mapping each mock idiom to its wrapture counterpart. After that, the worked examples, starting with testing code that calls external services, each take one question you might arrive with and answer it end to end.

At a glance

None of the classes below import wrapture or know they are observed:

place = wrapture.binding(OrderService, "place")
charge = wrapture.binding(Gateway, "charge")
record = wrapture.binding(Ledger, "record")

with wrapture.timeline(place, charge, record) as tape:
    OrderService().place(500)

print(tape.tree())
OrderService.place(amount=500)  -> {'id': 'ch_500', 'amount': 500}
  Gateway.charge(amount=500, currency='USD')  -> {'id': 'ch_500', 'amount': 500}
  Ledger.record(entry={'id': 'ch_500', 'amount': 500})  -> 'led_ch_500'

The same bindings intervene as well as observe: stub a result, inject a failure, or transform one argument while the real code keeps running.

What it does

One mechanism, three uses, in increasing order of machinery:

  1. Monkey patching. A clean lifecycle and behaviour vocabulary over wrapt's wrap_object(). Point at a method by name and stub it, fail it, transform its arguments or result, or wrap it with a decorator, then remove it again, with honest reporting if something else displaced the patch in the meantime. Useful entirely on its own, with nothing else switched on.

  2. Unit testing. Observe and assert on how calls actually flowed through a real call graph (nesting, ordering, arguments and return values) and optionally intervene (stub, transform, fail-inject). Unlike a unittest.mock Mock, which fabricates values and cannot see calls an object makes to itself, wrapture watches the real code run, and when a test must supply a stand-in it provides strict, recorded ones: stub() for a callable, spec-required mock() for a collaborator. This makes it possible to test code with no injectable seams at all, and to assert on what didn't happen on an error path: inject a gateway timeout, then verify the ledger was not written, the receipt was not sent, and the compensating refund was issued.

  3. Ad-hoc tracing. Attach bindings to a running application, including one you cannot modify or redeploy, and emit a structured, nested trace to process or chart elsewhere. Name a handful of methods and a call tree appears; no code changes required: with a wrapture.toml naming the methods and a sink, python -m wrapture manage.py runserver traces the application untouched. With autowrapt installed, not even the launcher is needed: AUTOWRAPT_BOOTSTRAP=wrapture in the environment applies the same config at interpreter startup, so the program starts with plain python.

On top of the tracing layer sits OpenTelemetry export: the same recorded events sent to any OTLP backend as traces, metrics and correlated logs, switched on by one [otel] table in the config, with the trace identity arriving and leaving in W3C traceparent headers so two observed services join one distributed trace. These are layers of one mechanism, not separate products: the binding vocabulary that stubs a method in a test is the same one that traces it in production, and the config that names methods for a printed call tree is the config that exports spans, so what starts as a monkey patch or a test assertion can grow into full observability without the code being rewritten along the way.

The distinction that matters: most instrumentation, OpenTelemetry's own included, either has to be written into the code as SDK calls, or arrives as auto-instrumentation covering only the frameworks it already knows. wrapture needs neither: you point at your own methods by name and a trace appears, and the same pointing is how it lands in a test, a terminal, or a backend.

Pre-built instrumentation

Pointing at your own methods is the core of wrapture, but for common third-party packages the pointing has already been done. The companion wrapture-instrumentation package provides ready-made instrumentation for popular Python packages such as web frameworks and template engines (currently Flask and Jinja2, with more targets to come), each recording a request or render as one structured tree:

$ pip install wrapture-instrumentation

Enabling a target is an [[instrument]] entry in wrapture.toml, or wrapture.instrumentation("flask", "jinja2") in code, and it composes with your own bindings in the same trace. The instrumentation packages page describes how these packages work and how to write one for a package not yet covered.

Why

No single existing tool covers "point at arbitrary methods, get a structured nested trace, assert on it or export it, in tests or in production":

  • unittest.mock records a flat call list, with no nesting and no return values, and a patched call returns a fabricated MagicMock rather than running the real code.
  • Span-assertion tools (logfire.testing, OpenTelemetry's InMemorySpanExporter) require the code to already be instrumented.
  • sys.settrace tools (hunter, snoop) give a firehose with no assertion API.
  • APM agents are all-or-nothing products rather than a toolkit.

wrapture fills that gap: a targeted call tree with normalized arguments and return values, produced by naming the methods you care about, usable as a testing assertion library, a tracing tool, or both at once.

What it is not

  • Not a fabrication tool. There is no spec-less Mock() here by design; stand-ins are strict and built from named specs, and unittest.mock remains the tool for invented objects.
  • Not a production APM. It is a toolkit that APM-like things could be built on.
  • Not an OpenTelemetry competitor. It should emit to OTel, not replace it.

How it was built

wrapture's code and documentation were written by an AI assistant under the direction of Graham Dumpleton, the author of wrapt, through a long process of specification, layered implementation, and validation against real-world test suites. How wrapture was built explains the process and the thinking.

Requirements

  • Python 3.12+
  • wrapt 2.4.0+

Issues

Bug reports and feature requests go to the issue tracker. Include the wrapture and Python versions and, where you can, a small binding that reproduces the problem.

License

BSD 2-Clause. See LICENSE.

Release files for wrapture 1.0.0a10

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

Source distribution (sdist)

Source distribution for wrapture 1.0.0a10
File Size Uploaded
wrapture-1.0.0a10.tar.gz 385.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wrapture 1.0.0a10
File Interpreter ABI Platform
wrapture-1.0.0a10-py3-none-any.whl Python 3 none any Details

Total release size: 627.6 kB

Release files / wrapture-1.0.0a10.tar.gz

Download URL wrapture-1.0.0a10.tar.gz
Size 385.3 kB
Tags Source
SHA-256 checksum
How to use checksums
5c6feb5cd4a15940a8b7e3a54ecd20fb7edba040fbbf46a1001c2f37048dbe5c
BLAKE2b-256 checksum
How to use checksums
7dcbf2887ace40b538e00aa634ad181d7fcdf376b8f6f6ed7a5e0d1edb720367
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.

Transparency log

Release files / wrapture-1.0.0a10-py3-none-any.whl

Download URL wrapture-1.0.0a10-py3-none-any.whl
Size 242.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
805238d85dcc0ae98aeee9ceae85afcb66e15c896c374c7ebec72547206b782c
BLAKE2b-256 checksum
How to use checksums
4af73899528fbd368508fcdbd3f83f8dee70776453eb46a8abdb43af7721e9e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0a10 This release

2 release files

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