python-etcd3-async 0.2.0__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.2.0 → python_etcd3_async-0.2.1}/.cnb.yml +7 -0
- python_etcd3_async-0.2.1/.gitignore +18 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/AGENTS.md +16 -2
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/CLAUDE.md +9 -4
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/PKG-INFO +29 -6
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/README.md +9 -3
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/README_CN.md +9 -3
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/getting-started/installation.md +6 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/advanced_queries.md +23 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/configuration.md +7 -1
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/index.md +1 -1
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/operations/async_error_handling.md +12 -2
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/operations/auto_reconnection.md +9 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/resources/history.md +38 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/resources/releasing.md +24 -3
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/getting-started/installation.md +5 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/advanced_queries.md +23 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/configuration.md +5 -1
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/index.md +1 -1
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/operations/async_error_handling.md +9 -2
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/operations/auto_reconnection.md +7 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/operations/error_handling.md +9 -5
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/operations/failover.md +5 -1
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/resources/history.md +31 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/resources/releasing.md +19 -2
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/__init__.py +1 -1
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_client_extensions.py +27 -13
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_multi_client.py +7 -6
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/sync_kv_operations.py +26 -39
- 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_grpc_compat.py +160 -0
- python_etcd3_async-0.2.1/tests/test_retry_behavior.py +218 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/uv.lock +11 -5
- python_etcd3_async-0.2.0/.gitignore +0 -18
- python_etcd3_async-0.2.0/gen_protos.py +0 -69
- python_etcd3_async-0.2.0/pyproject.toml +0 -72
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/.coverage +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/.pylintrc +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/.python-version +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/SKILL.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/assets/stylesheets/extra.css +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/authentication.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/cluster_maintenance.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/leader_election.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/leases_locks.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/service_discovery.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/service_registration.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/sync_client.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/transactions.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/usage.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/guide/watching.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/operations/error_handling.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/operations/failover.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/operations/monitoring.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/operations/observability.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/resources/authors.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/en/resources/contributing.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/superpowers/plans/2026-04-03-high-cohesion-low-coupling-plan.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/superpowers/specs/2026-04-03-high-cohesion-low-coupling-design.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/authentication.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/cluster_maintenance.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/leader_election.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/leases_locks.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/service_discovery.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/service_registration.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/sync_client.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/transactions.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/usage.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/guide/watching.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/operations/monitoring.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/operations/observability.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/resources/authors.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/resources/contributing.md +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_client.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_config.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_endpoint.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_managers.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_monitor.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_single_client.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/async_watch.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/auth.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/client.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/config.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/endpoint.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/errors.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/events.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/exceptions.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/exporters/__init__.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/exporters/prometheus.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/leases.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/locks.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/log.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/log_formatters.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/log_sampling.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/members.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/multi_client.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/observability.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/shared.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/single_client.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/sync_cluster_operations.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/sync_lock_operations.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/sync_maintenance_operations.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/trace.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/trace_attributes.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/transactions.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/utils.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcd3/watch.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcdrpc/__init__.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcdrpc/auth.proto +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcdrpc/kv.proto +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/etcdrpc/rpc.proto +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/examples/leader_election.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/examples/leader_election_sync.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/examples/service_discovery.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/examples/service_discovery_sync.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/examples/service_discovery_v2.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/examples/service_registration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/examples/service_registration_sync.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/examples/service_registry_v2.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/mkdocs.yml +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/scripts/etcd-test.sh +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/__init__.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/conftest.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_async_client.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_async_client_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_async_error_handling.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_async_health_check.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_async_kv_operations_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_async_managers.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_async_watch.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_async_watch_cancel.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_auth.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_auth_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_batch_operations.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_client.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_cluster_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_endpoint_selection.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_events.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_kv_operations.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_lease_keepalive_retry.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_leases.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_leases_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_locks.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_locks_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_log_formatters.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_log_sampling.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_maintenance_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_members.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_observability_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_return_semantics.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_sync_health_check.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_tls_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_trace_attributes.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_transactions.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_transactions_integration.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_utils.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_watch.py +0 -0
- {python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/tests/test_watch_integration.py +0 -0
- {python_etcd3_async-0.2.0 → 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:
|
|
@@ -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
|
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-etcd3-async
|
|
3
|
-
Version: 0.2.
|
|
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.2.0 → 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
|
|
|
@@ -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
|
+
## 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
|
+
|
|
52
75
|
## Batch Operations
|
|
53
76
|
|
|
54
77
|
The three batch entry points share one concurrency and failure policy:
|
|
@@ -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=`,
|
|
@@ -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.2.0 → 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.2.0 → python_etcd3_async-0.2.1}/docs/en/operations/auto_reconnection.md
RENAMED
|
@@ -67,6 +67,15 @@ client = AsyncMultiEndpointEtcd3Client(
|
|
|
67
67
|
)
|
|
68
68
|
```
|
|
69
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
|
+
|
|
70
79
|
Each `Endpoint` also carries `time_retry` (default 300 s) — the window in
|
|
71
80
|
which a failed endpoint stays marked failed before being considered again.
|
|
72
81
|
|
|
@@ -4,8 +4,46 @@ title: History
|
|
|
4
4
|
|
|
5
5
|
# History
|
|
6
6
|
|
|
7
|
+
## v0.2.1 (2026-11)
|
|
8
|
+
|
|
9
|
+
### grpcio / protobuf compatibility
|
|
10
|
+
|
|
11
|
+
- The `grpcio` and `protobuf` requirements are now **ranges**
|
|
12
|
+
(`grpcio>=1.76.0`, `protobuf>=6.31.1`) instead of exact pins, so the
|
|
13
|
+
package installs next to other packages that need a different grpcio
|
|
14
|
+
- The generated protobuf/gRPC code in `etcdrpc/` is checked in, and the
|
|
15
|
+
build no longer needs protoc or grpcio-tools - it works on every
|
|
16
|
+
supported Python
|
|
17
|
+
- The generator is pinned to the supported floor in a new `codegen`
|
|
18
|
+
dependency group, so regenerating can never silently raise the floor
|
|
19
|
+
and break users on older grpcio
|
|
20
|
+
- `scripts/gen-protos.sh` regenerates the code and
|
|
21
|
+
`scripts/gen-protos.sh --check` fails when it has drifted; the check
|
|
22
|
+
runs in CI
|
|
23
|
+
- `tests/test_grpc_compat.py` pins the contract: the declared floors must
|
|
24
|
+
match the versions embedded in the generated code
|
|
25
|
+
|
|
26
|
+
### Packaging
|
|
27
|
+
|
|
28
|
+
- The package metadata now points at the repository
|
|
29
|
+
(https://cnb.cool/owasd/python3-etcd-async) and carries the license,
|
|
30
|
+
author, keywords and classifiers - the PyPI page of the previous
|
|
31
|
+
releases had no repository link at all
|
|
32
|
+
- `scripts/publish.sh` publishes a release: it reads the token from
|
|
33
|
+
`~/.pypirc` (uv does not read that file itself), refuses placeholder
|
|
34
|
+
versions, only uploads the artifacts of the current version and asks
|
|
35
|
+
for a confirmation before uploading
|
|
36
|
+
|
|
37
|
+
### Documentation
|
|
38
|
+
|
|
39
|
+
- The guides now describe the 0.2.0 behaviour: the retry policy covers
|
|
40
|
+
the idempotent read paths only, the failover rules for both clients,
|
|
41
|
+
and the shared write results
|
|
42
|
+
|
|
7
43
|
## v0.2.0 (2026-11)
|
|
8
44
|
|
|
45
|
+
### Sync and async parity
|
|
46
|
+
|
|
9
47
|
Sync and async clients aligned on behaviour that had drifted apart:
|
|
10
48
|
|
|
11
49
|
- **Endpoint selection**: the async client now applies the load balancer
|
|
@@ -39,8 +39,8 @@ and hatch reads that attribute at build time — do not add a static
|
|
|
39
39
|
uv run ruff check tests/ etcd3/ && uv run ruff format --check tests/ etcd3/
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
5. **Build the distribution** (protobuf code is
|
|
43
|
-
|
|
42
|
+
5. **Build the distribution** (the generated protobuf code is checked in,
|
|
43
|
+
so the build needs no protoc).
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
uv build
|
|
@@ -56,10 +56,31 @@ and hatch reads that attribute at build time — do not add a static
|
|
|
56
56
|
7. **Publish** to the index and tag the release:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
59
|
+
scripts/publish.sh publish # builds if needed, uploads, confirms first
|
|
60
60
|
git tag vX.Y.Z
|
|
61
61
|
git push && git push --tags
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
`scripts/publish.sh` wraps `uv publish` because **`uv` does not read
|
|
65
|
+
`~/.pypirc`**: the script takes the token from the `[pypi]` section of
|
|
66
|
+
that file and passes it through `UV_PUBLISH_TOKEN`. In CI, where
|
|
67
|
+
trusted publishing (OIDC) is configured on PyPI, no token is needed and
|
|
68
|
+
the script publishes without one.
|
|
69
|
+
|
|
70
|
+
The script refuses to publish placeholder versions (`9.9.9`, `0.0.0`,
|
|
71
|
+
non-`MAJOR.MINOR.PATCH` strings) and warns when the version is already
|
|
72
|
+
on PyPI. A published version cannot be replaced, and it becomes the
|
|
73
|
+
version that installers resolve to, so a scratch version must never
|
|
74
|
+
reach the index - use `scripts/publish.sh dry-run` to test the upload
|
|
75
|
+
path instead.
|
|
76
|
+
|
|
77
|
+
Other commands: `check` (pre-flight checks only), `dry-run` (validates
|
|
78
|
+
the upload without sending anything), `build`, `verify` (waits until the
|
|
79
|
+
release shows up on PyPI).
|
|
80
|
+
|
|
81
|
+
Overrides: `PYPI_TOKEN`, `PYPI_CONFIG`, `PYPI_SECTION`,
|
|
82
|
+
`PYPI_PUBLISH_URL`, `PYPI_CHECK_URL`, `PUBLISH_CONFIRM=yes` (skips the
|
|
83
|
+
confirmation prompt).
|
|
84
|
+
|
|
64
85
|
8. **Verify the published artifact** resolves from a clean environment
|
|
65
86
|
(`uv add python-etcd3-async==X.Y.Z` in a scratch project).
|
{python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/getting-started/installation.md
RENAMED
|
@@ -28,6 +28,11 @@ uv pip install -e .
|
|
|
28
28
|
|
|
29
29
|
- Python 3.12+
|
|
30
30
|
- etcd v3 API 服务器(推荐 v3.1+)
|
|
31
|
+
- `grpcio>=1.76.0` 与 `protobuf>=6.31.1`
|
|
32
|
+
|
|
33
|
+
grpcio 与 protobuf 的依赖是**区间**而非精确锁定,因此本包可以与需要其它
|
|
34
|
+
grpcio 版本的包共存。生成的 protobuf 代码只会拒绝比生成时**更旧**的运行时
|
|
35
|
+
版本,因此更新的 grpcio 同样可用。
|
|
31
36
|
|
|
32
37
|
## 使用 uv 管理虚拟环境
|
|
33
38
|
|
|
@@ -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
|
+
## 写操作返回值
|
|
53
|
+
|
|
54
|
+
同步与异步客户端的写操作返回形态一致:
|
|
55
|
+
|
|
56
|
+
| 调用 | 返回值 |
|
|
57
|
+
|------|--------|
|
|
58
|
+
| `put(key, value)` | 响应头(含 revision 等信息) |
|
|
59
|
+
| `put(key, value, prev_kv=True)` | 描述**旧值**的 `KVMetadata` |
|
|
60
|
+
| `delete(key)` | 删除的键数量 |
|
|
61
|
+
| `delete_prefix(prefix)` | 删除的键数量 |
|
|
62
|
+
| `delete(key, return_response=True)` | 完整的 `DeleteRangeResponse` |
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# 这次删除移除了多少个键?
|
|
66
|
+
removed = await client.delete('/mykey')
|
|
67
|
+
|
|
68
|
+
# 写入之前的值是什么?
|
|
69
|
+
previous = await client.put('/mykey', 'new', prev_kv=True)
|
|
70
|
+
print(previous.value) # b'old'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
两个客户端的 `delete_prefix` 都支持 `return_response=True`。
|
|
74
|
+
|
|
52
75
|
## 批量操作
|
|
53
76
|
|
|
54
77
|
三个批量入口共享同一套并发与失败语义:条目并行执行,并发上限由
|
|
@@ -75,12 +75,16 @@ client = AsyncMultiEndpointEtcd3Client(
|
|
|
75
75
|
| `user` | str | None | 认证用户名 |
|
|
76
76
|
| `password` | str | None | 认证密码 |
|
|
77
77
|
| `failover` | bool | False | 启用端点间自动故障转移 |
|
|
78
|
-
| `max_retries` | int | 3 |
|
|
78
|
+
| `max_retries` | int | 3 | 瞬态错误的最大重试次数(仅读路径) |
|
|
79
79
|
| `retry_backoff` | float | 0.1 | 初始重试退避时间(秒) |
|
|
80
80
|
| `retry_max_backoff` | float | 10.0 | 最大重试退避时间(秒) |
|
|
81
81
|
| `load_balancer` | LoadBalancerStrategy | PRIORITY | 负载均衡策略 |
|
|
82
82
|
| `health_check_interval` | float | 30.0 | 健康检查间隔(秒) |
|
|
83
83
|
|
|
84
|
+
`failover` 与 `load_balancer` 共同决定请求发往哪个端点:`failover=True`
|
|
85
|
+
时失败端点会被绕过,`failover=False` 时客户端留在当前端点并原地重建
|
|
86
|
+
channel。重试参数只作用于幂等读路径——写操作永远不会自动重试。
|
|
87
|
+
|
|
84
88
|
## 配置类
|
|
85
89
|
|
|
86
90
|
上述所有关键字都可以通过 `config=` 以带类型的 dataclass 传入,
|
{python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/operations/async_error_handling.md
RENAMED
|
@@ -11,7 +11,7 @@ title: 异步错误处理
|
|
|
11
11
|
etcd3 异步客户端提供强大的错误处理功能,确保在生产环境中的可靠运行。主要特性包括:
|
|
12
12
|
|
|
13
13
|
- **全面错误映射**:所有 gRPC 状态码都映射到相应的 Python 异常
|
|
14
|
-
-
|
|
14
|
+
- **指数退避重试**:在幂等读路径上自动重试瞬态错误,支持可配置退避
|
|
15
15
|
- **资源管理**:正确清理 gRPC 通道和其他资源
|
|
16
16
|
- **监控**:详细收集错误率、延迟和端点健康状况指标
|
|
17
17
|
- **故障转移支持**:发生故障时自动切换端点
|
|
@@ -41,13 +41,20 @@ etcd3 异步客户端将 gRPC 状态码映射到以下异常类型:
|
|
|
41
41
|
|
|
42
42
|
## 重试机制
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
客户端在**幂等读路径**(`get`、`get_prefix`、`get_range`、`get_all` 及其
|
|
45
|
+
`*_response` 变体)上对瞬态错误实现指数退避重试。默认情况下,它将重试以下错误类型:
|
|
45
46
|
|
|
46
47
|
- `UNAVAILABLE`(连接错误)
|
|
47
48
|
- `DEADLINE_EXCEEDED`(超时)
|
|
48
49
|
- `INTERNAL`(服务器错误)
|
|
49
50
|
- `RESOURCE_EXHAUSTED`(资源限制)
|
|
50
51
|
|
|
52
|
+
**写操作不会自动重试。** 请求报错时可能已在服务端生效,重复执行可能产生
|
|
53
|
+
重复副作用。写操作请在应用层重试,且仅在可安全重复时重试(幂等的 put)。
|
|
54
|
+
遇到 `RevisionCompactedError` 应视为需要重新同步状态,而不是盲目重试。
|
|
55
|
+
|
|
56
|
+
同步客户端遵循同样的规则。
|
|
57
|
+
|
|
51
58
|
### 重试配置
|
|
52
59
|
|
|
53
60
|
创建客户端时可以配置重试行为:
|
{python_etcd3_async-0.2.0 → python_etcd3_async-0.2.1}/docs/zh/operations/auto_reconnection.md
RENAMED
|
@@ -60,6 +60,13 @@ client = AsyncMultiEndpointEtcd3Client(
|
|
|
60
60
|
每个 `Endpoint` 还带有 `time_retry`(默认 300 秒)—
|
|
61
61
|
失败端点在被重新纳入候选之前保持"失败"标记的时间窗口。
|
|
62
62
|
|
|
63
|
+
`max_retries` 及退避参数只作用于幂等读路径。写操作永远不会自动重试,
|
|
64
|
+
详见[错误处理](error_handling.md)。
|
|
65
|
+
|
|
66
|
+
`failover=True` 时,失败端点会被绕过,客户端改用其它健康端点;
|
|
67
|
+
`failover=False` 时,客户端留在当前端点并原地重建其 channel。
|
|
68
|
+
同步与异步客户端行为一致。
|
|
69
|
+
|
|
63
70
|
## 观察恢复过程
|
|
64
71
|
|
|
65
72
|
```python
|
|
@@ -59,7 +59,7 @@ except exceptions.NoServerAvailableError as e:
|
|
|
59
59
|
## 错误处理层
|
|
60
60
|
|
|
61
61
|
- **一元调用**:由生成的错误处理器包装,把 `grpc.RpcError` 映射为
|
|
62
|
-
|
|
62
|
+
上述异常。
|
|
63
63
|
- **生成器**(watch 迭代器、range 流):错误在迭代过程中浮现时被映射;
|
|
64
64
|
watch 被正常取消时,生成器处理器可能会静默结束流。
|
|
65
65
|
- **保活流**(租约刷新):映射后的错误会传播给消费方,
|
|
@@ -67,10 +67,14 @@ except exceptions.NoServerAvailableError as e:
|
|
|
67
67
|
|
|
68
68
|
## 重试建议
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
读操作(`get`、`get_prefix`、`get_range`、`get_all` 及其
|
|
71
|
+
`*_response` 变体)在遇到瞬时失败时会自动重试,次数由
|
|
72
|
+
`max_retries`、`retry_backoff`、`retry_max_backoff` 控制。
|
|
73
|
+
|
|
74
|
+
**写操作不会自动重试。** 请求报错时可能已在服务端生效,重复执行可能
|
|
75
|
+
产生重复副作用。写操作请在应用层重试,且仅在可安全重复时重试
|
|
76
|
+
(幂等的 put)。把 `RevisionCompactedError` 视为需要重新同步状态的信号,
|
|
77
|
+
而非盲目重试。
|
|
74
78
|
|
|
75
79
|
## 另见
|
|
76
80
|
|
|
@@ -41,10 +41,14 @@ client = AsyncMultiEndpointEtcd3Client(
|
|
|
41
41
|
2. 当请求因 UNAVAILABLE、DEADLINE_EXCEEDED 或 INTERNAL 失败时,
|
|
42
42
|
端点被标记为失败
|
|
43
43
|
3. 下一次操作时,客户端按负载均衡策略选择健康端点,
|
|
44
|
-
|
|
44
|
+
并在后台重建失败端点的通道。若 `failover=False`,
|
|
45
|
+
客户端则留在当前端点,并原地重建该端点的通道
|
|
45
46
|
4. 定期健康检查(`health_check_interval`)在服务器恢复应答后
|
|
46
47
|
将端点标记为健康
|
|
47
48
|
|
|
49
|
+
两个客户端行为一致:同步与异步多端点客户端使用相同的策略和相同的
|
|
50
|
+
故障转移规则。
|
|
51
|
+
|
|
48
52
|
## 负载均衡策略
|
|
49
53
|
|
|
50
54
|
| 策略 | 描述 |
|
|
@@ -4,8 +4,39 @@ title: 更新历史
|
|
|
4
4
|
|
|
5
5
|
# 更新历史
|
|
6
6
|
|
|
7
|
+
## v0.2.1 (2026-11)
|
|
8
|
+
|
|
9
|
+
### grpcio / protobuf 兼容性
|
|
10
|
+
|
|
11
|
+
- `grpcio` 与 `protobuf` 的依赖改为**区间**
|
|
12
|
+
(`grpcio>=1.76.0`、`protobuf>=6.31.1`),不再精确锁定,因此本包可以与
|
|
13
|
+
需要其它 grpcio 版本的包共存
|
|
14
|
+
- `etcdrpc/` 下生成的 protobuf/gRPC 代码改为入库,构建时不再需要 protoc
|
|
15
|
+
或 grpcio-tools,在所有受支持的 Python 上都能构建
|
|
16
|
+
- 生成器在新增的 `codegen` 依赖组中锁定到所支持的下限,重新生成代码不会
|
|
17
|
+
悄悄抬高下限、导致使用旧版 grpcio 的用户在导入时失败
|
|
18
|
+
- `scripts/gen-protos.sh` 用于重新生成代码,
|
|
19
|
+
`scripts/gen-protos.sh --check` 在代码发生漂移时报错,该检查已接入 CI
|
|
20
|
+
- `tests/test_grpc_compat.py` 固化此约定:声明的下限必须与生成代码中嵌入的
|
|
21
|
+
版本一致
|
|
22
|
+
|
|
23
|
+
### 打包
|
|
24
|
+
|
|
25
|
+
- 包元数据现在指向代码仓库(https://cnb.cool/owasd/python3-etcd-async),
|
|
26
|
+
并补齐了许可证、作者、关键词与分类器——此前版本的 PyPI 页面完全没有
|
|
27
|
+
仓库链接
|
|
28
|
+
- `scripts/publish.sh` 用于发布:从 `~/.pypirc` 读取 token(uv 本身不读该
|
|
29
|
+
文件),拒绝占位版本号,只上传当前版本的产物,并在上传前要求确认
|
|
30
|
+
|
|
31
|
+
### 文档
|
|
32
|
+
|
|
33
|
+
- 各指南已与 0.2.0 的行为对齐:重试策略只覆盖幂等读路径、两个客户端的
|
|
34
|
+
故障转移规则,以及统一的写操作返回值
|
|
35
|
+
|
|
7
36
|
## v0.2.0 (2026-11)
|
|
8
37
|
|
|
38
|
+
### 同步 / 异步一致性
|
|
39
|
+
|
|
9
40
|
同步与异步客户端在行为差异上完成对齐:
|
|
10
41
|
|
|
11
42
|
- **端点选择**:异步客户端现在与同步客户端一样应用负载均衡策略,并在
|