python-etcd3-async 0.1.6__tar.gz → 0.2.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.
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/.coverage +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/CLAUDE.md +10 -3
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/PKG-INFO +1 -1
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/advanced_queries.md +23 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/leases_locks.md +2 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/sync_client.md +9 -2
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/operations/auto_reconnection.md +24 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/operations/error_handling.md +10 -7
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/operations/failover.md +5 -1
- python_etcd3_async-0.2.0/docs/en/resources/history.md +72 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/advanced_queries.md +22 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/leases_locks.md +2 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/sync_client.md +8 -2
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/operations/auto_reconnection.md +19 -0
- python_etcd3_async-0.2.0/docs/zh/resources/history.md +63 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/__init__.py +3 -1
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_client.py +21 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_client_extensions.py +132 -58
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_endpoint.py +17 -3
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_managers.py +30 -5
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_multi_client.py +229 -101
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_single_client.py +18 -3
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_watch.py +125 -113
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/client.py +20 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/endpoint.py +16 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/errors.py +14 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/locks.py +49 -13
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/multi_client.py +236 -47
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/shared.py +1 -55
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/sync_kv_operations.py +53 -30
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/utils.py +0 -35
- python_etcd3_async-0.2.0/tests/test_async_health_check.py +178 -0
- python_etcd3_async-0.2.0/tests/test_async_watch.py +87 -0
- python_etcd3_async-0.2.0/tests/test_async_watch_cancel.py +90 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_batch_operations.py +90 -8
- python_etcd3_async-0.2.0/tests/test_endpoint_selection.py +201 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_locks.py +41 -0
- python_etcd3_async-0.2.0/tests/test_return_semantics.py +170 -0
- python_etcd3_async-0.2.0/tests/test_sync_health_check.py +245 -0
- python_etcd3_async-0.1.6/PHASE2_PLAN.md +0 -164
- python_etcd3_async-0.1.6/docs/en/resources/history.md +0 -39
- python_etcd3_async-0.1.6/docs/zh/resources/history.md +0 -38
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/.cnb.yml +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/.gitignore +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/.pylintrc +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/.python-version +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/AGENTS.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/README.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/README_CN.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/SKILL.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/assets/stylesheets/extra.css +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/getting-started/installation.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/authentication.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/cluster_maintenance.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/configuration.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/leader_election.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/service_discovery.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/service_registration.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/transactions.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/usage.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/guide/watching.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/index.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/operations/monitoring.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/operations/observability.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/resources/authors.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/resources/contributing.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/resources/releasing.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/superpowers/plans/2026-04-03-high-cohesion-low-coupling-plan.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/superpowers/specs/2026-04-03-high-cohesion-low-coupling-design.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/getting-started/installation.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/authentication.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/cluster_maintenance.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/configuration.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/leader_election.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/service_discovery.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/service_registration.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/transactions.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/usage.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/guide/watching.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/index.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/operations/error_handling.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/operations/failover.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/operations/monitoring.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/operations/observability.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/resources/authors.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/resources/contributing.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/resources/releasing.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_config.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/async_monitor.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/auth.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/config.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/events.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/exceptions.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/exporters/__init__.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/exporters/prometheus.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/leases.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/log.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/log_formatters.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/log_sampling.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/members.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/observability.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/single_client.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/sync_cluster_operations.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/sync_lock_operations.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/sync_maintenance_operations.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/trace.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/trace_attributes.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/transactions.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcd3/watch.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcdrpc/__init__.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcdrpc/auth.proto +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcdrpc/kv.proto +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/etcdrpc/rpc.proto +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/examples/leader_election.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/examples/leader_election_sync.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/examples/service_discovery.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/examples/service_discovery_sync.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/examples/service_discovery_v2.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/examples/service_registration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/examples/service_registration_sync.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/examples/service_registry_v2.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/gen_protos.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/mkdocs.yml +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/pyproject.toml +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/scripts/etcd-test.sh +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/__init__.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/conftest.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_async_client.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_async_client_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_async_error_handling.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_async_kv_operations_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_async_managers.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_auth.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_auth_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_client.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_cluster_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_events.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_kv_operations.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_lease_keepalive_retry.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_leases.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_leases_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_locks_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_log_formatters.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_log_sampling.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_maintenance_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_members.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_observability_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_tls_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_trace_attributes.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_transactions.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_transactions_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_utils.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_watch.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/tests/test_watch_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/usage_guide.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/uv.lock +0 -0
|
Binary file
|
|
@@ -266,7 +266,11 @@ auto_init() # Auto-initialize at import time
|
|
|
266
266
|
- Error handlers are created via `create_sync_error_handlers()` and `create_async_error_handlers()` factories from `errors.py`
|
|
267
267
|
- `_sync_handle_keepalive_errors` / `_async_handle_keepalive_errors` - Lease keepalive with `propagate_errors=True`
|
|
268
268
|
|
|
269
|
-
3. **Endpoint failover:** `MultiEndpointEtcd3Client`
|
|
269
|
+
3. **Endpoint failover:** Both `MultiEndpointEtcd3Client` and
|
|
270
|
+
`AsyncMultiEndpointEtcd3Client` track failed endpoints. With
|
|
271
|
+
`failover=True` a failed endpoint is bypassed and another healthy
|
|
272
|
+
endpoint is picked; with `failover=False` the client stays on the
|
|
273
|
+
endpoint it holds and rebuilds that endpoint's channel in place.
|
|
270
274
|
|
|
271
275
|
4. **Load balancing:** Multiple strategies supported via `LoadBalancerStrategy`:
|
|
272
276
|
- `PRIORITY` (default): Keep current endpoint if healthy, failover on failure
|
|
@@ -274,11 +278,14 @@ auto_init() # Auto-initialize at import time
|
|
|
274
278
|
- `RANDOM`: Random endpoint selection
|
|
275
279
|
- `STICKY`: Maintain connection to the same endpoint
|
|
276
280
|
|
|
277
|
-
5. **Auto-reconnection:** Async has `_get_channel()
|
|
281
|
+
5. **Auto-reconnection:** Async has `_get_channel()` / `_acquire_channel()`,
|
|
282
|
+
sync has a matching `_get_channel()`, both with a background health probe
|
|
278
283
|
|
|
279
284
|
6. **Lazy authentication:** Async uses `_init_auth()` with `asyncio.Lock()`, sync initializes in `__init__`
|
|
280
285
|
|
|
281
|
-
7. **Watch pattern:** Iterator + cancel function returns for both sync and
|
|
286
|
+
7. **Watch pattern:** Iterator + cancel function returns for both sync and
|
|
287
|
+
async. Cancelling sends a real cancel request to the server and unblocks
|
|
288
|
+
a pending iteration.
|
|
282
289
|
|
|
283
290
|
8. **Request context tracking:** Use `client.context()` to track operations across async tasks:
|
|
284
291
|
```python
|
|
@@ -49,6 +49,29 @@ async for value, meta in client.get_range('/start', '/end'):
|
|
|
49
49
|
print(f'{meta.key}: {value}')
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
## Batch Operations
|
|
53
|
+
|
|
54
|
+
The three batch entry points share one concurrency and failure policy:
|
|
55
|
+
items run in parallel, the concurrency ceiling is controlled by
|
|
56
|
+
`concurrency`, and a failing item never aborts the whole batch.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# Atomic transaction: all succeed or none do
|
|
60
|
+
count = await client.kv.batch_put([('/a', '1'), ('/b', '2')])
|
|
61
|
+
|
|
62
|
+
# Parallel execution: at most 10 at a time, failures only affect the count
|
|
63
|
+
count = await client.kv.batch_put(items, atomic=False, concurrency=10)
|
|
64
|
+
|
|
65
|
+
# Returns (success_count, [(failed_key, error), ...])
|
|
66
|
+
success, failed = await client.kv.put_many(items, concurrency=10)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`batch_put` and `put_many` accept either an iterable of `(key, value)`
|
|
70
|
+
tuples or a mapping; `batch_delete` accepts an iterable of keys.
|
|
71
|
+
|
|
72
|
+
The effective ceiling for `concurrency` is the client's `max_concurrent`
|
|
73
|
+
(default 100): callers may lower it, never raise it.
|
|
74
|
+
|
|
52
75
|
## See Also
|
|
53
76
|
|
|
54
77
|
- [API Usage](usage.md)
|
|
@@ -148,6 +148,8 @@ await lock.acquire()
|
|
|
148
148
|
async with lock.auto_refresh(interval=10): # default: ttl / 3
|
|
149
149
|
await do_long_running_work()
|
|
150
150
|
# background refresh stops on exit (the lock itself is NOT released here)
|
|
151
|
+
# if the keep-alive loop fails, the error is raised on exit — otherwise the
|
|
152
|
+
# lease may already be gone while the caller still believes it holds the lock
|
|
151
153
|
```
|
|
152
154
|
|
|
153
155
|
For sync clients, iterate the generator yourself:
|
|
@@ -38,8 +38,8 @@ from etcd3 import MultiEndpointEtcd3Client, Endpoint, LoadBalancerStrategy
|
|
|
38
38
|
|
|
39
39
|
etcd = MultiEndpointEtcd3Client(
|
|
40
40
|
endpoints=[
|
|
41
|
-
Endpoint('10.0.0.1', 2379),
|
|
42
|
-
Endpoint('10.0.0.2', 2379),
|
|
41
|
+
Endpoint('10.0.0.1', 2379, secure=False),
|
|
42
|
+
Endpoint('10.0.0.2', 2379, secure=False),
|
|
43
43
|
],
|
|
44
44
|
failover=True,
|
|
45
45
|
max_retries=3,
|
|
@@ -49,6 +49,10 @@ etcd = MultiEndpointEtcd3Client(
|
|
|
49
49
|
)
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
`Endpoint` defaults to `secure=True`, which requires TLS credentials. Pass
|
|
53
|
+
`secure=False` for a plaintext connection (or supply `ca_cert` /
|
|
54
|
+
`cert_cert` / `cert_key` for TLS).
|
|
55
|
+
|
|
52
56
|
## KV Operations
|
|
53
57
|
|
|
54
58
|
```python
|
|
@@ -119,6 +123,9 @@ success, responses = etcd.transaction(
|
|
|
119
123
|
| Lock keep-alive | iterate `lock.refresh()` | `async with lock.auto_refresh()` |
|
|
120
124
|
| Batch ops | top-level `batch_put`/`batch_delete` | `client.kv.batch_put/concurrency=...` |
|
|
121
125
|
| Maintenance | via `etcd.maintenance.*` | top-level `status/compact/...` |
|
|
126
|
+
| Members | `etcd.members` (property) | `async for m in etcd.members()` |
|
|
127
|
+
| Delete result | number of deleted keys | number of deleted keys |
|
|
128
|
+
| Put result | response header (or `KVMetadata` with `prev_kv=True`) | response header (or `KVMetadata` with `prev_kv=True`) |
|
|
122
129
|
| Close | `etcd.close()` | `await client.close()` |
|
|
123
130
|
|
|
124
131
|
## Blocking in Async Code
|
{python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/en/operations/auto_reconnection.md
RENAMED
|
@@ -25,6 +25,30 @@ required.
|
|
|
25
25
|
- Phase 3: Update endpoint state (under lock)
|
|
26
26
|
5. Once rebuilt successfully, the endpoint is marked as healthy again
|
|
27
27
|
|
|
28
|
+
## Health Probe Task
|
|
29
|
+
|
|
30
|
+
`health_check_interval` drives a background probe task:
|
|
31
|
+
|
|
32
|
+
- It starts lazily on the client's first request — a client that is never
|
|
33
|
+
used owns no task at all;
|
|
34
|
+
- Each cycle probes only the endpoints that are currently marked as failed,
|
|
35
|
+
so healthy endpoints cause no extra round trips;
|
|
36
|
+
- An endpoint that answers again is marked as healthy immediately, without
|
|
37
|
+
waiting for the `time_retry` window to expire;
|
|
38
|
+
- `close()` cancels the task, so its lifetime matches the client's exactly.
|
|
39
|
+
|
|
40
|
+
The sync client behaves the same way, with a daemon thread
|
|
41
|
+
(`etcd3-health-check`) as the carrier instead: it starts lazily when a
|
|
42
|
+
channel is first acquired, and `close()` signals it to exit and waits for
|
|
43
|
+
it to finish. The thread is a daemon, so it never keeps the process alive
|
|
44
|
+
even if `close()` is never called.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Inspect endpoint state: marked-failed / ok
|
|
48
|
+
for label, endpoint in client.endpoints.items():
|
|
49
|
+
print(label, 'marked-failed' if endpoint.is_failed() else 'ok')
|
|
50
|
+
```
|
|
51
|
+
|
|
28
52
|
## Tuning Reconnection
|
|
29
53
|
|
|
30
54
|
Recovery attempts are configured on the multi-endpoint clients:
|
|
@@ -60,8 +60,7 @@ current endpoint as failed, which drives [Failover](failover.md) and
|
|
|
60
60
|
## Handler Layers
|
|
61
61
|
|
|
62
62
|
- **Unary calls**: wrapped by generated error handlers that map
|
|
63
|
-
`grpc.RpcError` to the exceptions above
|
|
64
|
-
clients) trigger retry with backoff.
|
|
63
|
+
`grpc.RpcError` to the exceptions above.
|
|
65
64
|
- **Generators** (watch iterators, range streams): errors are mapped as
|
|
66
65
|
they surface during iteration; generator handlers may end the stream
|
|
67
66
|
quietly when the watch is cancelled normally.
|
|
@@ -71,11 +70,15 @@ current endpoint as failed, which drives [Failover](failover.md) and
|
|
|
71
70
|
|
|
72
71
|
## Retry Guidance
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
(`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
Read operations are retried automatically on transient failures
|
|
74
|
+
(`UNAVAILABLE`, `DEADLINE_EXCEEDED`, `INTERNAL`), bounded by
|
|
75
|
+
`max_retries`, `retry_backoff` and `retry_max_backoff`.
|
|
76
|
+
|
|
77
|
+
Writes are **not** retried automatically: the request may already have been
|
|
78
|
+
applied server-side when the error surfaced, so repeating it could duplicate
|
|
79
|
+
the effect. Retry writes at the application level only where they are safe
|
|
80
|
+
to repeat (idempotent puts), and treat `RevisionCompactedError` as a signal
|
|
81
|
+
to resync state rather than to retry blindly.
|
|
79
82
|
|
|
80
83
|
## See Also
|
|
81
84
|
|
|
@@ -44,10 +44,14 @@ see [Configuration](../guide/configuration.md).
|
|
|
44
44
|
the endpoint is marked failed
|
|
45
45
|
3. On the next operation the client picks a healthy endpoint (per the load
|
|
46
46
|
balancer strategy) and rebuilds the failed endpoint's channel in the
|
|
47
|
-
background
|
|
47
|
+
background. With `failover=False` the client instead stays on the
|
|
48
|
+
endpoint it holds and rebuilds that endpoint's channel in place.
|
|
48
49
|
4. Periodic health checks (`health_check_interval`) recover endpoints once
|
|
49
50
|
the server answers again
|
|
50
51
|
|
|
52
|
+
Both clients behave identically here: the sync and the async multi-endpoint
|
|
53
|
+
client apply the same strategy and the same failover rules.
|
|
54
|
+
|
|
51
55
|
## Load Balancer Strategies
|
|
52
56
|
|
|
53
57
|
| Strategy | Description |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: History
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# History
|
|
6
|
+
|
|
7
|
+
## v0.2.0 (2026-11)
|
|
8
|
+
|
|
9
|
+
Sync and async clients aligned on behaviour that had drifted apart:
|
|
10
|
+
|
|
11
|
+
- **Endpoint selection**: the async client now applies the load balancer
|
|
12
|
+
strategy and fails over to a healthy endpoint, like the sync client.
|
|
13
|
+
The `failover` flag is honoured by both clients: with failover enabled a
|
|
14
|
+
failed endpoint is bypassed, with failover disabled the client stays on
|
|
15
|
+
its endpoint and rebuilds that endpoint's channel in place
|
|
16
|
+
- **Watch cancellation**: the async client cancels the real watch instead
|
|
17
|
+
of watch id 0, so cancelling removes the server-side watch and unblocks
|
|
18
|
+
a pending iteration. The async watcher no longer races on a shared
|
|
19
|
+
event, and closing the client closes the watcher
|
|
20
|
+
- **Return values**: `delete()` / `delete_prefix()` return the number of
|
|
21
|
+
deleted keys on both clients, `put()` returns the response header (or
|
|
22
|
+
the previous key-value metadata with `prev_kv=True`), and the async
|
|
23
|
+
client gained `kv.transaction()`
|
|
24
|
+
- **Instrumentation**: every operation is recorded exactly once (the sync
|
|
25
|
+
client recorded it twice)
|
|
26
|
+
- **Retries**: `max_retries` / `retry_backoff` now take effect on the
|
|
27
|
+
idempotent read paths. Writes are not retried automatically, because the
|
|
28
|
+
request may already have been applied when the error surfaced
|
|
29
|
+
- **Compatibility shims**: `etcd3.client(...)` and
|
|
30
|
+
`await etcd3.async_client(...)` keep working after
|
|
31
|
+
`import etcd3.client` / `import etcd3.async_client`
|
|
32
|
+
- Sync `put_many()` honours its `timeout` argument; a transaction may
|
|
33
|
+
omit `success` / `failure` on both clients
|
|
34
|
+
|
|
35
|
+
## v0.1.6 (2026-10)
|
|
36
|
+
|
|
37
|
+
- Sync client: health probe for `health_check_interval`; batch operation
|
|
38
|
+
semantics unified
|
|
39
|
+
|
|
40
|
+
## v0.1.5 (2026-09)
|
|
41
|
+
|
|
42
|
+
- Sync client: gRPC error handling imported consistently across operation
|
|
43
|
+
modules; reconnection and watch cancel now reach the server
|
|
44
|
+
(`cancel_watch` no longer leaks threads/streams)
|
|
45
|
+
- Lock API: `Lock`/`AsyncLock.release()` return explicit status strings
|
|
46
|
+
(`released` / `not_owner` / `not_found` / `error`); sync `lock` restored
|
|
47
|
+
as a method with the operations namespace renamed to `lock_ops`
|
|
48
|
+
- Watch: `Watch.iterator()` renamed to `get_iterator()`; iterator cancel
|
|
49
|
+
is server-side on timeout
|
|
50
|
+
- Async: parallel endpoint health checks; error-mapping regression fixed
|
|
51
|
+
- Documentation site restructured (en + zh)
|
|
52
|
+
|
|
53
|
+
## v0.1.1 – v0.1.4 (2026-02 – 2026-04)
|
|
54
|
+
|
|
55
|
+
- High-cohesion module split: `single_client` / `multi_client` /
|
|
56
|
+
`async_single_client` / `async_multi_client`, `sync_*_operations`
|
|
57
|
+
mixins, `config` / `async_config`, `errors`, `auth`, `leases`, `locks`,
|
|
58
|
+
`members`
|
|
59
|
+
- `client.py` / `async_client.py` converted to compatibility shims
|
|
60
|
+
- Auth (user/role/permission) API added
|
|
61
|
+
- Async managers use the async channel for auto-reconnection
|
|
62
|
+
- Service examples improved (dataclass + v2 variants)
|
|
63
|
+
|
|
64
|
+
## v0.1.0
|
|
65
|
+
|
|
66
|
+
- Initial release
|
|
67
|
+
- Async client support (`grpc.aio`)
|
|
68
|
+
- Sync client support
|
|
69
|
+
- Lease, lock, watch and transaction operations
|
|
70
|
+
- Multi-endpoint support with failover
|
|
71
|
+
- Auto reconnection
|
|
72
|
+
- Monitoring metrics
|
|
@@ -49,6 +49,28 @@ async for value, meta in client.get_range('/start', '/end'):
|
|
|
49
49
|
print(f'{meta.key}: {value}')
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
## 批量操作
|
|
53
|
+
|
|
54
|
+
三个批量入口共享同一套并发与失败语义:条目并行执行,并发上限由
|
|
55
|
+
`concurrency` 控制,单个条目失败不会中断整批。
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# 原子事务:全部成功或全部失败
|
|
59
|
+
count = await client.kv.batch_put([('/a', '1'), ('/b', '2')])
|
|
60
|
+
|
|
61
|
+
# 并行执行:最多 10 个并发,单条失败只计入失败数
|
|
62
|
+
count = await client.kv.batch_put(items, atomic=False, concurrency=10)
|
|
63
|
+
|
|
64
|
+
# 返回 (成功数, [(失败的键, 异常), ...])
|
|
65
|
+
success, failed = await client.kv.put_many(items, concurrency=10)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`batch_put` 与 `put_many` 都接受 `(key, value)` 元组序列或字典;
|
|
69
|
+
`batch_delete` 接受键序列。
|
|
70
|
+
|
|
71
|
+
`concurrency` 的实际上限为客户端创建时的 `max_concurrent`(默认 100),
|
|
72
|
+
调用方只能调低、不能调高。
|
|
73
|
+
|
|
52
74
|
## 另见
|
|
53
75
|
|
|
54
76
|
- [API 使用](usage.md)
|
|
@@ -36,8 +36,8 @@ from etcd3 import MultiEndpointEtcd3Client, Endpoint, LoadBalancerStrategy
|
|
|
36
36
|
|
|
37
37
|
etcd = MultiEndpointEtcd3Client(
|
|
38
38
|
endpoints=[
|
|
39
|
-
Endpoint('10.0.0.1', 2379),
|
|
40
|
-
Endpoint('10.0.0.2', 2379),
|
|
39
|
+
Endpoint('10.0.0.1', 2379, secure=False),
|
|
40
|
+
Endpoint('10.0.0.2', 2379, secure=False),
|
|
41
41
|
],
|
|
42
42
|
failover=True,
|
|
43
43
|
max_retries=3,
|
|
@@ -47,6 +47,9 @@ etcd = MultiEndpointEtcd3Client(
|
|
|
47
47
|
)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
`Endpoint` 默认为 `secure=True`,此时必须提供 TLS 凭据。纯文本连接请传
|
|
51
|
+
`secure=False`(或提供 `ca_cert` / `cert_cert` / `cert_key` 启用 TLS)。
|
|
52
|
+
|
|
50
53
|
## 键值操作
|
|
51
54
|
|
|
52
55
|
```python
|
|
@@ -117,6 +120,9 @@ success, responses = etcd.transaction(
|
|
|
117
120
|
| 锁保活 | 迭代 `lock.refresh()` | `async with lock.auto_refresh()` |
|
|
118
121
|
| 批量操作 | 顶层 `batch_put`/`batch_delete` | `client.kv.batch_put(concurrency=...)` |
|
|
119
122
|
| 运维 | 经 `etcd.maintenance.*` | 顶层 `status/compact/...` |
|
|
123
|
+
| 成员列表 | `etcd.members`(属性) | `async for m in etcd.members()` |
|
|
124
|
+
| 删除结果 | 删除的键数量 | 删除的键数量 |
|
|
125
|
+
| 写入结果 | 响应头(`prev_kv=True` 时为 `KVMetadata`) | 响应头(`prev_kv=True` 时为 `KVMetadata`) |
|
|
120
126
|
| 关闭 | `etcd.close()` | `await client.close()` |
|
|
121
127
|
|
|
122
128
|
## 在异步代码中调用同步客户端
|
{python_etcd3_async-0.1.6 → python_etcd3_async-0.2.0}/docs/zh/operations/auto_reconnection.md
RENAMED
|
@@ -20,6 +20,25 @@ title: 自动重连
|
|
|
20
20
|
- 阶段3:更新端点状态(在锁内)
|
|
21
21
|
5. 重建成功后,端点被重新标记为健康
|
|
22
22
|
|
|
23
|
+
## 健康探测任务
|
|
24
|
+
|
|
25
|
+
`health_check_interval` 对应一个后台探测任务:
|
|
26
|
+
|
|
27
|
+
- 客户端首次发起请求时惰性启动 —— 从未使用的客户端不占用任何任务;
|
|
28
|
+
- 每个周期只探测被标记为失败的端点,健康端点不做无谓往返;
|
|
29
|
+
- 端点恢复应答后立即被标记为健康,不必等到 `time_retry` 窗口结束;
|
|
30
|
+
- `close()` 会取消该任务,任务生命周期与客户端严格一致。
|
|
31
|
+
|
|
32
|
+
同步客户端行为一致,只是载体换成守护线程(`etcd3-health-check`):
|
|
33
|
+
首次取通道时惰性启动,`close()` 通知其退出并等待收尾;
|
|
34
|
+
线程为守护线程,即使未调用 `close()` 也不会阻止进程退出。
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# 观察端点状态:marked-failed / ok
|
|
38
|
+
for label, endpoint in client.endpoints.items():
|
|
39
|
+
print(label, 'marked-failed' if endpoint.is_failed() else 'ok')
|
|
40
|
+
```
|
|
41
|
+
|
|
23
42
|
## 重连调优
|
|
24
43
|
|
|
25
44
|
恢复相关参数在多端点客户端上配置:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 更新历史
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# 更新历史
|
|
6
|
+
|
|
7
|
+
## v0.2.0 (2026-11)
|
|
8
|
+
|
|
9
|
+
同步与异步客户端在行为差异上完成对齐:
|
|
10
|
+
|
|
11
|
+
- **端点选择**:异步客户端现在与同步客户端一样应用负载均衡策略,并在
|
|
12
|
+
端点失败时切换到健康端点。两个客户端都真正尊重 `failover` 参数:
|
|
13
|
+
开启时绕过失败端点,关闭时留在当前端点并原地重建其 channel
|
|
14
|
+
- **Watch 取消**:异步客户端取消的是真实的 watch(此前取消的是并不存在
|
|
15
|
+
的 watch id 0),因此取消会真正移除服务端 watch 并唤醒阻塞中的迭代
|
|
16
|
+
器。异步 watcher 不再共享事件产生竞态,关闭客户端也会关闭 watcher
|
|
17
|
+
- **返回值**:两个客户端的 `delete()` / `delete_prefix()` 均返回删除的键
|
|
18
|
+
数量,`put()` 均返回响应头(`prev_kv=True` 时返回旧值元数据),异步
|
|
19
|
+
客户端新增 `kv.transaction()`
|
|
20
|
+
- **埋点**:每个操作只会被记录一次(同步客户端此前重复记录)
|
|
21
|
+
- **重试**:`max_retries` / `retry_backoff` 现在对幂等读路径生效。写操作
|
|
22
|
+
不自动重试,因为报错时请求可能已在服务端生效
|
|
23
|
+
- **兼容层**:`import etcd3.client` / `import etcd3.async_client` 之后,
|
|
24
|
+
`etcd3.client(...)` 与 `await etcd3.async_client(...)` 仍可正常调用
|
|
25
|
+
- 同步端 `put_many()` 现在真正使用其 `timeout` 参数;两个客户端的事务都
|
|
26
|
+
可以省略 `success` / `failure`
|
|
27
|
+
|
|
28
|
+
## v0.1.6 (2026-10)
|
|
29
|
+
|
|
30
|
+
- 同步客户端:实现 `health_check_interval` 健康探测;统一批量操作语义
|
|
31
|
+
|
|
32
|
+
## v0.1.5 (2026-09)
|
|
33
|
+
|
|
34
|
+
- 同步客户端:各操作模块统一复用 gRPC 错误处理;重连与 watch 取消
|
|
35
|
+
现在真正送达服务端(`cancel_watch` 不再泄漏线程/流)
|
|
36
|
+
- 锁 API:`Lock`/`AsyncLock.release()` 返回明确的语义状态
|
|
37
|
+
(`released` / `not_owner` / `not_found` / `error`);同步 `lock`
|
|
38
|
+
恢复为方法,操作命名空间更名为 `lock_ops`
|
|
39
|
+
- Watch:`Watch.iterator()` 更名为 `get_iterator()`;迭代器超时即
|
|
40
|
+
取消服务端 watch
|
|
41
|
+
- 异步:端点健康检查并行化;修复错误映射回归
|
|
42
|
+
- 文档站重构(中英双语)
|
|
43
|
+
|
|
44
|
+
## v0.1.1 – v0.1.4 (2026-02 – 2026-04)
|
|
45
|
+
|
|
46
|
+
- 高内聚模块拆分:`single_client` / `multi_client` /
|
|
47
|
+
`async_single_client` / `async_multi_client`、`sync_*_operations`
|
|
48
|
+
混入、`config` / `async_config`、`errors`、`auth`、`leases`、`locks`、
|
|
49
|
+
`members`
|
|
50
|
+
- `client.py` / `async_client.py` 转为兼容性 shim
|
|
51
|
+
- 新增 Auth(用户/角色/权限)API
|
|
52
|
+
- 异步 managers 使用异步 channel 实现自动重连
|
|
53
|
+
- 服务示例改进(dataclass + v2 版本)
|
|
54
|
+
|
|
55
|
+
## v0.1.0
|
|
56
|
+
|
|
57
|
+
- 初始发布
|
|
58
|
+
- 支持异步客户端(`grpc.aio`)
|
|
59
|
+
- 支持同步客户端
|
|
60
|
+
- 租约、锁、观察与事务操作
|
|
61
|
+
- 多端点支持与故障转移
|
|
62
|
+
- 自动重连
|
|
63
|
+
- 监控指标
|
|
@@ -8,6 +8,7 @@ from etcd3.shared import Transactions
|
|
|
8
8
|
from etcd3.async_single_client import AsyncEtcd3Client
|
|
9
9
|
from etcd3.async_multi_client import AsyncMultiEndpointEtcd3Client
|
|
10
10
|
from etcd3.async_endpoint import AsyncEndpoint
|
|
11
|
+
from etcd3.async_endpoint import AsyncEndpointConfig
|
|
11
12
|
from etcd3.async_client_extensions import get_request_context, set_request_context
|
|
12
13
|
from etcd3.exceptions import Etcd3Exception
|
|
13
14
|
from etcd3.observability import ObservabilityConfig
|
|
@@ -50,7 +51,7 @@ from etcd3.async_single_client import async_client
|
|
|
50
51
|
|
|
51
52
|
__author__ = "owasd"
|
|
52
53
|
__email__ = ""
|
|
53
|
-
__version__ = "0.
|
|
54
|
+
__version__ = "0.2.0"
|
|
54
55
|
|
|
55
56
|
__all__ = (
|
|
56
57
|
"etcdrpc",
|
|
@@ -71,6 +72,7 @@ __all__ = (
|
|
|
71
72
|
"Permission",
|
|
72
73
|
"MultiEndpointEtcd3Client",
|
|
73
74
|
"AsyncEndpoint",
|
|
75
|
+
"AsyncEndpointConfig",
|
|
74
76
|
"AsyncEtcd3Client",
|
|
75
77
|
"AsyncMultiEndpointEtcd3Client",
|
|
76
78
|
"async_client",
|
|
@@ -8,6 +8,9 @@ For new code, prefer importing from:
|
|
|
8
8
|
- etcd3.async_endpoint: AsyncEndpoint
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import sys
|
|
12
|
+
import types
|
|
13
|
+
|
|
11
14
|
# Re-export config classes
|
|
12
15
|
from etcd3.async_config import AsyncClientConfig, AsyncMultiEndpointConfig
|
|
13
16
|
from etcd3.config import RangeRequestConfig
|
|
@@ -48,3 +51,21 @@ __all__ = [
|
|
|
48
51
|
"_async_handle_keepalive_errors",
|
|
49
52
|
"async_client",
|
|
50
53
|
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _CallableModule(types.ModuleType):
|
|
57
|
+
"""Module that can also be awaited like the factory function it re-exports.
|
|
58
|
+
|
|
59
|
+
``import etcd3.async_client`` makes Python rebind the
|
|
60
|
+
``etcd3.async_client`` attribute from the factory function to this
|
|
61
|
+
module. Without this, the documented ``await etcd3.async_client(...)``
|
|
62
|
+
call would break for anyone who imported the compatibility shim.
|
|
63
|
+
Calling the module delegates to the factory function, so both
|
|
64
|
+
spellings keep working.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __call__(self, *args, **kwargs):
|
|
68
|
+
return async_client(*args, **kwargs)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
sys.modules[__name__].__class__ = _CallableModule
|