python-etcd3-async 0.1.5__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.5 → python_etcd3_async-0.1.6}/.coverage +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/.gitignore +1 -0
- python_etcd3_async-0.1.6/AGENTS.md +240 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/CLAUDE.md +48 -16
- python_etcd3_async-0.1.6/PHASE2_PLAN.md +164 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/PKG-INFO +1 -1
- 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.5 → python_etcd3_async-0.1.6}/docs/en/guide/configuration.md +52 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/guide/leader_election.md +1 -1
- python_etcd3_async-0.1.6/docs/en/guide/leases_locks.md +187 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/guide/service_discovery.md +1 -1
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/guide/service_registration.md +1 -1
- 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.5 → python_etcd3_async-0.1.6}/docs/en/guide/usage.md +13 -7
- python_etcd3_async-0.1.6/docs/en/guide/watching.md +145 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/index.md +6 -0
- 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.5 → python_etcd3_async-0.1.6}/docs/en/operations/monitoring.md +20 -13
- python_etcd3_async-0.1.6/docs/en/resources/contributing.md +46 -0
- 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/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.5 → python_etcd3_async-0.1.6}/docs/zh/guide/configuration.md +50 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/guide/leader_election.md +1 -1
- python_etcd3_async-0.1.6/docs/zh/guide/leases_locks.md +180 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/guide/service_discovery.md +1 -1
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/guide/service_registration.md +1 -1
- 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.5 → python_etcd3_async-0.1.6}/docs/zh/guide/usage.md +13 -7
- python_etcd3_async-0.1.6/docs/zh/guide/watching.md +140 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/index.md +6 -0
- 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.5 → python_etcd3_async-0.1.6}/docs/zh/operations/monitoring.md +20 -13
- python_etcd3_async-0.1.6/docs/zh/resources/contributing.md +45 -0
- 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.5 → python_etcd3_async-0.1.6}/etcd3/__init__.py +1 -1
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_client_extensions.py +2 -2
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_managers.py +7 -2
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_multi_client.py +36 -9
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/errors.py +85 -18
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/multi_client.py +24 -17
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/shared.py +14 -12
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/sync_cluster_operations.py +7 -2
- {python_etcd3_async-0.1.5 → 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.5 → python_etcd3_async-0.1.6}/tests/test_async_client.py +15 -35
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_client_integration.py +19 -67
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_error_handling.py +26 -42
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_kv_operations_integration.py +14 -59
- 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.5 → python_etcd3_async-0.1.6}/tests/test_batch_operations.py +12 -31
- python_etcd3_async-0.1.6/tests/test_cluster_integration.py +69 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_kv_operations.py +75 -74
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_lease_keepalive_retry.py +79 -35
- {python_etcd3_async-0.1.5 → 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.5 → 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.5 → python_etcd3_async-0.1.6}/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.1.6}/.pylintrc +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/.python-version +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/README.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/README_CN.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/SKILL.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/assets/stylesheets/extra.css +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/operations/observability.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/resources/authors.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/superpowers/plans/2026-04-03-high-cohesion-low-coupling-plan.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/superpowers/specs/2026-04-03-high-cohesion-low-coupling-design.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/operations/observability.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/resources/authors.md +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_client.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_config.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_endpoint.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_monitor.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_single_client.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_watch.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/auth.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/client.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/config.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/endpoint.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/events.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/exceptions.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/exporters/__init__.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/exporters/prometheus.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/leases.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/locks.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/log.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/log_formatters.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/log_sampling.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/members.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/observability.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/single_client.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/sync_kv_operations.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/sync_lock_operations.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/sync_maintenance_operations.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/trace.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/trace_attributes.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/transactions.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/utils.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/watch.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcdrpc/__init__.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcdrpc/auth.proto +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcdrpc/kv.proto +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcdrpc/rpc.proto +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/leader_election.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/leader_election_sync.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_discovery.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_discovery_sync.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_discovery_v2.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_registration.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_registration_sync.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_registry_v2.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/gen_protos.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/mkdocs.yml +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/__init__.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_managers.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_client.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_events.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_leases.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_locks.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_log_formatters.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_log_sampling.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_members.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_observability_integration.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_trace_attributes.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_transactions.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_utils.py +0 -0
- {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_watch.py +0 -0
- {python_etcd3_async-0.1.5 → 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
|
|
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
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# python-etcd3-async 二期开发计划
|
|
2
|
+
|
|
3
|
+
> 基线:一期完成后(commit `bac8307`),373 用例双模式全绿(无 etcd 285 passed/88 skipped;
|
|
4
|
+
> localhost:12379 全部通过),覆盖率 TOTAL 55%,无任何 CI 资产。
|
|
5
|
+
> 目标顺序执行,每个里程碑独立可验证、可提交。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## M1 — async 客户端自动认证闭环(P0,库核心)
|
|
10
|
+
|
|
11
|
+
**问题**:`async_client(user=, password=)` 存了凭据但从不认证——`_init_auth()`
|
|
12
|
+
(etcd3/async_multi_client.py:163) 是死代码;etcd 重启后 token 失效也不会重新认证。
|
|
13
|
+
一期 `tests/conftest.py` 的 `etcd_auth` fixture 靠显式调用 workaround。
|
|
14
|
+
|
|
15
|
+
**任务**:
|
|
16
|
+
1. lazy 认证:`_get_channel()` 成功后(或统一在进入操作 handler 前)若
|
|
17
|
+
`_user and _password and not _auth_done` 则 `await _init_auth()`;保持现有双检锁幂等
|
|
18
|
+
2. 重连重认证:endpoint channel 重建路径(`async_endpoint.py` 健康检查失败重建、
|
|
19
|
+
`async_multi_client` 的 NoServerAvailableError 重建处)将 `_auth_done` 置 False,
|
|
20
|
+
下次操作自动重新 Authenticate
|
|
21
|
+
3. 移除 conftest `etcd_auth` 中的显式 `_init_auth()` workaround 与 TODO 注释
|
|
22
|
+
4. 集成测试锁定行为:
|
|
23
|
+
- `tests/test_auth_integration.py` 新增:带凭据的 `async_client` 对 auth-enforced
|
|
24
|
+
实例直接读写成功(不再手工触发认证)
|
|
25
|
+
- 与 M3 的重启用例联动验证 token 失效后自动恢复
|
|
26
|
+
|
|
27
|
+
**验收**:`up-auth` 环境下 `ETCD_AUTH_*` 三元组齐备 → 全套通过;conftest 无 TODO;
|
|
28
|
+
无 etcd 与主实例模式回归不变。
|
|
29
|
+
|
|
30
|
+
**风险**:触碰连接/重连主线。缓解:先完成 M3-6(重启自动化)铺底,再改 M1;
|
|
31
|
+
若顺序被阻塞,M1 先只做 lazy 认证(第 1、3 步),重认证(第 2 步)拆后续提交。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## M2 — sync 客户端 auth 对等修复(P0,库核心)
|
|
36
|
+
|
|
37
|
+
**问题**:`multi_client.py:155-176` 构造时同步 Authenticate,且设置
|
|
38
|
+
`call_credentials`——一期已证明 grpc 拒绝在明文通道使用 call credentials
|
|
39
|
+
(commit `42db025` 修了 async 侧),sync 侧同样路径目前**零测试**,明文 + auth
|
|
40
|
+
实例下预期会失败。另外 `Exception("if using authentication credentials ...")`
|
|
41
|
+
应换成 `etcd3.exceptions` 的专用异常而不是裸 Exception(顺带小改,单独提交)。
|
|
42
|
+
|
|
43
|
+
**任务**:
|
|
44
|
+
1. metadata-only 对齐:sync `_authenticate` 分支删除 `call_credentials` 赋值,
|
|
45
|
+
token 仅走 `self.metadata`(检查所有 stub 调用点均传 `metadata=self.metadata`,
|
|
46
|
+
缺漏处补齐)
|
|
47
|
+
2. conftest 新增 `etcd_auth_sync` fixture(auth 三元组 → 同步 `client(...)` 带
|
|
48
|
+
user/password,否则回退主实例)
|
|
49
|
+
3. `tests/test_auth_integration.py` 补 sync 版用例(与 async 同构:user/role/权限
|
|
50
|
+
生命周期 + auth-enforced 实例上的普通读写)
|
|
51
|
+
4. 裸 `Exception` → `ValueError`/自定义异常(保持向后兼容看用法决定,先 grep tests/examples)
|
|
52
|
+
|
|
53
|
+
**验收**:`up-auth` + sync fixture 用例双路(fallback/enforced)通过;
|
|
54
|
+
`test_client.py`、`test_auth.py` 单元回归不变。
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## M3 — 测试深化(P1)
|
|
59
|
+
|
|
60
|
+
**任务**:
|
|
61
|
+
1. **id(etcd) 迁移收尾**:Group A 剩余 33 处 `id(etcd)` 型 key 改 `key_prefix`
|
|
62
|
+
(`f"x_{id(etcd)}"` → `f"{key_prefix}x"` + 签名加参),机械替换后全量回归
|
|
63
|
+
2. **cluster/maintenance 集成覆盖**(当前为 0):
|
|
64
|
+
- `tests/test_cluster_integration.py`:member_list 基线断言(单节点)、
|
|
65
|
+
member_add/remove 往返(容器内可寻址的 peer URL,注意 podman 网络限制,
|
|
66
|
+
必要时标记 `@pytest.mark.unstable`)
|
|
67
|
+
- `tests/test_maintenance_integration.py`:status/endpoint Health 对主客户端、
|
|
68
|
+
alarm 列表只读;defrag 在单节点容器上跑通即可
|
|
69
|
+
3. **watch 语义扩展**(并入现有 `test_async_client_integration.py` 或新文件):
|
|
70
|
+
多 watcher 并行、prev_kv、start_revision 指向已压缩 revision →
|
|
71
|
+
`RevisionCompactedError`(配合 compact 操作制造压缩点)、progress notify
|
|
72
|
+
4. **lease TTL 到期**:lease(2) + put,轮询 5s 内 key 消失
|
|
73
|
+
5. **etcd 重启自动化**:`TestRefreshLeaseIntegration` 的"需手动重启"改为自动
|
|
74
|
+
`podman restart etcd-test`;新 marker `@pytest.mark.restart`,默认 skip,
|
|
75
|
+
`ETCD_TEST_RESTART=1` 时启用;重启用例必须自恢复(重认证后写读成功,依赖 M1-2)
|
|
76
|
+
6. `scripts/etcd-test.sh` 增加 `restart` 子命令
|
|
77
|
+
|
|
78
|
+
**验收**:全量跑后 etcd 残留 key 仍为 0;无 env 模式下全部新用例正确 skip;
|
|
79
|
+
覆盖率 `etcd3/`(排除 etcdrpc)较基线提升 ≥8pp。
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## M4 — TLS/secure 通道测试(P0-3)
|
|
84
|
+
|
|
85
|
+
**任务**:
|
|
86
|
+
1. `scripts/etcd-test.sh gen-certs`:openssl 自签 CA + server cert(SAN 含
|
|
87
|
+
localhost/127.0.0.1),输出到 `.etcd-tls/`(加入 .gitignore)
|
|
88
|
+
2. `up-tls`:第三实例 `etcd-test-tls` 端口 24379,`--cert-file/--key-file/--trusted-ca-file`
|
|
89
|
+
+ `--auth`(认证叠加 TLS,call credentials 合法路径)
|
|
90
|
+
3. conftest 增加 `ETCD_TLS_*` 环境变量组与 `etcd_tls` fixture(secure + token
|
|
91
|
+
在 TLS 通道可继续用 call credentials,作为 M2 删除该路径的对照组)
|
|
92
|
+
4. `tests/test_tls_integration.py`:证书连接读写、错误 CA 报
|
|
93
|
+
`ConnectionFailedError`、auth-over-TLS、(可选)client cert 认证
|
|
94
|
+
5. 无 `ETCD_TLS_ENDPOINT` 时整套 auto-skip(纳入现有 gating:fixture 名集合加
|
|
95
|
+
`etcd_tls`)
|
|
96
|
+
|
|
97
|
+
**验收**:`up-tls` 下 TLS 套件通过;未配置时 skip;`down` 清理全部三个容器与证书目录。
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## M5 — CI 流水线(P2)
|
|
102
|
+
|
|
103
|
+
**前置调查**:cnb.cool 的流水线配置格式与 podman/service 容器支持(文档 + 一次
|
|
104
|
+
空跑验证);若不支持容器,退化为纯 lint+unit 流水线,integration 留本地。
|
|
105
|
+
|
|
106
|
+
**任务**(以 `.cnb.yml` 或平台等价物为例):
|
|
107
|
+
1. `lint`:`ruff check etcd3/ etcdrpc/ tests/` + `ruff format --check`(秒级,必跑)
|
|
108
|
+
2. `unit`:`uv run pytest -m unit`(无 etcd)
|
|
109
|
+
3. `integration`:起 etcd 服务(镜像同 `etcd-test.sh` 钉扎版本)→
|
|
110
|
+
`ETCD_ENDPOINT=... uv run pytest -m integration`
|
|
111
|
+
4. `auth`(可选 allow-failure 起步):双实例 + `ETCD_AUTH_*` 跑 auth 文件
|
|
112
|
+
5. 覆盖率产物:`--cov=etcd3` 输出到报告,**拆分** etcd3 与 etcdrpc 两个数字,
|
|
113
|
+
etcd3 基线首次记录进 CI 注释(阶段目标 70%,只防回退不硬卡)
|
|
114
|
+
|
|
115
|
+
**验收**:push 一个探针分支验证三条流水线端到端。
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## M6 — 工程杂务(P2)
|
|
120
|
+
|
|
121
|
+
1. 文档纠错:AGENTS.md / docs/en / docs/zh 的 `uv sync --extra dev`、
|
|
122
|
+
`uv sync --dev` 改为 `uv sync`(dev 是 `[dependency-groups]`,extras 不存在)
|
|
123
|
+
2. pylint 二选一:补最小 `.pylintrc`(对齐 ruff 规则子集、禁噪音项)并从 CI lint
|
|
124
|
+
步骤调用;或直接删掉文档中的 pylint 命令——**建议后者**,ruff 已覆盖
|
|
125
|
+
3. 版本单源化:`pyproject.toml` `dynamic = ["version"]` + hatch 读
|
|
126
|
+
`etcd3/__init__.py.__version__`(或反向),消灭 0.1.5 时手工三处同步;
|
|
127
|
+
附 `scripts/bump-version.sh` 或至少 AGENTS.md 记录唯一真相位置
|
|
128
|
+
4. 发布检查表进入 docs/dev(build wheel → 装 → `import etcd3` 冒烟 → tag)
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 里程碑顺序与提交切分
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
M3-5/M3-6(重启自动化,铺底)
|
|
136
|
+
→ M1(async 自动认证,含删 workaround)
|
|
137
|
+
→ M2(sync 对等修复)
|
|
138
|
+
→ M3-1..4(迁移与补盲)
|
|
139
|
+
→ M4(TLS)
|
|
140
|
+
→ M5(CI 固化)
|
|
141
|
+
→ M6(杂务,随时可并行插队)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
每个任务节点 1 个提交,沿用一期 style:`fix:` / `test:` / `chore(...)` / `docs:`。
|
|
145
|
+
|
|
146
|
+
## 全局验证协议(每个提交都要过)
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
env -u ETCD_ENDPOINT -u ETCD_HOST -u ETCD_PORT uv run pytest # 0 failed,集成 skip
|
|
150
|
+
./scripts/etcd-test.sh up && ETCD_ENDPOINT=localhost:12379 uv run pytest # 全绿
|
|
151
|
+
./scripts/etcd-test.sh up-auth && ETCD_ENDPOINT=... ETCD_AUTH_*=... uv run pytest -m integration
|
|
152
|
+
uv run ruff check etcd3/ etcdrpc/ tests/ && uv run ruff format --check etcd3/ etcdrpc/ tests/
|
|
153
|
+
podman exec etcd-test etcdctl get "" --prefix --keys-only | grep -c 'pytest-' # 恒为 0
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 风险登记
|
|
157
|
+
|
|
158
|
+
| 风险 | 缓解 |
|
|
159
|
+
|---|---|
|
|
160
|
+
| M1 改重连主线引入回归 | 先落 M3-5 自动重启测试;M1 拆两个提交(lazy / re-auth) |
|
|
161
|
+
| member_add 受 podman 单网络拓扑限制 | 断言降级为"add 成功 + list 可见",remove 允许失败标记 unstable |
|
|
162
|
+
| cnb CI 无容器服务 | M5 降级为 lint+unit,integration 保持本地脚本 |
|
|
163
|
+
| sync 删除 call_credentials 影响 secure 用户 | M4 的 TLS 对照用例证明 token-in-metadata 在 TLS 通道同样有效,否则保留 secure 分支条件赋值 |
|
|
164
|
+
| 覆盖率目标拉扯测试质量 | 只防回退不追数字,新断言必须真调 etcd API |
|