python-etcd3-async 0.1.0__tar.gz → 0.1.6__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/.cnb.yml +127 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/.coverage +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/.gitignore +1 -0
- python_etcd3_async-0.1.6/AGENTS.md +240 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/CLAUDE.md +112 -35
- python_etcd3_async-0.1.6/PHASE2_PLAN.md +164 -0
- python_etcd3_async-0.1.0/README.md → python_etcd3_async-0.1.6/PKG-INFO +54 -4
- python_etcd3_async-0.1.0/PKG-INFO → python_etcd3_async-0.1.6/README.md +44 -14
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/README_CN.md +42 -4
- python_etcd3_async-0.1.6/docs/en/getting-started/installation.md +73 -0
- python_etcd3_async-0.1.6/docs/en/guide/advanced_queries.md +55 -0
- python_etcd3_async-0.1.6/docs/en/guide/authentication.md +116 -0
- python_etcd3_async-0.1.6/docs/en/guide/cluster_maintenance.md +130 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/guide/configuration.md +52 -0
- python_etcd3_async-0.1.6/docs/en/guide/leader_election.md +129 -0
- python_etcd3_async-0.1.6/docs/en/guide/leases_locks.md +187 -0
- python_etcd3_async-0.1.6/docs/en/guide/service_discovery.md +167 -0
- python_etcd3_async-0.1.6/docs/en/guide/service_registration.md +159 -0
- python_etcd3_async-0.1.6/docs/en/guide/sync_client.md +138 -0
- python_etcd3_async-0.1.6/docs/en/guide/transactions.md +136 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/guide/usage.md +16 -7
- python_etcd3_async-0.1.6/docs/en/guide/watching.md +145 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/index.md +10 -1
- python_etcd3_async-0.1.6/docs/en/operations/auto_reconnection.md +84 -0
- python_etcd3_async-0.1.6/docs/en/operations/error_handling.md +85 -0
- python_etcd3_async-0.1.6/docs/en/operations/failover.md +74 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/operations/monitoring.md +20 -13
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/resources/contributing.md +10 -2
- python_etcd3_async-0.1.6/docs/en/resources/history.md +39 -0
- python_etcd3_async-0.1.6/docs/en/resources/releasing.md +65 -0
- python_etcd3_async-0.1.6/docs/superpowers/plans/2026-04-03-high-cohesion-low-coupling-plan.md +1093 -0
- python_etcd3_async-0.1.6/docs/superpowers/specs/2026-04-03-high-cohesion-low-coupling-design.md +255 -0
- python_etcd3_async-0.1.6/docs/zh/getting-started/installation.md +73 -0
- python_etcd3_async-0.1.6/docs/zh/guide/advanced_queries.md +55 -0
- python_etcd3_async-0.1.6/docs/zh/guide/authentication.md +112 -0
- python_etcd3_async-0.1.6/docs/zh/guide/cluster_maintenance.md +129 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/guide/configuration.md +50 -0
- python_etcd3_async-0.1.6/docs/zh/guide/leader_election.md +129 -0
- python_etcd3_async-0.1.6/docs/zh/guide/leases_locks.md +180 -0
- python_etcd3_async-0.1.6/docs/zh/guide/service_discovery.md +167 -0
- python_etcd3_async-0.1.6/docs/zh/guide/service_registration.md +159 -0
- python_etcd3_async-0.1.6/docs/zh/guide/sync_client.md +136 -0
- python_etcd3_async-0.1.6/docs/zh/guide/transactions.md +133 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/guide/usage.md +16 -7
- python_etcd3_async-0.1.6/docs/zh/guide/watching.md +140 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/index.md +10 -1
- python_etcd3_async-0.1.6/docs/zh/operations/auto_reconnection.md +77 -0
- python_etcd3_async-0.1.6/docs/zh/operations/error_handling.md +80 -0
- python_etcd3_async-0.1.6/docs/zh/operations/failover.md +70 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/operations/monitoring.md +20 -13
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/resources/contributing.md +9 -2
- python_etcd3_async-0.1.6/docs/zh/resources/history.md +38 -0
- python_etcd3_async-0.1.6/docs/zh/resources/releasing.md +63 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/__init__.py +28 -14
- python_etcd3_async-0.1.6/etcd3/async_client.py +50 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_client_extensions.py +61 -43
- python_etcd3_async-0.1.6/etcd3/async_config.py +97 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_endpoint.py +2 -2
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_managers.py +37 -20
- python_etcd3_async-0.1.0/etcd3/async_client.py → python_etcd3_async-0.1.6/etcd3/async_multi_client.py +113 -341
- python_etcd3_async-0.1.6/etcd3/async_single_client.py +133 -0
- python_etcd3_async-0.1.6/etcd3/auth.py +690 -0
- python_etcd3_async-0.1.6/etcd3/client.py +45 -0
- python_etcd3_async-0.1.6/etcd3/config.py +155 -0
- python_etcd3_async-0.1.6/etcd3/errors.py +264 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/events.py +2 -4
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/exceptions.py +6 -2
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/leases.py +2 -2
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/locks.py +45 -33
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/members.py +2 -4
- python_etcd3_async-0.1.0/etcd3/client.py → python_etcd3_async-0.1.6/etcd3/multi_client.py +163 -931
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/shared.py +37 -185
- python_etcd3_async-0.1.6/etcd3/single_client.py +148 -0
- python_etcd3_async-0.1.6/etcd3/sync_cluster_operations.py +141 -0
- python_etcd3_async-0.1.6/etcd3/sync_kv_operations.py +642 -0
- python_etcd3_async-0.1.6/etcd3/sync_lock_operations.py +37 -0
- python_etcd3_async-0.1.6/etcd3/sync_maintenance_operations.py +244 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/trace_attributes.py +91 -103
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/transactions.py +2 -4
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/utils.py +4 -3
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/watch.py +7 -6
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcdrpc/__init__.py +2 -1
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/examples/leader_election.py +5 -3
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/examples/leader_election_sync.py +4 -3
- python_etcd3_async-0.1.6/examples/service_discovery_sync.py +213 -0
- python_etcd3_async-0.1.6/examples/service_discovery_v2.py +235 -0
- python_etcd3_async-0.1.6/examples/service_registration_sync.py +197 -0
- python_etcd3_async-0.1.6/examples/service_registry_v2.py +237 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/pyproject.toml +23 -1
- python_etcd3_async-0.1.6/scripts/etcd-test.sh +191 -0
- python_etcd3_async-0.1.6/tests/conftest.py +244 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_async_client.py +17 -37
- python_etcd3_async-0.1.6/tests/test_async_client_integration.py +157 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_async_error_handling.py +26 -42
- python_etcd3_async-0.1.6/tests/test_async_kv_operations_integration.py +102 -0
- python_etcd3_async-0.1.6/tests/test_auth.py +186 -0
- python_etcd3_async-0.1.6/tests/test_auth_integration.py +427 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_batch_operations.py +12 -31
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_client.py +4 -4
- python_etcd3_async-0.1.6/tests/test_cluster_integration.py +69 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_kv_operations.py +75 -74
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_lease_keepalive_retry.py +79 -35
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_leases_integration.py +36 -58
- python_etcd3_async-0.1.6/tests/test_locks_integration.py +100 -0
- python_etcd3_async-0.1.6/tests/test_maintenance_integration.py +40 -0
- python_etcd3_async-0.1.6/tests/test_tls_integration.py +58 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_transactions_integration.py +24 -74
- python_etcd3_async-0.1.6/tests/test_watch_integration.py +117 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/uv.lock +0 -1
- python_etcd3_async-0.1.0/AGENTS.md +0 -138
- python_etcd3_async-0.1.0/docs/en/getting-started/installation.md +0 -35
- python_etcd3_async-0.1.0/docs/en/guide/advanced_queries.md +0 -43
- python_etcd3_async-0.1.0/docs/en/operations/auto_reconnection.md +0 -34
- python_etcd3_async-0.1.0/docs/en/operations/error_handling.md +0 -37
- python_etcd3_async-0.1.0/docs/en/operations/failover.md +0 -54
- python_etcd3_async-0.1.0/docs/en/resources/history.md +0 -18
- python_etcd3_async-0.1.0/docs/zh/getting-started/installation.md +0 -35
- python_etcd3_async-0.1.0/docs/zh/guide/advanced_queries.md +0 -43
- python_etcd3_async-0.1.0/docs/zh/operations/auto_reconnection.md +0 -34
- python_etcd3_async-0.1.0/docs/zh/operations/error_handling.md +0 -37
- python_etcd3_async-0.1.0/docs/zh/operations/failover.md +0 -54
- python_etcd3_async-0.1.0/docs/zh/resources/history.md +0 -18
- python_etcd3_async-0.1.0/examples/service_discovery_sync.py +0 -229
- python_etcd3_async-0.1.0/examples/service_registration_sync.py +0 -195
- python_etcd3_async-0.1.0/tests/test_locks_integration.py +0 -147
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/.pylintrc +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/.python-version +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/SKILL.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/assets/stylesheets/extra.css +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/operations/observability.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/resources/authors.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/operations/observability.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/resources/authors.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_monitor.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_watch.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/endpoint.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/exporters/__init__.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/exporters/prometheus.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/log.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/log_formatters.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/log_sampling.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/observability.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/trace.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcdrpc/auth.proto +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcdrpc/kv.proto +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcdrpc/rpc.proto +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/examples/service_discovery.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/examples/service_registration.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/gen_protos.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/mkdocs.yml +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/__init__.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_async_managers.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_events.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_leases.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_locks.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_log_formatters.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_log_sampling.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_members.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_observability_integration.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_trace_attributes.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_transactions.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_utils.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_watch.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/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
|
|
Binary file
|
|
@@ -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
|
|
39
53
|
|
|
40
|
-
#
|
|
41
|
-
|
|
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
|
|
58
|
+
|
|
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,14 +90,22 @@ uv run python gen_protos.py
|
|
|
63
90
|
### Environment Variables
|
|
64
91
|
| Variable | Description |
|
|
65
92
|
|----------|-------------|
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
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 |
|
|
99
|
+
| `ETCD_OBS_LOG_LEVEL` | Logging level (DEBUG, INFO, WARNING, ERROR) |
|
|
100
|
+
| `ETCD_OBS_LOG_FORMAT` | Log format (text, json, colored) |
|
|
101
|
+
| `ETCD_OBS_TRACING_ENABLED` | Enable OpenTelemetry tracing |
|
|
102
|
+
| `ETCD_OBS_PROMETHEUS_ENABLED` | Enable Prometheus metrics |
|
|
68
103
|
|
|
69
104
|
## Project Rules
|
|
70
105
|
|
|
71
106
|
1. Use `uv` for Python environment management, `hatchling` as build tool
|
|
72
107
|
2. Use `uv run python` to execute Python commands
|
|
73
|
-
3. Follow PEP8
|
|
108
|
+
3. Follow PEP8; use `ruff` for linting and formatting (etcdrpc/ is excluded as generated)
|
|
74
109
|
4. Use `pytest` and `pytest-asyncio` for sync and async tests
|
|
75
110
|
5. Use context7 MCP to query official documentation
|
|
76
111
|
|
|
@@ -78,23 +113,26 @@ uv run python gen_protos.py
|
|
|
78
113
|
|
|
79
114
|
### Core Client Classes
|
|
80
115
|
|
|
81
|
-
**Sync client
|
|
82
|
-
- `
|
|
83
|
-
- `
|
|
84
|
-
- `
|
|
116
|
+
**Sync client modules:**
|
|
117
|
+
- `etcd3/single_client.py` - `Etcd3Client` - Single endpoint client, inherits from `MultiEndpointEtcd3Client`
|
|
118
|
+
- `etcd3/multi_client.py` - `MultiEndpointEtcd3Client` - Base class with multiple endpoints and failover support, `Endpoint` - Single etcd cluster endpoint with gRPC channel
|
|
119
|
+
- `etcd3/client.py` - Compatibility shim (re-exports from the above modules)
|
|
85
120
|
|
|
86
|
-
**Async client
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `async_client
|
|
121
|
+
**Async client modules:**
|
|
122
|
+
- `etcd3/async_single_client.py` - `AsyncEtcd3Client` - Single endpoint async client
|
|
123
|
+
- `etcd3/async_multi_client.py` - `AsyncMultiEndpointEtcd3Client` - Main async client with failover support, `AsyncEndpoint` - Single etcd cluster endpoint with async gRPC channel
|
|
124
|
+
- `etcd3/async_client.py` - Compatibility shim (re-exports from the above modules)
|
|
90
125
|
|
|
91
126
|
### Configuration Classes
|
|
92
127
|
|
|
93
|
-
**`
|
|
94
|
-
|
|
95
|
-
|
|
128
|
+
**`etcd3/config.py`:**
|
|
129
|
+
- `RangeRequestConfig` - Dataclass for range query options (limit, revision, sort_order, etc.)
|
|
130
|
+
- `ClientConfig` - Client connection configuration (host, port, timeout, TLS, auth)
|
|
131
|
+
- `MultiEndpointConfig` - Multi-endpoint failover configuration
|
|
96
132
|
|
|
97
|
-
**`
|
|
133
|
+
**`etcd3/async_config.py`:**
|
|
134
|
+
- `AsyncClientConfig` - Async client connection configuration
|
|
135
|
+
- `AsyncMultiEndpointConfig` - Async multi-endpoint failover configuration
|
|
98
136
|
|
|
99
137
|
### Async Operation Extensions (`etcd3/async_client_extensions.py`)
|
|
100
138
|
|
|
@@ -117,12 +155,14 @@ Operation classes that wrap async functionality with error handling decorators:
|
|
|
117
155
|
**`etcd3/endpoint.py`** - Base endpoint with shared logic:
|
|
118
156
|
- `BaseEndpoint` - Common endpoint functionality (fail state, health check)
|
|
119
157
|
|
|
120
|
-
**`etcd3/async_endpoint.py`**
|
|
158
|
+
**`etcd3/async_endpoint.py`** - Async `AsyncEndpoint` class:
|
|
121
159
|
- Async gRPC channel with `is_healthy()` and `rebuild_channel()`
|
|
122
160
|
|
|
123
|
-
**`etcd3/
|
|
161
|
+
**`etcd3/multi_client.py`** - Sync `Endpoint` class:
|
|
124
162
|
- Sync gRPC channel with `is_healthy()` and `rebuild_channel()`
|
|
125
163
|
|
|
164
|
+
**`etcd3/async_multi_client.py`** - Async `AsyncEndpoint` class (re-exported from async_endpoint)
|
|
165
|
+
|
|
126
166
|
### Shared Components
|
|
127
167
|
|
|
128
168
|
**`etcd3/shared.py`:**
|
|
@@ -131,8 +171,12 @@ Operation classes that wrap async functionality with error handling decorators:
|
|
|
131
171
|
- `Status`, `Alarm` - Status information classes
|
|
132
172
|
- `EtcdTokenCallCredentials` - Authentication credentials
|
|
133
173
|
- `RequestBuilder` - gRPC request builder utility
|
|
134
|
-
|
|
135
|
-
|
|
174
|
+
|
|
175
|
+
**`etcd3/errors.py`:**
|
|
176
|
+
- `create_sync_error_handlers()` - Factory for sync error handling decorators
|
|
177
|
+
- `create_async_error_handlers()` - Factory for async error handling decorators
|
|
178
|
+
- `_make_sync_handle_errors_with_tracing` - Sync error handler with tracing
|
|
179
|
+
- `_make_async_handle_errors_with_tracing` - Async error handler with tracing
|
|
136
180
|
|
|
137
181
|
**`etcd3/trace.py`:** Request tracing and monitoring utilities
|
|
138
182
|
|
|
@@ -181,12 +225,45 @@ Compiled during build via `gen_protos.py` (hatchling build hook).
|
|
|
181
225
|
| `service_discovery.py` | Async service discovery with watch callbacks |
|
|
182
226
|
| `service_discovery_sync.py` | Sync version using polling |
|
|
183
227
|
|
|
228
|
+
### Observability Utilities
|
|
229
|
+
|
|
230
|
+
**Logging:**
|
|
231
|
+
- `get_logger(name)` - Get a logger instance
|
|
232
|
+
- `set_log_level(level)` - Set global log level
|
|
233
|
+
|
|
234
|
+
**Tracing:**
|
|
235
|
+
- `get_tracer()` - Get OpenTelemetry tracer
|
|
236
|
+
- `create_span(name, attributes)` - Create a tracing span
|
|
237
|
+
- `is_tracing_available()` - Check if tracing is enabled
|
|
238
|
+
|
|
239
|
+
**Prometheus Metrics:**
|
|
240
|
+
- `get_exporter()` - Get Prometheus exporter instance
|
|
241
|
+
- `is_prometheus_available()` - Check if Prometheus is available
|
|
242
|
+
|
|
243
|
+
**Configuration:**
|
|
244
|
+
```python
|
|
245
|
+
from etcd3 import ObservabilityConfig, configure_from_env, auto_init
|
|
246
|
+
|
|
247
|
+
# Configure programmatically
|
|
248
|
+
config = ObservabilityConfig(
|
|
249
|
+
tracing_enabled=True,
|
|
250
|
+
log_format="json",
|
|
251
|
+
log_level="INFO",
|
|
252
|
+
prometheus_enabled=True,
|
|
253
|
+
)
|
|
254
|
+
config.apply()
|
|
255
|
+
|
|
256
|
+
# Or from environment variables
|
|
257
|
+
configure_from_env()
|
|
258
|
+
auto_init() # Auto-initialize at import time
|
|
259
|
+
```
|
|
260
|
+
|
|
184
261
|
## Key Design Patterns
|
|
185
262
|
|
|
186
|
-
1. **Dual sync/async implementations:** Same API for sync (`
|
|
263
|
+
1. **Dual sync/async implementations:** Same API for sync and async clients (client modules in `single_client.py` / `async_single_client.py`, with shims for backward compatibility)
|
|
187
264
|
|
|
188
265
|
2. **Error handling decorators:**
|
|
189
|
-
- `
|
|
266
|
+
- Error handlers are created via `create_sync_error_handlers()` and `create_async_error_handlers()` factories from `errors.py`
|
|
190
267
|
- `_sync_handle_keepalive_errors` / `_async_handle_keepalive_errors` - Lease keepalive with `propagate_errors=True`
|
|
191
268
|
|
|
192
269
|
3. **Endpoint failover:** `MultiEndpointEtcd3Client` tracks failed endpoints with retry on `failover=True`
|