021-python-clients 0.4.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.
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/CHANGELOG.md +17 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/CONTRACT.md +39 -15
- 021_python_clients-0.4.0/README.md → 021_python_clients-0.5.0/PKG-INFO +99 -7
- 021_python_clients-0.4.0/PKG-INFO → 021_python_clients-0.5.0/README.md +63 -43
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/pyproject.toml +5 -5
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/__init__.py +2 -1
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/_observe.py +7 -6
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/compute/kubernetes.py +131 -7
- 021_python_clients-0.5.0/src/clients_021/featureflags/__init__.py +42 -0
- 021_python_clients-0.5.0/src/clients_021/featureflags/appconfig.py +246 -0
- 021_python_clients-0.5.0/tests/test_appconfig_flags.py +222 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_kubernetes.py +199 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/wheel_minimal.py +6 -2
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/wheel_smoke.py +3 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/.gitignore +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/CONTRIBUTING.md +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/LICENSE +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/_aws.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/cache/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/cache/redis.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/compute/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/crypto/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/crypto/kms.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/email/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/email/_message.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/email/resend.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/email/ses.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/errors.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/http/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/http/_observe.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/http/httpx.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/http/httpx2.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/httpx2.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/kms.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/llm/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/llm/bedrock.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/py.typed +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/queue/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/queue/qstash.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/queue/sqs.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/redis.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/s3.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/secrets/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/secrets/secrets_manager.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/storage/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/storage/s3.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/src/clients_021/storage/vercel_blob.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/__init__.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/conftest.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/qstash_local.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_bedrock.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_contract.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_email.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_http.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_http_stream_lifecycle.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_http_streaming.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_httpx2.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_kms.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_qstash.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_redis.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_s3.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_secrets_manager.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_ses.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_sqs.py +0 -0
- {021_python_clients-0.4.0 → 021_python_clients-0.5.0}/tests/test_vercel_blob.py +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
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
|
+
|
|
5
22
|
## [0.4.0](https://github.com/from021-io/python-clients/compare/v0.3.0...v0.4.0) (2026-10-22)
|
|
6
23
|
|
|
7
24
|
|
|
@@ -49,6 +49,8 @@ version, environment). Durations use 021-python-observability's buckets.
|
|
|
49
49
|
| `llm.client.token.usage` | Counter / `{token}` | dependency, operation, token.type | Bedrock (Converse and ConverseStream) |
|
|
50
50
|
| `compute.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | Kubernetes Jobs |
|
|
51
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) |
|
|
52
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) |
|
|
53
55
|
| `http.client.request.duration` | Histogram / `s` | same | HTTP, httpx2, QStash transport, Resend (its HTTP request) |
|
|
54
56
|
|
|
@@ -59,15 +61,18 @@ version, environment). Durations use 021-python-observability's buckets.
|
|
|
59
61
|
Email (Resend, SES): `send`. KMS:
|
|
60
62
|
`encrypt`, `decrypt`, `generate_data_key`; `seal` and `unseal` record the KMS call they make
|
|
61
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.
|
|
62
66
|
Bedrock: `converse`, `converse_stream`, `invoke_model`, `invoke_model_with_response_stream`,
|
|
63
67
|
`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
|
|
65
|
-
records its HTTP request (`http.client.request.*`) under the same
|
|
66
|
-
Job (`get`, `delete`) and an existing one
|
|
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`.
|
|
67
72
|
- `token.type`: `input`, `output`, `cache_read`, `cache_write`, as reported by Bedrock in
|
|
68
73
|
the Converse response or the ConverseStream `metadata` event. InvokeModel bodies are
|
|
69
74
|
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`,
|
|
75
|
+
- Redis/SQS/S3/Vercel Blob/QStash/email/KMS/Secrets Manager/Bedrock/Kubernetes Jobs/feature flags `error.type`: `timeout`, `connection_error`, `throttled`,
|
|
71
76
|
`status_<5xx>`, `cancelled`, or the exception class name for errors that are not outages
|
|
72
77
|
(`EmailRejected` for every email rejection; botocore's class, such as `ClientError` or
|
|
73
78
|
`NotFoundException`, for KMS).
|
|
@@ -87,10 +92,11 @@ version, environment). Durations use 021-python-observability's buckets.
|
|
|
87
92
|
CLIENT spans named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
|
|
88
93
|
metric labels plus `db.system.name=redis`, `messaging.system=aws_sqs`,
|
|
89
94
|
`messaging.system=upstash_qstash`, `messaging.system=resend`, `messaging.system=aws_ses`, or
|
|
90
|
-
`rpc.system=aws-api` + `rpc.service=S3` / `rpc.service=KMS` / `rpc.service=SecretsManager` /
|
|
95
|
+
`rpc.system=aws-api` + `rpc.service=S3` / `rpc.service=KMS` / `rpc.service=AppConfigData` / `rpc.service=SecretsManager` /
|
|
91
96
|
`rpc.service=BedrockRuntime` (plus `gen_ai.system=aws.bedrock`), `rpc.system=vercel-blob`, or
|
|
92
|
-
`rpc.system=kubernetes` + `rpc.service=Job
|
|
93
|
-
|
|
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.
|
|
94
100
|
Resend's `HTTP <dependency>` span is a child of its `<dependency> send` span. Status ERROR on failure (and on HTTP
|
|
95
101
|
status ≥ 400).
|
|
96
102
|
Cancellation propagates unchanged and records ERROR with `error.type=cancelled`.
|
|
@@ -117,7 +123,7 @@ Local input validation occurs before either operation is recorded.
|
|
|
117
123
|
|
|
118
124
|
## Logging and coverage limits
|
|
119
125
|
|
|
120
|
-
Redis, SQS, QStash, S3, Vercel Blob, Resend, SES, KMS, Secrets Manager, Bedrock
|
|
126
|
+
Redis, SQS, QStash, S3, Vercel Blob, Resend, SES, KMS, Secrets Manager, Bedrock, Kubernetes Jobs and feature flags emit a structured
|
|
121
127
|
`dependency.call_failed` warning when an observed remote operation raises an exception.
|
|
122
128
|
Fields are `dependency`, `operation` and `error_type`; provider messages and exception
|
|
123
129
|
chains are not logged. Successful calls and cancellation do not emit this warning;
|
|
@@ -157,7 +163,7 @@ telemetry reaches an external backend.
|
|
|
157
163
|
When enabled, both caller-supplied trace headers are replaced with the current context.
|
|
158
164
|
- Resend: nothing is sent. The `traceparent`, `tracestate` and `x-request-id` headers that
|
|
159
165
|
the HTTP transport adds are removed before the request leaves.
|
|
160
|
-
- SES, KMS, Secrets Manager, Bedrock 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.
|
|
161
167
|
- SQS: `traceparent` and `tracestate` are message attributes; `ReceivedMessage.trace_context()`
|
|
162
168
|
returns the producer's context. Caller-supplied values for both trace attributes are
|
|
163
169
|
replaced with the current context.
|
|
@@ -200,6 +206,14 @@ it. Resend's request is sent once; `idempotency_key` becomes its `Idempotency-Ke
|
|
|
200
206
|
(1–256 characters, kept by Resend for 24 hours). SES has no idempotency key: the argument is
|
|
201
207
|
validated and ignored.
|
|
202
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
|
+
|
|
203
217
|
KMS: throttling, 5xx (`KMSInternalException`, `DependencyTimeoutException`,
|
|
204
218
|
`KeyUnavailableException`), timeouts and connection failures are `DependencyUnavailable`.
|
|
205
219
|
Access denied, disabled or missing keys, invalid key state, a wrong encryption context or a
|
|
@@ -225,13 +239,21 @@ at INFO. A model call is not idempotent in cost: botocore's retries bill each at
|
|
|
225
239
|
|
|
226
240
|
Kubernetes Jobs: 429 is `throttled`, 5xx `status_<5xx>`, transport timeouts and connection
|
|
227
241
|
failures `timeout` / `connection_error`, all `DependencyUnavailable`. A 409 whose reason is
|
|
228
|
-
`AlreadyExists` on `create` and a 404 on `get` or `
|
|
242
|
+
`AlreadyExists` on `create` or `create_secret` and a 404 on `get`, `delete` or `delete_secret`
|
|
243
|
+
are results, not errors. Any other
|
|
229
244
|
non-success status raises `KubernetesApiError(status_code, reason)`, where `reason` is the
|
|
230
245
|
Status object's machine-readable reason (`Forbidden`, `Invalid`, `Conflict`, ...) or
|
|
231
246
|
`Unknown`; a 201 whose name does not match raises it with `InvalidResponse`. POST is sent
|
|
232
247
|
once; GET and DELETE retry 502/503/504 and network failures once. Local validation (DNS-label
|
|
233
248
|
names and namespace, `metadata.name` required, `generateName` refused, `kind` must be Job)
|
|
234
|
-
happens before any request.
|
|
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.
|
|
235
257
|
|
|
236
258
|
Vercel Blob (`clients_021.storage.vercel_blob`): HTTP 429 maps to `throttled`; 5xx to
|
|
237
259
|
`status_<code>`; HTTP timeouts and total deadlines to `timeout`; network/remote protocol
|
|
@@ -254,8 +276,8 @@ A new data key is generated for every envelope.
|
|
|
254
276
|
|
|
255
277
|
## Lifecycle and retry limits
|
|
256
278
|
|
|
257
|
-
HTTP, Redis, SQS, QStash, S3, Vercel Blob, Resend, SES, KMS, Secrets Manager, Bedrock
|
|
258
|
-
Jobs expose `aclose()` (Bedrock also `close()`). 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
|
|
259
281
|
in-flight calls complete; the wrapper owns the underlying client, including one supplied
|
|
260
282
|
through the constructor.
|
|
261
283
|
AWS factories configure three total attempts; injected clients retain their own policy.
|
|
@@ -307,13 +329,15 @@ these clients, or each request would be counted twice.
|
|
|
307
329
|
|
|
308
330
|
## Never recorded
|
|
309
331
|
|
|
310
|
-
|
|
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
|
|
311
334
|
names, provider error messages. Email: sender, recipient and reply-to addresses, subjects,
|
|
312
335
|
text and HTML bodies, tags, idempotency keys, API keys, configuration set names. KMS: key
|
|
313
336
|
ids, ARNs and aliases, encryption contexts, plaintext, ciphertext and data keys. Secrets
|
|
314
337
|
Manager: secret ids, names and ARNs, version ids, version stages and secret values. Bedrock:
|
|
315
338
|
model ids, inference profile ARNs, prompts, system prompts, outputs, tool arguments and
|
|
316
339
|
results, request ids and guardrail details. Kubernetes Jobs: namespaces, Job names, labels,
|
|
317
|
-
manifests, API server addresses, service account tokens and Status messages
|
|
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.
|
|
318
342
|
|
|
319
343
|
Vercel Blob: read-write tokens, store IDs, pathnames, delivery URLs, content and ETags.
|
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: 021-python-clients
|
|
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
|
+
Project-URL: Repository, https://github.com/from021-io/python-clients
|
|
6
|
+
Project-URL: Changelog, https://github.com/from021-io/python-clients/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Contract, https://github.com/from021-io/python-clients/blob/main/CONTRACT.md
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: appconfig,bedrock,email,feature-flags,httpx,kms,kubernetes,observability,opentelemetry,qstash,redis,resend,s3,secrets-manager,ses,sqs
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: System :: Monitoring
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Requires-Dist: 021-python-observability<0.5,>=0.3
|
|
23
|
+
Requires-Dist: httpx<1,>=0.28
|
|
24
|
+
Requires-Dist: opentelemetry-api<2,>=1.44
|
|
25
|
+
Provides-Extra: aws
|
|
26
|
+
Requires-Dist: boto3<2,>=1.40; extra == 'aws'
|
|
27
|
+
Requires-Dist: cryptography>=44; extra == 'aws'
|
|
28
|
+
Provides-Extra: httpx2
|
|
29
|
+
Requires-Dist: httpx2<3,>=2; extra == 'httpx2'
|
|
30
|
+
Provides-Extra: redis
|
|
31
|
+
Requires-Dist: redis<9,>=6; extra == 'redis'
|
|
32
|
+
Provides-Extra: vercel-blob
|
|
33
|
+
Requires-Dist: httpx2<3,>=2; extra == 'vercel-blob'
|
|
34
|
+
Requires-Dist: vercel-blob<0.6,>=0.5; extra == 'vercel-blob'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
1
37
|
# 021-python-clients
|
|
2
38
|
|
|
3
39
|
Clients for the external services our Python services call, with the observability of
|
|
@@ -23,7 +59,9 @@ uv add "021-python-clients[redis,aws,httpx2,vercel-blob]" # pick the extras yo
|
|
|
23
59
|
| `clients_021.email.ses` | `aws` | `SesSender`: the same protocol over Amazon SES v2 |
|
|
24
60
|
| `clients_021.crypto.kms` | `aws` | `KmsClient`: encrypt, decrypt and data keys bound to an encryption context; AES-256-GCM envelopes |
|
|
25
61
|
| `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 |
|
|
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 |
|
|
27
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 |
|
|
28
66
|
|
|
29
67
|
Every client takes the service's `Observability` (from `setup_observability`) and a
|
|
@@ -33,7 +71,7 @@ Every client takes the service's `Observability` (from `setup_observability`) an
|
|
|
33
71
|
|
|
34
72
|
Providers with the same purpose live under one capability package: `email/` contains
|
|
35
73
|
Resend and SES; `queue/` contains SQS and QStash; `storage/` contains S3 and Vercel Blob; `cache/`
|
|
36
|
-
contains Redis; `crypto/` contains KMS; `secrets/` contains Secrets Manager; `llm/` contains Bedrock; `compute/` contains Kubernetes Jobs. `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
|
|
37
75
|
transport. Import providers explicitly:
|
|
38
76
|
|
|
39
77
|
```python
|
|
@@ -65,13 +103,13 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
|
|
|
65
103
|
|
|
66
104
|
- A CLIENT span named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
|
|
67
105
|
system attribute (`db.system.name=redis`, `messaging.system=aws_sqs|resend|aws_ses`,
|
|
68
|
-
`rpc.service=S3|KMS|SecretsManager|BedrockRuntime`, `rpc.system=vercel-blob`, `rpc.system=kubernetes`;
|
|
106
|
+
`rpc.service=S3|KMS|SecretsManager|BedrockRuntime|AppConfigData`, `rpc.system=vercel-blob`, `rpc.system=kubernetes`;
|
|
69
107
|
Bedrock also `gen_ai.system=aws.bedrock`).
|
|
70
108
|
- A counter and a duration histogram: `db.client.operation.*` (Redis, shared with SQL),
|
|
71
109
|
`messaging.client.operation.*` (SQS, QStash, email), `storage.client.operation.*` (S3, Vercel Blob),
|
|
72
110
|
`crypto.client.operation.*` (KMS), `secrets.client.operation.*` (Secrets Manager),
|
|
73
111
|
`llm.client.operation.*` and `llm.client.token.usage` (Bedrock), `compute.client.operation.*`
|
|
74
|
-
(Kubernetes Jobs),
|
|
112
|
+
(Kubernetes Jobs), `config.client.operation.*` (feature flags),
|
|
75
113
|
`http.client.request.*` (HTTP, and the HTTP request
|
|
76
114
|
under QStash and Resend). Labels are bounded; see [CONTRACT.md](CONTRACT.md).
|
|
77
115
|
- Redis, SQS, QStash, S3, Vercel Blob, email, KMS, Secrets Manager, Bedrock and Kubernetes Jobs emit a `dependency.call_failed` warning on failure,
|
|
@@ -85,7 +123,7 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
|
|
|
85
123
|
Never in this library's spans, metrics or logs: keys, URLs, bodies, message ids or object
|
|
86
124
|
names; email addresses, subjects, bodies or tags; secret ids, version ids, stages and
|
|
87
125
|
values; model ids, prompts and outputs; namespaces, Job names, manifests and service
|
|
88
|
-
account tokens; KMS key ids, encryption contexts,
|
|
126
|
+
account tokens; feature flag names, values and AppConfig identifiers; KMS key ids, encryption contexts,
|
|
89
127
|
plaintext, ciphertext or data keys. Use static dependency names such as `identity-provider` or `uploads`; never derive
|
|
90
128
|
them from a tenant, user or request. All clients apply observability's identifier rule
|
|
91
129
|
(1–96 letters, digits, `_`, `.` or `-`, starting with a letter or digit).
|
|
@@ -268,6 +306,33 @@ DELETE retry 502, 503, 504 and network failures once. 429, 5xx, timeouts and con
|
|
|
268
306
|
failures raise `DependencyUnavailable`; other rejections raise `KubernetesApiError` with the
|
|
269
307
|
status code and the API's reason, never its message.
|
|
270
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
|
+
|
|
271
336
|
### Bedrock
|
|
272
337
|
|
|
273
338
|
```python
|
|
@@ -306,6 +371,33 @@ copy when the same secret is read often. A missing secret, access denied, a valu
|
|
|
306
371
|
decrypt or a secret scheduled for deletion keep their botocore type; throttling, 5xx and
|
|
307
372
|
timeouts raise `DependencyUnavailable`. `repr(secret)` never shows the value.
|
|
308
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
|
+
|
|
309
401
|
### KMS
|
|
310
402
|
|
|
311
403
|
The encryption context is required on every call, so a ciphertext only decrypts for the
|
|
@@ -422,7 +514,7 @@ Retry policies are explicit:
|
|
|
422
514
|
POST and PATCH run once. Invalid URLs/protocols and invalid request construction keep
|
|
423
515
|
their original exception. HTTP status responses are returned for the caller to handle.
|
|
424
516
|
- Resend sends each request once (POST); send an `idempotency_key` and retry in the caller.
|
|
425
|
-
- AWS factories (SQS, S3, SES, KMS, Secrets Manager, Bedrock) 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
|
|
426
518
|
attempts**.
|
|
427
519
|
Injected SDK clients keep their configured policy. SQS consumers must tolerate duplicate
|
|
428
520
|
messages; a timeout does not prove that a send or write was not applied.
|
|
@@ -445,7 +537,7 @@ any operation**. Map it at the service boundary and apply the operation's idempo
|
|
|
445
537
|
Its message and structured fields are safe to log when dependency names are static. Its
|
|
446
538
|
chained SDK exception may contain sensitive provider data; do not log exception chains.
|
|
447
539
|
|
|
448
|
-
S3, SQS, SES, KMS
|
|
540
|
+
S3, SQS, SES, KMS, Secrets Manager and feature flags run the synchronous AWS SDK in worker threads. Cancelling the await does not
|
|
449
541
|
stop an SDK request already running in its thread. Timeouts are per SDK/network attempt,
|
|
450
542
|
not an overall deadline. Limit consumer concurrency and let in-flight work finish before
|
|
451
543
|
closing the clients. Redis window counters require Redis server 7 or newer (`EXPIRE NX`).
|
|
@@ -1,39 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: 021-python-clients
|
|
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
|
-
Project-URL: Repository, https://github.com/from021-io/python-clients
|
|
6
|
-
Project-URL: Changelog, https://github.com/from021-io/python-clients/blob/main/CHANGELOG.md
|
|
7
|
-
Project-URL: Contract, https://github.com/from021-io/python-clients/blob/main/CONTRACT.md
|
|
8
|
-
License-Expression: Apache-2.0
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Keywords: bedrock,email,httpx,kms,kubernetes,observability,opentelemetry,qstash,redis,resend,s3,secrets-manager,ses,sqs
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
-
Classifier: Topic :: System :: Monitoring
|
|
20
|
-
Classifier: Typing :: Typed
|
|
21
|
-
Requires-Python: >=3.12
|
|
22
|
-
Requires-Dist: 021-python-observability<0.4,>=0.3
|
|
23
|
-
Requires-Dist: httpx<1,>=0.28
|
|
24
|
-
Requires-Dist: opentelemetry-api<2,>=1.44
|
|
25
|
-
Provides-Extra: aws
|
|
26
|
-
Requires-Dist: boto3<2,>=1.40; extra == 'aws'
|
|
27
|
-
Requires-Dist: cryptography>=44; extra == 'aws'
|
|
28
|
-
Provides-Extra: httpx2
|
|
29
|
-
Requires-Dist: httpx2<3,>=2; extra == 'httpx2'
|
|
30
|
-
Provides-Extra: redis
|
|
31
|
-
Requires-Dist: redis<9,>=6; extra == 'redis'
|
|
32
|
-
Provides-Extra: vercel-blob
|
|
33
|
-
Requires-Dist: httpx2<3,>=2; extra == 'vercel-blob'
|
|
34
|
-
Requires-Dist: vercel-blob<0.6,>=0.5; extra == 'vercel-blob'
|
|
35
|
-
Description-Content-Type: text/markdown
|
|
36
|
-
|
|
37
1
|
# 021-python-clients
|
|
38
2
|
|
|
39
3
|
Clients for the external services our Python services call, with the observability of
|
|
@@ -59,7 +23,9 @@ uv add "021-python-clients[redis,aws,httpx2,vercel-blob]" # pick the extras yo
|
|
|
59
23
|
| `clients_021.email.ses` | `aws` | `SesSender`: the same protocol over Amazon SES v2 |
|
|
60
24
|
| `clients_021.crypto.kms` | `aws` | `KmsClient`: encrypt, decrypt and data keys bound to an encryption context; AES-256-GCM envelopes |
|
|
61
25
|
| `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 |
|
|
26
|
+
| `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 |
|
|
27
|
+
| `clients_021.featureflags` | none | `Flag` and the `FeatureFlags` protocol services type against |
|
|
28
|
+
| `clients_021.featureflags.appconfig` | `aws` | `AppConfigFlags`: AWS AppConfig feature flags, cached and refreshed on an interval |
|
|
63
29
|
| `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 |
|
|
64
30
|
|
|
65
31
|
Every client takes the service's `Observability` (from `setup_observability`) and a
|
|
@@ -69,7 +35,7 @@ Every client takes the service's `Observability` (from `setup_observability`) an
|
|
|
69
35
|
|
|
70
36
|
Providers with the same purpose live under one capability package: `email/` contains
|
|
71
37
|
Resend and SES; `queue/` contains SQS and QStash; `storage/` contains S3 and Vercel Blob; `cache/`
|
|
72
|
-
contains Redis; `crypto/` contains KMS; `secrets/` contains Secrets Manager; `llm/` contains Bedrock; `compute/` contains Kubernetes Jobs. `http/` contains the httpx client and httpx2
|
|
38
|
+
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
|
|
73
39
|
transport. Import providers explicitly:
|
|
74
40
|
|
|
75
41
|
```python
|
|
@@ -101,13 +67,13 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
|
|
|
101
67
|
|
|
102
68
|
- A CLIENT span named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
|
|
103
69
|
system attribute (`db.system.name=redis`, `messaging.system=aws_sqs|resend|aws_ses`,
|
|
104
|
-
`rpc.service=S3|KMS|SecretsManager|BedrockRuntime`, `rpc.system=vercel-blob`, `rpc.system=kubernetes`;
|
|
70
|
+
`rpc.service=S3|KMS|SecretsManager|BedrockRuntime|AppConfigData`, `rpc.system=vercel-blob`, `rpc.system=kubernetes`;
|
|
105
71
|
Bedrock also `gen_ai.system=aws.bedrock`).
|
|
106
72
|
- A counter and a duration histogram: `db.client.operation.*` (Redis, shared with SQL),
|
|
107
73
|
`messaging.client.operation.*` (SQS, QStash, email), `storage.client.operation.*` (S3, Vercel Blob),
|
|
108
74
|
`crypto.client.operation.*` (KMS), `secrets.client.operation.*` (Secrets Manager),
|
|
109
75
|
`llm.client.operation.*` and `llm.client.token.usage` (Bedrock), `compute.client.operation.*`
|
|
110
|
-
(Kubernetes Jobs),
|
|
76
|
+
(Kubernetes Jobs), `config.client.operation.*` (feature flags),
|
|
111
77
|
`http.client.request.*` (HTTP, and the HTTP request
|
|
112
78
|
under QStash and Resend). Labels are bounded; see [CONTRACT.md](CONTRACT.md).
|
|
113
79
|
- Redis, SQS, QStash, S3, Vercel Blob, email, KMS, Secrets Manager, Bedrock and Kubernetes Jobs emit a `dependency.call_failed` warning on failure,
|
|
@@ -121,7 +87,7 @@ published exports; `clients_021.httpx2.ObservedTransport` remains an alias for
|
|
|
121
87
|
Never in this library's spans, metrics or logs: keys, URLs, bodies, message ids or object
|
|
122
88
|
names; email addresses, subjects, bodies or tags; secret ids, version ids, stages and
|
|
123
89
|
values; model ids, prompts and outputs; namespaces, Job names, manifests and service
|
|
124
|
-
account tokens; KMS key ids, encryption contexts,
|
|
90
|
+
account tokens; feature flag names, values and AppConfig identifiers; KMS key ids, encryption contexts,
|
|
125
91
|
plaintext, ciphertext or data keys. Use static dependency names such as `identity-provider` or `uploads`; never derive
|
|
126
92
|
them from a tenant, user or request. All clients apply observability's identifier rule
|
|
127
93
|
(1–96 letters, digits, `_`, `.` or `-`, starting with a letter or digit).
|
|
@@ -304,6 +270,33 @@ DELETE retry 502, 503, 504 and network failures once. 429, 5xx, timeouts and con
|
|
|
304
270
|
failures raise `DependencyUnavailable`; other rejections raise `KubernetesApiError` with the
|
|
305
271
|
status code and the API's reason, never its message.
|
|
306
272
|
|
|
273
|
+
A value the pod needs but that must not be readable in the Job's spec (a run token, say) goes
|
|
274
|
+
in a Secret the Job owns:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
created = await jobs.create(manifest) # the container reads env from secretKeyRef
|
|
278
|
+
uid = created.uid or (await jobs.get(name)).uid # None in created when the Job existed
|
|
279
|
+
await jobs.create_secret(f"{name}-token", {"token": run_token}, job=name, job_uid=uid)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
```yaml
|
|
283
|
+
env:
|
|
284
|
+
- name: RUN_TOKEN
|
|
285
|
+
valueFrom: { secretKeyRef: { name: cmd-4821-token, key: token } }
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The Secret is `Opaque` and `immutable`, with an owner reference to the Job's name and uid, so
|
|
289
|
+
the garbage collector deletes it with the Job (including through `ttlSecondsAfterFinished`).
|
|
290
|
+
The Job is created first because the reference needs its uid; the kubelet keeps retrying a
|
|
291
|
+
pod whose Secret does not exist yet. `create_secret` is idempotent by name like `create`: a
|
|
292
|
+
409 `AlreadyExists` returns `created=False` and keeps the existing Secret without comparing or
|
|
293
|
+
updating it, so a retry must reuse the same value (or `delete_secret` and create again).
|
|
294
|
+
`delete_secret` returns `False` when there was none. `blockOwnerDeletion` is not set, so the
|
|
295
|
+
caller needs only `create` and `delete` on `secrets` (no `get`, and nothing on
|
|
296
|
+
`jobs/finalizers`). Secret names, data keys and values never reach telemetry, logs or error
|
|
297
|
+
messages; the request body is still in the chained httpx exception's `request`, so do not log
|
|
298
|
+
`__cause__` details.
|
|
299
|
+
|
|
307
300
|
### Bedrock
|
|
308
301
|
|
|
309
302
|
```python
|
|
@@ -342,6 +335,33 @@ copy when the same secret is read often. A missing secret, access denied, a valu
|
|
|
342
335
|
decrypt or a secret scheduled for deletion keep their botocore type; throttling, 5xx and
|
|
343
336
|
timeouts raise `DependencyUnavailable`. `repr(secret)` never shows the value.
|
|
344
337
|
|
|
338
|
+
### Feature flags (AWS AppConfig)
|
|
339
|
+
|
|
340
|
+
Reads [AppConfig feature flags](https://aws.amazon.com/blogs/mt/using-aws-appconfig-feature-flags/)
|
|
341
|
+
through the AppConfig Data API and keeps a snapshot in memory; evaluating a flag is a lookup:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from clients_021.featureflags import FeatureFlags # the protocol, no extra needed
|
|
345
|
+
from clients_021.featureflags.appconfig import AppConfigFlags
|
|
346
|
+
|
|
347
|
+
flags: FeatureFlags = AppConfigFlags.create(
|
|
348
|
+
obs, settings.appconfig_app, settings.appconfig_env, settings.appconfig_profile,
|
|
349
|
+
region="us-east-1",
|
|
350
|
+
)
|
|
351
|
+
if await flags.is_enabled("new-checkout"): # unknown flag -> default=False
|
|
352
|
+
...
|
|
353
|
+
limit = await flags.flag("rate-limit") # Flag(enabled=True, attributes={"rps": 50})
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
The snapshot is refreshed by the first request after `refresh_seconds` (default 45, minimum
|
|
357
|
+
15), one poll at a time. If a refresh fails while a snapshot exists, the stale one keeps
|
|
358
|
+
serving and the next attempt is 15 seconds later; with no snapshot yet the outage raises
|
|
359
|
+
`DependencyUnavailable`. A configuration that is not valid flag JSON raises `ValueError` on
|
|
360
|
+
the first load and is ignored with a warning afterwards. Supports plain flags and their
|
|
361
|
+
attributes; multi-variant flags and rules need the AppConfig Agent, which evaluates them.
|
|
362
|
+
The IAM role needs `appconfig:StartConfigurationSession` and
|
|
363
|
+
`appconfig:GetLatestConfiguration` on the configuration profile.
|
|
364
|
+
|
|
345
365
|
### KMS
|
|
346
366
|
|
|
347
367
|
The encryption context is required on every call, so a ciphertext only decrypts for the
|
|
@@ -458,7 +478,7 @@ Retry policies are explicit:
|
|
|
458
478
|
POST and PATCH run once. Invalid URLs/protocols and invalid request construction keep
|
|
459
479
|
their original exception. HTTP status responses are returned for the caller to handle.
|
|
460
480
|
- Resend sends each request once (POST); send an `idempotency_key` and retry in the caller.
|
|
461
|
-
- AWS factories (SQS, S3, SES, KMS, Secrets Manager, Bedrock) use botocore standard retries with at most **three total
|
|
481
|
+
- AWS factories (SQS, S3, SES, KMS, Secrets Manager, Bedrock, feature flags) use botocore standard retries with at most **three total
|
|
462
482
|
attempts**.
|
|
463
483
|
Injected SDK clients keep their configured policy. SQS consumers must tolerate duplicate
|
|
464
484
|
messages; a timeout does not prove that a send or write was not applied.
|
|
@@ -481,7 +501,7 @@ any operation**. Map it at the service boundary and apply the operation's idempo
|
|
|
481
501
|
Its message and structured fields are safe to log when dependency names are static. Its
|
|
482
502
|
chained SDK exception may contain sensitive provider data; do not log exception chains.
|
|
483
503
|
|
|
484
|
-
S3, SQS, SES, KMS
|
|
504
|
+
S3, SQS, SES, KMS, Secrets Manager and feature flags run the synchronous AWS SDK in worker threads. Cancelling the await does not
|
|
485
505
|
stop an SDK request already running in its thread. Timeouts are per SDK/network attempt,
|
|
486
506
|
not an overall deadline. Limit consumer concurrency and let in-flight work finish before
|
|
487
507
|
closing the clients. Redis window counters require Redis server 7 or newer (`EXPIRE NX`).
|
|
@@ -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, KMS, Secrets Manager, Bedrock and Kubernetes Jobs, built on 021-python-observability"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
description = "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"
|
|
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", "secrets-manager", "bedrock", "kubernetes", "opentelemetry", "observability"]
|
|
13
|
+
keywords = ["redis", "sqs", "qstash", "s3", "httpx", "email", "ses", "resend", "kms", "secrets-manager", "bedrock", "kubernetes", "appconfig", "feature-flags", "opentelemetry", "observability"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
@@ -24,7 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Typing :: Typed",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
-
"021-python-observability>=0.3,<0.
|
|
27
|
+
"021-python-observability>=0.3,<0.5",
|
|
28
28
|
"httpx>=0.28,<1",
|
|
29
29
|
"opentelemetry-api>=1.44,<2",
|
|
30
30
|
]
|
|
@@ -51,7 +51,7 @@ dev = [
|
|
|
51
51
|
"ruff>=0.16,<0.17",
|
|
52
52
|
"pyright>=1.1.405",
|
|
53
53
|
"moto[sqs,s3,ses,kms,secretsmanager]>=5.2,<6",
|
|
54
|
-
"boto3-stubs[sqs,s3,sesv2,kms,secretsmanager,bedrock-runtime]>=1.40,<2",
|
|
54
|
+
"boto3-stubs[sqs,s3,sesv2,kms,secretsmanager,bedrock-runtime,appconfigdata]>=1.40,<2",
|
|
55
55
|
]
|
|
56
56
|
|
|
57
57
|
[tool.hatch.build.targets.wheel]
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
`clients_021.compute.kubernetes` need no extras;
|
|
5
5
|
`clients_021.cache.redis` needs the `redis` extra; `clients_021.queue.sqs`,
|
|
6
6
|
`clients_021.storage.s3`, `clients_021.email.ses`, `clients_021.crypto.kms`,
|
|
7
|
-
`clients_021.secrets.secrets_manager
|
|
7
|
+
`clients_021.secrets.secrets_manager`, `clients_021.llm.bedrock` and
|
|
8
|
+
`clients_021.featureflags.appconfig` the `aws` extra (`clients_021.featureflags` needs none);
|
|
8
9
|
`clients_021.http.httpx2` the `httpx2` extra.
|
|
9
10
|
`clients_021.storage.vercel_blob` needs the `vercel-blob` extra.
|
|
10
11
|
"""
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
The `db` family reuses the instruments 021-python-observability already creates for SQL
|
|
4
4
|
(`db.client.operation.*`), so Redis and Postgres share dashboards. `messaging` (SQS, email),
|
|
5
|
-
`storage` (S3, Vercel Blob), `crypto` (KMS), `secrets` (Secrets Manager), `llm` (Bedrock)
|
|
6
|
-
`compute` (Kubernetes Jobs) get their own pair on the same
|
|
7
|
-
measurement for synchronous SDK calls and for operations that end
|
|
8
|
-
as a model response stream. Attributes are bounded: dependency,
|
|
9
|
-
error.type; never keys, URLs, bodies or ids.
|
|
5
|
+
`storage` (S3, Vercel Blob), `crypto` (KMS), `secrets` (Secrets Manager), `llm` (Bedrock),
|
|
6
|
+
`compute` (Kubernetes Jobs) and `config` (feature flags) get their own pair on the same
|
|
7
|
+
meter. `Call` is the same measurement for synchronous SDK calls and for operations that end
|
|
8
|
+
later than they start, such as a model response stream. Attributes are bounded: dependency,
|
|
9
|
+
operation, status and error.type; never keys, URLs, bodies or ids.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -24,7 +24,7 @@ from opentelemetry.trace import SpanKind, StatusCode
|
|
|
24
24
|
|
|
25
25
|
from clients_021.errors import DependencyUnavailable
|
|
26
26
|
|
|
27
|
-
Family = Literal["db", "messaging", "storage", "crypto", "secrets", "llm", "compute"]
|
|
27
|
+
Family = Literal["db", "messaging", "storage", "crypto", "secrets", "llm", "compute", "config"]
|
|
28
28
|
# Returns the reason when an exception means "the dependency failed", None for a bug.
|
|
29
29
|
Classifier = Callable[[BaseException], str | None]
|
|
30
30
|
|
|
@@ -37,6 +37,7 @@ _UNITS = {
|
|
|
37
37
|
"secrets": "{operation}",
|
|
38
38
|
"llm": "{operation}",
|
|
39
39
|
"compute": "{operation}",
|
|
40
|
+
"config": "{operation}",
|
|
40
41
|
}
|
|
41
42
|
_cache: WeakKeyDictionary[Observability, dict[Family, tuple[Counter, Histogram]]] = (
|
|
42
43
|
WeakKeyDictionary()
|