Metadata-Version: 2.4
Name: wrapture
Version: 1.0.0.dev8
Summary: Monkey patch, test, and trace Python by attaching bindings to call sites, without modifying the code being observed. Built on wrapt.
Author-email: Graham Dumpleton <Graham.Dumpleton@gmail.com>
License-Expression: BSD-2-Clause
Project-URL: Homepage, https://github.com/GrahamDumpleton/wrapture
Project-URL: Documentation, https://wrapture.readthedocs.io
Project-URL: Bug Tracker, https://github.com/GrahamDumpleton/wrapture/issues/
Keywords: wrapper,monkey patching,tracing,testing
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: wrapt>=2.4.0rc4
Provides-Extra: dev
Requires-Dist: mypy; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: setuptools; extra == "dev"
Provides-Extra: docs
Requires-Dist: myst-parser; extra == "docs"
Requires-Dist: sphinx; extra == "docs"
Requires-Dist: sphinx-rtd-theme; extra == "docs"
Dynamic: license-file

# wrapture

**Wrap anything, capture everything, change nothing.**

[![Tests](https://github.com/GrahamDumpleton/wrapture/actions/workflows/build-test-release.yml/badge.svg?branch=develop)](https://github.com/GrahamDumpleton/wrapture/actions/workflows/build-test-release.yml)
[![Documentation](https://readthedocs.org/projects/wrapture/badge/?version=latest)](https://wrapture.readthedocs.io)

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](https://github.com/GrahamDumpleton/wrapt)
and [autowrapt](https://github.com/GrahamDumpleton/autowrapt), building on the
safe monkey-patching machinery wrapt provides.

> **Status: stabilising ahead of 1.0.0.** The feature set is complete for
> a first release and development previews are published to PyPI. The API
> is being exercised and tidied rather than extended, so small changes are
> still possible before 1.0.0.

## Installation

wrapture is on [PyPI](https://pypi.org/project/wrapture/):

```console
$ pip install wrapture
```

or with uv:

```console
$ uv add wrapture
```

## Documentation

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

## Thirty seconds of it

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

```python
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. 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](https://github.com/GrahamDumpleton/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`.

The distinction that matters: most tracing tools either need the code to
have been written with them in mind, or can only be switched on for the
whole program at once. wrapture needs neither: you point at a method by
name and a trace appears.

## 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 replacement for `unittest.mock`.** It complements mocking where
  code has seams; it exists for the code that doesn't.
- **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.

## Requirements

- Python 3.12+
- [wrapt](https://github.com/GrahamDumpleton/wrapt) 2.4.0+

## Issues

Bug reports and feature requests go to the
[issue tracker](https://github.com/GrahamDumpleton/wrapture/issues).
Include the wrapture and Python versions and, where you can, a small
binding that reproduces the problem.

## License

BSD 2-Clause. See
[LICENSE](https://github.com/GrahamDumpleton/wrapture/blob/develop/LICENSE).
