wrapture-instrumentation-aws 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.
Files changed (34) hide show
  1. wrapture_instrumentation_aws-1.0.0.dev1/CHANGES.md +9 -0
  2. wrapture_instrumentation_aws-1.0.0.dev1/LICENSE +24 -0
  3. wrapture_instrumentation_aws-1.0.0.dev1/MANIFEST.in +7 -0
  4. wrapture_instrumentation_aws-1.0.0.dev1/PKG-INFO +159 -0
  5. wrapture_instrumentation_aws-1.0.0.dev1/README.md +136 -0
  6. wrapture_instrumentation_aws-1.0.0.dev1/TESTING.md +197 -0
  7. wrapture_instrumentation_aws-1.0.0.dev1/demo/__init__.py +0 -0
  8. wrapture_instrumentation_aws-1.0.0.dev1/demo/botocore.py +136 -0
  9. wrapture_instrumentation_aws-1.0.0.dev1/pyproject.toml +127 -0
  10. wrapture_instrumentation_aws-1.0.0.dev1/setup.cfg +4 -0
  11. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws/__init__.py +26 -0
  12. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws/botocore/README.md +131 -0
  13. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws/botocore/__init__.py +52 -0
  14. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws/botocore/client.py +95 -0
  15. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws/botocore/common.py +69 -0
  16. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws/botocore/services.py +171 -0
  17. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws/py.typed +0 -0
  18. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws.egg-info/PKG-INFO +159 -0
  19. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws.egg-info/SOURCES.txt +32 -0
  20. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws.egg-info/dependency_links.txt +1 -0
  21. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws.egg-info/entry_points.txt +2 -0
  22. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws.egg-info/not-zip-safe +1 -0
  23. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws.egg-info/requires.txt +1 -0
  24. wrapture_instrumentation_aws-1.0.0.dev1/src/wrapture_instrumentation_aws.egg-info/top_level.txt +1 -0
  25. wrapture_instrumentation_aws-1.0.0.dev1/tests/__init__.py +0 -0
  26. wrapture_instrumentation_aws-1.0.0.dev1/tests/botocore/__init__.py +0 -0
  27. wrapture_instrumentation_aws-1.0.0.dev1/tests/botocore/conftest.py +49 -0
  28. wrapture_instrumentation_aws-1.0.0.dev1/tests/botocore/test_apply_remove.py +58 -0
  29. wrapture_instrumentation_aws-1.0.0.dev1/tests/botocore/test_class.py +70 -0
  30. wrapture_instrumentation_aws-1.0.0.dev1/tests/botocore/test_composition.py +168 -0
  31. wrapture_instrumentation_aws-1.0.0.dev1/tests/botocore/test_recording.py +190 -0
  32. wrapture_instrumentation_aws-1.0.0.dev1/tests/botocore/test_registration.py +81 -0
  33. wrapture_instrumentation_aws-1.0.0.dev1/tests/conftest.py +88 -0
  34. wrapture_instrumentation_aws-1.0.0.dev1/tests/test_package.py +181 -0
@@ -0,0 +1,9 @@
1
+ # Changes
2
+
3
+ ## Version 1.0.0
4
+
5
+ Initial release of wrapture-instrumentation-aws. Everything is new in
6
+ this version, so rather than listing changes, see the
7
+ [README](README.md) for what the package provides: the table of
8
+ covered targets there links to each target's own notes describing
9
+ what it records and its settings.
@@ -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,7 @@
1
+ # setuptools includes test_*.py files by default but not the helpers
2
+ # they import (conftest.py, the package __init__.py), so the tests
3
+ # directory is taken whole, with the repository's other notes.
4
+ graft tests
5
+ graft demo
6
+ include CHANGES.md TESTING.md
7
+ global-exclude __pycache__ *.py[cod]
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: wrapture-instrumentation-aws
3
+ Version: 1.0.0.dev1
4
+ Summary: Instrumentation for the AWS SDK (boto3 and botocore), applied through wrapture.
5
+ Author-email: Graham Dumpleton <Graham.Dumpleton@gmail.com>
6
+ License-Expression: BSD-2-Clause
7
+ Project-URL: Homepage, https://github.com/GrahamDumpleton/wrapture-instrumentation-aws
8
+ Project-URL: Documentation, https://wrapture.readthedocs.io
9
+ Project-URL: Bug Tracker, https://github.com/GrahamDumpleton/wrapture-instrumentation-aws/issues/
10
+ Keywords: wrapture,instrumentation,aws,boto3,botocore,tracing
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 :: 3.15
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: wrapture>=1.0.0a20
22
+ Dynamic: license-file
23
+
24
+ # wrapture-instrumentation-aws
25
+
26
+ Instrumentation for the AWS SDK (boto3 and botocore), applied through
27
+ [wrapture](https://github.com/GrahamDumpleton/wrapture).
28
+
29
+ wrapture attaches bindings to arbitrary Python call sites without
30
+ modifying the code being observed, and its config layer can switch on
31
+ packaged instrumentation for a third-party package by name. This is
32
+ the AWS package in that collection: one `wrapture.Instrumentation`
33
+ class for the AWS SDK, so tracing every call your application makes to
34
+ S3, DynamoDB, SQS and the rest is one config entry and no code.
35
+
36
+ > **Status: alpha, ahead of 1.0.0.** Developed against wrapture's
37
+ > alpha series, with pre-releases published to
38
+ > [PyPI](https://pypi.org/project/wrapture-instrumentation-aws/), and
39
+ > until 1.0.0 is final a plain `pip install
40
+ > wrapture-instrumentation-aws` picks up the latest pre-release
41
+ > automatically, so there is no need to pin a specific version.
42
+
43
+ ## Why a separate package
44
+
45
+ The core
46
+ [wrapture-instrumentation](https://github.com/GrahamDumpleton/wrapture-instrumentation)
47
+ package deliberately covers only the standard library and third-party
48
+ packages that can be exercised in-process, with no separate backend
49
+ product or service needed to test against. AWS is exactly the kind of
50
+ product the separate-package rule was drawn for. This package carries
51
+ its own heavier test dependencies (moto, which reimplements the AWS
52
+ APIs in memory and pulls in cryptography, PyYAML, Flask and werkzeug)
53
+ and its own release cadence, so the core package's free-threaded test
54
+ matrix stays light. It will grow to hold the AWS SDK's async twin
55
+ (aiobotocore) and a messaging trace-propagation layer.
56
+
57
+ ## Installation
58
+
59
+ ```console
60
+ $ pip install wrapture-instrumentation-aws
61
+ ```
62
+
63
+ Installing it brings wrapture and nothing else. boto3, botocore and
64
+ moto are not dependencies: the instrumentation is inert until botocore
65
+ is present, and wrapture checks the installed version against the
66
+ range the instrumentation supports at apply time.
67
+
68
+ ## Using it
69
+
70
+ An `[[instrument]]` entry in `wrapture.toml` names the target:
71
+
72
+ ```toml
73
+ [[instrument]]
74
+ name = "botocore"
75
+
76
+ [[sink]]
77
+ type = "printer"
78
+ ```
79
+
80
+ and the runner applies it before the application starts, so the patch
81
+ is in place before boto3 is imported:
82
+
83
+ ```console
84
+ $ python -m wrapture -m myapp
85
+ ```
86
+
87
+ The entry point name is `botocore`, the seam every AWS call passes
88
+ through: boto3 is sugar over botocore, so a config that traces
89
+ botocore traces boto3. The same config works through
90
+ [autowrapt](https://github.com/GrahamDumpleton/autowrapt) injection
91
+ (`AUTOWRAPT_BOOTSTRAP=wrapture python myapp.py`); through
92
+ [manual setup](https://wrapture.readthedocs.io/en/latest/manual-setup.html),
93
+ a few lines in the application's own startup where wrapping the launch
94
+ from outside is awkward; and, in a test, through
95
+ `wrapture.instrumentation("botocore")` scoping the instrumentation to
96
+ a block. The
97
+ [ad-hoc tracing guide](https://wrapture.readthedocs.io/en/latest/ad-hoc-tracing.html)
98
+ covers the config file itself.
99
+
100
+ To see what is installed, what it supports in the current
101
+ environment, and what settings it takes:
102
+
103
+ ```console
104
+ $ python -m wrapture.tools instrumentation --verbose
105
+ ```
106
+
107
+ ## Provided instrumentation
108
+
109
+ | Target | Supported versions | Records | Settings |
110
+ | ------ | ------------------ | ------- | -------- |
111
+ | [`botocore`](https://github.com/GrahamDumpleton/wrapture-instrumentation-aws/blob/develop/src/wrapture_instrumentation_aws/botocore/README.md) | botocore 1.34+ (1.x), and boto3 above it | Every AWS API call as one event at the SDK's single dispatch seam, whichever service or client made it, named `service/operation` (`s3/GetObject`) and categorised per service (S3 external, DynamoDB a datastore, SQS/SNS/Kinesis messaging, Lambda and Step Functions tasks); carrying the system, service, operation, region, endpoint host and port, the resource identifiers (bucket and key, table, queue, topic, stream, function), and from the response the status, request id and any retry count; a failing call's `ClientError` recorded as the exception with its status and AWS error code beside it. Retries fold into the one call; a paginator or waiter is one event per underlying call. The parameters reduce to a count and the response to its type: payloads, items and message bodies are never recorded. | `leaf` |
112
+
113
+ The entry point name is the config's `name`; the linked per-target
114
+ README is the full user documentation: what records, what the events
115
+ carry, the setting, and what is deliberately not traced.
116
+
117
+ ## What is not traced
118
+
119
+ By design, and where it goes:
120
+
121
+ - No trace identity is propagated to AWS in this version. A silenced
122
+ urllib3 beneath the call injects none either, by wrapture's
123
+ propagation-follows-recording contract, so AWS is never handed
124
+ headers it would not understand. Trace context into SQS, SNS and
125
+ Kinesis message attributes, and X-Ray `X-Amzn-Trace-Id`
126
+ propagation, are planned for later versions.
127
+
128
+ - The async SDK (aiobotocore, aioboto3) is a separate top-level
129
+ package and will get its own `Instrumentation` class and
130
+ `aiobotocore` entry point in this repository, once the sync target
131
+ is whole.
132
+
133
+ - s3transfer's multipart uploads fan out to worker threads that do
134
+ not carry the recording context, so those individual calls may not
135
+ nest under the caller.
136
+
137
+ - Bedrock and other LLM telemetry are out of scope.
138
+
139
+ ## Adding a target
140
+
141
+ The AWS SDK's one dispatch seam fronts every service, so a single
142
+ target covers them all; a future async twin (aiobotocore) would be a
143
+ second subpackage and entry point here. The subpackage's `__init__.py`
144
+ holds one `wrapture.Instrumentation` subclass and imports only
145
+ wrapture; everything that touches botocore lives in sibling modules
146
+ (`client.py` for the seam, `services.py` for the per-service table,
147
+ `common.py` for the capture policy), imported inside the hook. The
148
+ class is registered in `pyproject.toml` under
149
+ `[project.entry-points."wrapture.instrumentation"]`, and gets its own
150
+ test suite under `tests/<target>/` and a `README.md` linked from the
151
+ table above. The
152
+ [instrumentation packages](https://wrapture.readthedocs.io/en/latest/instrumentation-packages.html)
153
+ page of the wrapture documentation is the full contract; TESTING.md
154
+ here covers the tests.
155
+
156
+ ## License
157
+
158
+ BSD 2-Clause. See
159
+ [LICENSE](https://github.com/GrahamDumpleton/wrapture-instrumentation-aws/blob/develop/LICENSE).
@@ -0,0 +1,136 @@
1
+ # wrapture-instrumentation-aws
2
+
3
+ Instrumentation for the AWS SDK (boto3 and botocore), applied through
4
+ [wrapture](https://github.com/GrahamDumpleton/wrapture).
5
+
6
+ wrapture attaches bindings to arbitrary Python call sites without
7
+ modifying the code being observed, and its config layer can switch on
8
+ packaged instrumentation for a third-party package by name. This is
9
+ the AWS package in that collection: one `wrapture.Instrumentation`
10
+ class for the AWS SDK, so tracing every call your application makes to
11
+ S3, DynamoDB, SQS and the rest is one config entry and no code.
12
+
13
+ > **Status: alpha, ahead of 1.0.0.** Developed against wrapture's
14
+ > alpha series, with pre-releases published to
15
+ > [PyPI](https://pypi.org/project/wrapture-instrumentation-aws/), and
16
+ > until 1.0.0 is final a plain `pip install
17
+ > wrapture-instrumentation-aws` picks up the latest pre-release
18
+ > automatically, so there is no need to pin a specific version.
19
+
20
+ ## Why a separate package
21
+
22
+ The core
23
+ [wrapture-instrumentation](https://github.com/GrahamDumpleton/wrapture-instrumentation)
24
+ package deliberately covers only the standard library and third-party
25
+ packages that can be exercised in-process, with no separate backend
26
+ product or service needed to test against. AWS is exactly the kind of
27
+ product the separate-package rule was drawn for. This package carries
28
+ its own heavier test dependencies (moto, which reimplements the AWS
29
+ APIs in memory and pulls in cryptography, PyYAML, Flask and werkzeug)
30
+ and its own release cadence, so the core package's free-threaded test
31
+ matrix stays light. It will grow to hold the AWS SDK's async twin
32
+ (aiobotocore) and a messaging trace-propagation layer.
33
+
34
+ ## Installation
35
+
36
+ ```console
37
+ $ pip install wrapture-instrumentation-aws
38
+ ```
39
+
40
+ Installing it brings wrapture and nothing else. boto3, botocore and
41
+ moto are not dependencies: the instrumentation is inert until botocore
42
+ is present, and wrapture checks the installed version against the
43
+ range the instrumentation supports at apply time.
44
+
45
+ ## Using it
46
+
47
+ An `[[instrument]]` entry in `wrapture.toml` names the target:
48
+
49
+ ```toml
50
+ [[instrument]]
51
+ name = "botocore"
52
+
53
+ [[sink]]
54
+ type = "printer"
55
+ ```
56
+
57
+ and the runner applies it before the application starts, so the patch
58
+ is in place before boto3 is imported:
59
+
60
+ ```console
61
+ $ python -m wrapture -m myapp
62
+ ```
63
+
64
+ The entry point name is `botocore`, the seam every AWS call passes
65
+ through: boto3 is sugar over botocore, so a config that traces
66
+ botocore traces boto3. The same config works through
67
+ [autowrapt](https://github.com/GrahamDumpleton/autowrapt) injection
68
+ (`AUTOWRAPT_BOOTSTRAP=wrapture python myapp.py`); through
69
+ [manual setup](https://wrapture.readthedocs.io/en/latest/manual-setup.html),
70
+ a few lines in the application's own startup where wrapping the launch
71
+ from outside is awkward; and, in a test, through
72
+ `wrapture.instrumentation("botocore")` scoping the instrumentation to
73
+ a block. The
74
+ [ad-hoc tracing guide](https://wrapture.readthedocs.io/en/latest/ad-hoc-tracing.html)
75
+ covers the config file itself.
76
+
77
+ To see what is installed, what it supports in the current
78
+ environment, and what settings it takes:
79
+
80
+ ```console
81
+ $ python -m wrapture.tools instrumentation --verbose
82
+ ```
83
+
84
+ ## Provided instrumentation
85
+
86
+ | Target | Supported versions | Records | Settings |
87
+ | ------ | ------------------ | ------- | -------- |
88
+ | [`botocore`](https://github.com/GrahamDumpleton/wrapture-instrumentation-aws/blob/develop/src/wrapture_instrumentation_aws/botocore/README.md) | botocore 1.34+ (1.x), and boto3 above it | Every AWS API call as one event at the SDK's single dispatch seam, whichever service or client made it, named `service/operation` (`s3/GetObject`) and categorised per service (S3 external, DynamoDB a datastore, SQS/SNS/Kinesis messaging, Lambda and Step Functions tasks); carrying the system, service, operation, region, endpoint host and port, the resource identifiers (bucket and key, table, queue, topic, stream, function), and from the response the status, request id and any retry count; a failing call's `ClientError` recorded as the exception with its status and AWS error code beside it. Retries fold into the one call; a paginator or waiter is one event per underlying call. The parameters reduce to a count and the response to its type: payloads, items and message bodies are never recorded. | `leaf` |
89
+
90
+ The entry point name is the config's `name`; the linked per-target
91
+ README is the full user documentation: what records, what the events
92
+ carry, the setting, and what is deliberately not traced.
93
+
94
+ ## What is not traced
95
+
96
+ By design, and where it goes:
97
+
98
+ - No trace identity is propagated to AWS in this version. A silenced
99
+ urllib3 beneath the call injects none either, by wrapture's
100
+ propagation-follows-recording contract, so AWS is never handed
101
+ headers it would not understand. Trace context into SQS, SNS and
102
+ Kinesis message attributes, and X-Ray `X-Amzn-Trace-Id`
103
+ propagation, are planned for later versions.
104
+
105
+ - The async SDK (aiobotocore, aioboto3) is a separate top-level
106
+ package and will get its own `Instrumentation` class and
107
+ `aiobotocore` entry point in this repository, once the sync target
108
+ is whole.
109
+
110
+ - s3transfer's multipart uploads fan out to worker threads that do
111
+ not carry the recording context, so those individual calls may not
112
+ nest under the caller.
113
+
114
+ - Bedrock and other LLM telemetry are out of scope.
115
+
116
+ ## Adding a target
117
+
118
+ The AWS SDK's one dispatch seam fronts every service, so a single
119
+ target covers them all; a future async twin (aiobotocore) would be a
120
+ second subpackage and entry point here. The subpackage's `__init__.py`
121
+ holds one `wrapture.Instrumentation` subclass and imports only
122
+ wrapture; everything that touches botocore lives in sibling modules
123
+ (`client.py` for the seam, `services.py` for the per-service table,
124
+ `common.py` for the capture policy), imported inside the hook. The
125
+ class is registered in `pyproject.toml` under
126
+ `[project.entry-points."wrapture.instrumentation"]`, and gets its own
127
+ test suite under `tests/<target>/` and a `README.md` linked from the
128
+ table above. The
129
+ [instrumentation packages](https://wrapture.readthedocs.io/en/latest/instrumentation-packages.html)
130
+ page of the wrapture documentation is the full contract; TESTING.md
131
+ here covers the tests.
132
+
133
+ ## License
134
+
135
+ BSD 2-Clause. See
136
+ [LICENSE](https://github.com/GrahamDumpleton/wrapture-instrumentation-aws/blob/develop/LICENSE).
@@ -0,0 +1,197 @@
1
+ # Testing
2
+
3
+ ## Where the tests are
4
+
5
+ Tests live in the [tests/](tests/) directory at the top of the
6
+ repository, separate from the package code in
7
+ src/wrapture_instrumentation_aws/. Test files are named `test_*.py`
8
+ and are discovered by pytest, configured via the
9
+ `[tool.pytest.ini_options]` section of [pyproject.toml](pyproject.toml).
10
+
11
+ The directory has two levels:
12
+
13
+ - Package-level tests directly under tests/: the version, the rule
14
+ that importing the package or loading any registered class never
15
+ imports a target, and the listing tool reporting every entry
16
+ cleanly.
17
+
18
+ - One subdirectory per target, `tests/<target>/` (`tests/botocore/`),
19
+ holding that instrumentation's suite: settings validation, applying
20
+ and removing the class directly, the whole path through
21
+ `wrapture.instrumentation()` with a timeline recording what the
22
+ bindings observe, resolving the entry point by name, a check that
23
+ the installed botocore satisfies the class's `supports` range, and
24
+ the composition tests over a real wire.
25
+
26
+ Shared helpers live in [tests/conftest.py](tests/conftest.py).
27
+
28
+ ## Testing with moto
29
+
30
+ The suite drives real boto3 clients against
31
+ [moto](https://docs.getmoto.org/), which reimplements the AWS APIs in
32
+ memory. Two modes are used, and the split maps onto what the suite
33
+ needs:
34
+
35
+ - In-process mode (`moto.mock_aws` as a context manager) registers a
36
+ `before-send` handler on botocore's event system and answers each
37
+ request from the in-memory backend, no socket. Everything above
38
+ `before-send`, `_make_api_call` included, is real botocore
39
+ executing normally, so the instrumented path is exercised with full
40
+ fidelity. This is the workhorse: the `aws` fixture opens `mock_aws()`
41
+ with dummy credentials and a region in the environment, and the
42
+ tests drive real clients and assert the tape.
43
+
44
+ - Server mode (`ThreadedMotoServer` from `moto.server`, on a thread,
45
+ no docker) is a real HTTP server speaking the AWS protocols on
46
+ localhost, with boto3 pointed at it via `endpoint_url`. It supplies
47
+ a genuine wire for the composition tests with the core package's
48
+ urllib3 target, and for asserting what headers the request carried
49
+ on the wire.
50
+
51
+ Because the seam sits strictly above moto's in-process interception,
52
+ the tests are faithful; the corollary is that this instrumentation
53
+ must never bind at or below botocore's HTTP send, where in-process
54
+ moto would short-circuit beneath it.
55
+
56
+ moto needs cryptography, which has no free threaded wheel for 3.13 or
57
+ 3.15 yet, so those two builds are left out of the version matrix
58
+ rather than run with nothing installed. The suites also guard their
59
+ imports with `pytest.importorskip`, so a build where moto or the core
60
+ package is absent skips rather than errors.
61
+
62
+ ## Running the tests
63
+
64
+ All tooling in this project goes through
65
+ [uv](https://docs.astral.sh/uv/), which manages the project
66
+ environment and installs the package, its development dependencies
67
+ (including pytest) and the target packages the tests need.
68
+
69
+ The simplest way to run the test suite is via the Justfile target:
70
+
71
+ ```console
72
+ just test
73
+ ```
74
+
75
+ Extra arguments are passed through to pytest, for example:
76
+
77
+ ```console
78
+ just test -v
79
+ just test tests/botocore/test_recording.py
80
+ just test -k redirect
81
+ ```
82
+
83
+ One target's suite alone:
84
+
85
+ ```console
86
+ just test-target botocore
87
+ ```
88
+
89
+ Equivalently, run pytest directly with uv:
90
+
91
+ ```console
92
+ uv run pytest
93
+ ```
94
+
95
+ ## Watching what the tests record
96
+
97
+ The suites assert on tapes rather than printing anything, but the
98
+ recorded events can be watched live for visual verification. Setting
99
+ WRAPTURE_PRINTER in the environment installs a process-wide
100
+ wrapture.Printer sink for the session, streaming one line to stderr as
101
+ each operation begins and a closing line with its outcome and timing;
102
+ pytest captures stderr, so add -s to see it:
103
+
104
+ ```console
105
+ WRAPTURE_PRINTER=1 just test tests/botocore -s
106
+ ```
107
+
108
+ For a purpose-built run rather than the tests' traffic, the demo
109
+ module under demo/ applies the instrumentation, drives boto3 against
110
+ moto, and prints both the live stream and the reconstructed tree with
111
+ timings:
112
+
113
+ ```console
114
+ just demo-botocore
115
+ ```
116
+
117
+ With --otel the same events also export as OpenTelemetry spans over
118
+ OTLP (to http://localhost:4318, or wherever
119
+ OTEL_EXPORTER_OTLP_ENDPOINT points), for verifying the spans in a
120
+ local backend such as Jaeger; the Justfile target overlays the
121
+ wrapture[otel] dependencies for the run:
122
+
123
+ ```console
124
+ just demo-botocore --otel
125
+ ```
126
+
127
+ ## Testing across Python versions
128
+
129
+ The project supports Python 3.12 through 3.15, and the free threaded
130
+ build of 3.14. The supported list is defined at the top of the
131
+ [Justfile](Justfile); the free threaded builds of 3.13 and 3.15 are
132
+ excluded until moto's dependency wheels exist for them. The default
133
+ version used by plain `just test` is pinned in
134
+ [.python-version](.python-version).
135
+
136
+ ```console
137
+ just test-all
138
+ just test-python 3.14t
139
+ ```
140
+
141
+ uv downloads any Python version it does not already have, and each
142
+ version gets its own environment (.venv-VERSION) so the default .venv
143
+ is left untouched.
144
+
145
+ ## Testing across target versions
146
+
147
+ The `test` dependency group installs boto3 (and so botocore) at
148
+ whatever version the lock resolves. The instrumentation's `supports`
149
+ range is kept honest by running the suite against other boto3 lines,
150
+ overlaid on the project environment for that run; boto3 pins botocore
151
+ to its matching minor, so overlaying boto3 is how a botocore line is
152
+ tested. Each line has a place in `boto3_versions` in the Justfile, run
153
+ one at a time by `just test-botocore 1.34.162` or all by
154
+ `just test-botocore-all`, and the CI workflow runs the same matrix. A
155
+ test in the suite asserts the installed botocore satisfies `supports`,
156
+ so a matrix entry outside the range fails loudly rather than passing
157
+ vacuously.
158
+
159
+ ## Testing against unreleased wrapture
160
+
161
+ The package depends on a released wrapture. To run the tests against a
162
+ checkout of wrapture in the sibling directory ../wrapture, without
163
+ editing pyproject.toml:
164
+
165
+ ```console
166
+ just test-dev
167
+ ```
168
+
169
+ which overlays that checkout as an editable install for the run. This
170
+ is the way to exercise the package against unreleased wrapture
171
+ changes, such as a new resolver capability the seam relies on.
172
+
173
+ ## Writing tests
174
+
175
+ - Put new test files in tests/ (package level) or tests/<target>/
176
+ (for one instrumentation) and name them `test_*.py`.
177
+
178
+ - Import the package under test as `wrapture_instrumentation_aws`, and
179
+ the instrumentation classes from their subpackages. The project is
180
+ installed into the uv-managed environment, so no path manipulation
181
+ is needed.
182
+
183
+ - Guard the target imports with `pytest.importorskip` at the top of
184
+ the module (`boto3 = pytest.importorskip("boto3")`), so a build
185
+ without moto or boto3 skips the suite rather than erroring.
186
+
187
+ - Validate behaviour with wrapture's own unit testing layer:
188
+ `wrapture.timeline()` to record what the instrumentation's bindings
189
+ observe and the tape's tree and queries to assert on it, and
190
+ `wrapture.instrumentation()` to scope an application of a class to a
191
+ block, driving real boto3 clients against moto. Do not use
192
+ `unittest.mock`. If something cannot be expressed that way, write it
193
+ plainly with a comment naming the gap, and say so when summarising
194
+ the work.
195
+
196
+ - Tests should not depend on anything in the scratch/ directory,
197
+ which is not part of the repository.
@@ -0,0 +1,136 @@
1
+ """Drive boto3 against moto with the AWS SDK instrumentation applied.
2
+
3
+ The instrumentation is resolved by its entry point name, and the
4
+ calls go to moto's in-memory backend, so no real AWS and no
5
+ credentials beyond dummy ones are needed. The calls cover the shapes
6
+ that matter: an S3 put and get (external, with a bucket and key), a
7
+ DynamoDB put (a datastore, with a collection), an SQS send (messaging,
8
+ with a destination), a paginated list (one event per page), and a
9
+ failing get (a ClientError recorded with its status and code). Each
10
+ runs beneath an observed function, so the leaves sit in a tree.
11
+
12
+ Two views of the run always print: the live stream and the tree
13
+ reconstructed with timings. With --otel the same events also export
14
+ as OpenTelemetry spans to a local OTLP endpoint (http://localhost:4318
15
+ unless OTEL_EXPORTER_OTLP_ENDPOINT says otherwise), where each span's
16
+ kind and attributes follow the call's category: an S3 call CLIENT with
17
+ rpc attributes, a DynamoDB call CLIENT with db attributes, an SQS call
18
+ PRODUCER with messaging attributes.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import os
25
+ import sys
26
+
27
+ import wrapture
28
+
29
+
30
+ def add_otel_sink() -> None:
31
+ """Register the OpenTelemetry sink; exits with guidance when the
32
+ optional dependencies are missing."""
33
+
34
+ try:
35
+ import wrapture.otel
36
+ except ImportError as error:
37
+ raise SystemExit(
38
+ "the OpenTelemetry dependencies are not installed; run the"
39
+ " demo through `just demo-botocore --otel`, which overlays"
40
+ " wrapture[otel] for the run"
41
+ ) from error
42
+
43
+ wrapture.add_sink(wrapture.otel.sink(service_name="wrapture-botocore-demo"))
44
+
45
+
46
+ def main(arguments: list[str] | None = None) -> None:
47
+ """Run the demo: apply the instrumentation, make the calls against
48
+ moto, print the live stream and the tree, and flush any
49
+ exporters."""
50
+
51
+ parser = argparse.ArgumentParser(
52
+ prog="demo.botocore",
53
+ description="Drive boto3 against moto with the instrumentation"
54
+ " applied, printing the live stream and the tree.",
55
+ )
56
+ parser.add_argument(
57
+ "--otel",
58
+ action="store_true",
59
+ help="also export the events as OpenTelemetry spans over OTLP",
60
+ )
61
+ options = parser.parse_args(arguments)
62
+
63
+ os.environ.setdefault("AWS_ACCESS_KEY_ID", "testing")
64
+ os.environ.setdefault("AWS_SECRET_ACCESS_KEY", "testing")
65
+ os.environ.setdefault("AWS_SESSION_TOKEN", "testing")
66
+ os.environ.setdefault("AWS_DEFAULT_REGION", "us-east-1")
67
+
68
+ if options.otel:
69
+ add_otel_sink()
70
+
71
+ wrapture.add_sink(wrapture.Printer(stream=sys.stdout))
72
+
73
+ print("== live stream ==")
74
+
75
+ import boto3
76
+ from botocore.exceptions import ClientError
77
+ from moto import mock_aws
78
+
79
+ with (
80
+ mock_aws(),
81
+ wrapture.instrumentation("botocore"),
82
+ wrapture.timeline() as tape,
83
+ ):
84
+
85
+ @wrapture.observed
86
+ def exercise() -> None:
87
+ s3 = boto3.client("s3")
88
+ s3.create_bucket(Bucket="reports")
89
+ s3.put_object(Bucket="reports", Key="q1.csv", Body=b"secret contents")
90
+ s3.get_object(Bucket="reports", Key="q1.csv")["Body"].read()
91
+
92
+ dynamodb = boto3.client("dynamodb")
93
+ dynamodb.create_table(
94
+ TableName="orders",
95
+ KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
96
+ AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
97
+ BillingMode="PAY_PER_REQUEST",
98
+ )
99
+ dynamodb.put_item(TableName="orders", Item={"id": {"S": "1"}})
100
+
101
+ sqs = boto3.client("sqs")
102
+ queue = sqs.create_queue(QueueName="jobs")["QueueUrl"]
103
+ sqs.send_message(QueueUrl=queue, MessageBody="run report")
104
+
105
+ for name in ("a", "b"):
106
+ s3.put_object(Bucket="reports", Key=name, Body=b"x")
107
+ list(
108
+ s3.get_paginator("list_objects_v2").paginate(
109
+ Bucket="reports", PaginationConfig={"PageSize": 1}
110
+ )
111
+ )
112
+
113
+ try:
114
+ s3.get_object(Bucket="reports", Key="missing")
115
+ except ClientError as error:
116
+ print(f"expected failure: {error.response['Error']['Code']}")
117
+
118
+ exercise()
119
+
120
+ print()
121
+ print("== tree ==")
122
+ print(tape.tree(times=True))
123
+
124
+ wrapture.shutdown()
125
+
126
+ if options.otel:
127
+ endpoint = os.environ.get(
128
+ "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318"
129
+ )
130
+ print()
131
+ print("== otel ==")
132
+ print(f"spans flushed to {endpoint} as service wrapture-botocore-demo")
133
+
134
+
135
+ if __name__ == "__main__":
136
+ main()