wrapture 1.0.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. wrapture-1.0.0a1/LICENSE +24 -0
  2. wrapture-1.0.0a1/PKG-INFO +193 -0
  3. wrapture-1.0.0a1/README.md +162 -0
  4. wrapture-1.0.0a1/pyproject.toml +111 -0
  5. wrapture-1.0.0a1/setup.cfg +4 -0
  6. wrapture-1.0.0a1/src/wrapture/__init__.py +232 -0
  7. wrapture-1.0.0a1/src/wrapture/__main__.py +148 -0
  8. wrapture-1.0.0a1/src/wrapture/_wrappermixins.py +438 -0
  9. wrapture-1.0.0a1/src/wrapture/asgi.py +620 -0
  10. wrapture-1.0.0a1/src/wrapture/attributes.py +667 -0
  11. wrapture-1.0.0a1/src/wrapture/behaviours.py +947 -0
  12. wrapture-1.0.0a1/src/wrapture/bindings.py +2533 -0
  13. wrapture-1.0.0a1/src/wrapture/bootstrap.py +86 -0
  14. wrapture-1.0.0a1/src/wrapture/capture.py +195 -0
  15. wrapture-1.0.0a1/src/wrapture/collectors.py +377 -0
  16. wrapture-1.0.0a1/src/wrapture/config.py +1413 -0
  17. wrapture-1.0.0a1/src/wrapture/decorators.py +702 -0
  18. wrapture-1.0.0a1/src/wrapture/doubles.py +452 -0
  19. wrapture-1.0.0a1/src/wrapture/eventlogs.py +297 -0
  20. wrapture-1.0.0a1/src/wrapture/events.py +294 -0
  21. wrapture-1.0.0a1/src/wrapture/exceptions.py +74 -0
  22. wrapture-1.0.0a1/src/wrapture/export.py +299 -0
  23. wrapture-1.0.0a1/src/wrapture/iterators.py +485 -0
  24. wrapture-1.0.0a1/src/wrapture/lifecycle.py +84 -0
  25. wrapture-1.0.0a1/src/wrapture/observed.py +506 -0
  26. wrapture-1.0.0a1/src/wrapture/outputs.py +219 -0
  27. wrapture-1.0.0a1/src/wrapture/py.typed +0 -0
  28. wrapture-1.0.0a1/src/wrapture/pytest_plugin.py +119 -0
  29. wrapture-1.0.0a1/src/wrapture/scheduler.py +297 -0
  30. wrapture-1.0.0a1/src/wrapture/sinks.py +1176 -0
  31. wrapture-1.0.0a1/src/wrapture/stacks.py +152 -0
  32. wrapture-1.0.0a1/src/wrapture/timeline.py +670 -0
  33. wrapture-1.0.0a1/src/wrapture/tools/__init__.py +8 -0
  34. wrapture-1.0.0a1/src/wrapture/tools/__main__.py +51 -0
  35. wrapture-1.0.0a1/src/wrapture/tools/convert.py +150 -0
  36. wrapture-1.0.0a1/src/wrapture/values.py +163 -0
  37. wrapture-1.0.0a1/src/wrapture/windows.py +988 -0
  38. wrapture-1.0.0a1/src/wrapture/wsgi.py +637 -0
  39. wrapture-1.0.0a1/src/wrapture.egg-info/PKG-INFO +193 -0
  40. wrapture-1.0.0a1/src/wrapture.egg-info/SOURCES.txt +84 -0
  41. wrapture-1.0.0a1/src/wrapture.egg-info/dependency_links.txt +1 -0
  42. wrapture-1.0.0a1/src/wrapture.egg-info/entry_points.txt +2 -0
  43. wrapture-1.0.0a1/src/wrapture.egg-info/not-zip-safe +1 -0
  44. wrapture-1.0.0a1/src/wrapture.egg-info/requires.txt +12 -0
  45. wrapture-1.0.0a1/src/wrapture.egg-info/top_level.txt +1 -0
  46. wrapture-1.0.0a1/tests/test_asgi.py +728 -0
  47. wrapture-1.0.0a1/tests/test_async_stubs.py +450 -0
  48. wrapture-1.0.0a1/tests/test_attribute_events.py +303 -0
  49. wrapture-1.0.0a1/tests/test_attributes.py +586 -0
  50. wrapture-1.0.0a1/tests/test_behaviours.py +403 -0
  51. wrapture-1.0.0a1/tests/test_bindings.py +653 -0
  52. wrapture-1.0.0a1/tests/test_bootstrap.py +147 -0
  53. wrapture-1.0.0a1/tests/test_capture.py +315 -0
  54. wrapture-1.0.0a1/tests/test_collectors.py +214 -0
  55. wrapture-1.0.0a1/tests/test_config.py +1309 -0
  56. wrapture-1.0.0a1/tests/test_decorators.py +647 -0
  57. wrapture-1.0.0a1/tests/test_doubles.py +678 -0
  58. wrapture-1.0.0a1/tests/test_eventlogs.py +519 -0
  59. wrapture-1.0.0a1/tests/test_events.py +215 -0
  60. wrapture-1.0.0a1/tests/test_export.py +253 -0
  61. wrapture-1.0.0a1/tests/test_generator_events.py +441 -0
  62. wrapture-1.0.0a1/tests/test_iterators.py +563 -0
  63. wrapture-1.0.0a1/tests/test_jsonlines.py +440 -0
  64. wrapture-1.0.0a1/tests/test_mapping.py +383 -0
  65. wrapture-1.0.0a1/tests/test_module_attributes.py +389 -0
  66. wrapture-1.0.0a1/tests/test_observed.py +371 -0
  67. wrapture-1.0.0a1/tests/test_outputs.py +196 -0
  68. wrapture-1.0.0a1/tests/test_phases.py +920 -0
  69. wrapture-1.0.0a1/tests/test_predicates.py +231 -0
  70. wrapture-1.0.0a1/tests/test_pytest_plugin.py +198 -0
  71. wrapture-1.0.0a1/tests/test_recording.py +492 -0
  72. wrapture-1.0.0a1/tests/test_runner.py +213 -0
  73. wrapture-1.0.0a1/tests/test_sink_library.py +360 -0
  74. wrapture-1.0.0a1/tests/test_sinks.py +603 -0
  75. wrapture-1.0.0a1/tests/test_stacks.py +249 -0
  76. wrapture-1.0.0a1/tests/test_strict.py +228 -0
  77. wrapture-1.0.0a1/tests/test_targets.py +153 -0
  78. wrapture-1.0.0a1/tests/test_testing_patterns.py +136 -0
  79. wrapture-1.0.0a1/tests/test_threads.py +324 -0
  80. wrapture-1.0.0a1/tests/test_timeline.py +752 -0
  81. wrapture-1.0.0a1/tests/test_tools.py +235 -0
  82. wrapture-1.0.0a1/tests/test_values.py +535 -0
  83. wrapture-1.0.0a1/tests/test_version.py +33 -0
  84. wrapture-1.0.0a1/tests/test_windows.py +931 -0
  85. wrapture-1.0.0a1/tests/test_wrappermixins.py +214 -0
  86. wrapture-1.0.0a1/tests/test_wsgi.py +668 -0
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2026, Graham Dumpleton
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24
+ POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: wrapture
3
+ Version: 1.0.0a1
4
+ Summary: Monkey patch, test, and trace Python by attaching bindings to call sites, without modifying the code being observed. Built on wrapt.
5
+ Author-email: Graham Dumpleton <Graham.Dumpleton@gmail.com>
6
+ License-Expression: BSD-2-Clause
7
+ Project-URL: Homepage, https://github.com/GrahamDumpleton/wrapture
8
+ Project-URL: Documentation, https://wrapture.readthedocs.io
9
+ Project-URL: Bug Tracker, https://github.com/GrahamDumpleton/wrapture/issues/
10
+ Keywords: wrapper,monkey patching,tracing,testing
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Programming Language :: Python :: Implementation :: CPython
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: wrapt>=2.4.0rc5
21
+ Provides-Extra: dev
22
+ Requires-Dist: mypy; extra == "dev"
23
+ Requires-Dist: pytest; extra == "dev"
24
+ Requires-Dist: ruff; extra == "dev"
25
+ Requires-Dist: setuptools; extra == "dev"
26
+ Provides-Extra: docs
27
+ Requires-Dist: myst-parser; extra == "docs"
28
+ Requires-Dist: sphinx; extra == "docs"
29
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
30
+ Dynamic: license-file
31
+
32
+ # wrapture
33
+
34
+ **Wrap anything, capture everything, change nothing.**
35
+
36
+ [![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)
37
+ [![Documentation](https://readthedocs.org/projects/wrapture/badge/?version=latest)](https://wrapture.readthedocs.io)
38
+
39
+ wrapture (`wrapt` + `capture`) is a Python library for attaching bindings to
40
+ arbitrary call sites, without modifying the code being observed, and doing
41
+ something useful with what flows through them.
42
+
43
+ It is a sibling project to [wrapt](https://github.com/GrahamDumpleton/wrapt)
44
+ and [autowrapt](https://github.com/GrahamDumpleton/autowrapt), building on the
45
+ safe monkey-patching machinery wrapt provides.
46
+
47
+ > **Status: stabilising ahead of 1.0.0.** The feature set is complete for
48
+ > a first release and pre-releases are published to PyPI. Until 1.0.0 is
49
+ > final, a plain `pip install wrapture` picks up the latest pre-release
50
+ > automatically, so there is no need to pin a specific version. The API
51
+ > is being exercised and tidied rather than extended, so small changes are
52
+ > still possible before 1.0.0.
53
+
54
+ ## Installation
55
+
56
+ wrapture is on [PyPI](https://pypi.org/project/wrapture/):
57
+
58
+ ```console
59
+ $ pip install wrapture
60
+ ```
61
+
62
+ or with uv:
63
+
64
+ ```console
65
+ $ uv add wrapture
66
+ ```
67
+
68
+ ## Documentation
69
+
70
+ Full documentation is at [wrapture.readthedocs.io](https://wrapture.readthedocs.io).
71
+ Start with the [getting started](https://wrapture.readthedocs.io/en/latest/getting-started.html)
72
+ page: everything on it can be pasted into a Python interpreter. Coming
73
+ from `unittest.mock`? There is a
74
+ [comparison page](https://wrapture.readthedocs.io/en/latest/coming-from-mock.html)
75
+ mapping each mock idiom to its wrapture counterpart. After that, the
76
+ worked examples, starting with
77
+ [testing code that calls external services](https://wrapture.readthedocs.io/en/latest/example-external-services.html),
78
+ each take one question you might arrive with and answer it end to end.
79
+
80
+ ## At a glance
81
+
82
+ None of the classes below import wrapture or know they are observed:
83
+
84
+ ```python
85
+ place = wrapture.binding(OrderService, "place")
86
+ charge = wrapture.binding(Gateway, "charge")
87
+ record = wrapture.binding(Ledger, "record")
88
+
89
+ with wrapture.timeline(place, charge, record) as tape:
90
+ OrderService().place(500)
91
+
92
+ print(tape.tree())
93
+ ```
94
+
95
+ ```
96
+ OrderService.place(amount=500) -> {'id': 'ch_500', 'amount': 500}
97
+ Gateway.charge(amount=500, currency='USD') -> {'id': 'ch_500', 'amount': 500}
98
+ Ledger.record(entry={'id': 'ch_500', 'amount': 500}) -> 'led_ch_500'
99
+ ```
100
+
101
+ The same bindings intervene as well as observe: stub a result, inject a
102
+ failure, or transform one argument while the real code keeps running.
103
+
104
+ ## What it does
105
+
106
+ One mechanism, three uses, in increasing order of machinery:
107
+
108
+ 1. **Monkey patching.** A clean lifecycle and behaviour vocabulary over
109
+ wrapt's `wrap_object()`. Point at a method by name and stub it, fail it,
110
+ transform its arguments or result, or wrap it with a decorator, then
111
+ remove it again, with honest reporting if something else displaced the
112
+ patch in the meantime. Useful entirely on its own, with nothing else
113
+ switched on.
114
+
115
+ 2. **Unit testing.** Observe and assert on how calls actually flowed through a
116
+ *real* call graph (nesting, ordering, arguments and return values) and
117
+ optionally intervene (stub, transform, fail-inject). Unlike a
118
+ `unittest.mock` `Mock`, which fabricates values and cannot see calls an object makes to itself,
119
+ wrapture watches the real code run, and when a test must supply a
120
+ stand-in it provides strict, recorded ones: `stub()` for a callable,
121
+ spec-required `mock()` for a collaborator. This makes it possible to test code
122
+ with no injectable seams at all, and to assert on what *didn't* happen on
123
+ an error path: inject a gateway timeout, then verify the ledger was not
124
+ written, the receipt was not sent, and the compensating refund was issued.
125
+
126
+ 3. **Ad-hoc tracing.** Attach bindings to a running application, including
127
+ one you cannot modify or redeploy, and emit a structured, nested trace to
128
+ process or chart elsewhere. Name a handful of methods and a call tree
129
+ appears; no code changes required: with a `wrapture.toml` naming the
130
+ methods and a sink, `python -m wrapture manage.py runserver` traces the
131
+ application untouched. With [autowrapt](https://github.com/GrahamDumpleton/autowrapt)
132
+ installed, not even the launcher is needed: `AUTOWRAPT_BOOTSTRAP=wrapture`
133
+ in the environment applies the same config at interpreter startup, so the
134
+ program starts with plain `python`.
135
+
136
+ The distinction that matters: most tracing tools either need the code to
137
+ have been written with them in mind, or can only be switched on for the
138
+ whole program at once. wrapture needs neither: you point at a method by
139
+ name and a trace appears.
140
+
141
+ ## Why
142
+
143
+ No single existing tool covers "point at arbitrary methods, get a structured
144
+ nested trace, assert on it or export it, in tests or in production":
145
+
146
+ - `unittest.mock` records a flat call list, with no nesting and no return
147
+ values, and a patched call returns a fabricated `MagicMock` rather than
148
+ running the real code.
149
+ - Span-assertion tools (`logfire.testing`, OpenTelemetry's
150
+ `InMemorySpanExporter`) require the code to already be instrumented.
151
+ - `sys.settrace` tools (`hunter`, `snoop`) give a firehose with no assertion
152
+ API.
153
+ - APM agents are all-or-nothing products rather than a toolkit.
154
+
155
+ wrapture fills that gap: a targeted call tree with normalized arguments and
156
+ return values, produced by naming the methods you care about, usable as a
157
+ testing assertion library, a tracing tool, or both at once.
158
+
159
+ ## What it is not
160
+
161
+ - **Not a fabrication tool.** There is no spec-less `Mock()` here by
162
+ design; stand-ins are strict and built from named specs, and
163
+ `unittest.mock` remains the tool for invented objects.
164
+ - **Not a production APM.** It is a toolkit that APM-like things could be
165
+ built on.
166
+ - **Not an OpenTelemetry competitor.** It should emit to OTel, not replace
167
+ it.
168
+
169
+ ## How it was built
170
+
171
+ wrapture's code and documentation were written by an AI assistant under
172
+ the direction of Graham Dumpleton, the author of wrapt, through a long
173
+ process of specification, layered implementation, and validation against
174
+ real-world test suites.
175
+ [How wrapture was built](https://wrapture.readthedocs.io/en/latest/how-wrapture-was-built.html)
176
+ explains the process and the thinking.
177
+
178
+ ## Requirements
179
+
180
+ - Python 3.12+
181
+ - [wrapt](https://github.com/GrahamDumpleton/wrapt) 2.4.0+
182
+
183
+ ## Issues
184
+
185
+ Bug reports and feature requests go to the
186
+ [issue tracker](https://github.com/GrahamDumpleton/wrapture/issues).
187
+ Include the wrapture and Python versions and, where you can, a small
188
+ binding that reproduces the problem.
189
+
190
+ ## License
191
+
192
+ BSD 2-Clause. See
193
+ [LICENSE](https://github.com/GrahamDumpleton/wrapture/blob/develop/LICENSE).
@@ -0,0 +1,162 @@
1
+ # wrapture
2
+
3
+ **Wrap anything, capture everything, change nothing.**
4
+
5
+ [![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)
6
+ [![Documentation](https://readthedocs.org/projects/wrapture/badge/?version=latest)](https://wrapture.readthedocs.io)
7
+
8
+ wrapture (`wrapt` + `capture`) is a Python library for attaching bindings to
9
+ arbitrary call sites, without modifying the code being observed, and doing
10
+ something useful with what flows through them.
11
+
12
+ It is a sibling project to [wrapt](https://github.com/GrahamDumpleton/wrapt)
13
+ and [autowrapt](https://github.com/GrahamDumpleton/autowrapt), building on the
14
+ safe monkey-patching machinery wrapt provides.
15
+
16
+ > **Status: stabilising ahead of 1.0.0.** The feature set is complete for
17
+ > a first release and pre-releases are published to PyPI. Until 1.0.0 is
18
+ > final, a plain `pip install wrapture` picks up the latest pre-release
19
+ > automatically, so there is no need to pin a specific version. The API
20
+ > is being exercised and tidied rather than extended, so small changes are
21
+ > still possible before 1.0.0.
22
+
23
+ ## Installation
24
+
25
+ wrapture is on [PyPI](https://pypi.org/project/wrapture/):
26
+
27
+ ```console
28
+ $ pip install wrapture
29
+ ```
30
+
31
+ or with uv:
32
+
33
+ ```console
34
+ $ uv add wrapture
35
+ ```
36
+
37
+ ## Documentation
38
+
39
+ Full documentation is at [wrapture.readthedocs.io](https://wrapture.readthedocs.io).
40
+ Start with the [getting started](https://wrapture.readthedocs.io/en/latest/getting-started.html)
41
+ page: everything on it can be pasted into a Python interpreter. Coming
42
+ from `unittest.mock`? There is a
43
+ [comparison page](https://wrapture.readthedocs.io/en/latest/coming-from-mock.html)
44
+ mapping each mock idiom to its wrapture counterpart. After that, the
45
+ worked examples, starting with
46
+ [testing code that calls external services](https://wrapture.readthedocs.io/en/latest/example-external-services.html),
47
+ each take one question you might arrive with and answer it end to end.
48
+
49
+ ## At a glance
50
+
51
+ None of the classes below import wrapture or know they are observed:
52
+
53
+ ```python
54
+ place = wrapture.binding(OrderService, "place")
55
+ charge = wrapture.binding(Gateway, "charge")
56
+ record = wrapture.binding(Ledger, "record")
57
+
58
+ with wrapture.timeline(place, charge, record) as tape:
59
+ OrderService().place(500)
60
+
61
+ print(tape.tree())
62
+ ```
63
+
64
+ ```
65
+ OrderService.place(amount=500) -> {'id': 'ch_500', 'amount': 500}
66
+ Gateway.charge(amount=500, currency='USD') -> {'id': 'ch_500', 'amount': 500}
67
+ Ledger.record(entry={'id': 'ch_500', 'amount': 500}) -> 'led_ch_500'
68
+ ```
69
+
70
+ The same bindings intervene as well as observe: stub a result, inject a
71
+ failure, or transform one argument while the real code keeps running.
72
+
73
+ ## What it does
74
+
75
+ One mechanism, three uses, in increasing order of machinery:
76
+
77
+ 1. **Monkey patching.** A clean lifecycle and behaviour vocabulary over
78
+ wrapt's `wrap_object()`. Point at a method by name and stub it, fail it,
79
+ transform its arguments or result, or wrap it with a decorator, then
80
+ remove it again, with honest reporting if something else displaced the
81
+ patch in the meantime. Useful entirely on its own, with nothing else
82
+ switched on.
83
+
84
+ 2. **Unit testing.** Observe and assert on how calls actually flowed through a
85
+ *real* call graph (nesting, ordering, arguments and return values) and
86
+ optionally intervene (stub, transform, fail-inject). Unlike a
87
+ `unittest.mock` `Mock`, which fabricates values and cannot see calls an object makes to itself,
88
+ wrapture watches the real code run, and when a test must supply a
89
+ stand-in it provides strict, recorded ones: `stub()` for a callable,
90
+ spec-required `mock()` for a collaborator. This makes it possible to test code
91
+ with no injectable seams at all, and to assert on what *didn't* happen on
92
+ an error path: inject a gateway timeout, then verify the ledger was not
93
+ written, the receipt was not sent, and the compensating refund was issued.
94
+
95
+ 3. **Ad-hoc tracing.** Attach bindings to a running application, including
96
+ one you cannot modify or redeploy, and emit a structured, nested trace to
97
+ process or chart elsewhere. Name a handful of methods and a call tree
98
+ appears; no code changes required: with a `wrapture.toml` naming the
99
+ methods and a sink, `python -m wrapture manage.py runserver` traces the
100
+ application untouched. With [autowrapt](https://github.com/GrahamDumpleton/autowrapt)
101
+ installed, not even the launcher is needed: `AUTOWRAPT_BOOTSTRAP=wrapture`
102
+ in the environment applies the same config at interpreter startup, so the
103
+ program starts with plain `python`.
104
+
105
+ The distinction that matters: most tracing tools either need the code to
106
+ have been written with them in mind, or can only be switched on for the
107
+ whole program at once. wrapture needs neither: you point at a method by
108
+ name and a trace appears.
109
+
110
+ ## Why
111
+
112
+ No single existing tool covers "point at arbitrary methods, get a structured
113
+ nested trace, assert on it or export it, in tests or in production":
114
+
115
+ - `unittest.mock` records a flat call list, with no nesting and no return
116
+ values, and a patched call returns a fabricated `MagicMock` rather than
117
+ running the real code.
118
+ - Span-assertion tools (`logfire.testing`, OpenTelemetry's
119
+ `InMemorySpanExporter`) require the code to already be instrumented.
120
+ - `sys.settrace` tools (`hunter`, `snoop`) give a firehose with no assertion
121
+ API.
122
+ - APM agents are all-or-nothing products rather than a toolkit.
123
+
124
+ wrapture fills that gap: a targeted call tree with normalized arguments and
125
+ return values, produced by naming the methods you care about, usable as a
126
+ testing assertion library, a tracing tool, or both at once.
127
+
128
+ ## What it is not
129
+
130
+ - **Not a fabrication tool.** There is no spec-less `Mock()` here by
131
+ design; stand-ins are strict and built from named specs, and
132
+ `unittest.mock` remains the tool for invented objects.
133
+ - **Not a production APM.** It is a toolkit that APM-like things could be
134
+ built on.
135
+ - **Not an OpenTelemetry competitor.** It should emit to OTel, not replace
136
+ it.
137
+
138
+ ## How it was built
139
+
140
+ wrapture's code and documentation were written by an AI assistant under
141
+ the direction of Graham Dumpleton, the author of wrapt, through a long
142
+ process of specification, layered implementation, and validation against
143
+ real-world test suites.
144
+ [How wrapture was built](https://wrapture.readthedocs.io/en/latest/how-wrapture-was-built.html)
145
+ explains the process and the thinking.
146
+
147
+ ## Requirements
148
+
149
+ - Python 3.12+
150
+ - [wrapt](https://github.com/GrahamDumpleton/wrapt) 2.4.0+
151
+
152
+ ## Issues
153
+
154
+ Bug reports and feature requests go to the
155
+ [issue tracker](https://github.com/GrahamDumpleton/wrapture/issues).
156
+ Include the wrapture and Python versions and, where you can, a small
157
+ binding that reproduces the problem.
158
+
159
+ ## License
160
+
161
+ BSD 2-Clause. See
162
+ [LICENSE](https://github.com/GrahamDumpleton/wrapture/blob/develop/LICENSE).
@@ -0,0 +1,111 @@
1
+ [build-system]
2
+ # wrapt is needed at build time because reading the dynamic version imports
3
+ # the package, and the package imports wrapt at the top level.
4
+ requires = ["setuptools>=62.0", "wheel", "wrapt>=2.4.0rc5"]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "wrapture"
9
+ dynamic = ["version"]
10
+ description = "Monkey patch, test, and trace Python by attaching bindings to call sites, without modifying the code being observed. Built on wrapt."
11
+ readme = "README.md"
12
+ license = "BSD-2-Clause"
13
+ license-files = ["LICENSE"]
14
+ authors = [
15
+ {name = "Graham Dumpleton", email = "Graham.Dumpleton@gmail.com"}
16
+ ]
17
+ requires-python = ">=3.12"
18
+ keywords = ["wrapper", "monkey patching", "tracing", "testing"]
19
+ classifiers = [
20
+ "Development Status :: 2 - Pre-Alpha",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Programming Language :: Python :: Implementation :: CPython",
26
+ ]
27
+ dependencies = [
28
+ "wrapt>=2.4.0rc5",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/GrahamDumpleton/wrapture"
33
+ Documentation = "https://wrapture.readthedocs.io"
34
+ "Bug Tracker" = "https://github.com/GrahamDumpleton/wrapture/issues/"
35
+
36
+ # The zero-code injection trigger, in the shape autowrapt consumes:
37
+ # AUTOWRAPT_BOOTSTRAP=wrapture names this entry point group, which
38
+ # autowrapt hands to wrapt.discover_post_import_hooks() at interpreter
39
+ # startup. Each entry maps a trigger module to a callback; hooking os,
40
+ # which is always already imported by then, makes the callback fire
41
+ # immediately at startup. Inert metadata unless autowrapt is installed
42
+ # and the environment variable is set; wrapture itself has no
43
+ # dependency on autowrapt and never imports it.
44
+
45
+ [project.entry-points.wrapture]
46
+ os = "wrapture.bootstrap:bootstrap"
47
+
48
+ [project.optional-dependencies]
49
+ dev = [
50
+ "mypy",
51
+ "pytest",
52
+ "ruff",
53
+ "setuptools",
54
+ ]
55
+ docs = [
56
+ "myst-parser",
57
+ "sphinx",
58
+ "sphinx-rtd-theme",
59
+ ]
60
+
61
+ [tool.setuptools]
62
+ package-dir = {"" = "src"}
63
+ zip-safe = false
64
+ packages = ["wrapture", "wrapture.tools"]
65
+
66
+ [tool.setuptools.package-data]
67
+ wrapture = ["py.typed"]
68
+
69
+ [tool.setuptools.dynamic]
70
+ version = {attr = "wrapture.__version__"}
71
+
72
+ [dependency-groups]
73
+ test = [
74
+ "pytest",
75
+ ]
76
+ dev = [
77
+ {include-group = "test"},
78
+ "mypy",
79
+ "ruff",
80
+ "setuptools",
81
+ ]
82
+
83
+ [tool.pytest.ini_options]
84
+ # The docs are on the test paths so the interpreter transcripts in the
85
+ # getting started page run as doctests and cannot rot.
86
+ testpaths = ["tests", "docs"]
87
+ addopts = "--doctest-glob=*.md"
88
+ # ELLIPSIS lets a transcript elide values that vary between runs, such
89
+ # as the elapsed time on a printer's closing line.
90
+ doctest_optionflags = ["ELLIPSIS"]
91
+
92
+ [tool.mypy]
93
+ python_version = "3.12"
94
+ strict = true
95
+ files = ["src/wrapture", "tests"]
96
+
97
+ [tool.ruff]
98
+ line-length = 88
99
+ target-version = "py312"
100
+
101
+ [tool.ruff.lint]
102
+ # Version information is defined before the imports in __init__.py.
103
+ per-file-ignores = {"src/wrapture/__init__.py" = ["E402"]}
104
+ select = [
105
+ "E", # pycodestyle errors
106
+ "W", # pycodestyle warnings
107
+ "F", # pyflakes
108
+ "I", # isort
109
+ "UP", # pyupgrade
110
+ "B", # flake8-bugbear
111
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+