python-etcd3-async 0.1.5__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.2.0/.cnb.yml +127 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/.gitignore +1 -0
- python_etcd3_async-0.2.0/AGENTS.md +240 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/CLAUDE.md +58 -19
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/PKG-INFO +1 -1
- python_etcd3_async-0.2.0/docs/en/getting-started/installation.md +73 -0
- python_etcd3_async-0.2.0/docs/en/guide/advanced_queries.md +78 -0
- python_etcd3_async-0.2.0/docs/en/guide/authentication.md +116 -0
- python_etcd3_async-0.2.0/docs/en/guide/cluster_maintenance.md +130 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/guide/configuration.md +52 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/guide/leader_election.md +1 -1
- python_etcd3_async-0.2.0/docs/en/guide/leases_locks.md +189 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/guide/service_discovery.md +1 -1
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/guide/service_registration.md +1 -1
- python_etcd3_async-0.2.0/docs/en/guide/sync_client.md +145 -0
- python_etcd3_async-0.2.0/docs/en/guide/transactions.md +136 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/guide/usage.md +13 -7
- python_etcd3_async-0.2.0/docs/en/guide/watching.md +145 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/index.md +6 -0
- python_etcd3_async-0.2.0/docs/en/operations/auto_reconnection.md +108 -0
- python_etcd3_async-0.2.0/docs/en/operations/error_handling.md +88 -0
- python_etcd3_async-0.2.0/docs/en/operations/failover.md +78 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/operations/monitoring.md +20 -13
- python_etcd3_async-0.2.0/docs/en/resources/contributing.md +46 -0
- python_etcd3_async-0.2.0/docs/en/resources/history.md +72 -0
- python_etcd3_async-0.2.0/docs/en/resources/releasing.md +65 -0
- python_etcd3_async-0.2.0/docs/zh/getting-started/installation.md +73 -0
- python_etcd3_async-0.2.0/docs/zh/guide/advanced_queries.md +77 -0
- python_etcd3_async-0.2.0/docs/zh/guide/authentication.md +112 -0
- python_etcd3_async-0.2.0/docs/zh/guide/cluster_maintenance.md +129 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/guide/configuration.md +50 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/guide/leader_election.md +1 -1
- python_etcd3_async-0.2.0/docs/zh/guide/leases_locks.md +182 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/guide/service_discovery.md +1 -1
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/guide/service_registration.md +1 -1
- python_etcd3_async-0.2.0/docs/zh/guide/sync_client.md +142 -0
- python_etcd3_async-0.2.0/docs/zh/guide/transactions.md +133 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/guide/usage.md +13 -7
- python_etcd3_async-0.2.0/docs/zh/guide/watching.md +140 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/index.md +6 -0
- python_etcd3_async-0.2.0/docs/zh/operations/auto_reconnection.md +96 -0
- python_etcd3_async-0.2.0/docs/zh/operations/error_handling.md +80 -0
- python_etcd3_async-0.2.0/docs/zh/operations/failover.md +70 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/operations/monitoring.md +20 -13
- python_etcd3_async-0.2.0/docs/zh/resources/contributing.md +45 -0
- python_etcd3_async-0.2.0/docs/zh/resources/history.md +63 -0
- python_etcd3_async-0.2.0/docs/zh/resources/releasing.md +63 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/__init__.py +3 -1
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_client.py +21 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_client_extensions.py +132 -58
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_endpoint.py +17 -3
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_managers.py +37 -7
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_multi_client.py +265 -110
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_single_client.py +18 -3
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_watch.py +125 -113
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/client.py +20 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/endpoint.py +16 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/errors.py +99 -18
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/locks.py +49 -13
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/multi_client.py +260 -64
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/shared.py +10 -62
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/sync_cluster_operations.py +7 -2
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/sync_kv_operations.py +53 -30
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/utils.py +0 -35
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/pyproject.toml +23 -1
- python_etcd3_async-0.2.0/scripts/etcd-test.sh +191 -0
- python_etcd3_async-0.2.0/tests/conftest.py +244 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_async_client.py +15 -35
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_async_client_integration.py +19 -67
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_async_error_handling.py +26 -42
- python_etcd3_async-0.2.0/tests/test_async_health_check.py +178 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_async_kv_operations_integration.py +14 -59
- 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.2.0/tests/test_auth.py +186 -0
- python_etcd3_async-0.2.0/tests/test_auth_integration.py +427 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_batch_operations.py +101 -38
- python_etcd3_async-0.2.0/tests/test_cluster_integration.py +69 -0
- python_etcd3_async-0.2.0/tests/test_endpoint_selection.py +201 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_kv_operations.py +75 -74
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_lease_keepalive_retry.py +79 -35
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_leases_integration.py +36 -58
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_locks.py +41 -0
- python_etcd3_async-0.2.0/tests/test_locks_integration.py +100 -0
- python_etcd3_async-0.2.0/tests/test_maintenance_integration.py +40 -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.2.0/tests/test_tls_integration.py +58 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_transactions_integration.py +24 -74
- python_etcd3_async-0.2.0/tests/test_watch_integration.py +117 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/uv.lock +0 -1
- python_etcd3_async-0.1.5/AGENTS.md +0 -172
- python_etcd3_async-0.1.5/docs/en/getting-started/installation.md +0 -35
- python_etcd3_async-0.1.5/docs/en/guide/advanced_queries.md +0 -43
- python_etcd3_async-0.1.5/docs/en/operations/auto_reconnection.md +0 -34
- python_etcd3_async-0.1.5/docs/en/operations/error_handling.md +0 -37
- python_etcd3_async-0.1.5/docs/en/operations/failover.md +0 -54
- python_etcd3_async-0.1.5/docs/en/resources/contributing.md +0 -37
- python_etcd3_async-0.1.5/docs/en/resources/history.md +0 -18
- python_etcd3_async-0.1.5/docs/zh/getting-started/installation.md +0 -35
- python_etcd3_async-0.1.5/docs/zh/guide/advanced_queries.md +0 -43
- python_etcd3_async-0.1.5/docs/zh/operations/auto_reconnection.md +0 -34
- python_etcd3_async-0.1.5/docs/zh/operations/error_handling.md +0 -37
- python_etcd3_async-0.1.5/docs/zh/operations/failover.md +0 -54
- python_etcd3_async-0.1.5/docs/zh/resources/contributing.md +0 -37
- python_etcd3_async-0.1.5/docs/zh/resources/history.md +0 -18
- python_etcd3_async-0.1.5/tests/test_auth.py +0 -86
- python_etcd3_async-0.1.5/tests/test_auth_integration.py +0 -291
- python_etcd3_async-0.1.5/tests/test_locks_integration.py +0 -147
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/.coverage +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/.pylintrc +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/.python-version +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/README.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/README_CN.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/SKILL.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/assets/stylesheets/extra.css +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/operations/observability.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/en/resources/authors.md +0 -0
- {python_etcd3_async-0.1.5 → 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.5 → 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.5 → python_etcd3_async-0.2.0}/docs/zh/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/operations/observability.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/docs/zh/resources/authors.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_config.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/async_monitor.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/auth.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/config.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/events.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/exceptions.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/exporters/__init__.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/exporters/prometheus.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/leases.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/log.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/log_formatters.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/log_sampling.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/members.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/observability.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/single_client.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/sync_lock_operations.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/sync_maintenance_operations.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/trace.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/trace_attributes.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/transactions.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcd3/watch.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcdrpc/__init__.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcdrpc/auth.proto +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcdrpc/kv.proto +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/etcdrpc/rpc.proto +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/examples/leader_election.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/examples/leader_election_sync.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/examples/service_discovery.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/examples/service_discovery_sync.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/examples/service_discovery_v2.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/examples/service_registration.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/examples/service_registration_sync.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/examples/service_registry_v2.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/gen_protos.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/mkdocs.yml +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/__init__.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_async_managers.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_client.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_events.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_leases.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_log_formatters.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_log_sampling.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_members.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_observability_integration.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_trace_attributes.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_transactions.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_utils.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/tests/test_watch.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.2.0}/usage_guide.md +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# CNB pipeline for python-etcd3-async (Phase-2 M5).
|
|
2
|
+
#
|
|
3
|
+
# Four pipelines run on every push (and reuse the same list for pull requests):
|
|
4
|
+
# lint - ruff check + format gate (fast, always required)
|
|
5
|
+
# unit - pytest -m unit, no etcd needed
|
|
6
|
+
# integration - spins an etcd container via dind, runs pytest -m integration
|
|
7
|
+
# and reports etcd3 / etcdrpc coverage as separate numbers
|
|
8
|
+
# auth - opt-in auth-forced etcd instance (allowFailure: best-effort)
|
|
9
|
+
#
|
|
10
|
+
# The etcd image is pinned to match scripts/etcd-test.sh. Integration-only
|
|
11
|
+
# opt-in suites (member round-trip, restart, TLS) stay off here: they require
|
|
12
|
+
# extra containers/env and remain local via scripts/etcd-test.sh.
|
|
13
|
+
#
|
|
14
|
+
# Coverage policy: report-only, no --cov-fail-under gate (we track regressions,
|
|
15
|
+
# we do not chase the number). Baselines are recorded in the integration job.
|
|
16
|
+
|
|
17
|
+
$:
|
|
18
|
+
push: &pipelines
|
|
19
|
+
- name: lint
|
|
20
|
+
docker:
|
|
21
|
+
image: python:3.12
|
|
22
|
+
env:
|
|
23
|
+
PIP_INDEX_URL: https://mirrors.cloud.tencent.com/pypi/simple
|
|
24
|
+
# Overrides the tsinghua default index from pyproject (mirrors.tuna
|
|
25
|
+
# rejects CNB build-node traffic); Tencent mirror is same-region.
|
|
26
|
+
UV_DEFAULT_INDEX: https://mirrors.cloud.tencent.com/pypi/simple
|
|
27
|
+
stages:
|
|
28
|
+
- name: setup
|
|
29
|
+
script:
|
|
30
|
+
- python -m pip install --quiet --no-cache-dir uv
|
|
31
|
+
- uv sync
|
|
32
|
+
- name: ruff check
|
|
33
|
+
script:
|
|
34
|
+
- uv run --no-sync ruff check etcd3/ etcdrpc/ tests/
|
|
35
|
+
- name: ruff format
|
|
36
|
+
script:
|
|
37
|
+
- uv run --no-sync ruff format --check etcd3/ etcdrpc/ tests/
|
|
38
|
+
|
|
39
|
+
- name: unit
|
|
40
|
+
docker:
|
|
41
|
+
image: python:3.12
|
|
42
|
+
env:
|
|
43
|
+
PIP_INDEX_URL: https://mirrors.cloud.tencent.com/pypi/simple
|
|
44
|
+
UV_DEFAULT_INDEX: https://mirrors.cloud.tencent.com/pypi/simple
|
|
45
|
+
stages:
|
|
46
|
+
- name: setup
|
|
47
|
+
script:
|
|
48
|
+
- python -m pip install --quiet --no-cache-dir uv
|
|
49
|
+
- uv sync
|
|
50
|
+
- name: pytest
|
|
51
|
+
script:
|
|
52
|
+
- uv run --no-sync pytest -m unit
|
|
53
|
+
|
|
54
|
+
- name: integration
|
|
55
|
+
services:
|
|
56
|
+
- docker
|
|
57
|
+
docker:
|
|
58
|
+
image: python:3.12
|
|
59
|
+
env:
|
|
60
|
+
ETCD_TEST_IMAGE: public.ecr.aws/eks-distro/etcd-io/etcd:v3.6.12-eks-1-37-latest
|
|
61
|
+
PIP_INDEX_URL: https://mirrors.cloud.tencent.com/pypi/simple
|
|
62
|
+
UV_DEFAULT_INDEX: https://mirrors.cloud.tencent.com/pypi/simple
|
|
63
|
+
stages:
|
|
64
|
+
- name: setup
|
|
65
|
+
script:
|
|
66
|
+
- python -m pip install --quiet --no-cache-dir uv
|
|
67
|
+
- uv sync
|
|
68
|
+
- name: start etcd
|
|
69
|
+
script:
|
|
70
|
+
- >-
|
|
71
|
+
docker run -d --rm --name etcd-ci -p 2379:2379 "$ETCD_TEST_IMAGE"
|
|
72
|
+
etcd --name ci
|
|
73
|
+
--listen-client-urls http://0.0.0.0:2379
|
|
74
|
+
--advertise-client-urls http://localhost:2379
|
|
75
|
+
--watch-progress-notify-interval=1s
|
|
76
|
+
- for i in $(seq 1 30); do docker exec etcd-ci etcdctl endpoint health >/dev/null 2>&1 && break; sleep 2; done
|
|
77
|
+
- docker exec etcd-ci etcdctl endpoint health
|
|
78
|
+
- name: pytest + coverage
|
|
79
|
+
script:
|
|
80
|
+
- ETCD_ENDPOINT=localhost:2379 uv run --no-sync pytest -m integration --cov=etcd3 --cov=etcdrpc
|
|
81
|
+
# Coverage floor is report-only; refresh these numbers when they move
|
|
82
|
+
# intentionally. The gate is a human reading this, not a hard fail.
|
|
83
|
+
# Baselines below are for THIS integration job only (unit tests run in
|
|
84
|
+
# the `unit` job and do not add to these figures).
|
|
85
|
+
# etcd3: 56% (floor; combined with unit job the local full-suite
|
|
86
|
+
# figure is higher — stage target is 70% full-suite)
|
|
87
|
+
# etcdrpc: 36% (generated protobuf code, tracked but not gated)
|
|
88
|
+
- name: coverage report
|
|
89
|
+
script:
|
|
90
|
+
- uv run --no-sync coverage report --include="etcd3/*" -m
|
|
91
|
+
- uv run --no-sync coverage report --include="etcdrpc/*" -m
|
|
92
|
+
|
|
93
|
+
- name: auth
|
|
94
|
+
allowFailure: true
|
|
95
|
+
services:
|
|
96
|
+
- docker
|
|
97
|
+
docker:
|
|
98
|
+
image: python:3.12
|
|
99
|
+
env:
|
|
100
|
+
ETCD_TEST_IMAGE: public.ecr.aws/eks-distro/etcd-io/etcd:v3.6.12-eks-1-37-latest
|
|
101
|
+
PIP_INDEX_URL: https://mirrors.cloud.tencent.com/pypi/simple
|
|
102
|
+
UV_DEFAULT_INDEX: https://mirrors.cloud.tencent.com/pypi/simple
|
|
103
|
+
stages:
|
|
104
|
+
- name: setup
|
|
105
|
+
script:
|
|
106
|
+
- python -m pip install --quiet --no-cache-dir uv
|
|
107
|
+
- uv sync
|
|
108
|
+
- name: start auth etcd
|
|
109
|
+
script:
|
|
110
|
+
- >-
|
|
111
|
+
docker run -d --rm --name etcd-auth -p 22379:2379 "$ETCD_TEST_IMAGE"
|
|
112
|
+
etcd --name auth
|
|
113
|
+
--listen-client-urls http://0.0.0.0:2379
|
|
114
|
+
--advertise-client-urls http://localhost:2379
|
|
115
|
+
- for i in $(seq 1 30); do docker exec etcd-auth etcdctl endpoint health >/dev/null 2>&1 && break; sleep 2; done
|
|
116
|
+
- docker exec etcd-auth etcdctl user add "root:etcd-test-root-pw"
|
|
117
|
+
- docker exec etcd-auth etcdctl user grant-role root root
|
|
118
|
+
- docker exec etcd-auth etcdctl auth enable
|
|
119
|
+
- name: pytest -m auth
|
|
120
|
+
script:
|
|
121
|
+
- >-
|
|
122
|
+
ETCD_ENDPOINT=localhost:22379
|
|
123
|
+
ETCD_AUTH_ENDPOINT=localhost:22379
|
|
124
|
+
ETCD_AUTH_USER=root
|
|
125
|
+
ETCD_AUTH_PASSWORD=etcd-test-root-pw
|
|
126
|
+
uv run --no-sync pytest -m auth
|
|
127
|
+
pull_request: *pipelines
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# AGENTS.md - python-etcd3-async
|
|
2
|
+
|
|
3
|
+
AI coding agent guidelines for the python-etcd3-async project.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
- **Name**: python-etcd3-async
|
|
7
|
+
- **Python**: 3.12+
|
|
8
|
+
- **Package Manager**: uv
|
|
9
|
+
- **License**: Apache 2.0
|
|
10
|
+
|
|
11
|
+
## Build & Development Commands
|
|
12
|
+
|
|
13
|
+
### Setup
|
|
14
|
+
```bash
|
|
15
|
+
uv sync # Install dependencies (dev group included by default)
|
|
16
|
+
uv pip install -e . # Editable install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Testing
|
|
20
|
+
```bash
|
|
21
|
+
# Unit tests only — no etcd needed; integration tests auto-skip when no endpoint is configured
|
|
22
|
+
uv run pytest -m "not integration"
|
|
23
|
+
|
|
24
|
+
# Full suite — requires a running etcd (integration tests FAIL if an endpoint
|
|
25
|
+
# is configured but unreachable; that is intentional)
|
|
26
|
+
./scripts/etcd-test.sh up # podman: etcd-test @ localhost:12379
|
|
27
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest # all 396 tests
|
|
28
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest -m integration # integration only
|
|
29
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest --cov=etcd3 --cov=etcdrpc
|
|
30
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest -v -k "test_name" # Run tests matching pattern
|
|
31
|
+
|
|
32
|
+
# Restart-automated tests (opt-in; restarts the etcd-test container mid-run)
|
|
33
|
+
ETCD_ENDPOINT=localhost:12379 ETCD_TEST_RESTART=1 uv run pytest -m restart
|
|
34
|
+
./scripts/etcd-test.sh restart [primary|auth] # manual restart helper
|
|
35
|
+
|
|
36
|
+
# Member add/remove round-trip (opt-in; mutates cluster membership via a learner)
|
|
37
|
+
ETCD_ENDPOINT=localhost:12379 ETCD_TEST_MEMBER=1 uv run pytest tests/test_cluster_integration.py
|
|
38
|
+
|
|
39
|
+
# Auth-enabled instance (optional; tests/test_auth_integration.py routes through it)
|
|
40
|
+
./scripts/etcd-test.sh up-auth
|
|
41
|
+
ETCD_ENDPOINT=localhost:12379 ETCD_AUTH_ENDPOINT=localhost:22379 \
|
|
42
|
+
ETCD_AUTH_USER=root ETCD_AUTH_PASSWORD=etcd-test-root-pw uv run pytest tests/test_auth_integration.py
|
|
43
|
+
|
|
44
|
+
# TLS+auth instance (optional; tests/test_tls_integration.py; certs auto-generated)
|
|
45
|
+
./scripts/etcd-test.sh up-tls
|
|
46
|
+
ETCD_TLS_ENDPOINT=localhost:24379 uv run pytest tests/test_tls_integration.py
|
|
47
|
+
|
|
48
|
+
./scripts/etcd-test.sh down # remove all test containers + cert dir
|
|
49
|
+
```
|
|
50
|
+
- Integration tests write keys under a per-session random prefix cleaned up automatically
|
|
51
|
+
- Shared fixtures/markers live in `tests/conftest.py` (`etcd`, `key_prefix`, `etcd_auth`, `etcd_tls`)
|
|
52
|
+
|
|
53
|
+
### CI
|
|
54
|
+
- Pipeline: `.cnb.yml` (CNB), runs on push / pull_request (any branch):
|
|
55
|
+
- `lint`: `ruff check` + `ruff format --check`
|
|
56
|
+
- `unit`: `pytest -m unit` (no etcd)
|
|
57
|
+
- `integration`: etcd container via dind (same pinned image as `etcd-test.sh`) → `pytest -m integration` + etcd3/etcdrpc coverage
|
|
58
|
+
- `auth`: auth-enforced etcd instance, `pytest -m auth` (allowFailure)
|
|
59
|
+
- Member/restart/TLS suites stay local-only (opt-in env gates, see `scripts/etcd-test.sh`)
|
|
60
|
+
- Coverage is report-only (no fail-under gate); baselines recorded in `.cnb.yml` comments
|
|
61
|
+
|
|
62
|
+
### Linting
|
|
63
|
+
```bash
|
|
64
|
+
uv run ruff check etcd3/ etcdrpc/ # Check (etcdrpc/ is excluded as generated)
|
|
65
|
+
uv run ruff format etcd3/ etcdrpc/ # Format
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Other
|
|
69
|
+
```bash
|
|
70
|
+
uv run python gen_protos.py # Regenerate protobuf
|
|
71
|
+
uv run mkdocs serve # Serve docs
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Versioning
|
|
75
|
+
- Single source of truth: `__version__` in `etcd3/__init__.py`
|
|
76
|
+
- `pyproject.toml` uses `dynamic = ["version"]` via
|
|
77
|
+
`[tool.hatch.version] path = "etcd3/__init__.py"` — never add a static
|
|
78
|
+
`version` back; `uv.lock` refreshes automatically on the next `uv lock`
|
|
79
|
+
- Release checklist: `docs/en/resources/releasing.md`
|
|
80
|
+
|
|
81
|
+
## Code Style
|
|
82
|
+
|
|
83
|
+
### Formatting
|
|
84
|
+
- **Max line length**: 100 characters
|
|
85
|
+
- **Formatter**: ruff
|
|
86
|
+
- Follow PEP 8
|
|
87
|
+
- 4 spaces for indentation (no tabs)
|
|
88
|
+
- Trailing commas for multi-line structures
|
|
89
|
+
- Single blank line between top-level definitions
|
|
90
|
+
|
|
91
|
+
### Naming
|
|
92
|
+
- Classes: `PascalCase` (e.g., `AsyncEtcd3Client`)
|
|
93
|
+
- Functions/Methods: `snake_case` (e.g., `async_client()`, `get_prefix()`)
|
|
94
|
+
- Constants: `UPPER_CASE` (e.g., `FILTER_PUT`)
|
|
95
|
+
- Private: `_leading_underscore` (e.g., `_get_channel()`)
|
|
96
|
+
- Protected: `_leading_underscore` for internal use
|
|
97
|
+
- Type variables: `PascalCase` (e.g., `T`, `TKey`, `TValue`)
|
|
98
|
+
|
|
99
|
+
### Imports (3 groups, sorted)
|
|
100
|
+
1. Standard library (`asyncio`, `functools`, `typing`, etc.)
|
|
101
|
+
2. Third-party (`grpc`, `etcdrpc`, `pytest`)
|
|
102
|
+
3. Internal modules (`etcd3.*`, `etcdrpc.*`)
|
|
103
|
+
- Use absolute imports within package
|
|
104
|
+
- Sort alphabetically within groups
|
|
105
|
+
|
|
106
|
+
### Type Hints
|
|
107
|
+
- Use type hints for all parameters and returns
|
|
108
|
+
- Use `Optional[]` for nullable types, `Literal[]` for enums
|
|
109
|
+
- Use `|` syntax for unions (e.g., `str | bytes`)
|
|
110
|
+
- Use `X | None` over `Optional[X]`
|
|
111
|
+
- Generics: `list[int]`, `dict[str, Any]` over `List[int]`, `Dict[str, Any]`
|
|
112
|
+
|
|
113
|
+
### Docstrings
|
|
114
|
+
- Google-style
|
|
115
|
+
- Document all public methods/classes
|
|
116
|
+
- Include type info in params and returns
|
|
117
|
+
- First line should be a concise summary
|
|
118
|
+
- Use `:raises:` for exceptions that may be raised
|
|
119
|
+
|
|
120
|
+
### Error Handling
|
|
121
|
+
- Use custom exceptions from `etcd3/exceptions.py`:
|
|
122
|
+
- `Etcd3Exception` (base), `ConnectionFailedError`, `ConnectionTimeoutError`
|
|
123
|
+
- `RevisionCompactedError`, `LockReleaseError`, `BatchOperationError`
|
|
124
|
+
- Decorators: `_handle_errors`, `_async_handle_errors`
|
|
125
|
+
- Never catch broad `Exception` unless re-raising
|
|
126
|
+
- Always include context in error messages
|
|
127
|
+
|
|
128
|
+
## Architecture
|
|
129
|
+
|
|
130
|
+
### Dual Client Design
|
|
131
|
+
- **Sync**: `Etcd3Client` in `etcd3/single_client.py` (extends
|
|
132
|
+
`MultiEndpointEtcd3Client` in `etcd3/multi_client.py`, which also defines `Endpoint`)
|
|
133
|
+
- **Async**: `AsyncEtcd3Client` in `etcd3/async_single_client.py` (extends
|
|
134
|
+
`AsyncMultiEndpointEtcd3Client` in `etcd3/async_multi_client.py`; `AsyncEndpoint`
|
|
135
|
+
lives in `etcd3/async_endpoint.py`)
|
|
136
|
+
- `etcd3/client.py` / `etcd3/async_client.py` are compatibility shims re-exporting the above
|
|
137
|
+
- Factories: `client()` (sync, single endpoint) and `async_client()` (async, single endpoint)
|
|
138
|
+
|
|
139
|
+
### Extension Pattern (`etcd3/async_client_extensions.py`)
|
|
140
|
+
- `AsyncKVOperations`, `AsyncLeaseOperations`, `AsyncLockOperations`
|
|
141
|
+
- `AsyncClusterOperations`, `AsyncMaintenanceOperations`, `AsyncWatchOperations`
|
|
142
|
+
|
|
143
|
+
### Load Balancing
|
|
144
|
+
- `LoadBalancerStrategy` enum: `PRIORITY`, `ROUND_ROBIN`, `RANDOM`, `STICKY`
|
|
145
|
+
|
|
146
|
+
### Package Structure
|
|
147
|
+
```
|
|
148
|
+
etcd3/
|
|
149
|
+
__init__.py # Public exports (client, async_client, exceptions, etc.)
|
|
150
|
+
single_client.py # Etcd3Client (sync single-endpoint)
|
|
151
|
+
multi_client.py # MultiEndpointEtcd3Client + sync Endpoint
|
|
152
|
+
async_single_client.py # AsyncEtcd3Client (async single-endpoint)
|
|
153
|
+
async_multi_client.py # AsyncMultiEndpointEtcd3Client (main async client)
|
|
154
|
+
async_endpoint.py # AsyncEndpoint (async gRPC channel handler)
|
|
155
|
+
client.py # Compat shim (re-exports sync clients)
|
|
156
|
+
async_client.py # Compat shim (re-exports async clients)
|
|
157
|
+
async_client_extensions.py # Async operation mixins (KV/Lease/Lock/Cluster/Maintenance/Watch)
|
|
158
|
+
sync_kv_operations.py # Sync KV operations mixin
|
|
159
|
+
sync_lock_operations.py # Sync lock operations mixin
|
|
160
|
+
sync_cluster_operations.py # Sync cluster operations mixin
|
|
161
|
+
sync_maintenance_operations.py # Sync maintenance operations mixin
|
|
162
|
+
async_managers.py # Async watch/cluster/maintenance managers
|
|
163
|
+
config.py # ClientConfig, MultiEndpointConfig, RangeRequestConfig
|
|
164
|
+
async_config.py # AsyncClientConfig, AsyncMultiEndpointConfig
|
|
165
|
+
endpoint.py # BaseEndpoint (shared fail-state / health logic)
|
|
166
|
+
errors.py # Error-handler decorator factories
|
|
167
|
+
exceptions.py # Custom exceptions
|
|
168
|
+
auth.py # AuthOperations / AsyncAuthOperations
|
|
169
|
+
leases.py # Lease / AsyncLease objects
|
|
170
|
+
locks.py # Lock / AsyncLock objects
|
|
171
|
+
watch.py # Sync watch builder + iterator
|
|
172
|
+
async_watch.py # Async watch builder + event iterator
|
|
173
|
+
events.py # PutEvent / DeleteEvent classes
|
|
174
|
+
transactions.py # Transaction compare/put/get builders
|
|
175
|
+
members.py # Member class for cluster representation
|
|
176
|
+
shared.py # Shared code between sync/async (Status, KVMetadata, ...)
|
|
177
|
+
utils.py # Key encoding, prefixes, filter constants, LoadBalancerStrategy
|
|
178
|
+
trace.py / trace_attributes.py / monitor.py / async_monitor.py # Tracing & monitoring
|
|
179
|
+
log.py / log_formatters.py / log_sampling.py # Logging
|
|
180
|
+
observability.py # ObservabilityConfig / configure_from_env / auto_init
|
|
181
|
+
exporters/ # Prometheus exporter
|
|
182
|
+
etcdrpc/ # Generated protobuf/gRPC code
|
|
183
|
+
tests/ # Test files
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Observability (Environment Variables)
|
|
187
|
+
|
|
188
|
+
All prefixed with `ETCD_OBS_`:
|
|
189
|
+
|
|
190
|
+
| Variable | Default | Description |
|
|
191
|
+
|----------|---------|-------------|
|
|
192
|
+
| `ETCD_OBS_AUTO_INIT` | true | Auto-init on client create |
|
|
193
|
+
| `ETCD_OBS_LOG_LEVEL` | INFO | DEBUG/INFO/WARNING/ERROR |
|
|
194
|
+
| `ETCD_OBS_LOG_FORMAT` | text | text/json/colored |
|
|
195
|
+
| `ETCD_OBS_LOG_SAMPLE_RATE` | 1.0 | 0.0-1.0 |
|
|
196
|
+
| `ETCD_OBS_LOG_SAMPLE_STRATEGY` | always | always/rate/dynamic/level_based |
|
|
197
|
+
| `ETCD_OBS_TRACING_ENABLED` | true | Enable OpenTelemetry |
|
|
198
|
+
| `ETCD_OBS_TRACING_SERVICE_NAME` | etcd3-client | Service name |
|
|
199
|
+
| `ETCD_OBS_TRACING_SAMPLE_RATE` | 1.0 | Trace sampling |
|
|
200
|
+
| `ETCD_OBS_OTLP_ENDPOINT` | - | OTLP collector endpoint |
|
|
201
|
+
| `ETCD_OBS_PROMETHEUS_ENABLED` | false | Enable Prometheus |
|
|
202
|
+
|
|
203
|
+
## Testing
|
|
204
|
+
- Plain `uv run pytest` never fails for lack of etcd: integration tests
|
|
205
|
+
auto-skip unless an endpoint is configured (`ETCD_ENDPOINT` or `ETCD_HOST`/`ETCD_PORT`)
|
|
206
|
+
- Configured endpoint unreachable => integration tests fail (intentional signal)
|
|
207
|
+
- Markers: `-m unit` / `-m integration` / `-m auth`; shared fixtures in `tests/conftest.py`
|
|
208
|
+
- Local test server: `./scripts/etcd-test.sh up` (podman, localhost:12379)
|
|
209
|
+
- Integration keys live under a per-session random prefix, cleaned automatically
|
|
210
|
+
- Test files: `tests/test_*.py`
|
|
211
|
+
|
|
212
|
+
## Common Patterns
|
|
213
|
+
```python
|
|
214
|
+
# Async client (auto-initializes observability)
|
|
215
|
+
from etcd3 import async_client
|
|
216
|
+
etcd = await async_client(host='localhost', port=2379)
|
|
217
|
+
|
|
218
|
+
# Sync client
|
|
219
|
+
from etcd3 import client
|
|
220
|
+
etcd = client(host='localhost', port=2379)
|
|
221
|
+
|
|
222
|
+
# Manual observability config
|
|
223
|
+
from etcd3 import ObservabilityConfig, configure_from_env
|
|
224
|
+
config = configure_from_env()
|
|
225
|
+
config.apply()
|
|
226
|
+
|
|
227
|
+
# Context tracking
|
|
228
|
+
from etcd3 import get_request_context
|
|
229
|
+
async with etcd.context(operation="batch_put", source="worker_1"):
|
|
230
|
+
await etcd.kv.batch_put(items)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Security
|
|
234
|
+
- TLS, username/password, token-based auth supported
|
|
235
|
+
- Never commit credentials
|
|
236
|
+
|
|
237
|
+
## References
|
|
238
|
+
- [python-etcd3](https://github.com/kragniz/python-etcd3)
|
|
239
|
+
- [Go etcd client v3](https://pkg.go.dev/go.etcd.io/etcd/client/v3)
|
|
240
|
+
- [etcd docs](https://etcd.io/docs/)
|
|
@@ -10,12 +10,9 @@ python-etcd3-async is an async Python client library for the etcd v3 API using `
|
|
|
10
10
|
|
|
11
11
|
### Development Setup
|
|
12
12
|
```bash
|
|
13
|
-
# Install dependencies
|
|
13
|
+
# Install dependencies (includes the dev group)
|
|
14
14
|
uv sync
|
|
15
15
|
|
|
16
|
-
# Install dev dependencies
|
|
17
|
-
uv sync --extra dev
|
|
18
|
-
|
|
19
16
|
# Run Python commands
|
|
20
17
|
uv run python <script.py>
|
|
21
18
|
|
|
@@ -25,22 +22,55 @@ uv pip install -e .
|
|
|
25
22
|
|
|
26
23
|
### Testing
|
|
27
24
|
```bash
|
|
28
|
-
#
|
|
29
|
-
|
|
25
|
+
# Unit tests only — no etcd needed; integration tests auto-skip without an endpoint
|
|
26
|
+
uv run pytest -m "not integration"
|
|
27
|
+
|
|
28
|
+
# Start the local test etcd (podman, localhost:12379)
|
|
29
|
+
./scripts/etcd-test.sh up
|
|
30
|
+
|
|
31
|
+
# Run all tests against it
|
|
32
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest
|
|
30
33
|
|
|
31
34
|
# Run a single test file
|
|
32
|
-
ETCD_ENDPOINT=
|
|
35
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest tests/test_async_client.py
|
|
33
36
|
|
|
34
37
|
# Run a specific test method
|
|
35
|
-
ETCD_ENDPOINT=
|
|
38
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest tests/test_locks.py::TestAsyncLock::test_async_lock_acquire_succeeds_after_wait -v
|
|
39
|
+
|
|
40
|
+
# Integration tests only / unit tests only
|
|
41
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest -m integration
|
|
42
|
+
uv run pytest -m unit
|
|
43
|
+
|
|
44
|
+
# Restart-automated tests (opt-in; restarts the etcd-test container mid-run)
|
|
45
|
+
ETCD_ENDPOINT=localhost:12379 ETCD_TEST_RESTART=1 uv run pytest -m restart
|
|
46
|
+
./scripts/etcd-test.sh restart [primary|auth]
|
|
47
|
+
|
|
48
|
+
# Member add/remove round-trip (opt-in; mutates cluster membership via a learner)
|
|
49
|
+
ETCD_ENDPOINT=localhost:12379 ETCD_TEST_MEMBER=1 uv run pytest tests/test_cluster_integration.py
|
|
36
50
|
|
|
37
51
|
# Run with coverage
|
|
38
|
-
ETCD_ENDPOINT=
|
|
52
|
+
ETCD_ENDPOINT=localhost:12379 uv run pytest --cov=etcd3 --cov=etcdrpc
|
|
53
|
+
|
|
54
|
+
# Auth-enabled instance (optional)
|
|
55
|
+
./scripts/etcd-test.sh up-auth
|
|
56
|
+
ETCD_ENDPOINT=localhost:12379 ETCD_AUTH_ENDPOINT=localhost:22379 \
|
|
57
|
+
ETCD_AUTH_USER=root ETCD_AUTH_PASSWORD=etcd-test-root-pw uv run pytest tests/test_auth_integration.py
|
|
39
58
|
|
|
40
|
-
#
|
|
41
|
-
|
|
59
|
+
# TLS+auth instance (optional; certs generated into .etcd-tls/)
|
|
60
|
+
./scripts/etcd-test.sh up-tls
|
|
61
|
+
ETCD_TLS_ENDPOINT=localhost:24379 uv run pytest tests/test_tls_integration.py
|
|
42
62
|
```
|
|
43
63
|
|
|
64
|
+
### CI (`.cnb.yml`)
|
|
65
|
+
Runs on push / pull_request (any branch):
|
|
66
|
+
- `lint` — ruff check + format --check
|
|
67
|
+
- `unit` — `pytest -m unit` (no etcd)
|
|
68
|
+
- `integration` — etcd via dind (pinned image matches etcd-test.sh) → `pytest -m integration` + etcd3/etcdrpc coverage
|
|
69
|
+
- `auth` — auth-enforced etcd, `pytest -m auth` (allowFailure)
|
|
70
|
+
|
|
71
|
+
Member/restart/TLS suites stay local-only (opt-in env gates). Coverage is
|
|
72
|
+
report-only (no fail-under gate); baselines are recorded in `.cnb.yml`.
|
|
73
|
+
|
|
44
74
|
### Linting & Formatting
|
|
45
75
|
```bash
|
|
46
76
|
# Check with ruff
|
|
@@ -48,9 +78,6 @@ uv run ruff check etcd3/ etcdrpc/
|
|
|
48
78
|
|
|
49
79
|
# Format with ruff
|
|
50
80
|
uv run ruff format etcd3/ etcdrpc/
|
|
51
|
-
|
|
52
|
-
# Run pylint
|
|
53
|
-
pylint etcd3/ etcdrpc/
|
|
54
81
|
```
|
|
55
82
|
|
|
56
83
|
### Protobuf Generation
|
|
@@ -63,7 +90,12 @@ uv run python gen_protos.py
|
|
|
63
90
|
### Environment Variables
|
|
64
91
|
| Variable | Description |
|
|
65
92
|
|----------|-------------|
|
|
66
|
-
| `ETCD_ENDPOINT` | etcd server address for testing (e.g.,
|
|
93
|
+
| `ETCD_ENDPOINT` | etcd server address for testing (e.g., localhost:12379 via scripts/etcd-test.sh) |
|
|
94
|
+
| `ETCD_AUTH_ENDPOINT` | optional auth-enabled etcd address (e.g., localhost:22379) |
|
|
95
|
+
| `ETCD_AUTH_USER` / `ETCD_AUTH_PASSWORD` | credentials for the auth-enabled test instance |
|
|
96
|
+
| `ETCD_TLS_ENDPOINT` | TLS+auth etcd address for testing (e.g., localhost:24379 via scripts/etcd-test.sh up-tls) |
|
|
97
|
+
| `ETCD_TLS_CA` | CA cert path used to verify the TLS server (default: `.etcd-tls/ca.crt`) |
|
|
98
|
+
| `ETCD_TLS_USER` / `ETCD_TLS_PASSWORD` | credentials for the TLS+auth test instance |
|
|
67
99
|
| `ETCD_OBS_LOG_LEVEL` | Logging level (DEBUG, INFO, WARNING, ERROR) |
|
|
68
100
|
| `ETCD_OBS_LOG_FORMAT` | Log format (text, json, colored) |
|
|
69
101
|
| `ETCD_OBS_TRACING_ENABLED` | Enable OpenTelemetry tracing |
|
|
@@ -73,7 +105,7 @@ uv run python gen_protos.py
|
|
|
73
105
|
|
|
74
106
|
1. Use `uv` for Python environment management, `hatchling` as build tool
|
|
75
107
|
2. Use `uv run python` to execute Python commands
|
|
76
|
-
3. Follow PEP8
|
|
108
|
+
3. Follow PEP8; use `ruff` for linting and formatting (etcdrpc/ is excluded as generated)
|
|
77
109
|
4. Use `pytest` and `pytest-asyncio` for sync and async tests
|
|
78
110
|
5. Use context7 MCP to query official documentation
|
|
79
111
|
|
|
@@ -234,7 +266,11 @@ auto_init() # Auto-initialize at import time
|
|
|
234
266
|
- Error handlers are created via `create_sync_error_handlers()` and `create_async_error_handlers()` factories from `errors.py`
|
|
235
267
|
- `_sync_handle_keepalive_errors` / `_async_handle_keepalive_errors` - Lease keepalive with `propagate_errors=True`
|
|
236
268
|
|
|
237
|
-
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.
|
|
238
274
|
|
|
239
275
|
4. **Load balancing:** Multiple strategies supported via `LoadBalancerStrategy`:
|
|
240
276
|
- `PRIORITY` (default): Keep current endpoint if healthy, failover on failure
|
|
@@ -242,11 +278,14 @@ auto_init() # Auto-initialize at import time
|
|
|
242
278
|
- `RANDOM`: Random endpoint selection
|
|
243
279
|
- `STICKY`: Maintain connection to the same endpoint
|
|
244
280
|
|
|
245
|
-
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
|
|
246
283
|
|
|
247
284
|
6. **Lazy authentication:** Async uses `_init_auth()` with `asyncio.Lock()`, sync initializes in `__init__`
|
|
248
285
|
|
|
249
|
-
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.
|
|
250
289
|
|
|
251
290
|
8. **Request context tracking:** Use `client.context()` to track operations across async tasks:
|
|
252
291
|
```python
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Installation
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Installation
|
|
6
|
+
|
|
7
|
+
## Stable Release
|
|
8
|
+
|
|
9
|
+
To install python-etcd3-async, run this command in your terminal:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv pip install python-etcd3-async
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This is the preferred method to install, as it will always install the most recent stable release.
|
|
16
|
+
|
|
17
|
+
## From Sources
|
|
18
|
+
|
|
19
|
+
The sources for python-etcd3-async can be downloaded from the [CNB repo](https://cnb.cool/owasd/python3-etcd-async).
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://cnb.cool/owasd/python3-etcd-async.git
|
|
23
|
+
cd python3-etcd-async
|
|
24
|
+
uv pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- Python 3.12+
|
|
30
|
+
- etcd v3 API server (v3.1+ recommended)
|
|
31
|
+
|
|
32
|
+
## Virtual Environments with uv
|
|
33
|
+
|
|
34
|
+
For an application project managed by [uv](https://docs.astral.sh/uv/):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv init myapp && cd myapp
|
|
38
|
+
uv add python-etcd3-async
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
uv creates and syncs `.venv` automatically; run code with `uv run python ...`.
|
|
42
|
+
With pip/venv instead:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -m venv .venv && source .venv/bin/activate
|
|
46
|
+
pip install python-etcd3-async
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Verify the Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python -c "import etcd3; print(etcd3.__version__)"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then connect to a running etcd:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import asyncio
|
|
59
|
+
from etcd3 import async_client
|
|
60
|
+
|
|
61
|
+
async def main():
|
|
62
|
+
client = await async_client(host='localhost', port=2379)
|
|
63
|
+
print(await client.status())
|
|
64
|
+
await client.close()
|
|
65
|
+
|
|
66
|
+
asyncio.run(main())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## See Also
|
|
70
|
+
|
|
71
|
+
- [API Usage](../guide/usage.md)
|
|
72
|
+
- [Configuration](../guide/configuration.md)
|
|
73
|
+
- [Sync Client](../guide/sync_client.md)
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
## 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
|
+
|
|
75
|
+
## See Also
|
|
76
|
+
|
|
77
|
+
- [API Usage](usage.md)
|
|
78
|
+
- [Configuration](configuration.md)
|