021-python-clients 0.3.0__tar.gz → 0.4.0__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 (62) hide show
  1. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/CHANGELOG.md +9 -0
  2. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/CONTRACT.md +60 -10
  3. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/CONTRIBUTING.md +6 -1
  4. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/PKG-INFO +85 -11
  5. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/README.md +82 -8
  6. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/pyproject.toml +5 -5
  7. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/__init__.py +4 -2
  8. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/_observe.py +90 -4
  9. 021_python_clients-0.4.0/src/clients_021/compute/__init__.py +1 -0
  10. 021_python_clients-0.4.0/src/clients_021/compute/kubernetes.py +314 -0
  11. 021_python_clients-0.4.0/src/clients_021/llm/__init__.py +1 -0
  12. 021_python_clients-0.4.0/src/clients_021/llm/bedrock.py +250 -0
  13. 021_python_clients-0.4.0/src/clients_021/secrets/__init__.py +1 -0
  14. 021_python_clients-0.4.0/src/clients_021/secrets/secrets_manager.py +127 -0
  15. 021_python_clients-0.4.0/tests/test_bedrock.py +290 -0
  16. 021_python_clients-0.4.0/tests/test_kubernetes.py +299 -0
  17. 021_python_clients-0.4.0/tests/test_secrets_manager.py +258 -0
  18. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/wheel_minimal.py +27 -2
  19. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/wheel_smoke.py +7 -0
  20. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/.gitignore +0 -0
  21. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/LICENSE +0 -0
  22. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/_aws.py +0 -0
  23. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/cache/__init__.py +0 -0
  24. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/cache/redis.py +0 -0
  25. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/crypto/__init__.py +0 -0
  26. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/crypto/kms.py +0 -0
  27. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/email/__init__.py +0 -0
  28. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/email/_message.py +0 -0
  29. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/email/resend.py +0 -0
  30. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/email/ses.py +0 -0
  31. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/errors.py +0 -0
  32. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/http/__init__.py +0 -0
  33. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/http/_observe.py +0 -0
  34. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/http/httpx.py +0 -0
  35. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/http/httpx2.py +0 -0
  36. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/httpx2.py +0 -0
  37. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/kms.py +0 -0
  38. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/py.typed +0 -0
  39. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/queue/__init__.py +0 -0
  40. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/queue/qstash.py +0 -0
  41. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/queue/sqs.py +0 -0
  42. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/redis.py +0 -0
  43. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/s3.py +0 -0
  44. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/storage/__init__.py +0 -0
  45. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/storage/s3.py +0 -0
  46. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/storage/vercel_blob.py +0 -0
  47. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/__init__.py +0 -0
  48. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/conftest.py +0 -0
  49. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/qstash_local.py +0 -0
  50. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_contract.py +0 -0
  51. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_email.py +0 -0
  52. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_http.py +0 -0
  53. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_http_stream_lifecycle.py +0 -0
  54. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_http_streaming.py +0 -0
  55. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_httpx2.py +0 -0
  56. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_kms.py +0 -0
  57. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_qstash.py +0 -0
  58. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_redis.py +0 -0
  59. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_s3.py +0 -0
  60. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_ses.py +0 -0
  61. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_sqs.py +0 -0
  62. {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_vercel_blob.py +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes are recorded here by release-please from Conventional Commits.
4
4
 
5
+ ## [0.4.0](https://github.com/from021-io/python-clients/compare/v0.3.0...v0.4.0) (2026-10-22)
6
+
7
+
8
+ ### Features
9
+
10
+ * **compute:** add Kubernetes Jobs client over the HTTP client ([9b3c369](https://github.com/from021-io/python-clients/commit/9b3c36930f6a070d3fee9844e86c0d27f871346b))
11
+ * **llm:** add observed Bedrock Runtime client for SDKs ([5216293](https://github.com/from021-io/python-clients/commit/52162939416f58c610f4f4e217d911f5e5efdea9))
12
+ * **secrets:** add observed Secrets Manager client ([f14042f](https://github.com/from021-io/python-clients/commit/f14042f064ca61e8d267b338b033a12fd41690eb))
13
+
5
14
  ## [0.3.0](https://github.com/from021-io/python-clients/compare/v0.2.0...v0.3.0) (2026-10-21)
6
15
 
7
16
 
@@ -15,8 +15,10 @@ The queue package groups providers by purpose. SQS owns polling, receipts and vi
15
15
  QStash owns HTTP delivery. Their public methods and observability contracts remain distinct.
16
16
  Shared email types and the interchangeable `EmailSender` protocol live in `clients_021.email`.
17
17
 
18
- Redis lives in `clients_021.cache.redis`, S3 in `clients_021.storage.s3` and KMS in
19
- `clients_021.crypto.kms`. The 0.1.0 root modules (`redis`, `s3`, `kms`) continue to re-export
18
+ Redis lives in `clients_021.cache.redis`, S3 in `clients_021.storage.s3`, KMS in
19
+ `clients_021.crypto.kms`, Secrets Manager in `clients_021.secrets.secrets_manager`, Bedrock
20
+ in `clients_021.llm.bedrock` and Kubernetes Jobs in `clients_021.compute.kubernetes`, which
21
+ needs no extra. The 0.1.0 root modules (`redis`, `s3`, `kms`) continue to re-export
20
22
  the same clients and result types (`ObjectInfo`, `DataKey`). Existing call signatures,
21
23
  metric/span names and error semantics are unchanged. Capability package imports do not
22
24
  load optional SDKs. HTTP implementations live in `clients_021.http.httpx` and
@@ -40,6 +42,13 @@ version, environment). Durations use 021-python-observability's buckets.
40
42
  | `storage.client.operation.duration` | Histogram / `s` | same | S3, Vercel Blob |
41
43
  | `crypto.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | KMS |
42
44
  | `crypto.client.operation.duration` | Histogram / `s` | same | KMS |
45
+ | `secrets.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | Secrets Manager |
46
+ | `secrets.client.operation.duration` | Histogram / `s` | same | Secrets Manager |
47
+ | `llm.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | Bedrock |
48
+ | `llm.client.operation.duration` | Histogram / `s` | same | Bedrock |
49
+ | `llm.client.token.usage` | Counter / `{token}` | dependency, operation, token.type | Bedrock (Converse and ConverseStream) |
50
+ | `compute.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | Kubernetes Jobs |
51
+ | `compute.client.operation.duration` | Histogram / `s` | same | Kubernetes Jobs |
43
52
  | `http.client.request.count` | Counter / `{request}` | dependency, http.request.method, http.response.status_code, error.type, http.response.body.complete | HTTP, httpx2, QStash transport, Resend (its HTTP request) |
44
53
  | `http.client.request.duration` | Histogram / `s` | same | HTTP, httpx2, QStash transport, Resend (its HTTP request) |
45
54
 
@@ -49,8 +58,16 @@ version, environment). Durations use 021-python-observability's buckets.
49
58
  S3 and Vercel Blob: `put`, `get`, `head`, `delete`. QStash: `publish`.
50
59
  Email (Resend, SES): `send`. KMS:
51
60
  `encrypt`, `decrypt`, `generate_data_key`; `seal` and `unseal` record the KMS call they make
52
- (`generate_data_key`, `decrypt`), not the local AES work.
53
- - Redis/SQS/S3/Vercel Blob/QStash/email/KMS `error.type`: `timeout`, `connection_error`, `throttled`,
61
+ (`generate_data_key`, `decrypt`), not the local AES work. Secrets Manager: `get`.
62
+ Bedrock: `converse`, `converse_stream`, `invoke_model`, `invoke_model_with_response_stream`,
63
+ `count_tokens`. A streaming operation ends when its event stream is exhausted, fails or is
64
+ closed, not when the call returns. Kubernetes Jobs: `create`, `get`, `delete`; each also
65
+ records its HTTP request (`http.client.request.*`) under the same `dependency`. A missing
66
+ Job (`get`, `delete`) and an existing one (`create`) are `status=ok`.
67
+ - `token.type`: `input`, `output`, `cache_read`, `cache_write`, as reported by Bedrock in
68
+ the Converse response or the ConverseStream `metadata` event. InvokeModel bodies are
69
+ model-specific and are not read.
70
+ - Redis/SQS/S3/Vercel Blob/QStash/email/KMS/Secrets Manager/Bedrock/Kubernetes Jobs `error.type`: `timeout`, `connection_error`, `throttled`,
54
71
  `status_<5xx>`, `cancelled`, or the exception class name for errors that are not outages
55
72
  (`EmailRejected` for every email rejection; botocore's class, such as `ClientError` or
56
73
  `NotFoundException`, for KMS).
@@ -70,7 +87,10 @@ version, environment). Durations use 021-python-observability's buckets.
70
87
  CLIENT spans named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
71
88
  metric labels plus `db.system.name=redis`, `messaging.system=aws_sqs`,
72
89
  `messaging.system=upstash_qstash`, `messaging.system=resend`, `messaging.system=aws_ses`, or
73
- `rpc.system=aws-api` + `rpc.service=S3` / `rpc.service=KMS`, or `rpc.system=vercel-blob`.
90
+ `rpc.system=aws-api` + `rpc.service=S3` / `rpc.service=KMS` / `rpc.service=SecretsManager` /
91
+ `rpc.service=BedrockRuntime` (plus `gen_ai.system=aws.bedrock`), `rpc.system=vercel-blob`, or
92
+ `rpc.system=kubernetes` + `rpc.service=Job`. Kubernetes Jobs' `HTTP <dependency>` span is a
93
+ child of its `<dependency> <operation>` span.
74
94
  Resend's `HTTP <dependency>` span is a child of its `<dependency> send` span. Status ERROR on failure (and on HTTP
75
95
  status ≥ 400).
76
96
  Cancellation propagates unchanged and records ERROR with `error.type=cancelled`.
@@ -97,7 +117,7 @@ Local input validation occurs before either operation is recorded.
97
117
 
98
118
  ## Logging and coverage limits
99
119
 
100
- Redis, SQS, QStash, S3, Vercel Blob, Resend, SES and KMS emit a structured
120
+ Redis, SQS, QStash, S3, Vercel Blob, Resend, SES, KMS, Secrets Manager, Bedrock and Kubernetes Jobs emit a structured
101
121
  `dependency.call_failed` warning when an observed remote operation raises an exception.
102
122
  Fields are `dependency`, `operation` and `error_type`; provider messages and exception
103
123
  chains are not logged. Successful calls and cancellation do not emit this warning;
@@ -112,7 +132,7 @@ attempts emit no log. Intentional partial close emits `dependency.stream_closed`
112
132
  with dependency and operation; it is not an outage.
113
133
 
114
134
  `log_failures=False` suppresses these HTTP attempt logs without disabling metrics or spans.
115
- QStash and Resend set this option because their logical operation already emits a failure
135
+ QStash, Resend and Kubernetes Jobs set this option because their logical operation already emits a failure
116
136
  warning; they do not log the same failure at both levels. SDK retry loops can produce one
117
137
  warning per failed attempt. HTTP counters measure attempts, not logical API/SDK calls.
118
138
 
@@ -137,7 +157,7 @@ telemetry reaches an external backend.
137
157
  When enabled, both caller-supplied trace headers are replaced with the current context.
138
158
  - Resend: nothing is sent. The `traceparent`, `tracestate` and `x-request-id` headers that
139
159
  the HTTP transport adds are removed before the request leaves.
140
- - SES, KMS and Vercel Blob: nothing is sent by the default transports.
160
+ - SES, KMS, Secrets Manager, Bedrock and Vercel Blob: nothing is sent by the default transports.
141
161
  - SQS: `traceparent` and `tracestate` are message attributes; `ReceivedMessage.trace_context()`
142
162
  returns the producer's context. Caller-supplied values for both trace attributes are
143
163
  replaced with the current context.
@@ -188,6 +208,31 @@ tampered ciphertext (`InvalidCiphertextException`) and a ciphertext from another
188
208
  Every call requires a non-empty encryption context. `unseal` raises `ValueError` for a
189
209
  malformed envelope and `cryptography.exceptions.InvalidTag` when the payload was altered.
190
210
 
211
+ Secrets Manager: throttling, 5xx (`InternalServiceError`), timeouts and connection failures
212
+ are `DependencyUnavailable`. A missing secret (`ResourceNotFoundException`), access denied,
213
+ a value KMS cannot decrypt (`DecryptionFailure`), a secret scheduled for deletion
214
+ (`InvalidRequestException`) and invalid parameters keep their botocore type. `get` is a read:
215
+ retrying it is safe. A version with neither `SecretString` nor `SecretBinary` raises
216
+ `ValueError`.
217
+
218
+ Bedrock: throttling (`ThrottlingException`, and `throttlingException` inside an event stream),
219
+ 5xx, `ModelNotReadyException` (`throttled`), the stream errors `serviceUnavailableException`,
220
+ `internalServerException` and `modelStreamErrorException`, timeouts and connection failures
221
+ are `DependencyUnavailable`. Access denied, validation errors, a missing model or profile and
222
+ quota limits (`ServiceQuotaExceededException`) keep their botocore type. Leaving a stream
223
+ early or calling its `close()` ends the operation as `ok` and logs `dependency.stream_closed`
224
+ at INFO. A model call is not idempotent in cost: botocore's retries bill each attempt.
225
+
226
+ Kubernetes Jobs: 429 is `throttled`, 5xx `status_<5xx>`, transport timeouts and connection
227
+ failures `timeout` / `connection_error`, all `DependencyUnavailable`. A 409 whose reason is
228
+ `AlreadyExists` on `create` and a 404 on `get` or `delete` are results, not errors. Any other
229
+ non-success status raises `KubernetesApiError(status_code, reason)`, where `reason` is the
230
+ Status object's machine-readable reason (`Forbidden`, `Invalid`, `Conflict`, ...) or
231
+ `Unknown`; a 201 whose name does not match raises it with `InvalidResponse`. POST is sent
232
+ once; GET and DELETE retry 502/503/504 and network failures once. Local validation (DNS-label
233
+ names and namespace, `metadata.name` required, `generateName` refused, `kind` must be Job)
234
+ happens before any request.
235
+
191
236
  Vercel Blob (`clients_021.storage.vercel_blob`): HTTP 429 maps to `throttled`; 5xx to
192
237
  `status_<code>`; HTTP timeouts and total deadlines to `timeout`; network/remote protocol
193
238
  failures to `connection_error`. Known transport causes wrapped by the SDK are classified
@@ -209,7 +254,8 @@ A new data key is generated for every envelope.
209
254
 
210
255
  ## Lifecycle and retry limits
211
256
 
212
- HTTP, Redis, SQS, QStash, S3, Vercel Blob, Resend, SES and KMS expose `aclose()`. Close them after
257
+ HTTP, Redis, SQS, QStash, S3, Vercel Blob, Resend, SES, KMS, Secrets Manager, Bedrock and Kubernetes
258
+ Jobs expose `aclose()` (Bedrock also `close()`). Close them after
213
259
  in-flight calls complete; the wrapper owns the underlying client, including one supplied
214
260
  through the constructor.
215
261
  AWS factories configure three total attempts; injected clients retain their own policy.
@@ -264,6 +310,10 @@ these clients, or each request would be counted twice.
264
310
  Keys, URLs, query strings, bodies, message ids, receipts, object names, bucket or queue
265
311
  names, provider error messages. Email: sender, recipient and reply-to addresses, subjects,
266
312
  text and HTML bodies, tags, idempotency keys, API keys, configuration set names. KMS: key
267
- ids, ARNs and aliases, encryption contexts, plaintext, ciphertext and data keys.
313
+ ids, ARNs and aliases, encryption contexts, plaintext, ciphertext and data keys. Secrets
314
+ Manager: secret ids, names and ARNs, version ids, version stages and secret values. Bedrock:
315
+ model ids, inference profile ARNs, prompts, system prompts, outputs, tool arguments and
316
+ results, request ids and guardrail details. Kubernetes Jobs: namespaces, Job names, labels,
317
+ manifests, API server addresses, service account tokens and Status messages.
268
318
 
269
319
  Vercel Blob: read-write tokens, store IDs, pathnames, delivery URLs, content and ETags.
@@ -19,6 +19,9 @@ clients_021/
19
19
  cache/ # redis.py
20
20
  storage/ # s3.py, vercel_blob.py
21
21
  crypto/ # kms.py
22
+ secrets/ # secrets_manager.py
23
+ llm/ # bedrock.py
24
+ compute/ # kubernetes.py (over the shared HTTP client, no extra)
22
25
  ```
23
26
 
24
27
  The original `clients_021.redis`, `clients_021.s3` and `clients_021.kms` imports remain
@@ -62,7 +65,9 @@ Each implementation must make these decisions explicit in README.md and CONTRACT
62
65
  | Telemetry | Choose a metric family, fixed operations and system attribute. Never record URLs, credentials, bodies, object names or request-derived ids. |
63
66
  | Propagation | State the default and opt-out. Replace `traceparent` and `tracestate` together, including removing stale state. |
64
67
 
65
- Use `_observe.observed` for a logical cache, messaging, storage or crypto operation. HTTP-backed
68
+ Use `_observe.observed` for a logical cache, messaging, storage or crypto operation, and
69
+ `_observe.Call` for synchronous SDK calls or operations that end after the call returns
70
+ (event streams). HTTP-backed
66
71
  clients may also emit the transport's HTTP span/metrics: one logical operation and one
67
72
  network request are different measurements. Do not sum those counters as distinct
68
73
  business operations. Disable nested HTTP attempt logs with `log_failures=False` when the
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: 021-python-clients
3
- Version: 0.3.0
4
- Summary: Observed clients for Redis, SQS, QStash, S3, Vercel Blob, HTTP, email and KMS, built on 021-python-observability
3
+ Version: 0.4.0
4
+ Summary: Observed clients for Redis, SQS, QStash, S3, Vercel Blob, HTTP, email, KMS, Secrets Manager, Bedrock and Kubernetes Jobs, built on 021-python-observability
5
5
  Project-URL: Repository, https://github.com/from021-io/python-clients
6
6
  Project-URL: Changelog, https://github.com/from021-io/python-clients/blob/main/CHANGELOG.md
7
7
  Project-URL: Contract, https://github.com/from021-io/python-clients/blob/main/CONTRACT.md
8
8
  License-Expression: Apache-2.0
9
9
  License-File: LICENSE
10
- Keywords: email,httpx,kms,observability,opentelemetry,qstash,redis,resend,s3,ses,sqs
10
+ Keywords: bedrock,email,httpx,kms,kubernetes,observability,opentelemetry,qstash,redis,resend,s3,secrets-manager,ses,sqs
11
11
  Classifier: Development Status :: 3 - Alpha
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Operating System :: OS Independent
@@ -58,6 +58,9 @@ uv add "021-python-clients[redis,aws,httpx2,vercel-blob]" # pick the extras yo
58
58
  | `clients_021.email` | none | `EmailMessage`, the `EmailSender` protocol, `ResendSender` (Resend's HTTP API) |
59
59
  | `clients_021.email.ses` | `aws` | `SesSender`: the same protocol over Amazon SES v2 |
60
60
  | `clients_021.crypto.kms` | `aws` | `KmsClient`: encrypt, decrypt and data keys bound to an encryption context; AES-256-GCM envelopes |
61
+ | `clients_021.secrets.secrets_manager` | `aws` | `SecretsManagerClient`: read a secret by id, version id or stage; the value is never printed |
62
+ | `clients_021.compute.kubernetes` | none | `KubernetesJobsClient`: create (idempotent by name), read and delete Jobs in a namespace, in-cluster service account |
63
+ | `clients_021.llm.bedrock` | `aws` | `BedrockRuntime`: an observed `bedrock-runtime` client to pass to SDKs such as LangChain's `ChatBedrockConverse`; streams measured to the end, token usage counted |
61
64
 
62
65
  Every client takes the service's `Observability` (from `setup_observability`) and a
63
66
  `dependency` name, which becomes the `dependency` label of its metrics.
@@ -66,7 +69,7 @@ Every client takes the service's `Observability` (from `setup_observability`) an
66
69
 
67
70
  Providers with the same purpose live under one capability package: `email/` contains
68
71
  Resend and SES; `queue/` contains SQS and QStash; `storage/` contains S3 and Vercel Blob; `cache/`
69
- contains Redis; `crypto/` contains KMS. `http/` contains the httpx client and httpx2
72
+ contains Redis; `crypto/` contains KMS; `secrets/` contains Secrets Manager; `llm/` contains Bedrock; `compute/` contains Kubernetes Jobs. `http/` contains the httpx client and httpx2
70
73
  transport. Import providers explicitly:
71
74
 
72
75
  ```python
@@ -77,6 +80,9 @@ from clients_021.queue.qstash import QStashClient
77
80
  from clients_021.storage.s3 import S3Client
78
81
  from clients_021.cache.redis import RedisClient
79
82
  from clients_021.crypto.kms import KmsClient
83
+ from clients_021.secrets.secrets_manager import SecretsManagerClient
84
+ from clients_021.llm.bedrock import BedrockRuntime
85
+ from clients_021.compute.kubernetes import KubernetesJobsClient
80
86
  ```
81
87
 
82
88
  SQS supports polling, receipts and visibility changes; QStash delivers to an HTTP endpoint.
@@ -95,12 +101,16 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
95
101
 
96
102
  - A CLIENT span named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
97
103
  system attribute (`db.system.name=redis`, `messaging.system=aws_sqs|resend|aws_ses`,
98
- `rpc.service=S3|KMS`, `rpc.system=vercel-blob`).
104
+ `rpc.service=S3|KMS|SecretsManager|BedrockRuntime`, `rpc.system=vercel-blob`, `rpc.system=kubernetes`;
105
+ Bedrock also `gen_ai.system=aws.bedrock`).
99
106
  - A counter and a duration histogram: `db.client.operation.*` (Redis, shared with SQL),
100
107
  `messaging.client.operation.*` (SQS, QStash, email), `storage.client.operation.*` (S3, Vercel Blob),
101
- `crypto.client.operation.*` (KMS), `http.client.request.*` (HTTP, and the HTTP request
108
+ `crypto.client.operation.*` (KMS), `secrets.client.operation.*` (Secrets Manager),
109
+ `llm.client.operation.*` and `llm.client.token.usage` (Bedrock), `compute.client.operation.*`
110
+ (Kubernetes Jobs),
111
+ `http.client.request.*` (HTTP, and the HTTP request
102
112
  under QStash and Resend). Labels are bounded; see [CONTRACT.md](CONTRACT.md).
103
- - Redis, SQS, QStash, S3, Vercel Blob, email and KMS emit a `dependency.call_failed` warning on failure,
113
+ - Redis, SQS, QStash, S3, Vercel Blob, email, KMS, Secrets Manager, Bedrock and Kubernetes Jobs emit a `dependency.call_failed` warning on failure,
104
114
  with dependency, operation and error type only. HTTP/httpx2 also log failed attempts,
105
115
  including body failures and cancellation; QStash/Resend suppress the nested HTTP log.
106
116
  - `DependencyUnavailable(dependency, operation, reason)` when the service timed out,
@@ -109,7 +119,9 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
109
119
  original type: they are bugs, not outages.
110
120
 
111
121
  Never in this library's spans, metrics or logs: keys, URLs, bodies, message ids or object
112
- names; email addresses, subjects, bodies or tags; KMS key ids, encryption contexts,
122
+ names; email addresses, subjects, bodies or tags; secret ids, version ids, stages and
123
+ values; model ids, prompts and outputs; namespaces, Job names, manifests and service
124
+ account tokens; KMS key ids, encryption contexts,
113
125
  plaintext, ciphertext or data keys. Use static dependency names such as `identity-provider` or `uploads`; never derive
114
126
  them from a tenant, user or request. All clients apply observability's identifier rule
115
127
  (1–96 letters, digits, `_`, `.` or `-`, starting with a letter or digit).
@@ -272,6 +284,64 @@ except EmailRejected as rejected: # the address, the domain or the credentials;
272
284
  SES has no idempotency key (the argument is ignored): deduplicate in the caller when a retry
273
285
  must not send twice. Tags are for provider analytics; never put personal data in them.
274
286
 
287
+ ### Kubernetes Jobs
288
+
289
+ ```python
290
+ from clients_021.compute.kubernetes import KubernetesJobsClient
291
+
292
+ jobs = KubernetesJobsClient.in_cluster(obs, namespace="agent-runs")
293
+ created = await jobs.create({"metadata": {"name": "cmd-4821"}, "spec": job_spec})
294
+ status = await jobs.get("cmd-4821") # JobStatus(phase="running", ...) or None
295
+ await jobs.delete("cmd-4821", propagation="Background")
296
+ ```
297
+
298
+ No SDK: it calls the `batch/v1` REST API through `HttpClient`. `in_cluster` uses the pod's
299
+ service account (CA bundle, namespace, and a token it re-reads at most once a minute because
300
+ projected tokens rotate). `create` is idempotent by name: `generateName` is refused, and an
301
+ existing Job returns `created=False`, so a caller can repeat it after a timeout. `get`
302
+ returns `None` and `delete` returns `False` for a missing Job. POST is sent once; GET and
303
+ DELETE retry 502, 503, 504 and network failures once. 429, 5xx, timeouts and connection
304
+ failures raise `DependencyUnavailable`; other rejections raise `KubernetesApiError` with the
305
+ status code and the API's reason, never its message.
306
+
307
+ ### Bedrock
308
+
309
+ ```python
310
+ from clients_021.llm.bedrock import BedrockRuntime
311
+ from langchain_aws import ChatBedrockConverse
312
+
313
+ runtime = BedrockRuntime.create(obs, region="us-east-1")
314
+ model = ChatBedrockConverse(model=settings.inference_profile, client=runtime)
315
+ ```
316
+
317
+ `BedrockRuntime` keeps boto3's call signatures for `converse`, `converse_stream`,
318
+ `invoke_model`, `invoke_model_with_response_stream` and `count_tokens`, and passes every
319
+ other attribute (`meta`, `exceptions`) through to the boto3 client, so an SDK can take it
320
+ where it expects one. Calls stay synchronous like boto3's. A streaming call is measured
321
+ until its event stream ends; leaving the loop early or calling `close()` ends it as `ok`.
322
+ Converse and ConverseStream add the reported input, output and cache tokens to
323
+ `llm.client.token.usage`. Throttling (also mid-stream), 5xx, a model still loading and the
324
+ stream errors Bedrock documents as retryable raise `DependencyUnavailable`; access denied,
325
+ validation errors, a missing model and quota limits keep their botocore type. A retried
326
+ model call is billed again.
327
+
328
+ ### Secrets Manager
329
+
330
+ ```python
331
+ from clients_021.secrets.secrets_manager import SecretsManagerClient
332
+
333
+ secrets = SecretsManagerClient.create(obs, region="us-east-1")
334
+ secret = await secrets.get("connections/brain")
335
+ token = secret.text() # SecretString; secret.binary() for SecretBinary
336
+ previous = await secrets.get("connections/brain", version_stage="AWSPREVIOUS")
337
+ ```
338
+
339
+ `get` reads the current version unless a `version_id` or a `version_stage` is given (not
340
+ both). The client does not cache: every call is a billed request, so keep a short-lived
341
+ copy when the same secret is read often. A missing secret, access denied, a value KMS cannot
342
+ decrypt or a secret scheduled for deletion keep their botocore type; throttling, 5xx and
343
+ timeouts raise `DependencyUnavailable`. `repr(secret)` never shows the value.
344
+
275
345
  ### KMS
276
346
 
277
347
  The encryption context is required on every call, so a ciphertext only decrypts for the
@@ -375,6 +445,8 @@ async with AsyncExitStack() as resources:
375
445
  resources.push_async_callback(email.aclose)
376
446
  keys = KmsClient.create(obs, settings.session_key_arn, region="us-east-1")
377
447
  resources.push_async_callback(keys.aclose)
448
+ secrets = SecretsManagerClient.create(obs, region="us-east-1")
449
+ resources.push_async_callback(secrets.aclose)
378
450
  # Run the service here. Stop its background workers before leaving this scope.
379
451
  ```
380
452
 
@@ -386,7 +458,7 @@ Retry policies are explicit:
386
458
  POST and PATCH run once. Invalid URLs/protocols and invalid request construction keep
387
459
  their original exception. HTTP status responses are returned for the caller to handle.
388
460
  - Resend sends each request once (POST); send an `idempotency_key` and retry in the caller.
389
- - AWS factories (SQS, S3, SES, KMS) use botocore standard retries with at most **three total
461
+ - AWS factories (SQS, S3, SES, KMS, Secrets Manager, Bedrock) use botocore standard retries with at most **three total
390
462
  attempts**.
391
463
  Injected SDK clients keep their configured policy. SQS consumers must tolerate duplicate
392
464
  messages; a timeout does not prove that a send or write was not applied.
@@ -409,7 +481,7 @@ any operation**. Map it at the service boundary and apply the operation's idempo
409
481
  Its message and structured fields are safe to log when dependency names are static. Its
410
482
  chained SDK exception may contain sensitive provider data; do not log exception chains.
411
483
 
412
- S3, SQS, SES and KMS run the synchronous AWS SDK in worker threads. Cancelling the await does not
484
+ S3, SQS, SES, KMS and Secrets Manager run the synchronous AWS SDK in worker threads. Cancelling the await does not
413
485
  stop an SDK request already running in its thread. Timeouts are per SDK/network attempt,
414
486
  not an overall deadline. Limit consumer concurrency and let in-flight work finish before
415
487
  closing the clients. Redis window counters require Redis server 7 or newer (`EXPIRE NX`).
@@ -433,6 +505,8 @@ deduplication, start the [official local server](https://upstash.com/docs/qstash
433
505
  set its `QSTASH_URL` and `QSTASH_TOKEN`, and run `uv run python tests/qstash_local.py`.
434
506
  That smoke test accepts only a loopback QStash server and binds its destination dynamically.
435
507
 
436
- SQS, S3, SES and KMS tests run against [moto](https://github.com/getmoto/moto) (plus
508
+ Kubernetes Jobs tests use httpx's `MockTransport`. Bedrock tests use botocore's Stubber and
509
+ fake event streams. SQS, S3, SES, KMS and Secrets
510
+ Manager tests run against [moto](https://github.com/getmoto/moto) (plus
437
511
  botocore's Stubber where moto ignores a field or differs from AWS); Resend tests use httpx's
438
512
  `MockTransport`; Redis tests need a real Redis. Licensed under Apache-2.0.
@@ -22,6 +22,9 @@ uv add "021-python-clients[redis,aws,httpx2,vercel-blob]" # pick the extras yo
22
22
  | `clients_021.email` | none | `EmailMessage`, the `EmailSender` protocol, `ResendSender` (Resend's HTTP API) |
23
23
  | `clients_021.email.ses` | `aws` | `SesSender`: the same protocol over Amazon SES v2 |
24
24
  | `clients_021.crypto.kms` | `aws` | `KmsClient`: encrypt, decrypt and data keys bound to an encryption context; AES-256-GCM envelopes |
25
+ | `clients_021.secrets.secrets_manager` | `aws` | `SecretsManagerClient`: read a secret by id, version id or stage; the value is never printed |
26
+ | `clients_021.compute.kubernetes` | none | `KubernetesJobsClient`: create (idempotent by name), read and delete Jobs in a namespace, in-cluster service account |
27
+ | `clients_021.llm.bedrock` | `aws` | `BedrockRuntime`: an observed `bedrock-runtime` client to pass to SDKs such as LangChain's `ChatBedrockConverse`; streams measured to the end, token usage counted |
25
28
 
26
29
  Every client takes the service's `Observability` (from `setup_observability`) and a
27
30
  `dependency` name, which becomes the `dependency` label of its metrics.
@@ -30,7 +33,7 @@ Every client takes the service's `Observability` (from `setup_observability`) an
30
33
 
31
34
  Providers with the same purpose live under one capability package: `email/` contains
32
35
  Resend and SES; `queue/` contains SQS and QStash; `storage/` contains S3 and Vercel Blob; `cache/`
33
- contains Redis; `crypto/` contains KMS. `http/` contains the httpx client and httpx2
36
+ contains Redis; `crypto/` contains KMS; `secrets/` contains Secrets Manager; `llm/` contains Bedrock; `compute/` contains Kubernetes Jobs. `http/` contains the httpx client and httpx2
34
37
  transport. Import providers explicitly:
35
38
 
36
39
  ```python
@@ -41,6 +44,9 @@ from clients_021.queue.qstash import QStashClient
41
44
  from clients_021.storage.s3 import S3Client
42
45
  from clients_021.cache.redis import RedisClient
43
46
  from clients_021.crypto.kms import KmsClient
47
+ from clients_021.secrets.secrets_manager import SecretsManagerClient
48
+ from clients_021.llm.bedrock import BedrockRuntime
49
+ from clients_021.compute.kubernetes import KubernetesJobsClient
44
50
  ```
45
51
 
46
52
  SQS supports polling, receipts and visibility changes; QStash delivers to an HTTP endpoint.
@@ -59,12 +65,16 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
59
65
 
60
66
  - A CLIENT span named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
61
67
  system attribute (`db.system.name=redis`, `messaging.system=aws_sqs|resend|aws_ses`,
62
- `rpc.service=S3|KMS`, `rpc.system=vercel-blob`).
68
+ `rpc.service=S3|KMS|SecretsManager|BedrockRuntime`, `rpc.system=vercel-blob`, `rpc.system=kubernetes`;
69
+ Bedrock also `gen_ai.system=aws.bedrock`).
63
70
  - A counter and a duration histogram: `db.client.operation.*` (Redis, shared with SQL),
64
71
  `messaging.client.operation.*` (SQS, QStash, email), `storage.client.operation.*` (S3, Vercel Blob),
65
- `crypto.client.operation.*` (KMS), `http.client.request.*` (HTTP, and the HTTP request
72
+ `crypto.client.operation.*` (KMS), `secrets.client.operation.*` (Secrets Manager),
73
+ `llm.client.operation.*` and `llm.client.token.usage` (Bedrock), `compute.client.operation.*`
74
+ (Kubernetes Jobs),
75
+ `http.client.request.*` (HTTP, and the HTTP request
66
76
  under QStash and Resend). Labels are bounded; see [CONTRACT.md](CONTRACT.md).
67
- - Redis, SQS, QStash, S3, Vercel Blob, email and KMS emit a `dependency.call_failed` warning on failure,
77
+ - Redis, SQS, QStash, S3, Vercel Blob, email, KMS, Secrets Manager, Bedrock and Kubernetes Jobs emit a `dependency.call_failed` warning on failure,
68
78
  with dependency, operation and error type only. HTTP/httpx2 also log failed attempts,
69
79
  including body failures and cancellation; QStash/Resend suppress the nested HTTP log.
70
80
  - `DependencyUnavailable(dependency, operation, reason)` when the service timed out,
@@ -73,7 +83,9 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
73
83
  original type: they are bugs, not outages.
74
84
 
75
85
  Never in this library's spans, metrics or logs: keys, URLs, bodies, message ids or object
76
- names; email addresses, subjects, bodies or tags; KMS key ids, encryption contexts,
86
+ names; email addresses, subjects, bodies or tags; secret ids, version ids, stages and
87
+ values; model ids, prompts and outputs; namespaces, Job names, manifests and service
88
+ account tokens; KMS key ids, encryption contexts,
77
89
  plaintext, ciphertext or data keys. Use static dependency names such as `identity-provider` or `uploads`; never derive
78
90
  them from a tenant, user or request. All clients apply observability's identifier rule
79
91
  (1–96 letters, digits, `_`, `.` or `-`, starting with a letter or digit).
@@ -236,6 +248,64 @@ except EmailRejected as rejected: # the address, the domain or the credentials;
236
248
  SES has no idempotency key (the argument is ignored): deduplicate in the caller when a retry
237
249
  must not send twice. Tags are for provider analytics; never put personal data in them.
238
250
 
251
+ ### Kubernetes Jobs
252
+
253
+ ```python
254
+ from clients_021.compute.kubernetes import KubernetesJobsClient
255
+
256
+ jobs = KubernetesJobsClient.in_cluster(obs, namespace="agent-runs")
257
+ created = await jobs.create({"metadata": {"name": "cmd-4821"}, "spec": job_spec})
258
+ status = await jobs.get("cmd-4821") # JobStatus(phase="running", ...) or None
259
+ await jobs.delete("cmd-4821", propagation="Background")
260
+ ```
261
+
262
+ No SDK: it calls the `batch/v1` REST API through `HttpClient`. `in_cluster` uses the pod's
263
+ service account (CA bundle, namespace, and a token it re-reads at most once a minute because
264
+ projected tokens rotate). `create` is idempotent by name: `generateName` is refused, and an
265
+ existing Job returns `created=False`, so a caller can repeat it after a timeout. `get`
266
+ returns `None` and `delete` returns `False` for a missing Job. POST is sent once; GET and
267
+ DELETE retry 502, 503, 504 and network failures once. 429, 5xx, timeouts and connection
268
+ failures raise `DependencyUnavailable`; other rejections raise `KubernetesApiError` with the
269
+ status code and the API's reason, never its message.
270
+
271
+ ### Bedrock
272
+
273
+ ```python
274
+ from clients_021.llm.bedrock import BedrockRuntime
275
+ from langchain_aws import ChatBedrockConverse
276
+
277
+ runtime = BedrockRuntime.create(obs, region="us-east-1")
278
+ model = ChatBedrockConverse(model=settings.inference_profile, client=runtime)
279
+ ```
280
+
281
+ `BedrockRuntime` keeps boto3's call signatures for `converse`, `converse_stream`,
282
+ `invoke_model`, `invoke_model_with_response_stream` and `count_tokens`, and passes every
283
+ other attribute (`meta`, `exceptions`) through to the boto3 client, so an SDK can take it
284
+ where it expects one. Calls stay synchronous like boto3's. A streaming call is measured
285
+ until its event stream ends; leaving the loop early or calling `close()` ends it as `ok`.
286
+ Converse and ConverseStream add the reported input, output and cache tokens to
287
+ `llm.client.token.usage`. Throttling (also mid-stream), 5xx, a model still loading and the
288
+ stream errors Bedrock documents as retryable raise `DependencyUnavailable`; access denied,
289
+ validation errors, a missing model and quota limits keep their botocore type. A retried
290
+ model call is billed again.
291
+
292
+ ### Secrets Manager
293
+
294
+ ```python
295
+ from clients_021.secrets.secrets_manager import SecretsManagerClient
296
+
297
+ secrets = SecretsManagerClient.create(obs, region="us-east-1")
298
+ secret = await secrets.get("connections/brain")
299
+ token = secret.text() # SecretString; secret.binary() for SecretBinary
300
+ previous = await secrets.get("connections/brain", version_stage="AWSPREVIOUS")
301
+ ```
302
+
303
+ `get` reads the current version unless a `version_id` or a `version_stage` is given (not
304
+ both). The client does not cache: every call is a billed request, so keep a short-lived
305
+ copy when the same secret is read often. A missing secret, access denied, a value KMS cannot
306
+ decrypt or a secret scheduled for deletion keep their botocore type; throttling, 5xx and
307
+ timeouts raise `DependencyUnavailable`. `repr(secret)` never shows the value.
308
+
239
309
  ### KMS
240
310
 
241
311
  The encryption context is required on every call, so a ciphertext only decrypts for the
@@ -339,6 +409,8 @@ async with AsyncExitStack() as resources:
339
409
  resources.push_async_callback(email.aclose)
340
410
  keys = KmsClient.create(obs, settings.session_key_arn, region="us-east-1")
341
411
  resources.push_async_callback(keys.aclose)
412
+ secrets = SecretsManagerClient.create(obs, region="us-east-1")
413
+ resources.push_async_callback(secrets.aclose)
342
414
  # Run the service here. Stop its background workers before leaving this scope.
343
415
  ```
344
416
 
@@ -350,7 +422,7 @@ Retry policies are explicit:
350
422
  POST and PATCH run once. Invalid URLs/protocols and invalid request construction keep
351
423
  their original exception. HTTP status responses are returned for the caller to handle.
352
424
  - Resend sends each request once (POST); send an `idempotency_key` and retry in the caller.
353
- - AWS factories (SQS, S3, SES, KMS) use botocore standard retries with at most **three total
425
+ - AWS factories (SQS, S3, SES, KMS, Secrets Manager, Bedrock) use botocore standard retries with at most **three total
354
426
  attempts**.
355
427
  Injected SDK clients keep their configured policy. SQS consumers must tolerate duplicate
356
428
  messages; a timeout does not prove that a send or write was not applied.
@@ -373,7 +445,7 @@ any operation**. Map it at the service boundary and apply the operation's idempo
373
445
  Its message and structured fields are safe to log when dependency names are static. Its
374
446
  chained SDK exception may contain sensitive provider data; do not log exception chains.
375
447
 
376
- S3, SQS, SES and KMS run the synchronous AWS SDK in worker threads. Cancelling the await does not
448
+ S3, SQS, SES, KMS and Secrets Manager run the synchronous AWS SDK in worker threads. Cancelling the await does not
377
449
  stop an SDK request already running in its thread. Timeouts are per SDK/network attempt,
378
450
  not an overall deadline. Limit consumer concurrency and let in-flight work finish before
379
451
  closing the clients. Redis window counters require Redis server 7 or newer (`EXPIRE NX`).
@@ -397,6 +469,8 @@ deduplication, start the [official local server](https://upstash.com/docs/qstash
397
469
  set its `QSTASH_URL` and `QSTASH_TOKEN`, and run `uv run python tests/qstash_local.py`.
398
470
  That smoke test accepts only a loopback QStash server and binds its destination dynamically.
399
471
 
400
- SQS, S3, SES and KMS tests run against [moto](https://github.com/getmoto/moto) (plus
472
+ Kubernetes Jobs tests use httpx's `MockTransport`. Bedrock tests use botocore's Stubber and
473
+ fake event streams. SQS, S3, SES, KMS and Secrets
474
+ Manager tests run against [moto](https://github.com/getmoto/moto) (plus
401
475
  botocore's Stubber where moto ignores a field or differs from AWS); Resend tests use httpx's
402
476
  `MockTransport`; Redis tests need a real Redis. Licensed under Apache-2.0.
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "021-python-clients"
7
- version = "0.3.0"
8
- description = "Observed clients for Redis, SQS, QStash, S3, Vercel Blob, HTTP, email and KMS, built on 021-python-observability"
7
+ version = "0.4.0"
8
+ description = "Observed clients for Redis, SQS, QStash, S3, Vercel Blob, HTTP, email, KMS, Secrets Manager, Bedrock and Kubernetes Jobs, built on 021-python-observability"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
11
11
  license-files = ["LICENSE"]
12
12
  requires-python = ">=3.12"
13
- keywords = ["redis", "sqs", "qstash", "s3", "httpx", "email", "ses", "resend", "kms", "opentelemetry", "observability"]
13
+ keywords = ["redis", "sqs", "qstash", "s3", "httpx", "email", "ses", "resend", "kms", "secrets-manager", "bedrock", "kubernetes", "opentelemetry", "observability"]
14
14
  classifiers = [
15
15
  "Development Status :: 3 - Alpha",
16
16
  "Intended Audience :: Developers",
@@ -50,8 +50,8 @@ dev = [
50
50
  "pytest-asyncio>=1.3",
51
51
  "ruff>=0.16,<0.17",
52
52
  "pyright>=1.1.405",
53
- "moto[sqs,s3,ses,kms]>=5.2,<6",
54
- "boto3-stubs[sqs,s3,sesv2,kms]>=1.40,<2",
53
+ "moto[sqs,s3,ses,kms,secretsmanager]>=5.2,<6",
54
+ "boto3-stubs[sqs,s3,sesv2,kms,secretsmanager,bedrock-runtime]>=1.40,<2",
55
55
  ]
56
56
 
57
57
  [tool.hatch.build.targets.wheel]
@@ -1,8 +1,10 @@
1
1
  """Observed clients for the external services Python services call.
2
2
 
3
- `clients_021.http`, `clients_021.queue.qstash` and `clients_021.email` (Resend) need no extras;
3
+ `clients_021.http`, `clients_021.queue.qstash`, `clients_021.email` (Resend) and
4
+ `clients_021.compute.kubernetes` need no extras;
4
5
  `clients_021.cache.redis` needs the `redis` extra; `clients_021.queue.sqs`,
5
- `clients_021.storage.s3`, `clients_021.email.ses` and `clients_021.crypto.kms` the `aws` extra;
6
+ `clients_021.storage.s3`, `clients_021.email.ses`, `clients_021.crypto.kms`,
7
+ `clients_021.secrets.secrets_manager` and `clients_021.llm.bedrock` the `aws` extra;
6
8
  `clients_021.http.httpx2` the `httpx2` extra.
7
9
  `clients_021.storage.vercel_blob` needs the `vercel-blob` extra.
8
10
  """