wrapture 1.0.0.dev4__tar.gz → 1.0.0.dev7__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 (73) hide show
  1. {wrapture-1.0.0.dev4/src/wrapture.egg-info → wrapture-1.0.0.dev7}/PKG-INFO +13 -3
  2. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/README.md +12 -2
  3. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/__init__.py +4 -2
  4. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/bindings.py +2 -1
  5. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/events.py +11 -3
  6. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/exceptions.py +2 -1
  7. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/timeline.py +5 -0
  8. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7/src/wrapture.egg-info}/PKG-INFO +13 -3
  9. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_bindings.py +18 -0
  10. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_events.py +25 -1
  11. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_timeline.py +12 -0
  12. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/LICENSE +0 -0
  13. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/pyproject.toml +0 -0
  14. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/setup.cfg +0 -0
  15. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/__main__.py +0 -0
  16. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/asgi.py +0 -0
  17. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/attributes.py +0 -0
  18. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/behaviours.py +0 -0
  19. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/bootstrap.py +0 -0
  20. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/capture.py +0 -0
  21. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/collectors.py +0 -0
  22. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/config.py +0 -0
  23. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/eventlogs.py +0 -0
  24. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/export.py +0 -0
  25. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/iterators.py +0 -0
  26. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/lifecycle.py +0 -0
  27. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/observed.py +0 -0
  28. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/outputs.py +0 -0
  29. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/py.typed +0 -0
  30. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/pytest_plugin.py +0 -0
  31. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/scheduler.py +0 -0
  32. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/sinks.py +0 -0
  33. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/stacks.py +0 -0
  34. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/tools/__init__.py +0 -0
  35. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/tools/__main__.py +0 -0
  36. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/tools/convert.py +0 -0
  37. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/windows.py +0 -0
  38. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture/wsgi.py +0 -0
  39. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture.egg-info/SOURCES.txt +0 -0
  40. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture.egg-info/dependency_links.txt +0 -0
  41. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture.egg-info/entry_points.txt +0 -0
  42. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture.egg-info/not-zip-safe +0 -0
  43. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture.egg-info/requires.txt +0 -0
  44. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/src/wrapture.egg-info/top_level.txt +0 -0
  45. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_asgi.py +0 -0
  46. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_attribute_events.py +0 -0
  47. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_attributes.py +0 -0
  48. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_behaviours.py +0 -0
  49. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_bootstrap.py +0 -0
  50. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_capture.py +0 -0
  51. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_collectors.py +0 -0
  52. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_config.py +0 -0
  53. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_eventlogs.py +0 -0
  54. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_export.py +0 -0
  55. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_generator_events.py +0 -0
  56. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_iterators.py +0 -0
  57. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_jsonlines.py +0 -0
  58. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_observed.py +0 -0
  59. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_outputs.py +0 -0
  60. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_predicates.py +0 -0
  61. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_pytest_plugin.py +0 -0
  62. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_recording.py +0 -0
  63. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_runner.py +0 -0
  64. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_sink_library.py +0 -0
  65. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_sinks.py +0 -0
  66. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_stacks.py +0 -0
  67. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_targets.py +0 -0
  68. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_testing_patterns.py +0 -0
  69. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_threads.py +0 -0
  70. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_tools.py +0 -0
  71. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_version.py +0 -0
  72. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_windows.py +0 -0
  73. {wrapture-1.0.0.dev4 → wrapture-1.0.0.dev7}/tests/test_wsgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wrapture
3
- Version: 1.0.0.dev4
3
+ Version: 1.0.0.dev7
4
4
  Summary: Monkey patch, test, and trace Python by attaching bindings to call sites, without modifying the code being observed. Built on wrapt.
5
5
  Author-email: Graham Dumpleton <Graham.Dumpleton@gmail.com>
6
6
  License-Expression: BSD-2-Clause
@@ -31,7 +31,7 @@ Dynamic: license-file
31
31
 
32
32
  # wrapture
33
33
 
34
- **Trace assertions without instrumenting your code.**
34
+ **Wrap anything, capture everything, change nothing.**
35
35
 
36
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
37
  [![Documentation](https://readthedocs.org/projects/wrapture/badge/?version=latest)](https://wrapture.readthedocs.io)
@@ -70,7 +70,10 @@ Start with the [getting started](https://wrapture.readthedocs.io/en/latest/getti
70
70
  page: everything on it can be pasted into a Python interpreter. Coming
71
71
  from `unittest.mock`? There is a
72
72
  [comparison page](https://wrapture.readthedocs.io/en/latest/coming-from-mock.html)
73
- mapping each mock idiom to its wrapture counterpart.
73
+ mapping each mock idiom to its wrapture counterpart. After that, the
74
+ worked examples, starting with
75
+ [testing code that calls external services](https://wrapture.readthedocs.io/en/latest/example-external-services.html),
76
+ each take one question you might arrive with and answer it end to end.
74
77
 
75
78
  ## Thirty seconds of it
76
79
 
@@ -163,6 +166,13 @@ testing assertion library, a tracing tool, or both at once.
163
166
  - Python 3.12+
164
167
  - [wrapt](https://github.com/GrahamDumpleton/wrapt) 2.4.0+
165
168
 
169
+ ## Issues
170
+
171
+ Bug reports and feature requests go to the
172
+ [issue tracker](https://github.com/GrahamDumpleton/wrapture/issues).
173
+ Include the wrapture and Python versions and, where you can, a small
174
+ binding that reproduces the problem.
175
+
166
176
  ## License
167
177
 
168
178
  BSD 2-Clause. See
@@ -1,6 +1,6 @@
1
1
  # wrapture
2
2
 
3
- **Trace assertions without instrumenting your code.**
3
+ **Wrap anything, capture everything, change nothing.**
4
4
 
5
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
6
  [![Documentation](https://readthedocs.org/projects/wrapture/badge/?version=latest)](https://wrapture.readthedocs.io)
@@ -39,7 +39,10 @@ Start with the [getting started](https://wrapture.readthedocs.io/en/latest/getti
39
39
  page: everything on it can be pasted into a Python interpreter. Coming
40
40
  from `unittest.mock`? There is a
41
41
  [comparison page](https://wrapture.readthedocs.io/en/latest/coming-from-mock.html)
42
- mapping each mock idiom to its wrapture counterpart.
42
+ mapping each mock idiom to its wrapture counterpart. After that, the
43
+ worked examples, starting with
44
+ [testing code that calls external services](https://wrapture.readthedocs.io/en/latest/example-external-services.html),
45
+ each take one question you might arrive with and answer it end to end.
43
46
 
44
47
  ## Thirty seconds of it
45
48
 
@@ -132,6 +135,13 @@ testing assertion library, a tracing tool, or both at once.
132
135
  - Python 3.12+
133
136
  - [wrapt](https://github.com/GrahamDumpleton/wrapt) 2.4.0+
134
137
 
138
+ ## Issues
139
+
140
+ Bug reports and feature requests go to the
141
+ [issue tracker](https://github.com/GrahamDumpleton/wrapture/issues).
142
+ Include the wrapture and Python versions and, where you can, a small
143
+ binding that reproduces the problem.
144
+
135
145
  ## License
136
146
 
137
147
  BSD 2-Clause. See
@@ -17,10 +17,10 @@ def _format_version(parts: tuple[str, ...]) -> str:
17
17
  )
18
18
 
19
19
 
20
- __version_info__ = ("1", "0", "0", "dev4")
20
+ __version_info__ = ("1", "0", "0", "dev7")
21
21
  __version__ = _format_version(__version_info__)
22
22
 
23
- from wrapt import MISSING
23
+ from wrapt import MISSING, register_post_import_hook, when_imported
24
24
 
25
25
  from .asgi import (
26
26
  ASGIMiddleware,
@@ -131,6 +131,8 @@ from .wsgi import (
131
131
 
132
132
  __all__ = [
133
133
  "MISSING",
134
+ "register_post_import_hook",
135
+ "when_imported",
134
136
  "ASGIMiddleware",
135
137
  "Aggregate",
136
138
  "AlreadyAppliedError",
@@ -135,7 +135,8 @@ def _reject_deferred(target: Any) -> None:
135
135
  f" wrapt's trailing `?` syntax. A deferred wrap registers a"
136
136
  f" post-import hook and returns no handle, so a binding would"
137
137
  f" have nothing to remove, suspend or query. Import the module"
138
- f" first and bind against it."
138
+ f" first and bind against it, or create the binding inside a"
139
+ f" `wrapture.when_imported` hook for that module."
139
140
  )
140
141
 
141
142
 
@@ -181,9 +181,17 @@ def _format_time(seconds: float) -> str:
181
181
  def _own_time(event: Event) -> float | None:
182
182
  # The execution-time basis for self-time arithmetic. A generator's
183
183
  # wall duration includes the consumer's time between yields, so its
184
- # accumulated body time is the honest measure of the code itself;
185
- # everything else uses its duration. None when the event has not
186
- # closed with a time.
184
+ # accumulated body time is the honest measure of the code itself. A
185
+ # request has two phases of application code, the synchronous call
186
+ # and the body it then streams, and its own time is their sum, again
187
+ # leaving out the server's time between chunks. Everything else uses
188
+ # its duration. None when the event has not closed with a time.
189
+
190
+ if event.kind == "request" and event.body_duration is not None:
191
+ app_duration = event.data.get("app_duration")
192
+ if app_duration is None:
193
+ return event.duration
194
+ return float(app_duration) + event.body_duration
187
195
 
188
196
  if event.body_duration is not None:
189
197
  return event.body_duration
@@ -48,7 +48,8 @@ class NotImplementedYetError(NotImplementedError):
48
48
 
49
49
  class DeferredTargetError(ValueError):
50
50
  """A string target used wrapt's trailing `?` deferred-patching syntax,
51
- which is not supported. Import the module first and bind against it.
51
+ which is not supported. Import the module first and bind against it,
52
+ or create the binding inside a `wrapture.when_imported` hook.
52
53
  """
53
54
 
54
55
 
@@ -246,8 +246,13 @@ class Tape(Sink):
246
246
  def emit(event: Event) -> None:
247
247
  injected = " (injected)" if event.injected else ""
248
248
 
249
+ # A get event's str() already carries its value, so only the
250
+ # injected mark is added to it.
251
+
249
252
  if event.exception is not None:
250
253
  marker = f" !! {type(event.exception).__name__}{injected}"
254
+ elif event.kind == "get":
255
+ marker = injected
251
256
  elif event.result is not MISSING:
252
257
  marker = f" -> {event.result!r}{injected}"
253
258
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wrapture
3
- Version: 1.0.0.dev4
3
+ Version: 1.0.0.dev7
4
4
  Summary: Monkey patch, test, and trace Python by attaching bindings to call sites, without modifying the code being observed. Built on wrapt.
5
5
  Author-email: Graham Dumpleton <Graham.Dumpleton@gmail.com>
6
6
  License-Expression: BSD-2-Clause
@@ -31,7 +31,7 @@ Dynamic: license-file
31
31
 
32
32
  # wrapture
33
33
 
34
- **Trace assertions without instrumenting your code.**
34
+ **Wrap anything, capture everything, change nothing.**
35
35
 
36
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
37
  [![Documentation](https://readthedocs.org/projects/wrapture/badge/?version=latest)](https://wrapture.readthedocs.io)
@@ -70,7 +70,10 @@ Start with the [getting started](https://wrapture.readthedocs.io/en/latest/getti
70
70
  page: everything on it can be pasted into a Python interpreter. Coming
71
71
  from `unittest.mock`? There is a
72
72
  [comparison page](https://wrapture.readthedocs.io/en/latest/coming-from-mock.html)
73
- mapping each mock idiom to its wrapture counterpart.
73
+ mapping each mock idiom to its wrapture counterpart. After that, the
74
+ worked examples, starting with
75
+ [testing code that calls external services](https://wrapture.readthedocs.io/en/latest/example-external-services.html),
76
+ each take one question you might arrive with and answer it end to end.
74
77
 
75
78
  ## Thirty seconds of it
76
79
 
@@ -163,6 +166,13 @@ testing assertion library, a tracing tool, or both at once.
163
166
  - Python 3.12+
164
167
  - [wrapt](https://github.com/GrahamDumpleton/wrapt) 2.4.0+
165
168
 
169
+ ## Issues
170
+
171
+ Bug reports and feature requests go to the
172
+ [issue tracker](https://github.com/GrahamDumpleton/wrapture/issues).
173
+ Include the wrapture and Python versions and, where you can, a small
174
+ binding that reproduces the problem.
175
+
166
176
  ## License
167
177
 
168
178
  BSD 2-Clause. See
@@ -250,6 +250,24 @@ def test_deferred_target_error_is_a_value_error() -> None:
250
250
  assert issubclass(DeferredTargetError, ValueError)
251
251
 
252
252
 
253
+ def test_deferred_target_error_points_at_when_imported() -> None:
254
+ with pytest.raises(DeferredTargetError, match="wrapture.when_imported"):
255
+ binding("mymodule?", "handler")
256
+
257
+
258
+ def test_post_import_hook_helpers_are_reexported_from_wrapt() -> None:
259
+ # The supported route to patching a module before it is imported is
260
+ # a post-import hook, so wrapt's helpers are available under the
261
+ # wrapture name to save application code importing wrapt for them.
262
+
263
+ import wrapture
264
+
265
+ assert wrapture.when_imported is wrapt.when_imported
266
+ assert wrapture.register_post_import_hook is wrapt.register_post_import_hook
267
+ assert "when_imported" in wrapture.__all__
268
+ assert "register_post_import_hook" in wrapture.__all__
269
+
270
+
253
271
  # ---------------------------------------------------------------------------
254
272
  # modes and behaviour namespaces
255
273
  # ---------------------------------------------------------------------------
@@ -9,10 +9,16 @@ import gc
9
9
  import weakref
10
10
  from typing import Any
11
11
 
12
+ import pytest
12
13
  from wrapt import MISSING
13
14
 
14
15
  from wrapture import Event
15
- from wrapture.events import _signature, _signature_cache, normalized_arguments
16
+ from wrapture.events import (
17
+ _own_time,
18
+ _signature,
19
+ _signature_cache,
20
+ normalized_arguments,
21
+ )
16
22
 
17
23
 
18
24
  def charge(amount: int, currency: str = "USD", *, retries: int = 3) -> None:
@@ -189,3 +195,21 @@ def test_cache_does_not_keep_the_function_alive() -> None:
189
195
 
190
196
  gc.collect()
191
197
  assert alive() is None
198
+
199
+
200
+ def test_own_time_of_a_request_sums_both_application_phases() -> None:
201
+ # A request's own time is the synchronous call plus the body it
202
+ # produced, not the wall time that includes the server between chunks.
203
+
204
+ request = Event(kind="request", path="app:wsgi_app", duration=1.0)
205
+ request.body_duration = 0.2
206
+ request.data["app_duration"] = 0.3
207
+
208
+ assert _own_time(request) == pytest.approx(0.5)
209
+
210
+
211
+ def test_own_time_of_a_generator_is_its_body_time() -> None:
212
+ call = Event(kind="call", path="mod:gen", duration=1.0)
213
+ call.body_duration = 0.25
214
+
215
+ assert _own_time(call) == 0.25
@@ -354,6 +354,18 @@ def test_tree_renders_nesting_results_and_failures() -> None:
354
354
  assert lines[2] == "Gateway.refund(amount=100) !! TimeoutError (injected)"
355
355
 
356
356
 
357
+ def test_tree_shows_a_get_event_value_once() -> None:
358
+ class Settings:
359
+ retries = 3
360
+
361
+ retries = binding(Settings, "retries")
362
+
363
+ with timeline(retries) as tape:
364
+ _ = Settings().retries
365
+
366
+ assert tape.tree() == "get Settings.retries -> 3"
367
+
368
+
357
369
  def test_tree_of_an_empty_tape_is_empty() -> None:
358
370
  with timeline() as tape:
359
371
  pass
File without changes
File without changes