021-python-clients 0.3.0__tar.gz → 0.5.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 (66) hide show
  1. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/CHANGELOG.md +26 -0
  2. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/CONTRACT.md +85 -11
  3. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/CONTRIBUTING.md +6 -1
  4. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/PKG-INFO +142 -12
  5. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/README.md +138 -8
  6. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/pyproject.toml +6 -6
  7. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/__init__.py +5 -2
  8. 021_python_clients-0.5.0/src/clients_021/_observe.py +193 -0
  9. 021_python_clients-0.5.0/src/clients_021/compute/__init__.py +1 -0
  10. 021_python_clients-0.5.0/src/clients_021/compute/kubernetes.py +438 -0
  11. 021_python_clients-0.5.0/src/clients_021/featureflags/__init__.py +42 -0
  12. 021_python_clients-0.5.0/src/clients_021/featureflags/appconfig.py +246 -0
  13. 021_python_clients-0.5.0/src/clients_021/llm/__init__.py +1 -0
  14. 021_python_clients-0.5.0/src/clients_021/llm/bedrock.py +250 -0
  15. 021_python_clients-0.5.0/src/clients_021/secrets/__init__.py +1 -0
  16. 021_python_clients-0.5.0/src/clients_021/secrets/secrets_manager.py +127 -0
  17. 021_python_clients-0.5.0/tests/test_appconfig_flags.py +222 -0
  18. 021_python_clients-0.5.0/tests/test_bedrock.py +290 -0
  19. 021_python_clients-0.5.0/tests/test_kubernetes.py +498 -0
  20. 021_python_clients-0.5.0/tests/test_secrets_manager.py +258 -0
  21. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/wheel_minimal.py +31 -2
  22. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/wheel_smoke.py +10 -0
  23. 021_python_clients-0.3.0/src/clients_021/_observe.py +0 -106
  24. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/.gitignore +0 -0
  25. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/LICENSE +0 -0
  26. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/_aws.py +0 -0
  27. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/cache/__init__.py +0 -0
  28. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/cache/redis.py +0 -0
  29. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/crypto/__init__.py +0 -0
  30. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/crypto/kms.py +0 -0
  31. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/email/__init__.py +0 -0
  32. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/email/_message.py +0 -0
  33. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/email/resend.py +0 -0
  34. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/email/ses.py +0 -0
  35. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/errors.py +0 -0
  36. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/http/__init__.py +0 -0
  37. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/http/_observe.py +0 -0
  38. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/http/httpx.py +0 -0
  39. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/http/httpx2.py +0 -0
  40. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/httpx2.py +0 -0
  41. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/kms.py +0 -0
  42. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/py.typed +0 -0
  43. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/queue/__init__.py +0 -0
  44. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/queue/qstash.py +0 -0
  45. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/queue/sqs.py +0 -0
  46. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/redis.py +0 -0
  47. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/s3.py +0 -0
  48. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/storage/__init__.py +0 -0
  49. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/storage/s3.py +0 -0
  50. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/src/clients_021/storage/vercel_blob.py +0 -0
  51. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/__init__.py +0 -0
  52. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/conftest.py +0 -0
  53. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/qstash_local.py +0 -0
  54. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_contract.py +0 -0
  55. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_email.py +0 -0
  56. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_http.py +0 -0
  57. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_http_stream_lifecycle.py +0 -0
  58. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_http_streaming.py +0 -0
  59. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_httpx2.py +0 -0
  60. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_kms.py +0 -0
  61. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_qstash.py +0 -0
  62. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_redis.py +0 -0
  63. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_s3.py +0 -0
  64. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_ses.py +0 -0
  65. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_sqs.py +0 -0
  66. {021_python_clients-0.3.0 → 021_python_clients-0.5.0}/tests/test_vercel_blob.py +0 -0
@@ -2,6 +2,32 @@
2
2
 
3
3
  All notable changes are recorded here by release-please from Conventional Commits.
4
4
 
5
+ ## [0.5.0](https://github.com/from021-io/python-clients/compare/v0.4.0...v0.5.0) (2026-10-29)
6
+
7
+
8
+ ### Features
9
+
10
+ * **compute:** create and delete Job-owned Secrets ([7f62939](https://github.com/from021-io/python-clients/commit/7f62939c08b5bc1672543e8819cb0781028320b7))
11
+ * **compute:** create and delete Job-owned Secrets ([9507539](https://github.com/from021-io/python-clients/commit/9507539a84882b29a6f6baa63e1a9df17c08add8))
12
+ * **featureflags:** add AppConfig feature flags client ([039c2b2](https://github.com/from021-io/python-clients/commit/039c2b2cc27f3cc3715a892032e369047bb108a3))
13
+ * **featureflags:** add AppConfig feature flags client ([2c40ddb](https://github.com/from021-io/python-clients/commit/2c40ddbb493dca39d483406b28ffa7cfa883faba))
14
+
15
+
16
+ ### Bug fixes
17
+
18
+ * **ci:** actualiza herramientas de instalación y publicación ([5c35e84](https://github.com/from021-io/python-clients/commit/5c35e844f679ce905335629ed7d3b4608b77c988))
19
+ * **ci:** actualiza herramientas de instalación y publicación ([234a84a](https://github.com/from021-io/python-clients/commit/234a84af950a9e0d2f9460e807d124411b367953))
20
+ * **deps:** support observability 0.4 ([#23](https://github.com/from021-io/python-clients/issues/23)) ([7363894](https://github.com/from021-io/python-clients/commit/7363894a8010abd345a60a54f29a55247e3307a3))
21
+
22
+ ## [0.4.0](https://github.com/from021-io/python-clients/compare/v0.3.0...v0.4.0) (2026-10-22)
23
+
24
+
25
+ ### Features
26
+
27
+ * **compute:** add Kubernetes Jobs client over the HTTP client ([9b3c369](https://github.com/from021-io/python-clients/commit/9b3c36930f6a070d3fee9844e86c0d27f871346b))
28
+ * **llm:** add observed Bedrock Runtime client for SDKs ([5216293](https://github.com/from021-io/python-clients/commit/52162939416f58c610f4f4e217d911f5e5efdea9))
29
+ * **secrets:** add observed Secrets Manager client ([f14042f](https://github.com/from021-io/python-clients/commit/f14042f064ca61e8d267b338b033a12fd41690eb))
30
+
5
31
  ## [0.3.0](https://github.com/from021-io/python-clients/compare/v0.2.0...v0.3.0) (2026-10-21)
6
32
 
7
33
 
@@ -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,15 @@ 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 |
52
+ | `config.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | Feature flags (AppConfig) |
53
+ | `config.client.operation.duration` | Histogram / `s` | same | Feature flags (AppConfig) |
43
54
  | `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
55
  | `http.client.request.duration` | Histogram / `s` | same | HTTP, httpx2, QStash transport, Resend (its HTTP request) |
45
56
 
@@ -49,8 +60,19 @@ version, environment). Durations use 021-python-observability's buckets.
49
60
  S3 and Vercel Blob: `put`, `get`, `head`, `delete`. QStash: `publish`.
50
61
  Email (Resend, SES): `send`. KMS:
51
62
  `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`,
63
+ (`generate_data_key`, `decrypt`), not the local AES work. Secrets Manager: `get`.
64
+ Feature flags: `start_session`, `get_configuration`; reading a flag from the snapshot
65
+ records nothing.
66
+ Bedrock: `converse`, `converse_stream`, `invoke_model`, `invoke_model_with_response_stream`,
67
+ `count_tokens`. A streaming operation ends when its event stream is exhausted, fails or is
68
+ closed, not when the call returns. Kubernetes Jobs: `create`, `get`, `delete`, `create_secret`,
69
+ `delete_secret`; each also records its HTTP request (`http.client.request.*`) under the same
70
+ `dependency`. A missing Job or Secret (`get`, `delete`, `delete_secret`) and an existing one
71
+ (`create`, `create_secret`) are `status=ok`.
72
+ - `token.type`: `input`, `output`, `cache_read`, `cache_write`, as reported by Bedrock in
73
+ the Converse response or the ConverseStream `metadata` event. InvokeModel bodies are
74
+ model-specific and are not read.
75
+ - Redis/SQS/S3/Vercel Blob/QStash/email/KMS/Secrets Manager/Bedrock/Kubernetes Jobs/feature flags `error.type`: `timeout`, `connection_error`, `throttled`,
54
76
  `status_<5xx>`, `cancelled`, or the exception class name for errors that are not outages
55
77
  (`EmailRejected` for every email rejection; botocore's class, such as `ClientError` or
56
78
  `NotFoundException`, for KMS).
@@ -70,7 +92,11 @@ version, environment). Durations use 021-python-observability's buckets.
70
92
  CLIENT spans named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
71
93
  metric labels plus `db.system.name=redis`, `messaging.system=aws_sqs`,
72
94
  `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`.
95
+ `rpc.system=aws-api` + `rpc.service=S3` / `rpc.service=KMS` / `rpc.service=AppConfigData` / `rpc.service=SecretsManager` /
96
+ `rpc.service=BedrockRuntime` (plus `gen_ai.system=aws.bedrock`), `rpc.system=vercel-blob`, or
97
+ `rpc.system=kubernetes` + `rpc.service=Job` (`rpc.service=Secret` for `create_secret` and
98
+ `delete_secret`). Kubernetes Jobs' `HTTP <dependency>` span is a child of its
99
+ `<dependency> <operation>` span.
74
100
  Resend's `HTTP <dependency>` span is a child of its `<dependency> send` span. Status ERROR on failure (and on HTTP
75
101
  status ≥ 400).
76
102
  Cancellation propagates unchanged and records ERROR with `error.type=cancelled`.
@@ -97,7 +123,7 @@ Local input validation occurs before either operation is recorded.
97
123
 
98
124
  ## Logging and coverage limits
99
125
 
100
- Redis, SQS, QStash, S3, Vercel Blob, Resend, SES and KMS emit a structured
126
+ Redis, SQS, QStash, S3, Vercel Blob, Resend, SES, KMS, Secrets Manager, Bedrock, Kubernetes Jobs and feature flags emit a structured
101
127
  `dependency.call_failed` warning when an observed remote operation raises an exception.
102
128
  Fields are `dependency`, `operation` and `error_type`; provider messages and exception
103
129
  chains are not logged. Successful calls and cancellation do not emit this warning;
@@ -112,7 +138,7 @@ attempts emit no log. Intentional partial close emits `dependency.stream_closed`
112
138
  with dependency and operation; it is not an outage.
113
139
 
114
140
  `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
141
+ QStash, Resend and Kubernetes Jobs set this option because their logical operation already emits a failure
116
142
  warning; they do not log the same failure at both levels. SDK retry loops can produce one
117
143
  warning per failed attempt. HTTP counters measure attempts, not logical API/SDK calls.
118
144
 
@@ -137,7 +163,7 @@ telemetry reaches an external backend.
137
163
  When enabled, both caller-supplied trace headers are replaced with the current context.
138
164
  - Resend: nothing is sent. The `traceparent`, `tracestate` and `x-request-id` headers that
139
165
  the HTTP transport adds are removed before the request leaves.
140
- - SES, KMS and Vercel Blob: nothing is sent by the default transports.
166
+ - SES, KMS, Secrets Manager, Bedrock, feature flags and Vercel Blob: nothing is sent by the default transports.
141
167
  - SQS: `traceparent` and `tracestate` are message attributes; `ReceivedMessage.trace_context()`
142
168
  returns the producer's context. Caller-supplied values for both trace attributes are
143
169
  replaced with the current context.
@@ -180,6 +206,14 @@ it. Resend's request is sent once; `idempotency_key` becomes its `Idempotency-Ke
180
206
  (1–256 characters, kept by Resend for 24 hours). SES has no idempotency key: the argument is
181
207
  validated and ignored.
182
208
 
209
+ Feature flags: throttling, 5xx, timeouts and connection failures are `DependencyUnavailable`,
210
+ raised only while no snapshot exists; once one does, a failed refresh serves it stale and
211
+ retries after `retry_seconds`. Access denied and missing application, environment or profile
212
+ keep their botocore type. A configuration that is not feature flag JSON (an object of
213
+ `{"enabled": bool, ...}` entries) raises `ValueError` on the first load and is ignored, with a
214
+ `featureflags.invalid_configuration` warning, afterwards. An expired session token
215
+ (`BadRequestException`) opens one new session.
216
+
183
217
  KMS: throttling, 5xx (`KMSInternalException`, `DependencyTimeoutException`,
184
218
  `KeyUnavailableException`), timeouts and connection failures are `DependencyUnavailable`.
185
219
  Access denied, disabled or missing keys, invalid key state, a wrong encryption context or a
@@ -188,6 +222,39 @@ tampered ciphertext (`InvalidCiphertextException`) and a ciphertext from another
188
222
  Every call requires a non-empty encryption context. `unseal` raises `ValueError` for a
189
223
  malformed envelope and `cryptography.exceptions.InvalidTag` when the payload was altered.
190
224
 
225
+ Secrets Manager: throttling, 5xx (`InternalServiceError`), timeouts and connection failures
226
+ are `DependencyUnavailable`. A missing secret (`ResourceNotFoundException`), access denied,
227
+ a value KMS cannot decrypt (`DecryptionFailure`), a secret scheduled for deletion
228
+ (`InvalidRequestException`) and invalid parameters keep their botocore type. `get` is a read:
229
+ retrying it is safe. A version with neither `SecretString` nor `SecretBinary` raises
230
+ `ValueError`.
231
+
232
+ Bedrock: throttling (`ThrottlingException`, and `throttlingException` inside an event stream),
233
+ 5xx, `ModelNotReadyException` (`throttled`), the stream errors `serviceUnavailableException`,
234
+ `internalServerException` and `modelStreamErrorException`, timeouts and connection failures
235
+ are `DependencyUnavailable`. Access denied, validation errors, a missing model or profile and
236
+ quota limits (`ServiceQuotaExceededException`) keep their botocore type. Leaving a stream
237
+ early or calling its `close()` ends the operation as `ok` and logs `dependency.stream_closed`
238
+ at INFO. A model call is not idempotent in cost: botocore's retries bill each attempt.
239
+
240
+ Kubernetes Jobs: 429 is `throttled`, 5xx `status_<5xx>`, transport timeouts and connection
241
+ failures `timeout` / `connection_error`, all `DependencyUnavailable`. A 409 whose reason is
242
+ `AlreadyExists` on `create` or `create_secret` and a 404 on `get`, `delete` or `delete_secret`
243
+ are results, not errors. Any other
244
+ non-success status raises `KubernetesApiError(status_code, reason)`, where `reason` is the
245
+ Status object's machine-readable reason (`Forbidden`, `Invalid`, `Conflict`, ...) or
246
+ `Unknown`; a 201 whose name does not match raises it with `InvalidResponse`. POST is sent
247
+ once; GET and DELETE retry 502/503/504 and network failures once. Local validation (DNS-label
248
+ names and namespace, `metadata.name` required, `generateName` refused, `kind` must be Job)
249
+ happens before any request. `JobCreated.uid` (on 201) and `JobStatus.uid` carry the Job's
250
+ `metadata.uid`, or `None` when absent or when `create` found an existing Job.
251
+ `create_secret(name, data, *, job, job_uid)` posts an `Opaque`, `immutable` Secret with base64
252
+ `data` and one owner reference (`batch/v1` Job, name and uid, no `blockOwnerDeletion`); an
253
+ existing Secret is kept unchanged (`SecretCreated(created=False)`). Its local validation
254
+ (DNS-label Secret and Job names, non-empty `data`, keys of 1-253 letters, digits, `-`, `_`
255
+ or `.`, `str` or `bytes` values, a uid of letters, digits and `-`) raises `ValueError`
256
+ before any request, with a message that names neither keys nor values.
257
+
191
258
  Vercel Blob (`clients_021.storage.vercel_blob`): HTTP 429 maps to `throttled`; 5xx to
192
259
  `status_<code>`; HTTP timeouts and total deadlines to `timeout`; network/remote protocol
193
260
  failures to `connection_error`. Known transport causes wrapped by the SDK are classified
@@ -209,7 +276,8 @@ A new data key is generated for every envelope.
209
276
 
210
277
  ## Lifecycle and retry limits
211
278
 
212
- HTTP, Redis, SQS, QStash, S3, Vercel Blob, Resend, SES and KMS expose `aclose()`. Close them after
279
+ HTTP, Redis, SQS, QStash, S3, Vercel Blob, Resend, SES, KMS, Secrets Manager, Bedrock, Kubernetes
280
+ Jobs and feature flags expose `aclose()` (Bedrock also `close()`). Close them after
213
281
  in-flight calls complete; the wrapper owns the underlying client, including one supplied
214
282
  through the constructor.
215
283
  AWS factories configure three total attempts; injected clients retain their own policy.
@@ -261,9 +329,15 @@ these clients, or each request would be counted twice.
261
329
 
262
330
  ## Never recorded
263
331
 
264
- Keys, URLs, query strings, bodies, message ids, receipts, object names, bucket or queue
332
+ Feature flags: application, environment and profile identifiers, flag names, attributes, values
333
+ and session tokens. Keys, URLs, query strings, bodies, message ids, receipts, object names, bucket or queue
265
334
  names, provider error messages. Email: sender, recipient and reply-to addresses, subjects,
266
335
  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.
336
+ ids, ARNs and aliases, encryption contexts, plaintext, ciphertext and data keys. Secrets
337
+ Manager: secret ids, names and ARNs, version ids, version stages and secret values. Bedrock:
338
+ model ids, inference profile ARNs, prompts, system prompts, outputs, tool arguments and
339
+ results, request ids and guardrail details. Kubernetes Jobs: namespaces, Job names, labels,
340
+ manifests, API server addresses, service account tokens and Status messages; Secret names,
341
+ data keys and values (plain or base64), owner Job names and uids.
268
342
 
269
343
  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.5.0
4
+ Summary: Observed clients for Redis, SQS, QStash, S3, Vercel Blob, HTTP, email, KMS, Secrets Manager, Bedrock and Kubernetes Jobs and AppConfig feature flags, 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: appconfig,bedrock,email,feature-flags,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
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.14
19
19
  Classifier: Topic :: System :: Monitoring
20
20
  Classifier: Typing :: Typed
21
21
  Requires-Python: >=3.12
22
- Requires-Dist: 021-python-observability<0.4,>=0.3
22
+ Requires-Dist: 021-python-observability<0.5,>=0.3
23
23
  Requires-Dist: httpx<1,>=0.28
24
24
  Requires-Dist: opentelemetry-api<2,>=1.44
25
25
  Provides-Extra: aws
@@ -58,6 +58,11 @@ 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, and the Secrets they own; in-cluster service account |
63
+ | `clients_021.featureflags` | none | `Flag` and the `FeatureFlags` protocol services type against |
64
+ | `clients_021.featureflags.appconfig` | `aws` | `AppConfigFlags`: AWS AppConfig feature flags, cached and refreshed on an interval |
65
+ | `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
66
 
62
67
  Every client takes the service's `Observability` (from `setup_observability`) and a
63
68
  `dependency` name, which becomes the `dependency` label of its metrics.
@@ -66,7 +71,7 @@ Every client takes the service's `Observability` (from `setup_observability`) an
66
71
 
67
72
  Providers with the same purpose live under one capability package: `email/` contains
68
73
  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
74
+ contains Redis; `crypto/` contains KMS; `secrets/` contains Secrets Manager; `llm/` contains Bedrock; `compute/` contains Kubernetes Jobs; `featureflags/` contains AppConfig. `http/` contains the httpx client and httpx2
70
75
  transport. Import providers explicitly:
71
76
 
72
77
  ```python
@@ -77,6 +82,9 @@ from clients_021.queue.qstash import QStashClient
77
82
  from clients_021.storage.s3 import S3Client
78
83
  from clients_021.cache.redis import RedisClient
79
84
  from clients_021.crypto.kms import KmsClient
85
+ from clients_021.secrets.secrets_manager import SecretsManagerClient
86
+ from clients_021.llm.bedrock import BedrockRuntime
87
+ from clients_021.compute.kubernetes import KubernetesJobsClient
80
88
  ```
81
89
 
82
90
  SQS supports polling, receipts and visibility changes; QStash delivers to an HTTP endpoint.
@@ -95,12 +103,16 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
95
103
 
96
104
  - A CLIENT span named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
97
105
  system attribute (`db.system.name=redis`, `messaging.system=aws_sqs|resend|aws_ses`,
98
- `rpc.service=S3|KMS`, `rpc.system=vercel-blob`).
106
+ `rpc.service=S3|KMS|SecretsManager|BedrockRuntime|AppConfigData`, `rpc.system=vercel-blob`, `rpc.system=kubernetes`;
107
+ Bedrock also `gen_ai.system=aws.bedrock`).
99
108
  - A counter and a duration histogram: `db.client.operation.*` (Redis, shared with SQL),
100
109
  `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
110
+ `crypto.client.operation.*` (KMS), `secrets.client.operation.*` (Secrets Manager),
111
+ `llm.client.operation.*` and `llm.client.token.usage` (Bedrock), `compute.client.operation.*`
112
+ (Kubernetes Jobs), `config.client.operation.*` (feature flags),
113
+ `http.client.request.*` (HTTP, and the HTTP request
102
114
  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,
115
+ - Redis, SQS, QStash, S3, Vercel Blob, email, KMS, Secrets Manager, Bedrock and Kubernetes Jobs emit a `dependency.call_failed` warning on failure,
104
116
  with dependency, operation and error type only. HTTP/httpx2 also log failed attempts,
105
117
  including body failures and cancellation; QStash/Resend suppress the nested HTTP log.
106
118
  - `DependencyUnavailable(dependency, operation, reason)` when the service timed out,
@@ -109,7 +121,9 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
109
121
  original type: they are bugs, not outages.
110
122
 
111
123
  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,
124
+ names; email addresses, subjects, bodies or tags; secret ids, version ids, stages and
125
+ values; model ids, prompts and outputs; namespaces, Job names, manifests and service
126
+ account tokens; feature flag names, values and AppConfig identifiers; KMS key ids, encryption contexts,
113
127
  plaintext, ciphertext or data keys. Use static dependency names such as `identity-provider` or `uploads`; never derive
114
128
  them from a tenant, user or request. All clients apply observability's identifier rule
115
129
  (1–96 letters, digits, `_`, `.` or `-`, starting with a letter or digit).
@@ -272,6 +286,118 @@ except EmailRejected as rejected: # the address, the domain or the credentials;
272
286
  SES has no idempotency key (the argument is ignored): deduplicate in the caller when a retry
273
287
  must not send twice. Tags are for provider analytics; never put personal data in them.
274
288
 
289
+ ### Kubernetes Jobs
290
+
291
+ ```python
292
+ from clients_021.compute.kubernetes import KubernetesJobsClient
293
+
294
+ jobs = KubernetesJobsClient.in_cluster(obs, namespace="agent-runs")
295
+ created = await jobs.create({"metadata": {"name": "cmd-4821"}, "spec": job_spec})
296
+ status = await jobs.get("cmd-4821") # JobStatus(phase="running", ...) or None
297
+ await jobs.delete("cmd-4821", propagation="Background")
298
+ ```
299
+
300
+ No SDK: it calls the `batch/v1` REST API through `HttpClient`. `in_cluster` uses the pod's
301
+ service account (CA bundle, namespace, and a token it re-reads at most once a minute because
302
+ projected tokens rotate). `create` is idempotent by name: `generateName` is refused, and an
303
+ existing Job returns `created=False`, so a caller can repeat it after a timeout. `get`
304
+ returns `None` and `delete` returns `False` for a missing Job. POST is sent once; GET and
305
+ DELETE retry 502, 503, 504 and network failures once. 429, 5xx, timeouts and connection
306
+ failures raise `DependencyUnavailable`; other rejections raise `KubernetesApiError` with the
307
+ status code and the API's reason, never its message.
308
+
309
+ A value the pod needs but that must not be readable in the Job's spec (a run token, say) goes
310
+ in a Secret the Job owns:
311
+
312
+ ```python
313
+ created = await jobs.create(manifest) # the container reads env from secretKeyRef
314
+ uid = created.uid or (await jobs.get(name)).uid # None in created when the Job existed
315
+ await jobs.create_secret(f"{name}-token", {"token": run_token}, job=name, job_uid=uid)
316
+ ```
317
+
318
+ ```yaml
319
+ env:
320
+ - name: RUN_TOKEN
321
+ valueFrom: { secretKeyRef: { name: cmd-4821-token, key: token } }
322
+ ```
323
+
324
+ The Secret is `Opaque` and `immutable`, with an owner reference to the Job's name and uid, so
325
+ the garbage collector deletes it with the Job (including through `ttlSecondsAfterFinished`).
326
+ The Job is created first because the reference needs its uid; the kubelet keeps retrying a
327
+ pod whose Secret does not exist yet. `create_secret` is idempotent by name like `create`: a
328
+ 409 `AlreadyExists` returns `created=False` and keeps the existing Secret without comparing or
329
+ updating it, so a retry must reuse the same value (or `delete_secret` and create again).
330
+ `delete_secret` returns `False` when there was none. `blockOwnerDeletion` is not set, so the
331
+ caller needs only `create` and `delete` on `secrets` (no `get`, and nothing on
332
+ `jobs/finalizers`). Secret names, data keys and values never reach telemetry, logs or error
333
+ messages; the request body is still in the chained httpx exception's `request`, so do not log
334
+ `__cause__` details.
335
+
336
+ ### Bedrock
337
+
338
+ ```python
339
+ from clients_021.llm.bedrock import BedrockRuntime
340
+ from langchain_aws import ChatBedrockConverse
341
+
342
+ runtime = BedrockRuntime.create(obs, region="us-east-1")
343
+ model = ChatBedrockConverse(model=settings.inference_profile, client=runtime)
344
+ ```
345
+
346
+ `BedrockRuntime` keeps boto3's call signatures for `converse`, `converse_stream`,
347
+ `invoke_model`, `invoke_model_with_response_stream` and `count_tokens`, and passes every
348
+ other attribute (`meta`, `exceptions`) through to the boto3 client, so an SDK can take it
349
+ where it expects one. Calls stay synchronous like boto3's. A streaming call is measured
350
+ until its event stream ends; leaving the loop early or calling `close()` ends it as `ok`.
351
+ Converse and ConverseStream add the reported input, output and cache tokens to
352
+ `llm.client.token.usage`. Throttling (also mid-stream), 5xx, a model still loading and the
353
+ stream errors Bedrock documents as retryable raise `DependencyUnavailable`; access denied,
354
+ validation errors, a missing model and quota limits keep their botocore type. A retried
355
+ model call is billed again.
356
+
357
+ ### Secrets Manager
358
+
359
+ ```python
360
+ from clients_021.secrets.secrets_manager import SecretsManagerClient
361
+
362
+ secrets = SecretsManagerClient.create(obs, region="us-east-1")
363
+ secret = await secrets.get("connections/brain")
364
+ token = secret.text() # SecretString; secret.binary() for SecretBinary
365
+ previous = await secrets.get("connections/brain", version_stage="AWSPREVIOUS")
366
+ ```
367
+
368
+ `get` reads the current version unless a `version_id` or a `version_stage` is given (not
369
+ both). The client does not cache: every call is a billed request, so keep a short-lived
370
+ copy when the same secret is read often. A missing secret, access denied, a value KMS cannot
371
+ decrypt or a secret scheduled for deletion keep their botocore type; throttling, 5xx and
372
+ timeouts raise `DependencyUnavailable`. `repr(secret)` never shows the value.
373
+
374
+ ### Feature flags (AWS AppConfig)
375
+
376
+ Reads [AppConfig feature flags](https://aws.amazon.com/blogs/mt/using-aws-appconfig-feature-flags/)
377
+ through the AppConfig Data API and keeps a snapshot in memory; evaluating a flag is a lookup:
378
+
379
+ ```python
380
+ from clients_021.featureflags import FeatureFlags # the protocol, no extra needed
381
+ from clients_021.featureflags.appconfig import AppConfigFlags
382
+
383
+ flags: FeatureFlags = AppConfigFlags.create(
384
+ obs, settings.appconfig_app, settings.appconfig_env, settings.appconfig_profile,
385
+ region="us-east-1",
386
+ )
387
+ if await flags.is_enabled("new-checkout"): # unknown flag -> default=False
388
+ ...
389
+ limit = await flags.flag("rate-limit") # Flag(enabled=True, attributes={"rps": 50})
390
+ ```
391
+
392
+ The snapshot is refreshed by the first request after `refresh_seconds` (default 45, minimum
393
+ 15), one poll at a time. If a refresh fails while a snapshot exists, the stale one keeps
394
+ serving and the next attempt is 15 seconds later; with no snapshot yet the outage raises
395
+ `DependencyUnavailable`. A configuration that is not valid flag JSON raises `ValueError` on
396
+ the first load and is ignored with a warning afterwards. Supports plain flags and their
397
+ attributes; multi-variant flags and rules need the AppConfig Agent, which evaluates them.
398
+ The IAM role needs `appconfig:StartConfigurationSession` and
399
+ `appconfig:GetLatestConfiguration` on the configuration profile.
400
+
275
401
  ### KMS
276
402
 
277
403
  The encryption context is required on every call, so a ciphertext only decrypts for the
@@ -375,6 +501,8 @@ async with AsyncExitStack() as resources:
375
501
  resources.push_async_callback(email.aclose)
376
502
  keys = KmsClient.create(obs, settings.session_key_arn, region="us-east-1")
377
503
  resources.push_async_callback(keys.aclose)
504
+ secrets = SecretsManagerClient.create(obs, region="us-east-1")
505
+ resources.push_async_callback(secrets.aclose)
378
506
  # Run the service here. Stop its background workers before leaving this scope.
379
507
  ```
380
508
 
@@ -386,7 +514,7 @@ Retry policies are explicit:
386
514
  POST and PATCH run once. Invalid URLs/protocols and invalid request construction keep
387
515
  their original exception. HTTP status responses are returned for the caller to handle.
388
516
  - 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
517
+ - AWS factories (SQS, S3, SES, KMS, Secrets Manager, Bedrock, feature flags) use botocore standard retries with at most **three total
390
518
  attempts**.
391
519
  Injected SDK clients keep their configured policy. SQS consumers must tolerate duplicate
392
520
  messages; a timeout does not prove that a send or write was not applied.
@@ -409,7 +537,7 @@ any operation**. Map it at the service boundary and apply the operation's idempo
409
537
  Its message and structured fields are safe to log when dependency names are static. Its
410
538
  chained SDK exception may contain sensitive provider data; do not log exception chains.
411
539
 
412
- S3, SQS, SES and KMS run the synchronous AWS SDK in worker threads. Cancelling the await does not
540
+ S3, SQS, SES, KMS, Secrets Manager and feature flags run the synchronous AWS SDK in worker threads. Cancelling the await does not
413
541
  stop an SDK request already running in its thread. Timeouts are per SDK/network attempt,
414
542
  not an overall deadline. Limit consumer concurrency and let in-flight work finish before
415
543
  closing the clients. Redis window counters require Redis server 7 or newer (`EXPIRE NX`).
@@ -433,6 +561,8 @@ deduplication, start the [official local server](https://upstash.com/docs/qstash
433
561
  set its `QSTASH_URL` and `QSTASH_TOKEN`, and run `uv run python tests/qstash_local.py`.
434
562
  That smoke test accepts only a loopback QStash server and binds its destination dynamically.
435
563
 
436
- SQS, S3, SES and KMS tests run against [moto](https://github.com/getmoto/moto) (plus
564
+ Kubernetes Jobs tests use httpx's `MockTransport`. Bedrock tests use botocore's Stubber and
565
+ fake event streams. SQS, S3, SES, KMS and Secrets
566
+ Manager tests run against [moto](https://github.com/getmoto/moto) (plus
437
567
  botocore's Stubber where moto ignores a field or differs from AWS); Resend tests use httpx's
438
568
  `MockTransport`; Redis tests need a real Redis. Licensed under Apache-2.0.