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.
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/CHANGELOG.md +9 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/CONTRACT.md +60 -10
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/CONTRIBUTING.md +6 -1
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/PKG-INFO +85 -11
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/README.md +82 -8
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/pyproject.toml +5 -5
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/__init__.py +4 -2
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/_observe.py +90 -4
- 021_python_clients-0.4.0/src/clients_021/compute/__init__.py +1 -0
- 021_python_clients-0.4.0/src/clients_021/compute/kubernetes.py +314 -0
- 021_python_clients-0.4.0/src/clients_021/llm/__init__.py +1 -0
- 021_python_clients-0.4.0/src/clients_021/llm/bedrock.py +250 -0
- 021_python_clients-0.4.0/src/clients_021/secrets/__init__.py +1 -0
- 021_python_clients-0.4.0/src/clients_021/secrets/secrets_manager.py +127 -0
- 021_python_clients-0.4.0/tests/test_bedrock.py +290 -0
- 021_python_clients-0.4.0/tests/test_kubernetes.py +299 -0
- 021_python_clients-0.4.0/tests/test_secrets_manager.py +258 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/wheel_minimal.py +27 -2
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/wheel_smoke.py +7 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/.gitignore +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/LICENSE +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/_aws.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/cache/__init__.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/cache/redis.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/crypto/__init__.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/crypto/kms.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/email/__init__.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/email/_message.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/email/resend.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/email/ses.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/errors.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/http/__init__.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/http/_observe.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/http/httpx.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/http/httpx2.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/httpx2.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/kms.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/py.typed +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/queue/__init__.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/queue/qstash.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/queue/sqs.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/redis.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/s3.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/storage/__init__.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/storage/s3.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/src/clients_021/storage/vercel_blob.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/__init__.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/conftest.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/qstash_local.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_contract.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_email.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_http.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_http_stream_lifecycle.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_http_streaming.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_httpx2.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_kms.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_qstash.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_redis.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_s3.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_ses.py +0 -0
- {021_python_clients-0.3.0 → 021_python_clients-0.4.0}/tests/test_sqs.py +0 -0
- {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
|
|
19
|
-
`clients_021.crypto.kms
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
4
|
-
Summary: Observed clients for Redis, SQS, QStash, S3, Vercel Blob, HTTP, email and
|
|
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), `
|
|
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
|
|
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;
|
|
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
|
|
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
|
-
|
|
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), `
|
|
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
|
|
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;
|
|
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
|
|
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
|
-
|
|
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.
|
|
8
|
-
description = "Observed clients for Redis, SQS, QStash, S3, Vercel Blob, HTTP, email and
|
|
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
|
|
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
|
|
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
|
"""
|