python-etcd3-async 0.1.6__tar.gz → 0.2.1__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.1}/.cnb.yml +7 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/.coverage +0 -0
- python_etcd3_async-0.2.1/.gitignore +18 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/AGENTS.md +16 -2
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/CLAUDE.md +19 -7
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/PKG-INFO +29 -6
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/README.md +9 -3
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/README_CN.md +9 -3
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/getting-started/installation.md +6 -0
- python_etcd3_async-0.2.1/docs/en/guide/advanced_queries.md +101 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/configuration.md +7 -1
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/leases_locks.md +2 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/sync_client.md +9 -2
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/index.md +1 -1
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/operations/async_error_handling.md +12 -2
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/operations/auto_reconnection.md +33 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/operations/error_handling.md +10 -7
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/operations/failover.md +5 -1
- python_etcd3_async-0.2.1/docs/en/resources/history.md +110 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/resources/releasing.md +24 -3
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/getting-started/installation.md +5 -0
- python_etcd3_async-0.2.1/docs/zh/guide/advanced_queries.md +100 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/configuration.md +5 -1
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/leases_locks.md +2 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/sync_client.md +8 -2
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/index.md +1 -1
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/operations/async_error_handling.md +9 -2
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/operations/auto_reconnection.md +26 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/operations/error_handling.md +9 -5
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/operations/failover.md +5 -1
- python_etcd3_async-0.2.1/docs/zh/resources/history.md +94 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/resources/releasing.md +19 -2
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/__init__.py +3 -1
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_client.py +21 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_client_extensions.py +147 -59
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_endpoint.py +17 -3
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_managers.py +30 -5
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_multi_client.py +236 -107
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_single_client.py +18 -3
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_watch.py +125 -113
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/client.py +20 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/endpoint.py +16 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/errors.py +14 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/locks.py +49 -13
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/multi_client.py +236 -47
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/shared.py +1 -55
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/sync_kv_operations.py +69 -59
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/utils.py +0 -35
- python_etcd3_async-0.2.1/etcdrpc/auth_pb2.py +43 -0
- python_etcd3_async-0.2.1/etcdrpc/auth_pb2.pyi +44 -0
- python_etcd3_async-0.2.1/etcdrpc/auth_pb2_grpc.py +24 -0
- python_etcd3_async-0.2.1/etcdrpc/kv_pb2.py +40 -0
- python_etcd3_async-0.2.1/etcdrpc/kv_pb2.pyi +39 -0
- python_etcd3_async-0.2.1/etcdrpc/kv_pb2_grpc.py +24 -0
- python_etcd3_async-0.2.1/etcdrpc/rpc_pb2.py +237 -0
- python_etcd3_async-0.2.1/etcdrpc/rpc_pb2.pyi +811 -0
- python_etcd3_async-0.2.1/etcdrpc/rpc_pb2_grpc.py +1893 -0
- python_etcd3_async-0.2.1/pyproject.toml +113 -0
- python_etcd3_async-0.2.1/scripts/gen-protos.sh +90 -0
- python_etcd3_async-0.2.1/scripts/publish.sh +281 -0
- python_etcd3_async-0.2.1/tests/test_async_health_check.py +178 -0
- python_etcd3_async-0.2.1/tests/test_async_watch.py +87 -0
- python_etcd3_async-0.2.1/tests/test_async_watch_cancel.py +90 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_batch_operations.py +90 -8
- python_etcd3_async-0.2.1/tests/test_endpoint_selection.py +201 -0
- python_etcd3_async-0.2.1/tests/test_grpc_compat.py +160 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_locks.py +41 -0
- python_etcd3_async-0.2.1/tests/test_retry_behavior.py +218 -0
- python_etcd3_async-0.2.1/tests/test_return_semantics.py +170 -0
- python_etcd3_async-0.2.1/tests/test_sync_health_check.py +245 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/uv.lock +11 -5
- python_etcd3_async-0.1.6/.gitignore +0 -18
- python_etcd3_async-0.1.6/PHASE2_PLAN.md +0 -164
- python_etcd3_async-0.1.6/docs/en/guide/advanced_queries.md +0 -55
- python_etcd3_async-0.1.6/docs/en/resources/history.md +0 -39
- python_etcd3_async-0.1.6/docs/zh/guide/advanced_queries.md +0 -55
- python_etcd3_async-0.1.6/docs/zh/resources/history.md +0 -38
- python_etcd3_async-0.1.6/gen_protos.py +0 -69
- python_etcd3_async-0.1.6/pyproject.toml +0 -72
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/.pylintrc +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/.python-version +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/SKILL.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/assets/stylesheets/extra.css +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/authentication.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/cluster_maintenance.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/leader_election.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/service_discovery.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/service_registration.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/transactions.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/usage.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/guide/watching.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/operations/monitoring.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/operations/observability.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/resources/authors.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/resources/contributing.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/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.1}/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.1}/docs/zh/guide/authentication.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/cluster_maintenance.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/leader_election.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/service_discovery.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/service_registration.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/transactions.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/usage.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/guide/watching.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/operations/monitoring.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/operations/observability.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/resources/authors.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/zh/resources/contributing.md +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_config.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/async_monitor.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/auth.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/config.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/events.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/exceptions.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/exporters/__init__.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/exporters/prometheus.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/leases.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/log.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/log_formatters.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/log_sampling.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/members.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/observability.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/single_client.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/sync_cluster_operations.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/sync_lock_operations.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/sync_maintenance_operations.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/trace.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/trace_attributes.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/transactions.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcd3/watch.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcdrpc/__init__.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcdrpc/auth.proto +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcdrpc/kv.proto +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/etcdrpc/rpc.proto +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/examples/leader_election.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/examples/leader_election_sync.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/examples/service_discovery.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/examples/service_discovery_sync.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/examples/service_discovery_v2.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/examples/service_registration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/examples/service_registration_sync.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/examples/service_registry_v2.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/mkdocs.yml +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/scripts/etcd-test.sh +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/__init__.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/conftest.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_async_client.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_async_client_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_async_error_handling.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_async_kv_operations_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_async_managers.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_auth.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_auth_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_client.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_cluster_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_events.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_kv_operations.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_lease_keepalive_retry.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_leases.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_leases_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_locks_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_log_formatters.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_log_sampling.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_maintenance_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_members.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_observability_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_tls_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_trace_attributes.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_transactions.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_transactions_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_utils.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_watch.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/tests/test_watch_integration.py +0 -0
- {python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/usage_guide.md +0 -0
|
@@ -35,6 +35,13 @@ $:
|
|
|
35
35
|
- name: ruff format
|
|
36
36
|
script:
|
|
37
37
|
- uv run --no-sync ruff format --check etcd3/ etcdrpc/ tests/
|
|
38
|
+
- name: generated protobuf code is up to date
|
|
39
|
+
# The generated code in etcdrpc/ is checked in and carries the
|
|
40
|
+
# grpcio/protobuf version floors this package supports. Regenerate
|
|
41
|
+
# it with the pinned codegen toolchain and fail if it drifted.
|
|
42
|
+
script:
|
|
43
|
+
- uv sync --group codegen
|
|
44
|
+
- uv run --no-sync ./scripts/gen-protos.sh --check
|
|
38
45
|
|
|
39
46
|
- name: unit
|
|
40
47
|
docker:
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
# The generated protobuf/gRPC code in etcdrpc/ is checked in on purpose:
|
|
9
|
+
# it embeds GRPC_GENERATED_VERSION, which is the floor of the grpcio range
|
|
10
|
+
# this package supports. Regenerate it with scripts/gen-protos.sh.
|
|
11
|
+
.trae/documents/*
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
htmlcov/
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv
|
|
17
|
+
/site
|
|
18
|
+
.etcd-tls/
|
|
@@ -67,10 +67,24 @@ uv run ruff format etcd3/ etcdrpc/ # Format
|
|
|
67
67
|
|
|
68
68
|
### Other
|
|
69
69
|
```bash
|
|
70
|
-
|
|
70
|
+
./scripts/gen-protos.sh # Regenerate the protobuf/gRPC code in etcdrpc/
|
|
71
71
|
uv run mkdocs serve # Serve docs
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
### Protobuf / gRPC version floors
|
|
75
|
+
- `etcdrpc/*_pb2*.py` is **generated code that is checked in**. The
|
|
76
|
+
`grpcio` and `protobuf` floors in `pyproject.toml` must equal the
|
|
77
|
+
versions embedded in it (`GRPC_GENERATED_VERSION` and the protobuf
|
|
78
|
+
gencode version).
|
|
79
|
+
- The `codegen` dependency group pins the generator to that floor, so
|
|
80
|
+
regenerating never silently raises it. `scripts/gen-protos.sh` uses it.
|
|
81
|
+
- The runtime dependencies are **ranges** (`grpcio>=...`, `protobuf>=...`):
|
|
82
|
+
the generated code only rejects a runtime *older* than the floor, so
|
|
83
|
+
newer versions are fine and the package can be installed next to other
|
|
84
|
+
packages that need a different grpcio.
|
|
85
|
+
- `tests/test_grpc_compat.py` fails if the floors and the codegen pin
|
|
86
|
+
drift apart.
|
|
87
|
+
|
|
74
88
|
### Versioning
|
|
75
89
|
- Single source of truth: `__version__` in `etcd3/__init__.py`
|
|
76
90
|
- `pyproject.toml` uses `dynamic = ["version"]` via
|
|
@@ -179,7 +193,7 @@ etcd3/
|
|
|
179
193
|
log.py / log_formatters.py / log_sampling.py # Logging
|
|
180
194
|
observability.py # ObservabilityConfig / configure_from_env / auto_init
|
|
181
195
|
exporters/ # Prometheus exporter
|
|
182
|
-
etcdrpc/ #
|
|
196
|
+
etcdrpc/ # .proto sources + the generated protobuf/gRPC code
|
|
183
197
|
tests/ # Test files
|
|
184
198
|
```
|
|
185
199
|
|
|
@@ -82,9 +82,10 @@ uv run ruff format etcd3/ etcdrpc/
|
|
|
82
82
|
|
|
83
83
|
### Protobuf Generation
|
|
84
84
|
```bash
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
# The generated code is checked in; regenerate it with:
|
|
86
|
+
./scripts/gen-protos.sh
|
|
87
|
+
# CI fails if the checked-in code drifts from the .proto files:
|
|
88
|
+
./scripts/gen-protos.sh --check
|
|
88
89
|
```
|
|
89
90
|
|
|
90
91
|
### Environment Variables
|
|
@@ -212,7 +213,11 @@ Generated gRPC/Python bindings from etcd proto files:
|
|
|
212
213
|
- `kv_pb2.py`, `kv_pb2_grpc.py` - Key-value service
|
|
213
214
|
- `auth_pb2.py`, `auth_pb2_grpc.py` - Authentication service
|
|
214
215
|
|
|
215
|
-
|
|
216
|
+
The generated code is checked in (it embeds `GRPC_GENERATED_VERSION`,
|
|
217
|
+
the oldest grpcio this package supports) and regenerated with
|
|
218
|
+
`scripts/gen-protos.sh` using the pinned `codegen` toolchain. The runtime
|
|
219
|
+
dependencies are ranges, so newer grpcio/protobuf versions work and the
|
|
220
|
+
package installs next to other packages with a different grpcio.
|
|
216
221
|
|
|
217
222
|
### Examples (`examples/`)
|
|
218
223
|
|
|
@@ -266,7 +271,11 @@ auto_init() # Auto-initialize at import time
|
|
|
266
271
|
- Error handlers are created via `create_sync_error_handlers()` and `create_async_error_handlers()` factories from `errors.py`
|
|
267
272
|
- `_sync_handle_keepalive_errors` / `_async_handle_keepalive_errors` - Lease keepalive with `propagate_errors=True`
|
|
268
273
|
|
|
269
|
-
3. **Endpoint failover:** `MultiEndpointEtcd3Client`
|
|
274
|
+
3. **Endpoint failover:** Both `MultiEndpointEtcd3Client` and
|
|
275
|
+
`AsyncMultiEndpointEtcd3Client` track failed endpoints. With
|
|
276
|
+
`failover=True` a failed endpoint is bypassed and another healthy
|
|
277
|
+
endpoint is picked; with `failover=False` the client stays on the
|
|
278
|
+
endpoint it holds and rebuilds that endpoint's channel in place.
|
|
270
279
|
|
|
271
280
|
4. **Load balancing:** Multiple strategies supported via `LoadBalancerStrategy`:
|
|
272
281
|
- `PRIORITY` (default): Keep current endpoint if healthy, failover on failure
|
|
@@ -274,11 +283,14 @@ auto_init() # Auto-initialize at import time
|
|
|
274
283
|
- `RANDOM`: Random endpoint selection
|
|
275
284
|
- `STICKY`: Maintain connection to the same endpoint
|
|
276
285
|
|
|
277
|
-
5. **Auto-reconnection:** Async has `_get_channel()
|
|
286
|
+
5. **Auto-reconnection:** Async has `_get_channel()` / `_acquire_channel()`,
|
|
287
|
+
sync has a matching `_get_channel()`, both with a background health probe
|
|
278
288
|
|
|
279
289
|
6. **Lazy authentication:** Async uses `_init_auth()` with `asyncio.Lock()`, sync initializes in `__init__`
|
|
280
290
|
|
|
281
|
-
7. **Watch pattern:** Iterator + cancel function returns for both sync and
|
|
291
|
+
7. **Watch pattern:** Iterator + cancel function returns for both sync and
|
|
292
|
+
async. Cancelling sends a real cancel request to the server and unblocks
|
|
293
|
+
a pending iteration.
|
|
282
294
|
|
|
283
295
|
8. **Request context tracking:** Use `client.context()` to track operations across async tasks:
|
|
284
296
|
```python
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-etcd3-async
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: etcd3 async sdk
|
|
5
|
+
Project-URL: Repository, https://cnb.cool/owasd/python3-etcd-async
|
|
6
|
+
Project-URL: Source, https://cnb.cool/owasd/python3-etcd-async
|
|
7
|
+
Project-URL: Homepage, https://cnb.cool/owasd/python3-etcd-async
|
|
8
|
+
Project-URL: Issues, https://cnb.cool/owasd/python3-etcd-async/issues
|
|
9
|
+
Project-URL: Changelog, https://cnb.cool/owasd/python3-etcd-async/blob/main/docs/en/resources/history.md
|
|
10
|
+
Author: owasd
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
Keywords: async,etcd,etcd3,grpc,kubernetes
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Database
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
5
22
|
Requires-Python: <4,>=3.12
|
|
6
|
-
Requires-Dist: grpcio
|
|
23
|
+
Requires-Dist: grpcio>=1.76.0
|
|
7
24
|
Requires-Dist: prometheus-client>=0.24.1
|
|
8
|
-
Requires-Dist: protobuf
|
|
25
|
+
Requires-Dist: protobuf>=6.31.1
|
|
9
26
|
Description-Content-Type: text/markdown
|
|
10
27
|
|
|
11
28
|
# python-etcd3-async
|
|
@@ -23,7 +40,7 @@ Python async client for the etcd API v3, supporting both synchronous and asynchr
|
|
|
23
40
|
- **Async/Sync dual support**: Same API for both async and sync usage with feature parity
|
|
24
41
|
- **Automatic reconnection**: Auto-rebuild stale connections (e.g., after etcd server restart)
|
|
25
42
|
- **Keepalive error propagation**: Lease keepalive errors are properly propagated for connection loss detection
|
|
26
|
-
- **Endpoint failover**: Built-in support for multiple endpoints with automatic failover
|
|
43
|
+
- **Endpoint failover**: Built-in support for multiple endpoints with opt-in automatic failover (`failover=True`)
|
|
27
44
|
- **Context manager**: Clean resource management for both sync and async
|
|
28
45
|
- **Observability**: Structured logging, OpenTelemetry tracing, and Prometheus metrics
|
|
29
46
|
|
|
@@ -272,8 +289,14 @@ client = await async_client(
|
|
|
272
289
|
)
|
|
273
290
|
|
|
274
291
|
# Multiple endpoints with failover
|
|
275
|
-
|
|
276
|
-
|
|
292
|
+
# (the single-endpoint factory does not take endpoints=)
|
|
293
|
+
from etcd3 import AsyncMultiEndpointEtcd3Client, AsyncEndpoint
|
|
294
|
+
|
|
295
|
+
client = AsyncMultiEndpointEtcd3Client(
|
|
296
|
+
endpoints=[
|
|
297
|
+
AsyncEndpoint('localhost', 2379, secure=False),
|
|
298
|
+
AsyncEndpoint('localhost', 22379, secure=False),
|
|
299
|
+
],
|
|
277
300
|
failover=True, # Automatically switch to healthy endpoint
|
|
278
301
|
)
|
|
279
302
|
|
|
@@ -13,7 +13,7 @@ Python async client for the etcd API v3, supporting both synchronous and asynchr
|
|
|
13
13
|
- **Async/Sync dual support**: Same API for both async and sync usage with feature parity
|
|
14
14
|
- **Automatic reconnection**: Auto-rebuild stale connections (e.g., after etcd server restart)
|
|
15
15
|
- **Keepalive error propagation**: Lease keepalive errors are properly propagated for connection loss detection
|
|
16
|
-
- **Endpoint failover**: Built-in support for multiple endpoints with automatic failover
|
|
16
|
+
- **Endpoint failover**: Built-in support for multiple endpoints with opt-in automatic failover (`failover=True`)
|
|
17
17
|
- **Context manager**: Clean resource management for both sync and async
|
|
18
18
|
- **Observability**: Structured logging, OpenTelemetry tracing, and Prometheus metrics
|
|
19
19
|
|
|
@@ -262,8 +262,14 @@ client = await async_client(
|
|
|
262
262
|
)
|
|
263
263
|
|
|
264
264
|
# Multiple endpoints with failover
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
# (the single-endpoint factory does not take endpoints=)
|
|
266
|
+
from etcd3 import AsyncMultiEndpointEtcd3Client, AsyncEndpoint
|
|
267
|
+
|
|
268
|
+
client = AsyncMultiEndpointEtcd3Client(
|
|
269
|
+
endpoints=[
|
|
270
|
+
AsyncEndpoint('localhost', 2379, secure=False),
|
|
271
|
+
AsyncEndpoint('localhost', 22379, secure=False),
|
|
272
|
+
],
|
|
267
273
|
failover=True, # Automatically switch to healthy endpoint
|
|
268
274
|
)
|
|
269
275
|
|
|
@@ -13,7 +13,7 @@ Python etcd API v3 异步客户端,支持同步和异步操作。
|
|
|
13
13
|
- **同步/异步双模式**: 同一套 API 同时支持 sync 和 async,功能完全对等
|
|
14
14
|
- **自动重连**: 自动重建失效连接(如 etcd 服务器重启后)
|
|
15
15
|
- **Keepalive 错误传播**: 租约保活错误正确传播,便于连接断开检测
|
|
16
|
-
- **端点故障转移**:
|
|
16
|
+
- **端点故障转移**: 支持多节点,可选开启自动故障转移(`failover=True`)
|
|
17
17
|
- **上下文管理**: 资源清理更加简洁
|
|
18
18
|
- **可观测性**: 结构化日志、OpenTelemetry 追踪、Prometheus 指标
|
|
19
19
|
|
|
@@ -262,8 +262,14 @@ client = await async_client(
|
|
|
262
262
|
)
|
|
263
263
|
|
|
264
264
|
# 多节点故障转移
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
# (单端点工厂函数不接受 endpoints= 参数)
|
|
266
|
+
from etcd3 import AsyncMultiEndpointEtcd3Client, AsyncEndpoint
|
|
267
|
+
|
|
268
|
+
client = AsyncMultiEndpointEtcd3Client(
|
|
269
|
+
endpoints=[
|
|
270
|
+
AsyncEndpoint('localhost', 2379, secure=False),
|
|
271
|
+
AsyncEndpoint('localhost', 22379, secure=False),
|
|
272
|
+
],
|
|
267
273
|
failover=True, # 自动切换到健康节点
|
|
268
274
|
)
|
|
269
275
|
|
{python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/getting-started/installation.md
RENAMED
|
@@ -28,6 +28,12 @@ uv pip install -e .
|
|
|
28
28
|
|
|
29
29
|
- Python 3.12+
|
|
30
30
|
- etcd v3 API server (v3.1+ recommended)
|
|
31
|
+
- `grpcio>=1.76.0` and `protobuf>=6.31.1`
|
|
32
|
+
|
|
33
|
+
The grpcio and protobuf requirements are **ranges**, not exact pins, so the
|
|
34
|
+
package installs next to other packages that need a different grpcio. The
|
|
35
|
+
generated protobuf code only rejects a runtime that is *older* than the
|
|
36
|
+
version it was generated with, so newer grpcio releases work as well.
|
|
31
37
|
|
|
32
38
|
## Virtual Environments with uv
|
|
33
39
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Advanced Queries
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Advanced Queries
|
|
6
|
+
|
|
7
|
+
## Range Options
|
|
8
|
+
|
|
9
|
+
Range options are passed as keyword arguments to any range-based call
|
|
10
|
+
(`get`, `get_prefix`, `get_range`, `get_all` and their `*_response` variants);
|
|
11
|
+
they map to the fields of `RangeRequestConfig`.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
# Range options as keyword arguments
|
|
15
|
+
resp = await client.get_prefix_response(
|
|
16
|
+
'/app/',
|
|
17
|
+
limit=100, # Limit number of results
|
|
18
|
+
revision=5, # Historical revision
|
|
19
|
+
count_only=True, # Only return count
|
|
20
|
+
sort_order='ascend', # 'ascend' or 'descend'
|
|
21
|
+
sort_target='key', # 'key' | 'version' | 'create' | 'mod'
|
|
22
|
+
)
|
|
23
|
+
print(f'{resp.count} keys under /app/')
|
|
24
|
+
|
|
25
|
+
# Serializable read (hit the follower, lower latency)
|
|
26
|
+
value, meta = await client.get('/mykey', serializable=True)
|
|
27
|
+
|
|
28
|
+
# Keys only (no values transferred)
|
|
29
|
+
async for value, meta in client.get_prefix('/app/', keys_only=True):
|
|
30
|
+
print(meta.key)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Other available options: `min_mod_revision`, `max_mod_revision`,
|
|
34
|
+
`min_create_revision`, `max_create_revision`.
|
|
35
|
+
|
|
36
|
+
## Prefix Query
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# Get all keys with prefix
|
|
40
|
+
async for value, meta in client.get_prefix('/app/'):
|
|
41
|
+
print(f'{meta.key}: {value}')
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Range Query
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Get keys in range [start, end)
|
|
48
|
+
async for value, meta in client.get_range('/start', '/end'):
|
|
49
|
+
print(f'{meta.key}: {value}')
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Write Results
|
|
53
|
+
|
|
54
|
+
The sync and the async client return the same shapes for writes:
|
|
55
|
+
|
|
56
|
+
| Call | Result |
|
|
57
|
+
|------|--------|
|
|
58
|
+
| `put(key, value)` | the response header (revision, etc.) |
|
|
59
|
+
| `put(key, value, prev_kv=True)` | `KVMetadata` describing the **previous** value |
|
|
60
|
+
| `delete(key)` | the number of deleted keys |
|
|
61
|
+
| `delete_prefix(prefix)` | the number of deleted keys |
|
|
62
|
+
| `delete(key, return_response=True)` | the full `DeleteRangeResponse` |
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# How many keys did that delete remove?
|
|
66
|
+
removed = await client.delete('/mykey')
|
|
67
|
+
|
|
68
|
+
# What was stored before this write?
|
|
69
|
+
previous = await client.put('/mykey', 'new', prev_kv=True)
|
|
70
|
+
print(previous.value) # b'old'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`delete_prefix` accepts `return_response=True` on both clients as well.
|
|
74
|
+
|
|
75
|
+
## Batch Operations
|
|
76
|
+
|
|
77
|
+
The three batch entry points share one concurrency and failure policy:
|
|
78
|
+
items run in parallel, the concurrency ceiling is controlled by
|
|
79
|
+
`concurrency`, and a failing item never aborts the whole batch.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Atomic transaction: all succeed or none do
|
|
83
|
+
count = await client.kv.batch_put([('/a', '1'), ('/b', '2')])
|
|
84
|
+
|
|
85
|
+
# Parallel execution: at most 10 at a time, failures only affect the count
|
|
86
|
+
count = await client.kv.batch_put(items, atomic=False, concurrency=10)
|
|
87
|
+
|
|
88
|
+
# Returns (success_count, [(failed_key, error), ...])
|
|
89
|
+
success, failed = await client.kv.put_many(items, concurrency=10)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`batch_put` and `put_many` accept either an iterable of `(key, value)`
|
|
93
|
+
tuples or a mapping; `batch_delete` accepts an iterable of keys.
|
|
94
|
+
|
|
95
|
+
The effective ceiling for `concurrency` is the client's `max_concurrent`
|
|
96
|
+
(default 100): callers may lower it, never raise it.
|
|
97
|
+
|
|
98
|
+
## See Also
|
|
99
|
+
|
|
100
|
+
- [API Usage](usage.md)
|
|
101
|
+
- [Configuration](configuration.md)
|
|
@@ -75,12 +75,18 @@ client = AsyncMultiEndpointEtcd3Client(
|
|
|
75
75
|
| `user` | str | None | Username for authentication |
|
|
76
76
|
| `password` | str | None | Password for authentication |
|
|
77
77
|
| `failover` | bool | False | Enable automatic failover between endpoints |
|
|
78
|
-
| `max_retries` | int | 3 | Maximum retries for transient errors |
|
|
78
|
+
| `max_retries` | int | 3 | Maximum retries for transient errors (read paths only) |
|
|
79
79
|
| `retry_backoff` | float | 0.1 | Initial retry backoff in seconds |
|
|
80
80
|
| `retry_max_backoff` | float | 10.0 | Maximum retry backoff in seconds |
|
|
81
81
|
| `load_balancer` | LoadBalancerStrategy | PRIORITY | Load balancing strategy |
|
|
82
82
|
| `health_check_interval` | float | 30.0 | Health check interval in seconds |
|
|
83
83
|
|
|
84
|
+
`failover` and `load_balancer` decide which endpoint a request goes to:
|
|
85
|
+
with `failover=True` a failed endpoint is bypassed, with `failover=False`
|
|
86
|
+
the client stays on its endpoint and rebuilds the channel in place. The
|
|
87
|
+
retry settings apply to the idempotent read paths only — writes are
|
|
88
|
+
never retried automatically.
|
|
89
|
+
|
|
84
90
|
## Configuration Classes
|
|
85
91
|
|
|
86
92
|
Every keyword above can also be passed as a typed dataclass via `config=`,
|
|
@@ -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
|
|
@@ -10,7 +10,7 @@ python-etcd3-async is an async Python client library for the etcd v3 API based o
|
|
|
10
10
|
|
|
11
11
|
- **Async First**: Pure async operations using `grpc.aio`
|
|
12
12
|
- **Sync Support**: Synchronous client API also available
|
|
13
|
-
- **Failover**: Multi-endpoint support with automatic failover
|
|
13
|
+
- **Failover**: Multi-endpoint support with opt-in automatic failover
|
|
14
14
|
- **Auto Reconnection**: Automatic channel rebuild on connection failure
|
|
15
15
|
- **Complete API**: Full support for all major etcd v3 features
|
|
16
16
|
|
{python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/docs/en/operations/async_error_handling.md
RENAMED
|
@@ -22,7 +22,7 @@ This guide provides comprehensive information on the error handling mechanisms o
|
|
|
22
22
|
The etcd3 async client provides robust error handling capabilities to ensure reliable operation in production environments. Key features include:
|
|
23
23
|
|
|
24
24
|
- **Comprehensive error mapping**: All gRPC status codes are mapped to appropriate Python exceptions
|
|
25
|
-
- **Exponential backoff retry**:
|
|
25
|
+
- **Exponential backoff retry**: Transient errors are retried on the idempotent read paths
|
|
26
26
|
- **Resource management**: Proper cleanup of gRPC channels and other resources
|
|
27
27
|
- **Monitoring**: Detailed metrics collection for error rates, latencies, and endpoint health
|
|
28
28
|
- **Failover support**: Automatic switching between endpoints when failures occur
|
|
@@ -52,13 +52,23 @@ The etcd3 async client maps gRPC status codes to the following exception types:
|
|
|
52
52
|
|
|
53
53
|
## Retry Mechanism
|
|
54
54
|
|
|
55
|
-
The
|
|
55
|
+
The client retries transient errors on the **idempotent read paths** (`get`,
|
|
56
|
+
`get_prefix`, `get_range`, `get_all` and their `*_response` variants).
|
|
57
|
+
By default, it will retry the following error types:
|
|
56
58
|
|
|
57
59
|
- `UNAVAILABLE` (connection errors)
|
|
58
60
|
- `DEADLINE_EXCEEDED` (timeouts)
|
|
59
61
|
- `INTERNAL` (server errors)
|
|
60
62
|
- `RESOURCE_EXHAUSTED` (resource constraints)
|
|
61
63
|
|
|
64
|
+
**Writes are not retried automatically.** A failed request may already have
|
|
65
|
+
been applied server-side, so repeating it could duplicate the effect.
|
|
66
|
+
Retry writes in the application, and only where they are safe to repeat
|
|
67
|
+
(idempotent puts). Treat `RevisionCompactedError` as a signal to resync
|
|
68
|
+
state rather than to retry blindly.
|
|
69
|
+
|
|
70
|
+
The same rules apply to the sync client.
|
|
71
|
+
|
|
62
72
|
### Retry Configuration
|
|
63
73
|
|
|
64
74
|
You can configure the retry behavior when creating the client:
|
{python_etcd3_async-0.1.6 → python_etcd3_async-0.2.1}/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:
|
|
@@ -43,6 +67,15 @@ client = AsyncMultiEndpointEtcd3Client(
|
|
|
43
67
|
)
|
|
44
68
|
```
|
|
45
69
|
|
|
70
|
+
`max_retries` and the backoff settings apply to the idempotent read paths.
|
|
71
|
+
Writes are never retried automatically — see
|
|
72
|
+
[Error Handling](error_handling.md#retry-guidance).
|
|
73
|
+
|
|
74
|
+
With `failover=True` a failed endpoint is bypassed and the client picks
|
|
75
|
+
another healthy one. With `failover=False` the client stays on the
|
|
76
|
+
endpoint it holds and rebuilds that endpoint's channel in place. The sync
|
|
77
|
+
and the async client behave the same way.
|
|
78
|
+
|
|
46
79
|
Each `Endpoint` also carries `time_retry` (default 300 s) — the window in
|
|
47
80
|
which a failed endpoint stays marked failed before being considered again.
|
|
48
81
|
|
|
@@ -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 |
|