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.
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/AGENTS.md +38 -4
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/CLAUDE.md +65 -20
- python_etcd3_async-0.1.0/README.md → python_etcd3_async-0.1.5/PKG-INFO +54 -4
- python_etcd3_async-0.1.0/PKG-INFO → python_etcd3_async-0.1.5/README.md +44 -14
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/README_CN.md +42 -4
- python_etcd3_async-0.1.5/docs/en/guide/leader_election.md +129 -0
- python_etcd3_async-0.1.5/docs/en/guide/service_discovery.md +167 -0
- python_etcd3_async-0.1.5/docs/en/guide/service_registration.md +159 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/guide/usage.md +3 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/index.md +4 -1
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/resources/contributing.md +2 -3
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/resources/history.md +1 -1
- python_etcd3_async-0.1.5/docs/superpowers/plans/2026-04-03-high-cohesion-low-coupling-plan.md +1093 -0
- python_etcd3_async-0.1.5/docs/superpowers/specs/2026-04-03-high-cohesion-low-coupling-design.md +255 -0
- python_etcd3_async-0.1.5/docs/zh/guide/leader_election.md +129 -0
- python_etcd3_async-0.1.5/docs/zh/guide/service_discovery.md +167 -0
- python_etcd3_async-0.1.5/docs/zh/guide/service_registration.md +159 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/guide/usage.md +3 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/index.md +4 -1
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/resources/contributing.md +2 -3
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/resources/history.md +1 -1
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/__init__.py +27 -13
- python_etcd3_async-0.1.5/etcd3/async_client.py +50 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_client_extensions.py +59 -41
- python_etcd3_async-0.1.5/etcd3/async_config.py +97 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_endpoint.py +2 -2
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_managers.py +30 -18
- python_etcd3_async-0.1.0/etcd3/async_client.py → python_etcd3_async-0.1.5/etcd3/async_multi_client.py +77 -332
- python_etcd3_async-0.1.5/etcd3/async_single_client.py +133 -0
- python_etcd3_async-0.1.5/etcd3/auth.py +690 -0
- python_etcd3_async-0.1.5/etcd3/client.py +45 -0
- python_etcd3_async-0.1.5/etcd3/config.py +155 -0
- python_etcd3_async-0.1.5/etcd3/errors.py +197 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/events.py +2 -4
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/exceptions.py +6 -2
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/leases.py +2 -2
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/locks.py +45 -33
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/members.py +2 -4
- python_etcd3_async-0.1.0/etcd3/client.py → python_etcd3_async-0.1.5/etcd3/multi_client.py +145 -920
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/shared.py +33 -183
- python_etcd3_async-0.1.5/etcd3/single_client.py +148 -0
- python_etcd3_async-0.1.5/etcd3/sync_cluster_operations.py +136 -0
- python_etcd3_async-0.1.5/etcd3/sync_kv_operations.py +642 -0
- python_etcd3_async-0.1.5/etcd3/sync_lock_operations.py +37 -0
- python_etcd3_async-0.1.5/etcd3/sync_maintenance_operations.py +244 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/trace_attributes.py +91 -103
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/transactions.py +2 -4
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/utils.py +4 -3
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/watch.py +7 -6
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcdrpc/__init__.py +2 -1
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/examples/leader_election.py +5 -3
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/examples/leader_election_sync.py +4 -3
- python_etcd3_async-0.1.5/examples/service_discovery_sync.py +213 -0
- python_etcd3_async-0.1.5/examples/service_discovery_v2.py +235 -0
- python_etcd3_async-0.1.5/examples/service_registration_sync.py +197 -0
- python_etcd3_async-0.1.5/examples/service_registry_v2.py +237 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/pyproject.toml +1 -1
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_async_client.py +2 -2
- python_etcd3_async-0.1.5/tests/test_async_client_integration.py +205 -0
- python_etcd3_async-0.1.5/tests/test_async_kv_operations_integration.py +147 -0
- python_etcd3_async-0.1.5/tests/test_auth.py +86 -0
- python_etcd3_async-0.1.5/tests/test_auth_integration.py +291 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_client.py +4 -4
- python_etcd3_async-0.1.0/examples/service_discovery_sync.py +0 -229
- python_etcd3_async-0.1.0/examples/service_registration_sync.py +0 -195
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/.coverage +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/.gitignore +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/.pylintrc +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/.python-version +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/SKILL.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/assets/stylesheets/extra.css +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/getting-started/installation.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/guide/advanced_queries.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/guide/configuration.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/auto_reconnection.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/error_handling.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/failover.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/monitoring.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/operations/observability.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/en/resources/authors.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/getting-started/installation.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/guide/advanced_queries.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/guide/configuration.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/async_error_handling.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/auto_reconnection.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/error_handling.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/failover.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/monitoring.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/operations/observability.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/docs/zh/resources/authors.md +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_monitor.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/async_watch.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/endpoint.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/exporters/__init__.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/exporters/prometheus.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/log.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/log_formatters.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/log_sampling.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/observability.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcd3/trace.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcdrpc/auth.proto +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcdrpc/kv.proto +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/etcdrpc/rpc.proto +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/examples/service_discovery.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/examples/service_registration.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/gen_protos.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/mkdocs.yml +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/__init__.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_async_error_handling.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_async_managers.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_batch_operations.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_events.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_kv_operations.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_lease_keepalive_retry.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_leases.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_leases_integration.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_locks.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_locks_integration.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_log_formatters.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_log_sampling.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_members.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_observability_integration.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_trace_attributes.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_transactions.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_transactions_integration.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_utils.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/tests/test_watch.py +0 -0
- {python_etcd3_async-0.1.0 → python_etcd3_async-0.1.5}/usage_guide.md +0 -0
- {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
|
|
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=
|
|
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
|
|
82
|
-
- `
|
|
83
|
-
- `
|
|
84
|
-
- `
|
|
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
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `async_client
|
|
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
|
-
**`
|
|
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
|
-
**`
|
|
96
|
-
|
|
97
|
-
|
|
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`**
|
|
126
|
+
**`etcd3/async_endpoint.py`** - Async `AsyncEndpoint` class:
|
|
121
127
|
- Async gRPC channel with `is_healthy()` and `rebuild_channel()`
|
|
122
128
|
|
|
123
|
-
**`etcd3/
|
|
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
|
-
|
|
135
|
-
|
|
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 (`
|
|
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
|
-
- `
|
|
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
|
-
##
|
|
298
|
+
## Observability
|
|
288
299
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
##
|
|
288
|
+
## Observability
|
|
298
289
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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)
|