Introducing wrapture
<p><strong><a href="https://grahamdumpleton.me/posts/2026/08/introducing-wrapture/">Introducing wrapture</a></strong></p> New from Graham Dumpleton (of <a href="https://pypi.org/project/wrapt/">wrapt</a>, mod_wsgi, and New Relic's Python agent fame), who describes Wrapture as taking the monkeypatching ideas from wrapt and extending them to apply to testing and tracing at the same time.</p> <p>Wrapture (<a href="https://wrapture.readthedocs.io/">full documentation here</a>) makes it easy to wrap any function or method such that all access can be traced, or can be overridden to return a different value.</p> <p>It acts as both an alternative to <code>unittest.mock</code> and a way to implement tracing against an existing project:</p> <blockquote> <p>Attaching observation to code you do not control, recording what flows through it, and doing so without disturbing the program being watched, is a problem I have never really stopped thinking about.</p> </blockquote> <p>Wrapture includes <a href="https://wrapture.readthedocs.io/en/latest/otel-export.html">OpenTelemetry support</a> and even has an entirely configuration-based mechanism for adding tracing to an existing Python project, which looks like this:</p> <div class="highlight highlight-source-toml"><pre><span class="pl-smi">capture</span> = <span class="pl-s"><span class="pl-pds">"</span>summary<span class="pl-pds">"</span></span> [[<span class="pl-en">observe</span>]] <span class="pl-smi">target</span> = <span class="pl-s"><span class="pl-pds">"</span>domain:Calculator<span class="pl-pds">"</span></span> <span class="pl-smi">name</span> = [<span class="pl-s"><span class="pl-pds">"</span>outer<span class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>inner<span class="pl-pds">"</span></span>] [[<span class="pl-en">sink</span>]] <span class="pl-smi">type</span> = <span class="pl-s"><span class="pl-pds">"</span>jsonlines<span class="pl-pds">"</span></span> <span class="pl-smi">path</span> = <span class="pl-s"><span class="pl-pds">"</span>trace.jsonl<span class="pl-pds">"</span></span></pre></div> <p>This is still a very young project - just a few weeks old - but it's off to a very promising start.</p> <p>Interestingly, this is also Graham's first attempt at large entirely agent-driven project:</p> <blockquote> <p>Every line of code and documentation in wrapture was written by an AI assistant working under my direction. I want to be upfront about that, and equally upfront about what it was not. This was not vibe coding, where a one-shot prompt produces a pile of generated code and the person driving hopes for the best because they lack the knowledge to judge what came back. Vibe coding has earned its bad reputation. I engineered wrapture carefully from the start. I have spent a long time in this particular corner of Python and knew exactly what the result needed to be, and the AI was the means of producing it rather than the source of the design.</p> </blockquote> <p>In a follow-up post, <a href="https://grahamdumpleton.me/posts/2026/09/unit-testing-with-wrapture/">Unit testing with wrapture</a>, Graham shows the testing patterns supported by the new library:</p> <pre><span class="pl-k">def</span> <span class="pl-en">test_stub_with_wrapture</span>(): <span class="pl-k">with</span> <span class="pl-s1">wrapture</span>.<span class="pl-c1">binding</span>( <span class="pl-v">Gateway</span>, <span class="pl-s">"charge"</span> ).<span class="pl-c1">on_call</span>.<span class="pl-c1">returns</span>({ <span class="pl-s">"id"</span>: <span class="pl-s">"stub"</span>, <span class="pl-s">"amount"</span>: <span class="pl-c1">0</span>} ): <span class="pl-k">assert</span> <span class="pl-en">OrderService</span>().<span class="pl-c1">place</span>( <span class="pl-c1">500</span> )[<span class="pl-s">"id"</span>] <span class="pl-c1">==</span> <span class="pl-s">"stub"</span></pre> <p>And this neat example of a test that calls and then modifies the return value from the original method:</p> <pre><span class="pl-k">def</span> <span class="pl-en">test_pinned_result_with_wrapture</span>(): <span class="pl-s1">charge</span> <span class="pl-c1">=</span> <span class="pl-s1">wrapture</span>.<span class="pl-c1">binding</span>( <span class="pl-v">Gateway</span>, <span class="pl-s">"charge"</span> ) <span class="pl-s1">charge</span>.<span class="pl-c1">on_call</span>.<span class="pl-c1">transforms_result</span>( <span class="pl-k">lambda</span> <span class="pl-s1">r</span>: {<span class="pl-c1">**</span><span class="pl-s1">r</span>, <span class="pl-s">"id"</span>: <span class="pl-s">"ch_TEST"</span>} ) <span class="pl-k">with</span> <span class="pl-s1">charge</span>: <span class="pl-k">assert</span> <span class="pl-en">OrderService</span>().<span class="pl-c1">place</span>( <span class="pl-c1">500</span> ) <span class="pl-c1">==</span> { <span class="pl-s">"id"</span>: <span class="pl-s">"ch_TEST"</span>, <span class="pl-s">"amount"</span>: <span class="pl-c1">500</span> }</pre> <p>(In both of these examples the <code>OrderService().place(...)</code> method calls <code>Gateway().charge(...)</code>.) <p>Tags: <a href="https://simonwillison.net/tags/graham-dumpleton">graham-dumpleton</a>, <a href="https://simonwillison.net/tags/monkey-patching">monkey-patching</a>, <a href="https://simonwillison.net/tags/python">python</a>, <a href="https://simonwillison.net/tags/testing">testing</a>, <a href="https://simonwillison.net/tags/pytest">pytest</a>, <a href="https://simonwillison.net/tags/observability">observability</a>, <a href="https://simonwillison.net/tags/ai-assisted-programming">ai-assisted-programming</a>, <a href="https://simonwillison.net/tags/agentic-engineering">agentic-engineering</a>, <a href="https://simonwillison.net/tags/opentelemetry">opentelemetry</a></p>
Introducing wrapture
Simon Willison’s Weblog
Subscribe
31st August 2026 - Link Blog
Introducing wrapture. New from Graham Dumpleton (of wrapt, mod_wsgi, and New Relic's Python agent fame), who describes Wrapture as taking the monkeypatching ideas from wrapt and extending them to apply to testing and tracing at the same time.
Wrapture (full documentation here) makes it easy to wrap any function or method such that all access can be traced, or can be overridden to return a different value.
It acts as both an alternative to unittest.mock and a way to implement tracing against an existing project:
Attaching observation to code you do not control, recording what flows through it, and doing so without disturbing the program being watched, is a problem I have never really stopped thinking about.
Wrapture includes OpenTelemetry support and even has an entirely configuration-based mechanism for adding tracing to an existing Python project, which looks like this:
capture = "summary"
[[observe]] target = "domain:Calculator" name = ["outer", "inner"]
[[sink]] type = "jsonlines" path = "trace.jsonl"
This is still a very young project - just a few weeks old - but it's off to a very promising start.
Interestingly, this is also Graham's first attempt at large entirely agent-driven project:
Every line of code and documentation in wrapture was written by an AI assistant working under my direction. I want to be upfront about that, and equally upfront about what it was not. This was not vibe coding, where a one-shot prompt produces a pile of generated code and the person driving hopes for the best because they lack the knowledge to judge what came back. Vibe coding has earned its bad reputation. I engineered wrapture carefully from the start. I have spent a long time in this particular corner of Python and knew exactly what the result needed to be, and the AI was the means of producing it rather than the source of the design.
In a follow-up post, Unit testing with wrapture, Graham shows the testing patterns supported by the new library:
def test_stub_with_wrapture(): with wrapture.binding( Gateway, "charge" ).on_call.returns({ "id": "stub", "amount": 0} ): assert OrderService().place( 500 )["id"] == "stub"
And this neat example of a test that calls and then modifies the return value from the original method:
def test_pinned_result_with_wrapture(): charge = wrapture.binding( Gateway, "charge" ) charge.on_call.transforms_result( lambda r: {**r, "id": "ch_TEST"} ) with charge: assert OrderService().place( 500 ) == { "id": "ch_TEST", "amount": 500 }
(In both of these examples the OrderService().place(...) method calls Gateway().charge(...).)
Recent articles
Understanding ChatGPT Work - 30th August 2026
Conceptual integrity and counting lines of code - 19th August 2026
Qwen 3.8 27B is excellent, but it defaults to wildly overthinking things - 16th August 2026
This is a link post by Simon Willison, posted on 31st August 2026.
graham-dumpleton 5
monkey-patching 9
python 1,279
testing 94
pytest 25
observability 9
ai-assisted-programming 405
agentic-engineering 60
opentelemetry 3
Monthly briefing
Sponsor me for $10/month and get a curated email digest of the month's most important LLM developments.
Pay me to send you less!
Sponsor & subscribe
Disclosures
Colophon
©
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026