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.
Files changed (166) hide show
  1. python_etcd3_async-0.1.6/.cnb.yml +127 -0
  2. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/.coverage +0 -0
  3. {python_etcd3_async-0.1.0 → 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.0 → python_etcd3_async-0.1.6}/CLAUDE.md +112 -35
  6. python_etcd3_async-0.1.6/PHASE2_PLAN.md +164 -0
  7. python_etcd3_async-0.1.0/README.md → python_etcd3_async-0.1.6/PKG-INFO +54 -4
  8. python_etcd3_async-0.1.0/PKG-INFO → python_etcd3_async-0.1.6/README.md +44 -14
  9. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/README_CN.md +42 -4
  10. python_etcd3_async-0.1.6/docs/en/getting-started/installation.md +73 -0
  11. python_etcd3_async-0.1.6/docs/en/guide/advanced_queries.md +55 -0
  12. python_etcd3_async-0.1.6/docs/en/guide/authentication.md +116 -0
  13. python_etcd3_async-0.1.6/docs/en/guide/cluster_maintenance.md +130 -0
  14. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/guide/configuration.md +52 -0
  15. python_etcd3_async-0.1.6/docs/en/guide/leader_election.md +129 -0
  16. python_etcd3_async-0.1.6/docs/en/guide/leases_locks.md +187 -0
  17. python_etcd3_async-0.1.6/docs/en/guide/service_discovery.md +167 -0
  18. python_etcd3_async-0.1.6/docs/en/guide/service_registration.md +159 -0
  19. python_etcd3_async-0.1.6/docs/en/guide/sync_client.md +138 -0
  20. python_etcd3_async-0.1.6/docs/en/guide/transactions.md +136 -0
  21. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/guide/usage.md +16 -7
  22. python_etcd3_async-0.1.6/docs/en/guide/watching.md +145 -0
  23. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/index.md +10 -1
  24. python_etcd3_async-0.1.6/docs/en/operations/auto_reconnection.md +84 -0
  25. python_etcd3_async-0.1.6/docs/en/operations/error_handling.md +85 -0
  26. python_etcd3_async-0.1.6/docs/en/operations/failover.md +74 -0
  27. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/operations/monitoring.md +20 -13
  28. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/resources/contributing.md +10 -2
  29. python_etcd3_async-0.1.6/docs/en/resources/history.md +39 -0
  30. python_etcd3_async-0.1.6/docs/en/resources/releasing.md +65 -0
  31. python_etcd3_async-0.1.6/docs/superpowers/plans/2026-04-03-high-cohesion-low-coupling-plan.md +1093 -0
  32. python_etcd3_async-0.1.6/docs/superpowers/specs/2026-04-03-high-cohesion-low-coupling-design.md +255 -0
  33. python_etcd3_async-0.1.6/docs/zh/getting-started/installation.md +73 -0
  34. python_etcd3_async-0.1.6/docs/zh/guide/advanced_queries.md +55 -0
  35. python_etcd3_async-0.1.6/docs/zh/guide/authentication.md +112 -0
  36. python_etcd3_async-0.1.6/docs/zh/guide/cluster_maintenance.md +129 -0
  37. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/guide/configuration.md +50 -0
  38. python_etcd3_async-0.1.6/docs/zh/guide/leader_election.md +129 -0
  39. python_etcd3_async-0.1.6/docs/zh/guide/leases_locks.md +180 -0
  40. python_etcd3_async-0.1.6/docs/zh/guide/service_discovery.md +167 -0
  41. python_etcd3_async-0.1.6/docs/zh/guide/service_registration.md +159 -0
  42. python_etcd3_async-0.1.6/docs/zh/guide/sync_client.md +136 -0
  43. python_etcd3_async-0.1.6/docs/zh/guide/transactions.md +133 -0
  44. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/guide/usage.md +16 -7
  45. python_etcd3_async-0.1.6/docs/zh/guide/watching.md +140 -0
  46. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/index.md +10 -1
  47. python_etcd3_async-0.1.6/docs/zh/operations/auto_reconnection.md +77 -0
  48. python_etcd3_async-0.1.6/docs/zh/operations/error_handling.md +80 -0
  49. python_etcd3_async-0.1.6/docs/zh/operations/failover.md +70 -0
  50. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/operations/monitoring.md +20 -13
  51. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/resources/contributing.md +9 -2
  52. python_etcd3_async-0.1.6/docs/zh/resources/history.md +38 -0
  53. python_etcd3_async-0.1.6/docs/zh/resources/releasing.md +63 -0
  54. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/__init__.py +28 -14
  55. python_etcd3_async-0.1.6/etcd3/async_client.py +50 -0
  56. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_client_extensions.py +61 -43
  57. python_etcd3_async-0.1.6/etcd3/async_config.py +97 -0
  58. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_endpoint.py +2 -2
  59. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_managers.py +37 -20
  60. python_etcd3_async-0.1.0/etcd3/async_client.py → python_etcd3_async-0.1.6/etcd3/async_multi_client.py +113 -341
  61. python_etcd3_async-0.1.6/etcd3/async_single_client.py +133 -0
  62. python_etcd3_async-0.1.6/etcd3/auth.py +690 -0
  63. python_etcd3_async-0.1.6/etcd3/client.py +45 -0
  64. python_etcd3_async-0.1.6/etcd3/config.py +155 -0
  65. python_etcd3_async-0.1.6/etcd3/errors.py +264 -0
  66. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/events.py +2 -4
  67. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/exceptions.py +6 -2
  68. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/leases.py +2 -2
  69. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/locks.py +45 -33
  70. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/members.py +2 -4
  71. python_etcd3_async-0.1.0/etcd3/client.py → python_etcd3_async-0.1.6/etcd3/multi_client.py +163 -931
  72. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/shared.py +37 -185
  73. python_etcd3_async-0.1.6/etcd3/single_client.py +148 -0
  74. python_etcd3_async-0.1.6/etcd3/sync_cluster_operations.py +141 -0
  75. python_etcd3_async-0.1.6/etcd3/sync_kv_operations.py +642 -0
  76. python_etcd3_async-0.1.6/etcd3/sync_lock_operations.py +37 -0
  77. python_etcd3_async-0.1.6/etcd3/sync_maintenance_operations.py +244 -0
  78. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/trace_attributes.py +91 -103
  79. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/transactions.py +2 -4
  80. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/utils.py +4 -3
  81. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/watch.py +7 -6
  82. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcdrpc/__init__.py +2 -1
  83. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/examples/leader_election.py +5 -3
  84. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/examples/leader_election_sync.py +4 -3
  85. python_etcd3_async-0.1.6/examples/service_discovery_sync.py +213 -0
  86. python_etcd3_async-0.1.6/examples/service_discovery_v2.py +235 -0
  87. python_etcd3_async-0.1.6/examples/service_registration_sync.py +197 -0
  88. python_etcd3_async-0.1.6/examples/service_registry_v2.py +237 -0
  89. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/pyproject.toml +23 -1
  90. python_etcd3_async-0.1.6/scripts/etcd-test.sh +191 -0
  91. python_etcd3_async-0.1.6/tests/conftest.py +244 -0
  92. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_async_client.py +17 -37
  93. python_etcd3_async-0.1.6/tests/test_async_client_integration.py +157 -0
  94. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_async_error_handling.py +26 -42
  95. python_etcd3_async-0.1.6/tests/test_async_kv_operations_integration.py +102 -0
  96. python_etcd3_async-0.1.6/tests/test_auth.py +186 -0
  97. python_etcd3_async-0.1.6/tests/test_auth_integration.py +427 -0
  98. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_batch_operations.py +12 -31
  99. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_client.py +4 -4
  100. python_etcd3_async-0.1.6/tests/test_cluster_integration.py +69 -0
  101. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_kv_operations.py +75 -74
  102. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_lease_keepalive_retry.py +79 -35
  103. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_leases_integration.py +36 -58
  104. python_etcd3_async-0.1.6/tests/test_locks_integration.py +100 -0
  105. python_etcd3_async-0.1.6/tests/test_maintenance_integration.py +40 -0
  106. python_etcd3_async-0.1.6/tests/test_tls_integration.py +58 -0
  107. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_transactions_integration.py +24 -74
  108. python_etcd3_async-0.1.6/tests/test_watch_integration.py +117 -0
  109. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/uv.lock +0 -1
  110. python_etcd3_async-0.1.0/AGENTS.md +0 -138
  111. python_etcd3_async-0.1.0/docs/en/getting-started/installation.md +0 -35
  112. python_etcd3_async-0.1.0/docs/en/guide/advanced_queries.md +0 -43
  113. python_etcd3_async-0.1.0/docs/en/operations/auto_reconnection.md +0 -34
  114. python_etcd3_async-0.1.0/docs/en/operations/error_handling.md +0 -37
  115. python_etcd3_async-0.1.0/docs/en/operations/failover.md +0 -54
  116. python_etcd3_async-0.1.0/docs/en/resources/history.md +0 -18
  117. python_etcd3_async-0.1.0/docs/zh/getting-started/installation.md +0 -35
  118. python_etcd3_async-0.1.0/docs/zh/guide/advanced_queries.md +0 -43
  119. python_etcd3_async-0.1.0/docs/zh/operations/auto_reconnection.md +0 -34
  120. python_etcd3_async-0.1.0/docs/zh/operations/error_handling.md +0 -37
  121. python_etcd3_async-0.1.0/docs/zh/operations/failover.md +0 -54
  122. python_etcd3_async-0.1.0/docs/zh/resources/history.md +0 -18
  123. python_etcd3_async-0.1.0/examples/service_discovery_sync.py +0 -229
  124. python_etcd3_async-0.1.0/examples/service_registration_sync.py +0 -195
  125. python_etcd3_async-0.1.0/tests/test_locks_integration.py +0 -147
  126. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/.pylintrc +0 -0
  127. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/.python-version +0 -0
  128. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/SKILL.md +0 -0
  129. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/assets/stylesheets/extra.css +0 -0
  130. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/operations/async_error_handling.md +0 -0
  131. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/operations/observability.md +0 -0
  132. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/en/resources/authors.md +0 -0
  133. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/operations/async_error_handling.md +0 -0
  134. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/operations/observability.md +0 -0
  135. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/docs/zh/resources/authors.md +0 -0
  136. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_monitor.py +0 -0
  137. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/async_watch.py +0 -0
  138. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/endpoint.py +0 -0
  139. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/exporters/__init__.py +0 -0
  140. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/exporters/prometheus.py +0 -0
  141. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/log.py +0 -0
  142. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/log_formatters.py +0 -0
  143. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/log_sampling.py +0 -0
  144. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/observability.py +0 -0
  145. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcd3/trace.py +0 -0
  146. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcdrpc/auth.proto +0 -0
  147. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcdrpc/kv.proto +0 -0
  148. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/etcdrpc/rpc.proto +0 -0
  149. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/examples/service_discovery.py +0 -0
  150. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/examples/service_registration.py +0 -0
  151. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/gen_protos.py +0 -0
  152. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/mkdocs.yml +0 -0
  153. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/__init__.py +0 -0
  154. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_async_managers.py +0 -0
  155. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_events.py +0 -0
  156. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_leases.py +0 -0
  157. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_locks.py +0 -0
  158. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_log_formatters.py +0 -0
  159. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_log_sampling.py +0 -0
  160. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_members.py +0 -0
  161. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_observability_integration.py +0 -0
  162. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_trace_attributes.py +0 -0
  163. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_transactions.py +0 -0
  164. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_utils.py +0 -0
  165. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.6}/tests/test_watch.py +0 -0
  166. {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
@@ -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=192.168.31.15:12379 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
39
53
 
40
- # Run with verbose output
41
- ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest -v
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
- | `ETCD3_LOG_LEVEL` | Set logging level (DEBUG, INFO, WARNING, ERROR) |
67
- | `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 |
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规范, use `pylint` for static analysis
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 (`etcd3/client.py`):**
82
- - `MultiEndpointEtcd3Client` - Base class with multiple endpoints and failover support
83
- - `Etcd3Client` - Single endpoint client, inherits from `MultiEndpointEtcd3Client`
84
- - `Endpoint` - Single etcd cluster endpoint with gRPC channel
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 (`etcd3/async_client.py`):**
87
- - `AsyncMultiEndpointEtcd3Client` - Main async client with failover support
88
- - `AsyncEtcd3Client` - Alias for backward compatibility
89
- - `async_client()` - Factory function
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
- **`RangeRequestConfig`** - Dataclass for range query options (limit, revision, sort_order, etc.)
94
-
95
- **`ClientConfig`** / **`AsyncClientConfig`** - Client connection configuration (host, port, timeout, TLS, auth)
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
- **`AsyncMultiEndpointConfig`** - Multi-endpoint failover configuration
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`** (async `AsyncEndpoint` class):
158
+ **`etcd3/async_endpoint.py`** - Async `AsyncEndpoint` class:
121
159
  - Async gRPC channel with `is_healthy()` and `rebuild_channel()`
122
160
 
123
- **`etcd3/client.py`** (sync `Endpoint` class):
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
- - `_handle_errors` / `_async_handle_errors` - Error handling decorators
135
- - `_make_sync_handle_generator_errors_with_tracing` - Generator error decorator with `propagate_errors` support
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 (`client.py`) and async (`async_client.py`)
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
- - `_handle_errors` / `_async_handle_errors` - Standard error handling
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`