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.
Files changed (168) hide show
  1. python_etcd3_async-0.1.6/.cnb.yml +127 -0
  2. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/.coverage +0 -0
  3. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/.gitignore +1 -0
  4. python_etcd3_async-0.1.6/AGENTS.md +240 -0
  5. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/CLAUDE.md +48 -16
  6. python_etcd3_async-0.1.6/PHASE2_PLAN.md +164 -0
  7. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/PKG-INFO +1 -1
  8. python_etcd3_async-0.1.6/docs/en/getting-started/installation.md +73 -0
  9. python_etcd3_async-0.1.6/docs/en/guide/advanced_queries.md +55 -0
  10. python_etcd3_async-0.1.6/docs/en/guide/authentication.md +116 -0
  11. python_etcd3_async-0.1.6/docs/en/guide/cluster_maintenance.md +130 -0
  12. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/guide/configuration.md +52 -0
  13. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/guide/leader_election.md +1 -1
  14. python_etcd3_async-0.1.6/docs/en/guide/leases_locks.md +187 -0
  15. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/guide/service_discovery.md +1 -1
  16. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/guide/service_registration.md +1 -1
  17. python_etcd3_async-0.1.6/docs/en/guide/sync_client.md +138 -0
  18. python_etcd3_async-0.1.6/docs/en/guide/transactions.md +136 -0
  19. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/guide/usage.md +13 -7
  20. python_etcd3_async-0.1.6/docs/en/guide/watching.md +145 -0
  21. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/index.md +6 -0
  22. python_etcd3_async-0.1.6/docs/en/operations/auto_reconnection.md +84 -0
  23. python_etcd3_async-0.1.6/docs/en/operations/error_handling.md +85 -0
  24. python_etcd3_async-0.1.6/docs/en/operations/failover.md +74 -0
  25. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/operations/monitoring.md +20 -13
  26. python_etcd3_async-0.1.6/docs/en/resources/contributing.md +46 -0
  27. python_etcd3_async-0.1.6/docs/en/resources/history.md +39 -0
  28. python_etcd3_async-0.1.6/docs/en/resources/releasing.md +65 -0
  29. python_etcd3_async-0.1.6/docs/zh/getting-started/installation.md +73 -0
  30. python_etcd3_async-0.1.6/docs/zh/guide/advanced_queries.md +55 -0
  31. python_etcd3_async-0.1.6/docs/zh/guide/authentication.md +112 -0
  32. python_etcd3_async-0.1.6/docs/zh/guide/cluster_maintenance.md +129 -0
  33. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/guide/configuration.md +50 -0
  34. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/guide/leader_election.md +1 -1
  35. python_etcd3_async-0.1.6/docs/zh/guide/leases_locks.md +180 -0
  36. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/guide/service_discovery.md +1 -1
  37. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/guide/service_registration.md +1 -1
  38. python_etcd3_async-0.1.6/docs/zh/guide/sync_client.md +136 -0
  39. python_etcd3_async-0.1.6/docs/zh/guide/transactions.md +133 -0
  40. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/guide/usage.md +13 -7
  41. python_etcd3_async-0.1.6/docs/zh/guide/watching.md +140 -0
  42. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/index.md +6 -0
  43. python_etcd3_async-0.1.6/docs/zh/operations/auto_reconnection.md +77 -0
  44. python_etcd3_async-0.1.6/docs/zh/operations/error_handling.md +80 -0
  45. python_etcd3_async-0.1.6/docs/zh/operations/failover.md +70 -0
  46. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/operations/monitoring.md +20 -13
  47. python_etcd3_async-0.1.6/docs/zh/resources/contributing.md +45 -0
  48. python_etcd3_async-0.1.6/docs/zh/resources/history.md +38 -0
  49. python_etcd3_async-0.1.6/docs/zh/resources/releasing.md +63 -0
  50. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/__init__.py +1 -1
  51. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_client_extensions.py +2 -2
  52. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_managers.py +7 -2
  53. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_multi_client.py +36 -9
  54. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/errors.py +85 -18
  55. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/multi_client.py +24 -17
  56. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/shared.py +14 -12
  57. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/sync_cluster_operations.py +7 -2
  58. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/pyproject.toml +23 -1
  59. python_etcd3_async-0.1.6/scripts/etcd-test.sh +191 -0
  60. python_etcd3_async-0.1.6/tests/conftest.py +244 -0
  61. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_client.py +15 -35
  62. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_client_integration.py +19 -67
  63. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_error_handling.py +26 -42
  64. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_kv_operations_integration.py +14 -59
  65. python_etcd3_async-0.1.6/tests/test_auth.py +186 -0
  66. python_etcd3_async-0.1.6/tests/test_auth_integration.py +427 -0
  67. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_batch_operations.py +12 -31
  68. python_etcd3_async-0.1.6/tests/test_cluster_integration.py +69 -0
  69. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_kv_operations.py +75 -74
  70. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_lease_keepalive_retry.py +79 -35
  71. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_leases_integration.py +36 -58
  72. python_etcd3_async-0.1.6/tests/test_locks_integration.py +100 -0
  73. python_etcd3_async-0.1.6/tests/test_maintenance_integration.py +40 -0
  74. python_etcd3_async-0.1.6/tests/test_tls_integration.py +58 -0
  75. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_transactions_integration.py +24 -74
  76. python_etcd3_async-0.1.6/tests/test_watch_integration.py +117 -0
  77. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/uv.lock +0 -1
  78. python_etcd3_async-0.1.5/AGENTS.md +0 -172
  79. python_etcd3_async-0.1.5/docs/en/getting-started/installation.md +0 -35
  80. python_etcd3_async-0.1.5/docs/en/guide/advanced_queries.md +0 -43
  81. python_etcd3_async-0.1.5/docs/en/operations/auto_reconnection.md +0 -34
  82. python_etcd3_async-0.1.5/docs/en/operations/error_handling.md +0 -37
  83. python_etcd3_async-0.1.5/docs/en/operations/failover.md +0 -54
  84. python_etcd3_async-0.1.5/docs/en/resources/contributing.md +0 -37
  85. python_etcd3_async-0.1.5/docs/en/resources/history.md +0 -18
  86. python_etcd3_async-0.1.5/docs/zh/getting-started/installation.md +0 -35
  87. python_etcd3_async-0.1.5/docs/zh/guide/advanced_queries.md +0 -43
  88. python_etcd3_async-0.1.5/docs/zh/operations/auto_reconnection.md +0 -34
  89. python_etcd3_async-0.1.5/docs/zh/operations/error_handling.md +0 -37
  90. python_etcd3_async-0.1.5/docs/zh/operations/failover.md +0 -54
  91. python_etcd3_async-0.1.5/docs/zh/resources/contributing.md +0 -37
  92. python_etcd3_async-0.1.5/docs/zh/resources/history.md +0 -18
  93. python_etcd3_async-0.1.5/tests/test_auth.py +0 -86
  94. python_etcd3_async-0.1.5/tests/test_auth_integration.py +0 -291
  95. python_etcd3_async-0.1.5/tests/test_locks_integration.py +0 -147
  96. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/.pylintrc +0 -0
  97. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/.python-version +0 -0
  98. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/README.md +0 -0
  99. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/README_CN.md +0 -0
  100. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/SKILL.md +0 -0
  101. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/assets/stylesheets/extra.css +0 -0
  102. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/operations/async_error_handling.md +0 -0
  103. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/operations/observability.md +0 -0
  104. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/en/resources/authors.md +0 -0
  105. {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
  106. {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
  107. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/operations/async_error_handling.md +0 -0
  108. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/operations/observability.md +0 -0
  109. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/docs/zh/resources/authors.md +0 -0
  110. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_client.py +0 -0
  111. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_config.py +0 -0
  112. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_endpoint.py +0 -0
  113. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_monitor.py +0 -0
  114. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_single_client.py +0 -0
  115. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/async_watch.py +0 -0
  116. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/auth.py +0 -0
  117. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/client.py +0 -0
  118. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/config.py +0 -0
  119. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/endpoint.py +0 -0
  120. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/events.py +0 -0
  121. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/exceptions.py +0 -0
  122. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/exporters/__init__.py +0 -0
  123. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/exporters/prometheus.py +0 -0
  124. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/leases.py +0 -0
  125. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/locks.py +0 -0
  126. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/log.py +0 -0
  127. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/log_formatters.py +0 -0
  128. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/log_sampling.py +0 -0
  129. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/members.py +0 -0
  130. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/observability.py +0 -0
  131. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/single_client.py +0 -0
  132. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/sync_kv_operations.py +0 -0
  133. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/sync_lock_operations.py +0 -0
  134. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/sync_maintenance_operations.py +0 -0
  135. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/trace.py +0 -0
  136. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/trace_attributes.py +0 -0
  137. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/transactions.py +0 -0
  138. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/utils.py +0 -0
  139. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcd3/watch.py +0 -0
  140. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcdrpc/__init__.py +0 -0
  141. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcdrpc/auth.proto +0 -0
  142. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcdrpc/kv.proto +0 -0
  143. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/etcdrpc/rpc.proto +0 -0
  144. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/leader_election.py +0 -0
  145. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/leader_election_sync.py +0 -0
  146. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_discovery.py +0 -0
  147. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_discovery_sync.py +0 -0
  148. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_discovery_v2.py +0 -0
  149. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_registration.py +0 -0
  150. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_registration_sync.py +0 -0
  151. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/examples/service_registry_v2.py +0 -0
  152. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/gen_protos.py +0 -0
  153. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/mkdocs.yml +0 -0
  154. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/__init__.py +0 -0
  155. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_async_managers.py +0 -0
  156. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_client.py +0 -0
  157. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_events.py +0 -0
  158. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_leases.py +0 -0
  159. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_locks.py +0 -0
  160. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_log_formatters.py +0 -0
  161. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_log_sampling.py +0 -0
  162. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_members.py +0 -0
  163. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_observability_integration.py +0 -0
  164. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_trace_attributes.py +0 -0
  165. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_transactions.py +0 -0
  166. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_utils.py +0 -0
  167. {python_etcd3_async-0.1.5 → python_etcd3_async-0.1.6}/tests/test_watch.py +0 -0
  168. {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
@@ -15,3 +15,4 @@ htmlcov/
15
15
  # Virtual environments
16
16
  .venv
17
17
  /site
18
+ .etcd-tls/
@@ -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
- # Run all tests (requires ETCD_ENDPOINT environment variable)
29
- ETCD_ENDPOINT=127.0.0.1:2379 uv run pytest
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=192.168.31.15:12379 uv run pytest tests/test_async_client.py
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=192.168.31.15:12379 uv run pytest tests/test_locks.py::TestAsyncLock::test_lock_acquire -v
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=192.168.31.15:12379 uv run pytest --cov=etcd3 --cov=etcdrpc
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
- # Run with verbose output
41
- ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest -v
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., 192.168.31.15:12379) |
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规范, use `pylint` for static analysis
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 |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-etcd3-async
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: etcd3 async sdk
5
5
  Requires-Python: <4,>=3.12
6
6
  Requires-Dist: grpcio==1.76.0