wrapture 1.0.0.dev1__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.
- wrapture-1.0.0.dev1/LICENSE +24 -0
- wrapture-1.0.0.dev1/PKG-INFO +155 -0
- wrapture-1.0.0.dev1/README.md +124 -0
- wrapture-1.0.0.dev1/pyproject.toml +96 -0
- wrapture-1.0.0.dev1/setup.cfg +4 -0
- wrapture-1.0.0.dev1/src/wrapture/__init__.py +119 -0
- wrapture-1.0.0.dev1/src/wrapture/attributes.py +344 -0
- wrapture-1.0.0.dev1/src/wrapture/behaviours.py +428 -0
- wrapture-1.0.0.dev1/src/wrapture/bindings.py +1110 -0
- wrapture-1.0.0.dev1/src/wrapture/capture.py +165 -0
- wrapture-1.0.0.dev1/src/wrapture/eventlogs.py +232 -0
- wrapture-1.0.0.dev1/src/wrapture/events.py +201 -0
- wrapture-1.0.0.dev1/src/wrapture/exceptions.py +45 -0
- wrapture-1.0.0.dev1/src/wrapture/iterators.py +451 -0
- wrapture-1.0.0.dev1/src/wrapture/py.typed +0 -0
- wrapture-1.0.0.dev1/src/wrapture/pytest_plugin.py +119 -0
- wrapture-1.0.0.dev1/src/wrapture/stacks.py +93 -0
- wrapture-1.0.0.dev1/src/wrapture/timeline.py +364 -0
- wrapture-1.0.0.dev1/src/wrapture.egg-info/PKG-INFO +155 -0
- wrapture-1.0.0.dev1/src/wrapture.egg-info/SOURCES.txt +39 -0
- wrapture-1.0.0.dev1/src/wrapture.egg-info/dependency_links.txt +1 -0
- wrapture-1.0.0.dev1/src/wrapture.egg-info/not-zip-safe +1 -0
- wrapture-1.0.0.dev1/src/wrapture.egg-info/requires.txt +12 -0
- wrapture-1.0.0.dev1/src/wrapture.egg-info/top_level.txt +1 -0
- wrapture-1.0.0.dev1/tests/test_attribute_events.py +291 -0
- wrapture-1.0.0.dev1/tests/test_attributes.py +597 -0
- wrapture-1.0.0.dev1/tests/test_behaviours.py +290 -0
- wrapture-1.0.0.dev1/tests/test_bindings.py +402 -0
- wrapture-1.0.0.dev1/tests/test_capture.py +300 -0
- wrapture-1.0.0.dev1/tests/test_eventlogs.py +353 -0
- wrapture-1.0.0.dev1/tests/test_events.py +183 -0
- wrapture-1.0.0.dev1/tests/test_generator_events.py +436 -0
- wrapture-1.0.0.dev1/tests/test_iterators.py +513 -0
- wrapture-1.0.0.dev1/tests/test_pytest_plugin.py +198 -0
- wrapture-1.0.0.dev1/tests/test_recording.py +453 -0
- wrapture-1.0.0.dev1/tests/test_stacks.py +190 -0
- wrapture-1.0.0.dev1/tests/test_targets.py +153 -0
- wrapture-1.0.0.dev1/tests/test_testing_patterns.py +136 -0
- wrapture-1.0.0.dev1/tests/test_threads.py +259 -0
- wrapture-1.0.0.dev1/tests/test_timeline.py +414 -0
- wrapture-1.0.0.dev1/tests/test_version.py +33 -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,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wrapture
|
|
3
|
+
Version: 1.0.0.dev1
|
|
4
|
+
Summary: Library for attaching bindings to arbitrary Python call sites for monkey patching, testing, tracing and profiling.
|
|
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,profiling
|
|
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.0rc4
|
|
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
|
+
**Trace assertions without instrumenting your code.**
|
|
35
|
+
|
|
36
|
+
[](https://github.com/GrahamDumpleton/wrapture/actions/workflows/build-test-release.yml)
|
|
37
|
+
[](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: early development.** The monkey patching and unit testing layers
|
|
48
|
+
> are implemented; the tracing and profiling layers are designed but not
|
|
49
|
+
> built. Nothing is published to PyPI yet.
|
|
50
|
+
|
|
51
|
+
## Documentation
|
|
52
|
+
|
|
53
|
+
Full documentation is at [wrapture.readthedocs.io](https://wrapture.readthedocs.io).
|
|
54
|
+
Start with the [getting started](https://wrapture.readthedocs.io/en/latest/getting-started.html)
|
|
55
|
+
page: everything on it can be pasted into a Python interpreter. Coming
|
|
56
|
+
from `unittest.mock`? There is a
|
|
57
|
+
[comparison page](https://wrapture.readthedocs.io/en/latest/coming-from-mock.html)
|
|
58
|
+
mapping each mock idiom to its wrapture counterpart.
|
|
59
|
+
|
|
60
|
+
## Thirty seconds of it
|
|
61
|
+
|
|
62
|
+
None of the classes below import wrapture or know they are observed:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
place = wrapture.binding(OrderService, "place")
|
|
66
|
+
charge = wrapture.binding(Gateway, "charge")
|
|
67
|
+
record = wrapture.binding(Ledger, "record")
|
|
68
|
+
|
|
69
|
+
with wrapture.timeline(place, charge, record) as tape:
|
|
70
|
+
OrderService().place(500)
|
|
71
|
+
|
|
72
|
+
print(tape.tree())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
OrderService.place(amount=500) -> {'id': 'ch_500', 'amount': 500}
|
|
77
|
+
Gateway.charge(amount=500, currency='USD') -> {'id': 'ch_500', 'amount': 500}
|
|
78
|
+
Ledger.record(entry={'id': 'ch_500', 'amount': 500}) -> 'led_ch_500'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The same bindings intervene as well as observe: stub a result, inject a
|
|
82
|
+
failure, or transform one argument while the real code keeps running.
|
|
83
|
+
|
|
84
|
+
## What it does
|
|
85
|
+
|
|
86
|
+
One mechanism, four uses, in increasing order of machinery:
|
|
87
|
+
|
|
88
|
+
1. **Monkey patching.** A clean lifecycle and behaviour vocabulary over
|
|
89
|
+
wrapt's `wrap_object()`. Point at a method by name and stub it, fail it,
|
|
90
|
+
transform its arguments or result, or wrap it with a decorator, then
|
|
91
|
+
remove it again, with honest reporting if something else displaced the
|
|
92
|
+
patch in the meantime. Useful entirely on its own, with nothing else
|
|
93
|
+
switched on.
|
|
94
|
+
|
|
95
|
+
2. **Unit testing.** Observe and assert on how calls actually flowed through a
|
|
96
|
+
*real* call graph (nesting, ordering, arguments and return values) and
|
|
97
|
+
optionally intervene (stub, transform, fail-inject). Unlike a `Mock`,
|
|
98
|
+
which fabricates values and cannot see calls an object makes to itself,
|
|
99
|
+
wrapture watches the real code run. This makes it possible to test code
|
|
100
|
+
with no injectable seams at all, and to assert on what *didn't* happen on
|
|
101
|
+
an error path: inject a gateway timeout, then verify the ledger was not
|
|
102
|
+
written, the receipt was not sent, and the compensating refund was issued.
|
|
103
|
+
|
|
104
|
+
3. **Ad-hoc tracing.** Attach bindings to a running application, including
|
|
105
|
+
one you cannot modify or redeploy, and emit a structured, nested trace to
|
|
106
|
+
process or chart elsewhere. Name a handful of methods and a call tree
|
|
107
|
+
appears; no code changes required.
|
|
108
|
+
|
|
109
|
+
4. **Targeted profiling.** Use a binding as a *scope* within which CPython's
|
|
110
|
+
own profiling machinery is active, so you can profile one subsystem of a
|
|
111
|
+
live process instead of everything.
|
|
112
|
+
|
|
113
|
+
The distinction that matters: most tracing and profiling tools either need
|
|
114
|
+
the code to have been written with them in mind, or can only be switched on
|
|
115
|
+
for the whole program at once. wrapture needs neither: you point at a method
|
|
116
|
+
by name and a trace appears.
|
|
117
|
+
|
|
118
|
+
## Why
|
|
119
|
+
|
|
120
|
+
No single existing tool covers "point at arbitrary methods, get a structured
|
|
121
|
+
nested trace, assert on it or export it, in tests or in production":
|
|
122
|
+
|
|
123
|
+
- `unittest.mock` records a flat call list, with no nesting and no return
|
|
124
|
+
values, and a patched call returns a fabricated `MagicMock` rather than
|
|
125
|
+
running the real code.
|
|
126
|
+
- Span-assertion tools (`logfire.testing`, OpenTelemetry's
|
|
127
|
+
`InMemorySpanExporter`) require the code to already be instrumented.
|
|
128
|
+
- `sys.settrace` tools (`hunter`, `snoop`) give a firehose with no assertion
|
|
129
|
+
API.
|
|
130
|
+
- `cProfile` cannot scope to a subsystem in a live process, and APM agents
|
|
131
|
+
are all-or-nothing products rather than a toolkit.
|
|
132
|
+
|
|
133
|
+
wrapture fills that gap: a targeted call tree with normalized arguments and
|
|
134
|
+
return values, produced by naming the methods you care about, usable as a
|
|
135
|
+
testing assertion library, a tracing tool, or both at once.
|
|
136
|
+
|
|
137
|
+
## What it is not
|
|
138
|
+
|
|
139
|
+
- **Not a replacement for `unittest.mock`.** It complements mocking where
|
|
140
|
+
code has seams; it exists for the code that doesn't.
|
|
141
|
+
- **Not a sampling profiler.** `py-spy` and `austin` do that better and
|
|
142
|
+
without distortion.
|
|
143
|
+
- **Not a production APM.** It is a toolkit that APM-like things could be
|
|
144
|
+
built on.
|
|
145
|
+
- **Not an OpenTelemetry competitor.** It should emit to OTel, not replace
|
|
146
|
+
it.
|
|
147
|
+
|
|
148
|
+
## Requirements
|
|
149
|
+
|
|
150
|
+
- Python 3.12+
|
|
151
|
+
- [wrapt](https://github.com/GrahamDumpleton/wrapt) 2.4.0+
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
BSD 2-Clause. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# wrapture
|
|
2
|
+
|
|
3
|
+
**Trace assertions without instrumenting your code.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/GrahamDumpleton/wrapture/actions/workflows/build-test-release.yml)
|
|
6
|
+
[](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: early development.** The monkey patching and unit testing layers
|
|
17
|
+
> are implemented; the tracing and profiling layers are designed but not
|
|
18
|
+
> built. Nothing is published to PyPI yet.
|
|
19
|
+
|
|
20
|
+
## Documentation
|
|
21
|
+
|
|
22
|
+
Full documentation is at [wrapture.readthedocs.io](https://wrapture.readthedocs.io).
|
|
23
|
+
Start with the [getting started](https://wrapture.readthedocs.io/en/latest/getting-started.html)
|
|
24
|
+
page: everything on it can be pasted into a Python interpreter. Coming
|
|
25
|
+
from `unittest.mock`? There is a
|
|
26
|
+
[comparison page](https://wrapture.readthedocs.io/en/latest/coming-from-mock.html)
|
|
27
|
+
mapping each mock idiom to its wrapture counterpart.
|
|
28
|
+
|
|
29
|
+
## Thirty seconds of it
|
|
30
|
+
|
|
31
|
+
None of the classes below import wrapture or know they are observed:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
place = wrapture.binding(OrderService, "place")
|
|
35
|
+
charge = wrapture.binding(Gateway, "charge")
|
|
36
|
+
record = wrapture.binding(Ledger, "record")
|
|
37
|
+
|
|
38
|
+
with wrapture.timeline(place, charge, record) as tape:
|
|
39
|
+
OrderService().place(500)
|
|
40
|
+
|
|
41
|
+
print(tape.tree())
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
OrderService.place(amount=500) -> {'id': 'ch_500', 'amount': 500}
|
|
46
|
+
Gateway.charge(amount=500, currency='USD') -> {'id': 'ch_500', 'amount': 500}
|
|
47
|
+
Ledger.record(entry={'id': 'ch_500', 'amount': 500}) -> 'led_ch_500'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The same bindings intervene as well as observe: stub a result, inject a
|
|
51
|
+
failure, or transform one argument while the real code keeps running.
|
|
52
|
+
|
|
53
|
+
## What it does
|
|
54
|
+
|
|
55
|
+
One mechanism, four uses, in increasing order of machinery:
|
|
56
|
+
|
|
57
|
+
1. **Monkey patching.** A clean lifecycle and behaviour vocabulary over
|
|
58
|
+
wrapt's `wrap_object()`. Point at a method by name and stub it, fail it,
|
|
59
|
+
transform its arguments or result, or wrap it with a decorator, then
|
|
60
|
+
remove it again, with honest reporting if something else displaced the
|
|
61
|
+
patch in the meantime. Useful entirely on its own, with nothing else
|
|
62
|
+
switched on.
|
|
63
|
+
|
|
64
|
+
2. **Unit testing.** Observe and assert on how calls actually flowed through a
|
|
65
|
+
*real* call graph (nesting, ordering, arguments and return values) and
|
|
66
|
+
optionally intervene (stub, transform, fail-inject). Unlike a `Mock`,
|
|
67
|
+
which fabricates values and cannot see calls an object makes to itself,
|
|
68
|
+
wrapture watches the real code run. This makes it possible to test code
|
|
69
|
+
with no injectable seams at all, and to assert on what *didn't* happen on
|
|
70
|
+
an error path: inject a gateway timeout, then verify the ledger was not
|
|
71
|
+
written, the receipt was not sent, and the compensating refund was issued.
|
|
72
|
+
|
|
73
|
+
3. **Ad-hoc tracing.** Attach bindings to a running application, including
|
|
74
|
+
one you cannot modify or redeploy, and emit a structured, nested trace to
|
|
75
|
+
process or chart elsewhere. Name a handful of methods and a call tree
|
|
76
|
+
appears; no code changes required.
|
|
77
|
+
|
|
78
|
+
4. **Targeted profiling.** Use a binding as a *scope* within which CPython's
|
|
79
|
+
own profiling machinery is active, so you can profile one subsystem of a
|
|
80
|
+
live process instead of everything.
|
|
81
|
+
|
|
82
|
+
The distinction that matters: most tracing and profiling tools either need
|
|
83
|
+
the code to have been written with them in mind, or can only be switched on
|
|
84
|
+
for the whole program at once. wrapture needs neither: you point at a method
|
|
85
|
+
by name and a trace appears.
|
|
86
|
+
|
|
87
|
+
## Why
|
|
88
|
+
|
|
89
|
+
No single existing tool covers "point at arbitrary methods, get a structured
|
|
90
|
+
nested trace, assert on it or export it, in tests or in production":
|
|
91
|
+
|
|
92
|
+
- `unittest.mock` records a flat call list, with no nesting and no return
|
|
93
|
+
values, and a patched call returns a fabricated `MagicMock` rather than
|
|
94
|
+
running the real code.
|
|
95
|
+
- Span-assertion tools (`logfire.testing`, OpenTelemetry's
|
|
96
|
+
`InMemorySpanExporter`) require the code to already be instrumented.
|
|
97
|
+
- `sys.settrace` tools (`hunter`, `snoop`) give a firehose with no assertion
|
|
98
|
+
API.
|
|
99
|
+
- `cProfile` cannot scope to a subsystem in a live process, and APM agents
|
|
100
|
+
are all-or-nothing products rather than a toolkit.
|
|
101
|
+
|
|
102
|
+
wrapture fills that gap: a targeted call tree with normalized arguments and
|
|
103
|
+
return values, produced by naming the methods you care about, usable as a
|
|
104
|
+
testing assertion library, a tracing tool, or both at once.
|
|
105
|
+
|
|
106
|
+
## What it is not
|
|
107
|
+
|
|
108
|
+
- **Not a replacement for `unittest.mock`.** It complements mocking where
|
|
109
|
+
code has seams; it exists for the code that doesn't.
|
|
110
|
+
- **Not a sampling profiler.** `py-spy` and `austin` do that better and
|
|
111
|
+
without distortion.
|
|
112
|
+
- **Not a production APM.** It is a toolkit that APM-like things could be
|
|
113
|
+
built on.
|
|
114
|
+
- **Not an OpenTelemetry competitor.** It should emit to OTel, not replace
|
|
115
|
+
it.
|
|
116
|
+
|
|
117
|
+
## Requirements
|
|
118
|
+
|
|
119
|
+
- Python 3.12+
|
|
120
|
+
- [wrapt](https://github.com/GrahamDumpleton/wrapt) 2.4.0+
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
BSD 2-Clause. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,96 @@
|
|
|
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.0rc4"]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "wrapture"
|
|
9
|
+
dynamic = ["version"]
|
|
10
|
+
description = "Library for attaching bindings to arbitrary Python call sites for monkey patching, testing, tracing and profiling."
|
|
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", "profiling"]
|
|
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.0rc4",
|
|
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
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"mypy",
|
|
39
|
+
"pytest",
|
|
40
|
+
"ruff",
|
|
41
|
+
"setuptools",
|
|
42
|
+
]
|
|
43
|
+
docs = [
|
|
44
|
+
"myst-parser",
|
|
45
|
+
"sphinx",
|
|
46
|
+
"sphinx-rtd-theme",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools]
|
|
50
|
+
package-dir = {"" = "src"}
|
|
51
|
+
zip-safe = false
|
|
52
|
+
packages = ["wrapture"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
wrapture = ["py.typed"]
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.dynamic]
|
|
58
|
+
version = {attr = "wrapture.__version__"}
|
|
59
|
+
|
|
60
|
+
[dependency-groups]
|
|
61
|
+
test = [
|
|
62
|
+
"pytest",
|
|
63
|
+
]
|
|
64
|
+
dev = [
|
|
65
|
+
{include-group = "test"},
|
|
66
|
+
"mypy",
|
|
67
|
+
"ruff",
|
|
68
|
+
"setuptools",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[tool.pytest.ini_options]
|
|
72
|
+
# The docs are on the test paths so the interpreter transcripts in the
|
|
73
|
+
# getting started page run as doctests and cannot rot.
|
|
74
|
+
testpaths = ["tests", "docs"]
|
|
75
|
+
addopts = "--doctest-glob=*.md"
|
|
76
|
+
|
|
77
|
+
[tool.mypy]
|
|
78
|
+
python_version = "3.12"
|
|
79
|
+
strict = true
|
|
80
|
+
files = ["src/wrapture", "tests"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
line-length = 88
|
|
84
|
+
target-version = "py312"
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint]
|
|
87
|
+
# Version information is defined before the imports in __init__.py.
|
|
88
|
+
per-file-ignores = {"src/wrapture/__init__.py" = ["E402"]}
|
|
89
|
+
select = [
|
|
90
|
+
"E", # pycodestyle errors
|
|
91
|
+
"W", # pycodestyle warnings
|
|
92
|
+
"F", # pyflakes
|
|
93
|
+
"I", # isort
|
|
94
|
+
"UP", # pyupgrade
|
|
95
|
+
"B", # flake8-bugbear
|
|
96
|
+
]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wrapture is a library for attaching bindings to arbitrary Python call sites,
|
|
3
|
+
without modifying the code being observed, for use in monkey patching,
|
|
4
|
+
testing, tracing and profiling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _format_version(parts: tuple[str, ...]) -> str:
|
|
9
|
+
base = ".".join(parts[:3])
|
|
10
|
+
|
|
11
|
+
if len(parts) == 3:
|
|
12
|
+
return base
|
|
13
|
+
|
|
14
|
+
suffix = parts[3]
|
|
15
|
+
return (
|
|
16
|
+
f"{base}.{suffix}" if suffix.startswith(("dev", "post")) else f"{base}{suffix}"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__version_info__ = ("1", "0", "0", "dev1")
|
|
21
|
+
__version__ = _format_version(__version_info__)
|
|
22
|
+
|
|
23
|
+
from .behaviours import (
|
|
24
|
+
CallBehaviour,
|
|
25
|
+
DeleteBehaviour,
|
|
26
|
+
GetBehaviour,
|
|
27
|
+
SetBehaviour,
|
|
28
|
+
)
|
|
29
|
+
from .bindings import (
|
|
30
|
+
Binding,
|
|
31
|
+
BindingGroup,
|
|
32
|
+
binding,
|
|
33
|
+
bindings,
|
|
34
|
+
)
|
|
35
|
+
from .capture import (
|
|
36
|
+
NONE,
|
|
37
|
+
REFERENCE,
|
|
38
|
+
SNAPSHOT,
|
|
39
|
+
SUMMARY,
|
|
40
|
+
TYPES,
|
|
41
|
+
redact,
|
|
42
|
+
)
|
|
43
|
+
from .eventlogs import (
|
|
44
|
+
EventLog,
|
|
45
|
+
)
|
|
46
|
+
from .events import (
|
|
47
|
+
Event,
|
|
48
|
+
)
|
|
49
|
+
from .exceptions import (
|
|
50
|
+
AlreadyAppliedError,
|
|
51
|
+
DeferredTargetError,
|
|
52
|
+
ExpectationNotMetError,
|
|
53
|
+
NeverAppliedError,
|
|
54
|
+
NotImplementedYetError,
|
|
55
|
+
RecordingGapWarning,
|
|
56
|
+
WrongModeError,
|
|
57
|
+
)
|
|
58
|
+
from .iterators import (
|
|
59
|
+
AbandonBehaviour,
|
|
60
|
+
ErrorBehaviour,
|
|
61
|
+
FinishBehaviour,
|
|
62
|
+
ItemBehaviour,
|
|
63
|
+
IteratorProxy,
|
|
64
|
+
iterator,
|
|
65
|
+
)
|
|
66
|
+
from .stacks import (
|
|
67
|
+
StackFrame,
|
|
68
|
+
caller,
|
|
69
|
+
full,
|
|
70
|
+
stack_frames,
|
|
71
|
+
)
|
|
72
|
+
from .timeline import (
|
|
73
|
+
Tape,
|
|
74
|
+
Timeline,
|
|
75
|
+
annotate,
|
|
76
|
+
current_event,
|
|
77
|
+
timeline,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"NONE",
|
|
82
|
+
"REFERENCE",
|
|
83
|
+
"SNAPSHOT",
|
|
84
|
+
"SUMMARY",
|
|
85
|
+
"TYPES",
|
|
86
|
+
"AbandonBehaviour",
|
|
87
|
+
"AlreadyAppliedError",
|
|
88
|
+
"Binding",
|
|
89
|
+
"BindingGroup",
|
|
90
|
+
"CallBehaviour",
|
|
91
|
+
"DeferredTargetError",
|
|
92
|
+
"DeleteBehaviour",
|
|
93
|
+
"ErrorBehaviour",
|
|
94
|
+
"Event",
|
|
95
|
+
"EventLog",
|
|
96
|
+
"ExpectationNotMetError",
|
|
97
|
+
"FinishBehaviour",
|
|
98
|
+
"GetBehaviour",
|
|
99
|
+
"ItemBehaviour",
|
|
100
|
+
"IteratorProxy",
|
|
101
|
+
"NeverAppliedError",
|
|
102
|
+
"NotImplementedYetError",
|
|
103
|
+
"RecordingGapWarning",
|
|
104
|
+
"SetBehaviour",
|
|
105
|
+
"StackFrame",
|
|
106
|
+
"Tape",
|
|
107
|
+
"Timeline",
|
|
108
|
+
"WrongModeError",
|
|
109
|
+
"annotate",
|
|
110
|
+
"binding",
|
|
111
|
+
"bindings",
|
|
112
|
+
"caller",
|
|
113
|
+
"current_event",
|
|
114
|
+
"full",
|
|
115
|
+
"iterator",
|
|
116
|
+
"redact",
|
|
117
|
+
"stack_frames",
|
|
118
|
+
"timeline",
|
|
119
|
+
]
|