021-python-clients 0.2.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.2.0 → 021_python_clients-0.4.0}/CHANGELOG.md +29 -0
- 021_python_clients-0.4.0/CONTRACT.md +319 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/CONTRIBUTING.md +17 -7
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/PKG-INFO +141 -24
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/README.md +138 -21
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/pyproject.toml +5 -5
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/__init__.py +5 -3
- {021_python_clients-0.2.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.2.0 → 021_python_clients-0.4.0}/src/clients_021/email/resend.py +5 -1
- 021_python_clients-0.4.0/src/clients_021/http/__init__.py +11 -0
- 021_python_clients-0.4.0/src/clients_021/http/_observe.py +175 -0
- 021_python_clients-0.4.0/src/clients_021/http/httpx.py +280 -0
- 021_python_clients-0.4.0/src/clients_021/http/httpx2.py +76 -0
- 021_python_clients-0.4.0/src/clients_021/httpx2.py +5 -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.2.0 → 021_python_clients-0.4.0}/src/clients_021/queue/qstash.py +1 -1
- 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.2.0 → 021_python_clients-0.4.0}/tests/test_contract.py +1 -1
- 021_python_clients-0.4.0/tests/test_http_stream_lifecycle.py +270 -0
- 021_python_clients-0.4.0/tests/test_http_streaming.py +482 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_httpx2.py +8 -2
- 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.2.0 → 021_python_clients-0.4.0}/tests/wheel_minimal.py +37 -2
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/wheel_smoke.py +19 -3
- 021_python_clients-0.2.0/CONTRACT.md +0 -191
- 021_python_clients-0.2.0/src/clients_021/http.py +0 -131
- 021_python_clients-0.2.0/src/clients_021/httpx2.py +0 -93
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/.gitignore +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/LICENSE +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/_aws.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/cache/__init__.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/cache/redis.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/crypto/__init__.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/crypto/kms.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/email/__init__.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/email/_message.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/email/ses.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/errors.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/kms.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/py.typed +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/queue/__init__.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/queue/sqs.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/redis.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/s3.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/storage/__init__.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/storage/s3.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/src/clients_021/storage/vercel_blob.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/__init__.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/conftest.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/qstash_local.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_email.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_http.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_kms.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_qstash.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_redis.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_s3.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_ses.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_sqs.py +0 -0
- {021_python_clients-0.2.0 → 021_python_clients-0.4.0}/tests/test_vercel_blob.py +0 -0
|
@@ -2,6 +2,31 @@
|
|
|
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
|
+
|
|
14
|
+
## [0.3.0](https://github.com/from021-io/python-clients/compare/v0.2.0...v0.3.0) (2026-10-21)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### ⚠ BREAKING CHANGES
|
|
18
|
+
|
|
19
|
+
* **http:** HTTP durations include body consumption and cleanup, counts emit at completion and all HTTP series gain http.response.body.complete. Attempt failure logs are enabled by default. See CONTRACT.md for dashboard and consumer migration notes.
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* **http:** observe complete response bodies and streams ([333cece](https://github.com/from021-io/python-clients/commit/333cece69c41a9ef16828f83e303be4183a5a187))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Refactors
|
|
27
|
+
|
|
28
|
+
* **http:** group clients and transports by capability ([1a7b41a](https://github.com/from021-io/python-clients/commit/1a7b41a5dba214fb109acccc9e3e08a9d123e3ee))
|
|
29
|
+
|
|
5
30
|
## [0.2.0](https://github.com/from021-io/python-clients/compare/v0.1.0...v0.2.0) (2026-10-21)
|
|
6
31
|
|
|
7
32
|
|
|
@@ -22,10 +47,14 @@ All notable changes are recorded here by release-please from Conventional Commit
|
|
|
22
47
|
|
|
23
48
|
### Added
|
|
24
49
|
|
|
50
|
+
- Add `HttpClient.stream` and a native httpx `ObservedTransport` for incremental bodies with explicit lifetime and no streaming retries.
|
|
51
|
+
|
|
25
52
|
- Add an optional Vercel Blob client with observed CRUD, explicit public/private access, bounded operations and SDK-native metadata/errors.
|
|
26
53
|
|
|
27
54
|
### Changed
|
|
28
55
|
|
|
56
|
+
- **Breaking — HTTP telemetry:** HTTP/httpx2 spans and request durations now include body consumption and resource close. Counters are emitted at completion, gain `http.response.body.complete`, and failures produce sanitized attempt logs. QStash/Resend retain one logical failure log. Cancellation closes resources before propagating; closed HTTP clients cannot reopen.
|
|
57
|
+
|
|
29
58
|
- Group Redis, S3 and KMS under cache, storage and crypto, retaining the 0.1.0 imports as compatibility aliases.
|
|
30
59
|
|
|
31
60
|
## 0.1.0 (2026-10-21)
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Contract
|
|
2
|
+
|
|
3
|
+
What dashboards, alerts and services may rely on. Changing a name, a label or the error
|
|
4
|
+
semantics below is a breaking change.
|
|
5
|
+
|
|
6
|
+
## Imports and capability packages
|
|
7
|
+
|
|
8
|
+
Queue providers live in `clients_021.queue.sqs` (`SqsClient`, `ReceivedMessage`) and
|
|
9
|
+
`clients_021.queue.qstash` (`QStashClient`, `PublishedMessage`, `QStashResponseError`).
|
|
10
|
+
Use these imports in place of the initial draft's root-level `clients_021.sqs` and
|
|
11
|
+
`clients_021.qstash` paths. These capability paths shipped in 0.1.0.
|
|
12
|
+
Importing `clients_021.queue` or its QStash module does not require the `aws` extra.
|
|
13
|
+
|
|
14
|
+
The queue package groups providers by purpose. SQS owns polling, receipts and visibility;
|
|
15
|
+
QStash owns HTTP delivery. Their public methods and observability contracts remain distinct.
|
|
16
|
+
Shared email types and the interchangeable `EmailSender` protocol live in `clients_021.email`.
|
|
17
|
+
|
|
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
|
|
22
|
+
the same clients and result types (`ObjectInfo`, `DataKey`). Existing call signatures,
|
|
23
|
+
metric/span names and error semantics are unchanged. Capability package imports do not
|
|
24
|
+
load optional SDKs. HTTP implementations live in `clients_021.http.httpx` and
|
|
25
|
+
`clients_021.http.httpx2`. `clients_021.http` retains `HttpClient`, `HttpResponse`,
|
|
26
|
+
`HttpMethod`, `IDEMPOTENT_METHODS` and `RETRY_STATUS`; the root `clients_021` exports remain
|
|
27
|
+
unchanged. `clients_021.httpx2.ObservedTransport` re-exports the same class as the new path.
|
|
28
|
+
Importing `clients_021.http` does not require the `httpx2` extra.
|
|
29
|
+
|
|
30
|
+
## Metrics
|
|
31
|
+
|
|
32
|
+
All on the service's `Observability` meter, so they carry its resource (service name,
|
|
33
|
+
version, environment). Durations use 021-python-observability's buckets.
|
|
34
|
+
|
|
35
|
+
| Instrument | Type / unit | Labels | Clients |
|
|
36
|
+
| --- | --- | --- | --- |
|
|
37
|
+
| `db.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | Redis (shared with SQL from 021-python-observability, which has no `operation`) |
|
|
38
|
+
| `db.client.operation.duration` | Histogram / `s` | same | Redis |
|
|
39
|
+
| `messaging.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | SQS, QStash, email (Resend, SES) |
|
|
40
|
+
| `messaging.client.operation.duration` | Histogram / `s` | same | SQS, QStash, email (Resend, SES) |
|
|
41
|
+
| `storage.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | S3, Vercel Blob |
|
|
42
|
+
| `storage.client.operation.duration` | Histogram / `s` | same | S3, Vercel Blob |
|
|
43
|
+
| `crypto.client.operation.count` | Counter / `{operation}` | dependency, operation, status, error.type | KMS |
|
|
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
|
+
| `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
|
+
| `http.client.request.duration` | Histogram / `s` | same | HTTP, httpx2, QStash transport, Resend (its HTTP request) |
|
|
54
|
+
|
|
55
|
+
- `status`: `ok` or `error`. `error.type` only on errors.
|
|
56
|
+
- `operation`: a fixed set per client. Redis: `get_json`, `set_json`, `take_json`,
|
|
57
|
+
`delete`, `incr_window`, `ping`. SQS: `send`, `receive`, `delete`, `change_visibility`.
|
|
58
|
+
S3 and Vercel Blob: `put`, `get`, `head`, `delete`. QStash: `publish`.
|
|
59
|
+
Email (Resend, SES): `send`. KMS:
|
|
60
|
+
`encrypt`, `decrypt`, `generate_data_key`; `seal` and `unseal` record the KMS call they make
|
|
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`,
|
|
71
|
+
`status_<5xx>`, `cancelled`, or the exception class name for errors that are not outages
|
|
72
|
+
(`EmailRejected` for every email rejection; botocore's class, such as `ClientError` or
|
|
73
|
+
`NotFoundException`, for KMS).
|
|
74
|
+
- Resend records two levels: the `send` operation (`messaging.client.operation.*`) and,
|
|
75
|
+
inside it, its HTTP request (`http.client.request.*`), both under the same `dependency`.
|
|
76
|
+
- HTTP transport metrics use the bounded labels (`timeout`, `cancelled`,
|
|
77
|
+
`connection_error` or `internal_error`). These describe the transport exception, while
|
|
78
|
+
`DependencyUnavailable.reason` describes the failure returned to the caller.
|
|
79
|
+
- httpx2 uses `timeout`, `connection_error`, `cancelled`, or the exception class name.
|
|
80
|
+
HTTP status errors set the span's ERROR status and status-code label, with no `error.type`.
|
|
81
|
+
- A missing S3/Blob object or Redis key is `status=ok`.
|
|
82
|
+
- `dependency` is a static identifier, validated consistently with 021-python-observability.
|
|
83
|
+
User ids, tenant ids and request-derived values are forbidden even if they match its syntax.
|
|
84
|
+
|
|
85
|
+
## Spans
|
|
86
|
+
|
|
87
|
+
CLIENT spans named `<dependency> <operation>` (`HTTP <dependency>` for HTTP), with the
|
|
88
|
+
metric labels plus `db.system.name=redis`, `messaging.system=aws_sqs`,
|
|
89
|
+
`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` /
|
|
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.
|
|
94
|
+
Resend's `HTTP <dependency>` span is a child of its `<dependency> send` span. Status ERROR on failure (and on HTTP
|
|
95
|
+
status ≥ 400).
|
|
96
|
+
Cancellation propagates unchanged and records ERROR with `error.type=cancelled`.
|
|
97
|
+
HTTP transports measure from sending the request through response-body EOF and resource
|
|
98
|
+
close, or explicit early close. Uploads and incremental downloads are included, as are
|
|
99
|
+
consumer pauses between chunks and cleanup latency. Each attempt ends its span and emits
|
|
100
|
+
one count and one duration exactly once. An HTTP 200 followed by a body timeout is ERROR
|
|
101
|
+
with status code 200 and `error.type=timeout`; the status code describes the headers only.
|
|
102
|
+
|
|
103
|
+
`http.response.body.complete` is a boolean on HTTP spans and metric points: true after
|
|
104
|
+
response-body EOF, false when no response arrived or it was closed before EOF. It does not
|
|
105
|
+
imply success: an HTTP rejection or close failure can still set ERROR. An intentional
|
|
106
|
+
partial close without an error does not set ERROR. Cancellation sets `error.type=cancelled`,
|
|
107
|
+
including cancellation while reading, closing or waiting between chunks in a stream context.
|
|
108
|
+
|
|
109
|
+
The HTTP span is current only while sending, reading, closing and emitting its log. It is
|
|
110
|
+
not left attached while a caller processes a chunk; interleaved and cross-task consumers
|
|
111
|
+
keep their own context. All HTTP attempt spans retain the parent active at request start.
|
|
112
|
+
|
|
113
|
+
QStash adds one logical `publish` operation around the HTTP transport request. The
|
|
114
|
+
messaging duration includes reading and validating the acknowledgement; the nested HTTP
|
|
115
|
+
span includes reading and closing the response body. Both use the configured static dependency name.
|
|
116
|
+
Local input validation occurs before either operation is recorded.
|
|
117
|
+
|
|
118
|
+
## Logging and coverage limits
|
|
119
|
+
|
|
120
|
+
Redis, SQS, QStash, S3, Vercel Blob, Resend, SES, KMS, Secrets Manager, Bedrock and Kubernetes Jobs emit a structured
|
|
121
|
+
`dependency.call_failed` warning when an observed remote operation raises an exception.
|
|
122
|
+
Fields are `dependency`, `operation` and `error_type`; provider messages and exception
|
|
123
|
+
chains are not logged. Successful calls and cancellation do not emit this warning;
|
|
124
|
+
cancellation still records an error span and metrics.
|
|
125
|
+
|
|
126
|
+
HTTP and httpx2 emit one `dependency.call_failed` warning per failed attempt (transport,
|
|
127
|
+
body or close exception, cancellation, or HTTP status >= 400). Fields are only `dependency`,
|
|
128
|
+
`operation` (normalized method) and `error_type`. HTTP rejection logs use `status_<code>`;
|
|
129
|
+
the metric still uses only its status-code label for that case. The logger runs within the
|
|
130
|
+
HTTP span so configured log processors can attach its trace/span IDs. Successful completed
|
|
131
|
+
attempts emit no log. Intentional partial close emits `dependency.stream_closed` at INFO
|
|
132
|
+
with dependency and operation; it is not an outage.
|
|
133
|
+
|
|
134
|
+
`log_failures=False` suppresses these HTTP attempt logs without disabling metrics or spans.
|
|
135
|
+
QStash, Resend and Kubernetes Jobs set this option because their logical operation already emits a failure
|
|
136
|
+
warning; they do not log the same failure at both levels. SDK retry loops can produce one
|
|
137
|
+
warning per failed attempt. HTTP counters measure attempts, not logical API/SDK calls.
|
|
138
|
+
|
|
139
|
+
The transport observes network bytes and connection cleanup. Parsing JSON, SSE events,
|
|
140
|
+
model output, content decoding above the raw stream, and application errors while processing
|
|
141
|
+
chunks belong to the caller/SDK operation. A 200 SSE response containing an application
|
|
142
|
+
error event is not detected by an HTTP transport.
|
|
143
|
+
S3 presigning and local AES work in KMS are outside remote-operation telemetry.
|
|
144
|
+
|
|
145
|
+
Instrumentation takes effect only when applications use these wrappers. The application
|
|
146
|
+
must also configure logging, exporters and shutdown/flush through 021-python-observability.
|
|
147
|
+
Defining a client in this package does not migrate existing SDK usage or guarantee that
|
|
148
|
+
telemetry reaches an external backend.
|
|
149
|
+
|
|
150
|
+
## Trace propagation
|
|
151
|
+
|
|
152
|
+
- HTTP (`HttpClient`, `http.httpx.ObservedTransport`): the current `traceparent` and
|
|
153
|
+
`tracestate` replace the caller's trace pair, and a bound `x-request-id` is sent.
|
|
154
|
+
A native httpx transport can disable injection with `propagate=False`.
|
|
155
|
+
- httpx2 (`ObservedTransport`): no context is injected unless `propagate=True`.
|
|
156
|
+
With injection disabled, caller-supplied headers are left as supplied.
|
|
157
|
+
When enabled, both caller-supplied trace headers are replaced with the current context.
|
|
158
|
+
- Resend: nothing is sent. The `traceparent`, `tracestate` and `x-request-id` headers that
|
|
159
|
+
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.
|
|
161
|
+
- SQS: `traceparent` and `tracestate` are message attributes; `ReceivedMessage.trace_context()`
|
|
162
|
+
returns the producer's context. Caller-supplied values for both trace attributes are
|
|
163
|
+
replaced with the current context.
|
|
164
|
+
- QStash: the `publish` span's context is sent as `Upstash-Forward-traceparent` and
|
|
165
|
+
`Upstash-Forward-tracestate` for delivery to the consumer. `propagate=False` removes
|
|
166
|
+
both forwarded trace headers. Caller values never override them. The request to QStash
|
|
167
|
+
itself still uses the HTTP transport's propagation policy.
|
|
168
|
+
|
|
169
|
+
## Errors
|
|
170
|
+
|
|
171
|
+
`DependencyUnavailable(dependency, operation, reason)` with `reason` one of `timeout`,
|
|
172
|
+
`connection_error`, `throttled`, `status_<5xx>`. Raised only when retrying later may work.
|
|
173
|
+
`HttpClient.request`: only after the last attempt; HTTP statuses are returned, never raised.
|
|
174
|
+
`HttpClient.stream`: exactly one attempt; transport/body failures use the same conversion,
|
|
175
|
+
with no replay after headers or a partial body. Native httpx/httpx2 `ObservedTransport`
|
|
176
|
+
instances preserve SDK exceptions and never retry.
|
|
177
|
+
HTTP local protocol/unsupported protocol errors and Redis authentication
|
|
178
|
+
or authorization errors retain their native type and are not treated as outages.
|
|
179
|
+
|
|
180
|
+
QStash maps HTTP 429 to `throttled`, 5xx to `status_<code>`, and transport outages to
|
|
181
|
+
`timeout` or `connection_error`, with operation `publish`. Other non-2xx responses raise
|
|
182
|
+
`QStashResponseError(status_code, reason="rejected")`. A 2xx response must contain a
|
|
183
|
+
non-empty `messageId` and, if present, a boolean `deduplicated`; otherwise it raises
|
|
184
|
+
`QStashResponseError` with `reason="invalid_response"`. A missing deduplication flag
|
|
185
|
+
is inferred from status 202. Neither error includes the provider response body.
|
|
186
|
+
`PublishedMessage(message_id, deduplicated)` represents acceptance, not completed delivery.
|
|
187
|
+
|
|
188
|
+
The exception's message and fields contain only the static dependency, operation and reason.
|
|
189
|
+
The chained provider exception is preserved for debugging and is not sanitized.
|
|
190
|
+
|
|
191
|
+
Email: `EmailRejected(dependency, status, code)` when the provider refused the message or
|
|
192
|
+
the credentials, for both senders. `status` is the HTTP status; `code` is Resend's error
|
|
193
|
+
`name` or the SES error code, reduced to `unknown` unless it is a short identifier. Its
|
|
194
|
+
message is `<dependency> send: rejected (<status> <code>)`; never the provider's message.
|
|
195
|
+
Resend: 429 is `throttled`, 5xx is `status_<5xx>`, transport timeouts and connection
|
|
196
|
+
failures are `timeout` / `connection_error`; every other non-2xx is `EmailRejected` (409
|
|
197
|
+
`concurrent_idempotent_requests` included: the first request is still running). SES: the
|
|
198
|
+
shared AWS classification, and every other `ClientError` becomes `EmailRejected` chained to
|
|
199
|
+
it. Resend's request is sent once; `idempotency_key` becomes its `Idempotency-Key` header
|
|
200
|
+
(1–256 characters, kept by Resend for 24 hours). SES has no idempotency key: the argument is
|
|
201
|
+
validated and ignored.
|
|
202
|
+
|
|
203
|
+
KMS: throttling, 5xx (`KMSInternalException`, `DependencyTimeoutException`,
|
|
204
|
+
`KeyUnavailableException`), timeouts and connection failures are `DependencyUnavailable`.
|
|
205
|
+
Access denied, disabled or missing keys, invalid key state, a wrong encryption context or a
|
|
206
|
+
tampered ciphertext (`InvalidCiphertextException`) and a ciphertext from another key
|
|
207
|
+
(`IncorrectKeyException`) keep their botocore type. `decrypt` always names the client's key.
|
|
208
|
+
Every call requires a non-empty encryption context. `unseal` raises `ValueError` for a
|
|
209
|
+
malformed envelope and `cryptography.exceptions.InvalidTag` when the payload was altered.
|
|
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
|
+
|
|
236
|
+
Vercel Blob (`clients_021.storage.vercel_blob`): HTTP 429 maps to `throttled`; 5xx to
|
|
237
|
+
`status_<code>`; HTTP timeouts and total deadlines to `timeout`; network/remote protocol
|
|
238
|
+
failures to `connection_error`. Known transport causes wrapped by the SDK are classified
|
|
239
|
+
without inspecting provider messages. Other errors retain their SDK type, including
|
|
240
|
+
`BlobAccessError`, `BlobStoreNotFoundError`, `BlobPreconditionFailedError`, and
|
|
241
|
+
`BlobStreamError` for malformed acknowledgements. Only `BlobNotFoundError` becomes `None`
|
|
242
|
+
for `get`/`head`, counted as success. `put` returns the SDK's `PutResult` (exported as
|
|
243
|
+
`BlobUpload`); `head` returns its `HeadResult` (`BlobInfo`). These types are provider-specific.
|
|
244
|
+
SDK validation and credential failures can produce an error span but perform no network I/O.
|
|
245
|
+
|
|
246
|
+
## Sealed envelope format
|
|
247
|
+
|
|
248
|
+
`KmsClient.seal` output is stored data, so its layout is part of this contract: version
|
|
249
|
+
byte `0x01`, wrapped data key length (2 bytes, big endian), the wrapped key (KMS
|
|
250
|
+
`GenerateDataKey`, `AES_256`, same encryption context), a 12-byte random nonce, then the
|
|
251
|
+
AES-256-GCM ciphertext and 16-byte tag. The GCM additional data is the header (version,
|
|
252
|
+
length and wrapped key) followed by the encryption context as compact JSON with sorted keys.
|
|
253
|
+
A new data key is generated for every envelope.
|
|
254
|
+
|
|
255
|
+
## Lifecycle and retry limits
|
|
256
|
+
|
|
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
|
|
259
|
+
in-flight calls complete; the wrapper owns the underlying client, including one supplied
|
|
260
|
+
through the constructor.
|
|
261
|
+
AWS factories configure three total attempts; injected clients retain their own policy.
|
|
262
|
+
Cancellation of an AWS await does not cancel its worker thread or undo a remote write.
|
|
263
|
+
A transient failure does not guarantee that replaying a non-idempotent operation is safe.
|
|
264
|
+
|
|
265
|
+
QStash publishes once, never automatically replays a POST, and preserves the required
|
|
266
|
+
deduplication key exactly. Keys containing `:` fail locally, matching the provider
|
|
267
|
+
restriction without lossy normalization. Delivery retries, delay and delivery timeout are separate
|
|
268
|
+
provider options. The publication timeout is mandatory and finite. Consumers remain
|
|
269
|
+
responsible for idempotency and signature verification. A closed QStash client cannot
|
|
270
|
+
publish again.
|
|
271
|
+
|
|
272
|
+
`HttpClient.request` and `HttpClient.stream` accept at most one of `json`, `form` or raw byte `content`. Passing
|
|
273
|
+
more than one non-None body format raises `ValueError` before sending a request.
|
|
274
|
+
|
|
275
|
+
`HttpClient.stream` is an async context manager yielding a native httpx response; it does
|
|
276
|
+
not buffer the response and also accepts an async iterable of upload bytes. Use `async with`
|
|
277
|
+
and `aiter_bytes`, `aiter_lines` or `aiter_raw`. SDK callers using manual `send(stream=True)`
|
|
278
|
+
must eventually call `response.aclose()`; an abandoned response cannot finalize its
|
|
279
|
+
telemetry or free its connection. Client shutdown does not replace closing active responses.
|
|
280
|
+
Close responses and finish consumers before closing the client/Observability.
|
|
281
|
+
|
|
282
|
+
Body resources close once on EOF, read failure or explicit close. Cleanup is shielded from
|
|
283
|
+
cancellation and awaited; cancellation is then re-raised. A secondary cleanup failure does
|
|
284
|
+
not replace a read failure. These async clients run on asyncio. Custom transports must implement a terminating `aclose()`.
|
|
285
|
+
Timeouts remain per network I/O, not a total stream deadline. `HttpClient` closes an injected
|
|
286
|
+
transport once even when multiple dependency pools borrow it; closed clients cannot reopen.
|
|
287
|
+
|
|
288
|
+
Vercel Blob uses the optional `vercel-blob>=0.5,<0.6` SDK and explicit read-write credentials,
|
|
289
|
+
store ID and access mode. It owns a shared transport; each operation borrows it in a separate
|
|
290
|
+
SDK session. No environment credentials or global SDK client perform requests. The total
|
|
291
|
+
operation deadline defaults to 10 seconds and includes buffered downloads. Timeout and
|
|
292
|
+
cancellation do not imply a remote write was undone. Neither this wrapper nor the SDK
|
|
293
|
+
retries operations; an injected transport must respect this policy. Overwrite and random
|
|
294
|
+
suffixes default to false. Callers decide whether a repeated write is safe. A closed client
|
|
295
|
+
cannot reopen its pool. No additional HTTP metrics are emitted by this client.
|
|
296
|
+
|
|
297
|
+
## Migration from 0.2.x
|
|
298
|
+
|
|
299
|
+
This is a breaking telemetry change, released with the next pre-1.0 minor version. HTTP
|
|
300
|
+
metric names and span names stay the same, but their durations now include bodies, consumer
|
|
301
|
+
backpressure and close; counts are emitted at completion instead of at headers. Existing
|
|
302
|
+
series gain `http.response.body.complete`. Revisit latency thresholds, filter/aggregate the
|
|
303
|
+
new label and expect long-lived streams to report only when they complete or close. Native
|
|
304
|
+
transport logs are now enabled by default; use the explicit opt-out when an enclosing
|
|
305
|
+
logical operation already owns the warning. Do not add a second observed transport around
|
|
306
|
+
these clients, or each request would be counted twice.
|
|
307
|
+
|
|
308
|
+
## Never recorded
|
|
309
|
+
|
|
310
|
+
Keys, URLs, query strings, bodies, message ids, receipts, object names, bucket or queue
|
|
311
|
+
names, provider error messages. Email: sender, recipient and reply-to addresses, subjects,
|
|
312
|
+
text and HTML bodies, tags, idempotency keys, API keys, configuration set names. KMS: key
|
|
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.
|
|
318
|
+
|
|
319
|
+
Vercel Blob: read-write tokens, store IDs, pathnames, delivery URLs, content and ETags.
|
|
@@ -8,23 +8,27 @@ not need to be reimplemented here.
|
|
|
8
8
|
## Layout and public API
|
|
9
9
|
|
|
10
10
|
Group domain clients by capability, with a module per provider even when the package
|
|
11
|
-
currently has one provider. HTTP
|
|
12
|
-
|
|
11
|
+
currently has one provider. HTTP implementations live under `http/`, with explicit
|
|
12
|
+
imports for optional transports. Tests stay in `tests/test_<provider>.py`.
|
|
13
13
|
|
|
14
14
|
```text
|
|
15
15
|
clients_021/
|
|
16
16
|
email/ # _message.py (shared protocol/types), resend.py, ses.py
|
|
17
17
|
queue/ # sqs.py, qstash.py
|
|
18
|
-
http.py
|
|
19
|
-
httpx2.py
|
|
18
|
+
http/ # httpx.py (HttpClient/ObservedTransport), httpx2.py (ObservedTransport)
|
|
20
19
|
cache/ # redis.py
|
|
21
20
|
storage/ # s3.py, vercel_blob.py
|
|
22
21
|
crypto/ # kms.py
|
|
22
|
+
secrets/ # secrets_manager.py
|
|
23
|
+
llm/ # bedrock.py
|
|
24
|
+
compute/ # kubernetes.py (over the shared HTTP client, no extra)
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
The original `clients_021.redis`, `clients_021.s3` and `clients_021.kms` imports remain
|
|
26
28
|
compatibility re-exports of the same classes and result types. New code uses the capability
|
|
27
|
-
paths.
|
|
29
|
+
paths. `clients_021.http` re-exports its existing types from `http.httpx`, and the original
|
|
30
|
+
`clients_021.httpx2` remains an alias for `http.httpx2`. Keep `clients_021.HttpClient`
|
|
31
|
+
available too. Do not remove compatibility modules without a breaking release.
|
|
28
32
|
|
|
29
33
|
Public result and exception types live beside their provider unless they are truly shared.
|
|
30
34
|
A common protocol requires interchangeable operations and semantics: `EmailSender.send`
|
|
@@ -61,10 +65,16 @@ Each implementation must make these decisions explicit in README.md and CONTRACT
|
|
|
61
65
|
| Telemetry | Choose a metric family, fixed operations and system attribute. Never record URLs, credentials, bodies, object names or request-derived ids. |
|
|
62
66
|
| Propagation | State the default and opt-out. Replace `traceparent` and `tracestate` together, including removing stale state. |
|
|
63
67
|
|
|
64
|
-
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
|
|
65
71
|
clients may also emit the transport's HTTP span/metrics: one logical operation and one
|
|
66
72
|
network request are different measurements. Do not sum those counters as distinct
|
|
67
|
-
business operations. Disable
|
|
73
|
+
business operations. Disable nested HTTP attempt logs with `log_failures=False` when the
|
|
74
|
+
logical operation owns the failure log. HTTP transport duration includes raw-body consumption
|
|
75
|
+
and resource close; keep measurement once-only, incremental, and detached from consumer
|
|
76
|
+
context between chunks. Test read/close errors, cancellation (including repeated cancellation
|
|
77
|
+
while closing), early close, and cross-task consumption. Disable exception recording when provider errors can contain data.
|
|
68
78
|
The safe wrapper message does not make a native chained exception safe to log.
|
|
69
79
|
|
|
70
80
|
Expose small, stable results when the caller needs them. Validate acknowledgements before
|