python-etcd3-async 0.1.0__tar.gz → 0.1.5__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 (130) hide show
  1. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/AGENTS.md +38 -4
  2. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/CLAUDE.md +65 -20
  3. python_etcd3_async-0.1.0/README.md → python_etcd3_async-0.1.5/PKG-INFO +54 -4
  4. python_etcd3_async-0.1.0/PKG-INFO → python_etcd3_async-0.1.5/README.md +44 -14
  5. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/README_CN.md +42 -4
  6. python_etcd3_async-0.1.5/docs/en/guide/leader_election.md +129 -0
  7. python_etcd3_async-0.1.5/docs/en/guide/service_discovery.md +167 -0
  8. python_etcd3_async-0.1.5/docs/en/guide/service_registration.md +159 -0
  9. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/guide/usage.md +3 -0
  10. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/index.md +4 -1
  11. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/resources/contributing.md +2 -3
  12. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/resources/history.md +1 -1
  13. python_etcd3_async-0.1.5/docs/superpowers/plans/2026-04-03-high-cohesion-low-coupling-plan.md +1093 -0
  14. python_etcd3_async-0.1.5/docs/superpowers/specs/2026-04-03-high-cohesion-low-coupling-design.md +255 -0
  15. python_etcd3_async-0.1.5/docs/zh/guide/leader_election.md +129 -0
  16. python_etcd3_async-0.1.5/docs/zh/guide/service_discovery.md +167 -0
  17. python_etcd3_async-0.1.5/docs/zh/guide/service_registration.md +159 -0
  18. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/guide/usage.md +3 -0
  19. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/index.md +4 -1
  20. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/resources/contributing.md +2 -3
  21. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/resources/history.md +1 -1
  22. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/__init__.py +27 -13
  23. python_etcd3_async-0.1.5/etcd3/async_client.py +50 -0
  24. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_client_extensions.py +59 -41
  25. python_etcd3_async-0.1.5/etcd3/async_config.py +97 -0
  26. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_endpoint.py +2 -2
  27. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_managers.py +30 -18
  28. python_etcd3_async-0.1.0/etcd3/async_client.py → python_etcd3_async-0.1.5/etcd3/async_multi_client.py +77 -332
  29. python_etcd3_async-0.1.5/etcd3/async_single_client.py +133 -0
  30. python_etcd3_async-0.1.5/etcd3/auth.py +690 -0
  31. python_etcd3_async-0.1.5/etcd3/client.py +45 -0
  32. python_etcd3_async-0.1.5/etcd3/config.py +155 -0
  33. python_etcd3_async-0.1.5/etcd3/errors.py +197 -0
  34. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/events.py +2 -4
  35. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/exceptions.py +6 -2
  36. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/leases.py +2 -2
  37. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/locks.py +45 -33
  38. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/members.py +2 -4
  39. python_etcd3_async-0.1.0/etcd3/client.py → python_etcd3_async-0.1.5/etcd3/multi_client.py +145 -920
  40. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/shared.py +33 -183
  41. python_etcd3_async-0.1.5/etcd3/single_client.py +148 -0
  42. python_etcd3_async-0.1.5/etcd3/sync_cluster_operations.py +136 -0
  43. python_etcd3_async-0.1.5/etcd3/sync_kv_operations.py +642 -0
  44. python_etcd3_async-0.1.5/etcd3/sync_lock_operations.py +37 -0
  45. python_etcd3_async-0.1.5/etcd3/sync_maintenance_operations.py +244 -0
  46. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/trace_attributes.py +91 -103
  47. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/transactions.py +2 -4
  48. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/utils.py +4 -3
  49. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/watch.py +7 -6
  50. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcdrpc/__init__.py +2 -1
  51. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/examples/leader_election.py +5 -3
  52. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/examples/leader_election_sync.py +4 -3
  53. python_etcd3_async-0.1.5/examples/service_discovery_sync.py +213 -0
  54. python_etcd3_async-0.1.5/examples/service_discovery_v2.py +235 -0
  55. python_etcd3_async-0.1.5/examples/service_registration_sync.py +197 -0
  56. python_etcd3_async-0.1.5/examples/service_registry_v2.py +237 -0
  57. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/pyproject.toml +1 -1
  58. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_async_client.py +2 -2
  59. python_etcd3_async-0.1.5/tests/test_async_client_integration.py +205 -0
  60. python_etcd3_async-0.1.5/tests/test_async_kv_operations_integration.py +147 -0
  61. python_etcd3_async-0.1.5/tests/test_auth.py +86 -0
  62. python_etcd3_async-0.1.5/tests/test_auth_integration.py +291 -0
  63. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_client.py +4 -4
  64. python_etcd3_async-0.1.0/examples/service_discovery_sync.py +0 -229
  65. python_etcd3_async-0.1.0/examples/service_registration_sync.py +0 -195
  66. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/.coverage +0 -0
  67. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/.gitignore +0 -0
  68. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/.pylintrc +0 -0
  69. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/.python-version +0 -0
  70. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/SKILL.md +0 -0
  71. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/assets/stylesheets/extra.css +0 -0
  72. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/getting-started/installation.md +0 -0
  73. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/guide/advanced_queries.md +0 -0
  74. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/guide/configuration.md +0 -0
  75. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/async_error_handling.md +0 -0
  76. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/auto_reconnection.md +0 -0
  77. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/error_handling.md +0 -0
  78. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/failover.md +0 -0
  79. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/monitoring.md +0 -0
  80. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/observability.md +0 -0
  81. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/resources/authors.md +0 -0
  82. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/getting-started/installation.md +0 -0
  83. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/guide/advanced_queries.md +0 -0
  84. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/guide/configuration.md +0 -0
  85. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/async_error_handling.md +0 -0
  86. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/auto_reconnection.md +0 -0
  87. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/error_handling.md +0 -0
  88. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/failover.md +0 -0
  89. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/monitoring.md +0 -0
  90. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/observability.md +0 -0
  91. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/resources/authors.md +0 -0
  92. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_monitor.py +0 -0
  93. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_watch.py +0 -0
  94. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/endpoint.py +0 -0
  95. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/exporters/__init__.py +0 -0
  96. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/exporters/prometheus.py +0 -0
  97. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/log.py +0 -0
  98. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/log_formatters.py +0 -0
  99. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/log_sampling.py +0 -0
  100. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/observability.py +0 -0
  101. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/trace.py +0 -0
  102. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcdrpc/auth.proto +0 -0
  103. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcdrpc/kv.proto +0 -0
  104. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcdrpc/rpc.proto +0 -0
  105. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/examples/service_discovery.py +0 -0
  106. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/examples/service_registration.py +0 -0
  107. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/gen_protos.py +0 -0
  108. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/mkdocs.yml +0 -0
  109. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/__init__.py +0 -0
  110. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_async_error_handling.py +0 -0
  111. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_async_managers.py +0 -0
  112. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_batch_operations.py +0 -0
  113. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_events.py +0 -0
  114. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_kv_operations.py +0 -0
  115. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_lease_keepalive_retry.py +0 -0
  116. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_leases.py +0 -0
  117. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_leases_integration.py +0 -0
  118. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_locks.py +0 -0
  119. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_locks_integration.py +0 -0
  120. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_log_formatters.py +0 -0
  121. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_log_sampling.py +0 -0
  122. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_members.py +0 -0
  123. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_observability_integration.py +0 -0
  124. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_trace_attributes.py +0 -0
  125. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_transactions.py +0 -0
  126. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_transactions_integration.py +0 -0
  127. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_utils.py +0 -0
  128. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_watch.py +0 -0
  129. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/usage_guide.md +0 -0
  130. {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/uv.lock +0 -0
@@ -22,6 +22,8 @@ uv pip install -e . # Editable install
22
22
  ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest
23
23
  ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest tests/test_async_client.py::TestAsyncClient::test_client_init -v
24
24
  ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest --cov=etcd3 --cov=etcdrpc
25
+ ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest tests/ -v -k "test_name" # Run tests matching pattern
26
+ ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest tests/ --ignore=tests/test_integration.py # Skip integration tests
25
27
  ```
26
28
 
27
29
  ### Linting
@@ -43,32 +45,46 @@ uv run mkdocs serve # Serve docs
43
45
  - **Max line length**: 100 characters
44
46
  - **Formatter**: ruff
45
47
  - Follow PEP 8
48
+ - 4 spaces for indentation (no tabs)
49
+ - Trailing commas for multi-line structures
50
+ - Single blank line between top-level definitions
46
51
 
47
52
  ### Naming
48
53
  - Classes: `PascalCase` (e.g., `AsyncEtcd3Client`)
49
54
  - Functions/Methods: `snake_case` (e.g., `async_client()`, `get_prefix()`)
50
55
  - Constants: `UPPER_CASE` (e.g., `FILTER_PUT`)
51
56
  - Private: `_leading_underscore` (e.g., `_get_channel()`)
57
+ - Protected: `_leading_underscore` for internal use
58
+ - Type variables: `PascalCase` (e.g., `T`, `TKey`, `TValue`)
52
59
 
53
60
  ### Imports (3 groups, sorted)
54
- 1. Standard library
55
- 2. Third-party (grpc, etcdrpc, pytest)
56
- 3. Internal modules
61
+ 1. Standard library (`asyncio`, `functools`, `typing`, etc.)
62
+ 2. Third-party (`grpc`, `etcdrpc`, `pytest`)
63
+ 3. Internal modules (`etcd3.*`, `etcdrpc.*`)
64
+ - Use absolute imports within package
65
+ - Sort alphabetically within groups
57
66
 
58
67
  ### Type Hints
59
68
  - Use type hints for all parameters and returns
60
69
  - Use `Optional[]` for nullable types, `Literal[]` for enums
70
+ - Use `|` syntax for unions (e.g., `str | bytes`)
71
+ - Use `X | None` over `Optional[X]`
72
+ - Generics: `list[int]`, `dict[str, Any]` over `List[int]`, `Dict[str, Any]`
61
73
 
62
74
  ### Docstrings
63
75
  - Google-style
64
76
  - Document all public methods/classes
65
- - Include type info
77
+ - Include type info in params and returns
78
+ - First line should be a concise summary
79
+ - Use `:raises:` for exceptions that may be raised
66
80
 
67
81
  ### Error Handling
68
82
  - Use custom exceptions from `etcd3/exceptions.py`:
69
83
  - `Etcd3Exception` (base), `ConnectionFailedError`, `ConnectionTimeoutError`
70
84
  - `RevisionCompactedError`, `LockReleaseError`, `BatchOperationError`
71
85
  - Decorators: `_handle_errors`, `_async_handle_errors`
86
+ - Never catch broad `Exception` unless re-raising
87
+ - Always include context in error messages
72
88
 
73
89
  ## Architecture
74
90
 
@@ -84,6 +100,24 @@ uv run mkdocs serve # Serve docs
84
100
  ### Load Balancing
85
101
  - `LoadBalancerStrategy` enum: `PRIORITY`, `ROUND_ROBIN`, `RANDOM`, `STICKY`
86
102
 
103
+ ### Package Structure
104
+ ```
105
+ etcd3/
106
+ __init__.py # Public exports (client, async_client, exceptions, etc.)
107
+ async_client.py # AsyncEtcd3Client (main async client)
108
+ client.py # Etcd3Client (sync client)
109
+ async_client_extensions.py # Operation mixins
110
+ async_endpoint.py # AsyncEndpoint (single endpoint handler)
111
+ async_managers.py # Watch, Cluster, Maintenance managers
112
+ exceptions.py # Custom exceptions
113
+ transactions.py # Transaction support
114
+ utils.py # Utility functions
115
+ log.py # Logging utilities
116
+ shared.py # Shared code between sync/async
117
+ etcdrpc/ # Generated protobuf/gRPC code
118
+ tests/ # Test files
119
+ ```
120
+
87
121
  ## Observability (Environment Variables)
88
122
 
89
123
  All prefixed with `ETCD_OBS_`:
@@ -26,7 +26,7 @@ uv pip install -e .
26
26
  ### Testing
27
27
  ```bash
28
28
  # Run all tests (requires ETCD_ENDPOINT environment variable)
29
- ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest
29
+ ETCD_ENDPOINT=127.0.0.1:2379 uv run pytest
30
30
 
31
31
  # Run a single test file
32
32
  ETCD_ENDPOINT=192.168.31.15:12379 uv run pytest tests/test_async_client.py
@@ -63,8 +63,11 @@ uv run python gen_protos.py
63
63
  ### Environment Variables
64
64
  | Variable | Description |
65
65
  |----------|-------------|
66
- | `ETCD3_LOG_LEVEL` | Set logging level (DEBUG, INFO, WARNING, ERROR) |
67
66
  | `ETCD_ENDPOINT` | etcd server address for testing (e.g., 192.168.31.15:12379) |
67
+ | `ETCD_OBS_LOG_LEVEL` | Logging level (DEBUG, INFO, WARNING, ERROR) |
68
+ | `ETCD_OBS_LOG_FORMAT` | Log format (text, json, colored) |
69
+ | `ETCD_OBS_TRACING_ENABLED` | Enable OpenTelemetry tracing |
70
+ | `ETCD_OBS_PROMETHEUS_ENABLED` | Enable Prometheus metrics |
68
71
 
69
72
  ## Project Rules
70
73
 
@@ -78,23 +81,26 @@ uv run python gen_protos.py
78
81
 
79
82
  ### Core Client Classes
80
83
 
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
84
+ **Sync client modules:**
85
+ - `etcd3/single_client.py` - `Etcd3Client` - Single endpoint client, inherits from `MultiEndpointEtcd3Client`
86
+ - `etcd3/multi_client.py` - `MultiEndpointEtcd3Client` - Base class with multiple endpoints and failover support, `Endpoint` - Single etcd cluster endpoint with gRPC channel
87
+ - `etcd3/client.py` - Compatibility shim (re-exports from the above modules)
85
88
 
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
89
+ **Async client modules:**
90
+ - `etcd3/async_single_client.py` - `AsyncEtcd3Client` - Single endpoint async client
91
+ - `etcd3/async_multi_client.py` - `AsyncMultiEndpointEtcd3Client` - Main async client with failover support, `AsyncEndpoint` - Single etcd cluster endpoint with async gRPC channel
92
+ - `etcd3/async_client.py` - Compatibility shim (re-exports from the above modules)
90
93
 
91
94
  ### Configuration Classes
92
95
 
93
- **`RangeRequestConfig`** - Dataclass for range query options (limit, revision, sort_order, etc.)
96
+ **`etcd3/config.py`:**
97
+ - `RangeRequestConfig` - Dataclass for range query options (limit, revision, sort_order, etc.)
98
+ - `ClientConfig` - Client connection configuration (host, port, timeout, TLS, auth)
99
+ - `MultiEndpointConfig` - Multi-endpoint failover configuration
94
100
 
95
- **`ClientConfig`** / **`AsyncClientConfig`** - Client connection configuration (host, port, timeout, TLS, auth)
96
-
97
- **`AsyncMultiEndpointConfig`** - Multi-endpoint failover configuration
101
+ **`etcd3/async_config.py`:**
102
+ - `AsyncClientConfig` - Async client connection configuration
103
+ - `AsyncMultiEndpointConfig` - Async multi-endpoint failover configuration
98
104
 
99
105
  ### Async Operation Extensions (`etcd3/async_client_extensions.py`)
100
106
 
@@ -117,12 +123,14 @@ Operation classes that wrap async functionality with error handling decorators:
117
123
  **`etcd3/endpoint.py`** - Base endpoint with shared logic:
118
124
  - `BaseEndpoint` - Common endpoint functionality (fail state, health check)
119
125
 
120
- **`etcd3/async_endpoint.py`** (async `AsyncEndpoint` class):
126
+ **`etcd3/async_endpoint.py`** - Async `AsyncEndpoint` class:
121
127
  - Async gRPC channel with `is_healthy()` and `rebuild_channel()`
122
128
 
123
- **`etcd3/client.py`** (sync `Endpoint` class):
129
+ **`etcd3/multi_client.py`** - Sync `Endpoint` class:
124
130
  - Sync gRPC channel with `is_healthy()` and `rebuild_channel()`
125
131
 
132
+ **`etcd3/async_multi_client.py`** - Async `AsyncEndpoint` class (re-exported from async_endpoint)
133
+
126
134
  ### Shared Components
127
135
 
128
136
  **`etcd3/shared.py`:**
@@ -131,8 +139,12 @@ Operation classes that wrap async functionality with error handling decorators:
131
139
  - `Status`, `Alarm` - Status information classes
132
140
  - `EtcdTokenCallCredentials` - Authentication credentials
133
141
  - `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
142
+
143
+ **`etcd3/errors.py`:**
144
+ - `create_sync_error_handlers()` - Factory for sync error handling decorators
145
+ - `create_async_error_handlers()` - Factory for async error handling decorators
146
+ - `_make_sync_handle_errors_with_tracing` - Sync error handler with tracing
147
+ - `_make_async_handle_errors_with_tracing` - Async error handler with tracing
136
148
 
137
149
  **`etcd3/trace.py`:** Request tracing and monitoring utilities
138
150
 
@@ -181,12 +193,45 @@ Compiled during build via `gen_protos.py` (hatchling build hook).
181
193
  | `service_discovery.py` | Async service discovery with watch callbacks |
182
194
  | `service_discovery_sync.py` | Sync version using polling |
183
195
 
196
+ ### Observability Utilities
197
+
198
+ **Logging:**
199
+ - `get_logger(name)` - Get a logger instance
200
+ - `set_log_level(level)` - Set global log level
201
+
202
+ **Tracing:**
203
+ - `get_tracer()` - Get OpenTelemetry tracer
204
+ - `create_span(name, attributes)` - Create a tracing span
205
+ - `is_tracing_available()` - Check if tracing is enabled
206
+
207
+ **Prometheus Metrics:**
208
+ - `get_exporter()` - Get Prometheus exporter instance
209
+ - `is_prometheus_available()` - Check if Prometheus is available
210
+
211
+ **Configuration:**
212
+ ```python
213
+ from etcd3 import ObservabilityConfig, configure_from_env, auto_init
214
+
215
+ # Configure programmatically
216
+ config = ObservabilityConfig(
217
+ tracing_enabled=True,
218
+ log_format="json",
219
+ log_level="INFO",
220
+ prometheus_enabled=True,
221
+ )
222
+ config.apply()
223
+
224
+ # Or from environment variables
225
+ configure_from_env()
226
+ auto_init() # Auto-initialize at import time
227
+ ```
228
+
184
229
  ## Key Design Patterns
185
230
 
186
- 1. **Dual sync/async implementations:** Same API for sync (`client.py`) and async (`async_client.py`)
231
+ 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
232
 
188
233
  2. **Error handling decorators:**
189
- - `_handle_errors` / `_async_handle_errors` - Standard error handling
234
+ - Error handlers are created via `create_sync_error_handlers()` and `create_async_error_handlers()` factories from `errors.py`
190
235
  - `_sync_handle_keepalive_errors` / `_async_handle_keepalive_errors` - Lease keepalive with `propagate_errors=True`
191
236
 
192
237
  3. **Endpoint failover:** `MultiEndpointEtcd3Client` tracks failed endpoints with retry on `failover=True`
@@ -1,3 +1,13 @@
1
+ Metadata-Version: 2.5
2
+ Name: python-etcd3-async
3
+ Version: 0.1.5
4
+ Summary: etcd3 async sdk
5
+ Requires-Python: <4,>=3.12
6
+ Requires-Dist: grpcio==1.76.0
7
+ Requires-Dist: prometheus-client>=0.24.1
8
+ Requires-Dist: protobuf==6.31.1
9
+ Description-Content-Type: text/markdown
10
+
1
11
  # python-etcd3-async
2
12
 
3
13
  [中文文档](./README_CN.md) | English
@@ -15,6 +25,7 @@ Python async client for the etcd API v3, supporting both synchronous and asynchr
15
25
  - **Keepalive error propagation**: Lease keepalive errors are properly propagated for connection loss detection
16
26
  - **Endpoint failover**: Built-in support for multiple endpoints with automatic failover
17
27
  - **Context manager**: Clean resource management for both sync and async
28
+ - **Observability**: Structured logging, OpenTelemetry tracing, and Prometheus metrics
18
29
 
19
30
  ## Quick Start
20
31
 
@@ -284,11 +295,50 @@ client = await async_client(
284
295
  )
285
296
  ```
286
297
 
287
- ## Environment Variables
298
+ ## Observability
288
299
 
289
- | Variable | Description |
290
- |----------|-------------|
291
- | `ETCD3_LOG_LEVEL` | Set logging level (DEBUG, INFO, WARNING, ERROR) |
300
+ ### Configure via Environment
301
+
302
+ ```bash
303
+ export ETCD_OBS_LOG_LEVEL=DEBUG
304
+ export ETCD_OBS_LOG_FORMAT=json
305
+ export ETCD_OBS_TRACING_ENABLED=true
306
+ export ETCD_OBS_PROMETHEUS_ENABLED=true
307
+ ```
308
+
309
+ ### Configure Programmatically
310
+
311
+ ```python
312
+ from etcd3 import ObservabilityConfig
313
+
314
+ config = ObservabilityConfig(
315
+ tracing_enabled=True,
316
+ log_format="json",
317
+ log_level="INFO",
318
+ prometheus_enabled=True,
319
+ )
320
+ config.apply()
321
+ ```
322
+
323
+ ### Access Metrics
324
+
325
+ ```python
326
+ from etcd3.exporters.prometheus import get_exporter
327
+
328
+ exporter = get_exporter()
329
+ if exporter:
330
+ print(exporter.metrics().decode())
331
+ ```
332
+
333
+ ## API Reference
334
+
335
+ | Variable | Default | Description |
336
+ |----------|---------|-------------|
337
+ | `ETCD_OBS_LOG_LEVEL` | INFO | Logging level (DEBUG, INFO, WARNING, ERROR) |
338
+ | `ETCD_OBS_LOG_FORMAT` | text | Log format (text, json, colored) |
339
+ | `ETCD_OBS_TRACING_ENABLED` | true | Enable OpenTelemetry tracing |
340
+ | `ETCD_OBS_TRACING_SERVICE_NAME` | etcd3-client | Service name for tracing |
341
+ | `ETCD_OBS_PROMETHEUS_ENABLED` | false | Enable Prometheus metrics export |
292
342
 
293
343
  ## API Reference
294
344
 
@@ -1,13 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: python-etcd3-async
3
- Version: 0.1.0
4
- Summary: etcd3 async sdk
5
- Requires-Python: <4,>=3.12
6
- Requires-Dist: grpcio==1.76.0
7
- Requires-Dist: prometheus-client>=0.24.1
8
- Requires-Dist: protobuf==6.31.1
9
- Description-Content-Type: text/markdown
10
-
11
1
  # python-etcd3-async
12
2
 
13
3
  [中文文档](./README_CN.md) | English
@@ -25,6 +15,7 @@ Python async client for the etcd API v3, supporting both synchronous and asynchr
25
15
  - **Keepalive error propagation**: Lease keepalive errors are properly propagated for connection loss detection
26
16
  - **Endpoint failover**: Built-in support for multiple endpoints with automatic failover
27
17
  - **Context manager**: Clean resource management for both sync and async
18
+ - **Observability**: Structured logging, OpenTelemetry tracing, and Prometheus metrics
28
19
 
29
20
  ## Quick Start
30
21
 
@@ -294,11 +285,50 @@ client = await async_client(
294
285
  )
295
286
  ```
296
287
 
297
- ## Environment Variables
288
+ ## Observability
298
289
 
299
- | Variable | Description |
300
- |----------|-------------|
301
- | `ETCD3_LOG_LEVEL` | Set logging level (DEBUG, INFO, WARNING, ERROR) |
290
+ ### Configure via Environment
291
+
292
+ ```bash
293
+ export ETCD_OBS_LOG_LEVEL=DEBUG
294
+ export ETCD_OBS_LOG_FORMAT=json
295
+ export ETCD_OBS_TRACING_ENABLED=true
296
+ export ETCD_OBS_PROMETHEUS_ENABLED=true
297
+ ```
298
+
299
+ ### Configure Programmatically
300
+
301
+ ```python
302
+ from etcd3 import ObservabilityConfig
303
+
304
+ config = ObservabilityConfig(
305
+ tracing_enabled=True,
306
+ log_format="json",
307
+ log_level="INFO",
308
+ prometheus_enabled=True,
309
+ )
310
+ config.apply()
311
+ ```
312
+
313
+ ### Access Metrics
314
+
315
+ ```python
316
+ from etcd3.exporters.prometheus import get_exporter
317
+
318
+ exporter = get_exporter()
319
+ if exporter:
320
+ print(exporter.metrics().decode())
321
+ ```
322
+
323
+ ## API Reference
324
+
325
+ | Variable | Default | Description |
326
+ |----------|---------|-------------|
327
+ | `ETCD_OBS_LOG_LEVEL` | INFO | Logging level (DEBUG, INFO, WARNING, ERROR) |
328
+ | `ETCD_OBS_LOG_FORMAT` | text | Log format (text, json, colored) |
329
+ | `ETCD_OBS_TRACING_ENABLED` | true | Enable OpenTelemetry tracing |
330
+ | `ETCD_OBS_TRACING_SERVICE_NAME` | etcd3-client | Service name for tracing |
331
+ | `ETCD_OBS_PROMETHEUS_ENABLED` | false | Enable Prometheus metrics export |
302
332
 
303
333
  ## API Reference
304
334
 
@@ -15,6 +15,7 @@ Python etcd API v3 异步客户端,支持同步和异步操作。
15
15
  - **Keepalive 错误传播**: 租约保活错误正确传播,便于连接断开检测
16
16
  - **端点故障转移**: 支持多节点自动故障转移
17
17
  - **上下文管理**: 资源清理更加简洁
18
+ - **可观测性**: 结构化日志、OpenTelemetry 追踪、Prometheus 指标
18
19
 
19
20
  ## 快速开始
20
21
 
@@ -284,14 +285,51 @@ client = await async_client(
284
285
  )
285
286
  ```
286
287
 
287
- ## 环境变量
288
+ ## 可观测性
288
289
 
289
- | 变量 | 说明 |
290
- |------|------|
291
- | `ETCD3_LOG_LEVEL` | 设置日志级别 (DEBUG, INFO, WARNING, ERROR) |
290
+ ### 通过环境变量配置
291
+
292
+ ```bash
293
+ export ETCD_OBS_LOG_LEVEL=DEBUG
294
+ export ETCD_OBS_LOG_FORMAT=json
295
+ export ETCD_OBS_TRACING_ENABLED=true
296
+ export ETCD_OBS_PROMETHEUS_ENABLED=true
297
+ ```
298
+
299
+ ### 通过代码配置
300
+
301
+ ```python
302
+ from etcd3 import ObservabilityConfig
303
+
304
+ config = ObservabilityConfig(
305
+ tracing_enabled=True,
306
+ log_format="json",
307
+ log_level="INFO",
308
+ prometheus_enabled=True,
309
+ )
310
+ config.apply()
311
+ ```
312
+
313
+ ### 获取指标
314
+
315
+ ```python
316
+ from etcd3.exporters.prometheus import get_exporter
317
+
318
+ exporter = get_exporter()
319
+ if exporter:
320
+ print(exporter.metrics().decode())
321
+ ```
292
322
 
293
323
  ## API 参考
294
324
 
325
+ | 变量 | 默认值 | 说明 |
326
+ |------|--------|------|
327
+ | `ETCD_OBS_LOG_LEVEL` | INFO | 日志级别 (DEBUG, INFO, WARNING, ERROR) |
328
+ | `ETCD_OBS_LOG_FORMAT` | text | 日志格式 (text, json, colored) |
329
+ | `ETCD_OBS_TRACING_ENABLED` | true | 启用 OpenTelemetry 追踪 |
330
+ | `ETCD_OBS_TRACING_SERVICE_NAME` | etcd3-client | 追踪服务名称 |
331
+ | `ETCD_OBS_PROMETHEUS_ENABLED` | false | 启用 Prometheus 指标导出 |
332
+
295
333
  ### 客户端工厂函数
296
334
 
297
335
  | 函数 | 说明 |
@@ -0,0 +1,129 @@
1
+ ---
2
+ title: Leader Election
3
+ ---
4
+
5
+ # Leader Election
6
+
7
+ The leader election pattern is used to designate one instance as the primary coordinator among multiple instances. python-etcd3-async provides a `LeaderElection` class that implements this pattern using etcd distributed locks.
8
+
9
+ ## Design Principles
10
+
11
+ - **Lease-based**: Uses etcd Lease for automatic expiration
12
+ - **Auto-renewal**: Automatically renews the lease while holding the lock
13
+ - **Graceful shutdown**: Properly releases the lock on shutdown
14
+ - **Lock loss callback**: Notifies the application when the lock is lost
15
+
16
+ ## Basic Usage
17
+
18
+ ```python
19
+ import asyncio
20
+ from etcd3 import async_client, AsyncLock
21
+
22
+ class LeaderElection:
23
+ def __init__(self, client, name: str, ttl: int = 30):
24
+ self.client = client
25
+ self.name = name
26
+ self.key = f"election/{name}"
27
+ self.ttl = ttl
28
+ self._lock: Optional[AsyncLock] = None
29
+ self._is_leader = False
30
+ self._refresh_task: Optional[asyncio.Task] = None
31
+
32
+ async def try_become_leader(self, timeout: float = None) -> bool:
33
+ """Attempt to become leader"""
34
+ self._lock = await self.client.lock(self.key, ttl=self.ttl)
35
+ self._is_leader = await self._lock.acquire(timeout=timeout)
36
+ if self._is_leader:
37
+ self._start_refresh()
38
+ return self._is_leader
39
+
40
+ async def is_leader(self) -> bool:
41
+ """Check if current instance is leader"""
42
+ return self._is_leader
43
+
44
+ async def resign(self) -> bool:
45
+ """Voluntarily give up leader status"""
46
+ self._stop_refresh()
47
+ self._is_leader = False
48
+ if self._lock:
49
+ return await self._lock.release()
50
+ return False
51
+ ```
52
+
53
+ ## Complete Example
54
+
55
+ ```python
56
+ import asyncio
57
+ import logging
58
+ import os
59
+ from etcd3 import async_client
60
+
61
+ logging.basicConfig(level=logging.INFO)
62
+
63
+
64
+ async def main():
65
+ # Create client
66
+ client = await async_client(
67
+ host=os.environ.get("ETCD_HOST", "localhost"),
68
+ port=int(os.environ.get("ETCD_PORT", "2379"))
69
+ )
70
+
71
+ # Create election
72
+ election = LeaderElection(client, "my-service", ttl=30)
73
+
74
+ try:
75
+ # Try to become leader
76
+ if await election.try_become_leader():
77
+ print("Became leader")
78
+ # Do leader work...
79
+ while election.is_acquired:
80
+ await asyncio.sleep(10)
81
+ print("Leader task running...")
82
+ else:
83
+ print("Could not become leader")
84
+ finally:
85
+ await election.close()
86
+ await client.close()
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ ## TTL Considerations
92
+
93
+ | TTL Value | Use Case |
94
+ |-----------|----------|
95
+ | 5-10s | Fast failover required |
96
+ | 15-30s | General use (recommended) |
97
+ | 60s+ | Low network frequency |
98
+
99
+ - **Shorter TTL**: Faster failover when leader fails
100
+ - **Longer TTL**: Lower network overhead, less sensitive to network jitter
101
+
102
+ ## Lock Loss Detection
103
+
104
+ Set a callback to handle lock loss:
105
+
106
+ ```python
107
+ def on_lock_lost(name, data):
108
+ print(f"Lock lost for {name}, will retry")
109
+ # Trigger re-election
110
+
111
+ election.set_on_lost_callback(on_lock_lost)
112
+ ```
113
+
114
+ ## Watching for Leader Changes
115
+
116
+ Monitor election results:
117
+
118
+ ```python
119
+ async def on_new_leader(name, data):
120
+ print(f"New leader elected: {data}")
121
+
122
+ await election.watch_election(on_new_leader)
123
+ ```
124
+
125
+ ## See Also
126
+
127
+ - [Lock Operations](usage.md)
128
+ - [Lease Operations](usage.md)
129
+ - [examples/leader_election.py](https://github.com/owasd/python3-etcd-async/blob/main/examples/leader_election.py)