python-etcd3-async 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
- etcd3/__init__.py +1 -1
- etcd3/async_client_extensions.py +27 -13
- etcd3/async_multi_client.py +7 -6
- etcd3/sync_kv_operations.py +26 -39
- {python_etcd3_async-0.2.0.dist-info → python_etcd3_async-0.2.1.dist-info}/METADATA +29 -6
- {python_etcd3_async-0.2.0.dist-info → python_etcd3_async-0.2.1.dist-info}/RECORD +7 -7
- {python_etcd3_async-0.2.0.dist-info → python_etcd3_async-0.2.1.dist-info}/WHEEL +0 -0
etcd3/__init__.py
CHANGED
etcd3/async_client_extensions.py
CHANGED
|
@@ -176,20 +176,20 @@ class AsyncKVOperations:
|
|
|
176
176
|
channel = await self.client._get_channel()
|
|
177
177
|
return etcdrpc.KVStub(channel)
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"""Get the value of a key from etcd.
|
|
179
|
+
async def _range(self, range_request):
|
|
180
|
+
"""Run a range request.
|
|
182
181
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
Reads are idempotent, so a transient failure can be retried. The
|
|
183
|
+
stub is created per attempt so that a retry picks up a freshly
|
|
184
|
+
built channel.
|
|
185
|
+
|
|
186
|
+
:param range_request: the request to send
|
|
187
|
+
:type range_request: etcdrpc.RangeRequest
|
|
188
|
+
:returns: the range response
|
|
189
|
+
:rtype: etcdrpc.RangeResponse
|
|
187
190
|
"""
|
|
188
|
-
range_request = self.client._build_get_range_request(key, **kwargs)
|
|
189
191
|
|
|
190
|
-
async def
|
|
191
|
-
# The stub is created per attempt so that a retry picks up a
|
|
192
|
-
# freshly built channel.
|
|
192
|
+
async def _do_range():
|
|
193
193
|
kv_stub = await self._get_kv_stub()
|
|
194
194
|
return await kv_stub.Range(
|
|
195
195
|
range_request,
|
|
@@ -198,8 +198,19 @@ class AsyncKVOperations:
|
|
|
198
198
|
metadata=self.client.metadata,
|
|
199
199
|
)
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
return await self.client._retry_with_backoff(_do_range)
|
|
202
|
+
|
|
203
|
+
@_async_handle_errors
|
|
204
|
+
async def get_response(self, key, **kwargs):
|
|
205
|
+
"""Get the value of a key from etcd.
|
|
206
|
+
|
|
207
|
+
:param key: key in etcd to get
|
|
208
|
+
:type key: str or bytes
|
|
209
|
+
:param kwargs: Additional arguments (revision, limit, sort_order, etc.)
|
|
210
|
+
:returns: RangeResponse from etcd
|
|
211
|
+
"""
|
|
212
|
+
range_request = self.client._build_get_range_request(key, **kwargs)
|
|
213
|
+
response = await self._range(range_request)
|
|
203
214
|
|
|
204
215
|
# Set trace attributes from response
|
|
205
216
|
endpoint = (
|
|
@@ -293,6 +304,9 @@ class AsyncKVOperations:
|
|
|
293
304
|
:type ignore_value: bool
|
|
294
305
|
:param ignore_lease: if True, update the key using the current lease
|
|
295
306
|
:type ignore_lease: bool
|
|
307
|
+
:returns: the response header, or the previous key-value metadata
|
|
308
|
+
when ``prev_kv`` is True
|
|
309
|
+
:rtype: :class:`.rpc_pb2.ResponseHeader` or :class:`.KVMetadata`
|
|
296
310
|
"""
|
|
297
311
|
put_request = RequestBuilder.build_put_request(
|
|
298
312
|
key=key,
|
etcd3/async_multi_client.py
CHANGED
|
@@ -840,8 +840,9 @@ class AsyncMultiEndpointEtcd3Client:
|
|
|
840
840
|
:type ignore_value: bool
|
|
841
841
|
:param ignore_lease: if True, update the key using the current lease
|
|
842
842
|
:type ignore_lease: bool
|
|
843
|
-
:returns:
|
|
844
|
-
|
|
843
|
+
:returns: the response header, or the previous key-value metadata
|
|
844
|
+
when ``prev_kv`` is True
|
|
845
|
+
:rtype: :class:`.rpc_pb2.ResponseHeader` or :class:`.KVMetadata`
|
|
845
846
|
"""
|
|
846
847
|
return await self.kv.put(
|
|
847
848
|
key,
|
|
@@ -1026,10 +1027,10 @@ class AsyncMultiEndpointEtcd3Client:
|
|
|
1026
1027
|
:type return_response: bool
|
|
1027
1028
|
:param range_end: End of the key range (for range deletes)
|
|
1028
1029
|
:type range_end: str or bytes, optional
|
|
1029
|
-
:returns:
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1030
|
+
:returns: the number of deleted keys when ``return_response`` is
|
|
1031
|
+
False, and a response containing a header, the number of
|
|
1032
|
+
deleted keys and prev_kvs when ``return_response`` is True
|
|
1033
|
+
:rtype: int or :class:`.rpc_pb2.DeleteRangeResponse`
|
|
1033
1034
|
"""
|
|
1034
1035
|
return await self.kv.delete(
|
|
1035
1036
|
key, prev_kv=prev_kv, return_response=return_response, range_end=range_end
|
etcd3/sync_kv_operations.py
CHANGED
|
@@ -83,16 +83,20 @@ class SyncKVOperations:
|
|
|
83
83
|
max_create_revision=max_create_revision,
|
|
84
84
|
)
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
def _range(self, range_request):
|
|
87
|
+
"""Run a range request.
|
|
88
|
+
|
|
89
|
+
Reads are idempotent, so a transient failure can be retried. The
|
|
90
|
+
stub is created per attempt so that a retry picks up a freshly
|
|
91
|
+
built channel.
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
:param range_request: the request to send
|
|
94
|
+
:type range_request: etcdrpc.RangeRequest
|
|
95
|
+
:returns: the range response
|
|
96
|
+
:rtype: etcdrpc.RangeResponse
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def _do_range():
|
|
96
100
|
kv_stub = etcdrpc.KVStub(self.channel)
|
|
97
101
|
return kv_stub.Range(
|
|
98
102
|
range_request,
|
|
@@ -101,8 +105,15 @@ class SyncKVOperations:
|
|
|
101
105
|
metadata=self.metadata,
|
|
102
106
|
)
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
return self.client._retry_with_backoff(_do_range)
|
|
109
|
+
|
|
110
|
+
@_sync_handle_errors
|
|
111
|
+
@monitor_request("kv.get_response")
|
|
112
|
+
@log_operation("kv.get")
|
|
113
|
+
def get_response(self, key, **kwargs):
|
|
114
|
+
"""Get the value of a key from etcd."""
|
|
115
|
+
range_request = self._build_get_range_request(key, **kwargs)
|
|
116
|
+
return self._range(range_request)
|
|
106
117
|
|
|
107
118
|
def get(self, key, **kwargs):
|
|
108
119
|
"""
|
|
@@ -139,15 +150,7 @@ class SyncKVOperations:
|
|
|
139
150
|
range_end=utils.prefix_range_end(utils.to_bytes(key_prefix)),
|
|
140
151
|
**kwargs,
|
|
141
152
|
)
|
|
142
|
-
|
|
143
|
-
# Create KV stub directly
|
|
144
|
-
kv_stub = etcdrpc.KVStub(self.channel)
|
|
145
|
-
return kv_stub.Range(
|
|
146
|
-
range_request,
|
|
147
|
-
self.timeout,
|
|
148
|
-
credentials=self.call_credentials,
|
|
149
|
-
metadata=self.metadata,
|
|
150
|
-
)
|
|
153
|
+
return self._range(range_request)
|
|
151
154
|
|
|
152
155
|
def get_prefix(self, key_prefix, **kwargs):
|
|
153
156
|
"""
|
|
@@ -176,15 +179,7 @@ class SyncKVOperations:
|
|
|
176
179
|
sort_target=sort_target,
|
|
177
180
|
**kwargs,
|
|
178
181
|
)
|
|
179
|
-
|
|
180
|
-
# Create KV stub directly
|
|
181
|
-
kv_stub = etcdrpc.KVStub(self.channel)
|
|
182
|
-
return kv_stub.Range(
|
|
183
|
-
range_request,
|
|
184
|
-
self.timeout,
|
|
185
|
-
credentials=self.call_credentials,
|
|
186
|
-
metadata=self.metadata,
|
|
187
|
-
)
|
|
182
|
+
return self._range(range_request)
|
|
188
183
|
|
|
189
184
|
def get_range(self, range_start, range_end, **kwargs):
|
|
190
185
|
"""
|
|
@@ -209,15 +204,7 @@ class SyncKVOperations:
|
|
|
209
204
|
sort_target=sort_target,
|
|
210
205
|
keys_only=keys_only,
|
|
211
206
|
)
|
|
212
|
-
|
|
213
|
-
# Create KV stub directly
|
|
214
|
-
kv_stub = etcdrpc.KVStub(self.channel)
|
|
215
|
-
return kv_stub.Range(
|
|
216
|
-
range_request,
|
|
217
|
-
self.timeout,
|
|
218
|
-
credentials=self.call_credentials,
|
|
219
|
-
metadata=self.metadata,
|
|
220
|
-
)
|
|
207
|
+
return self._range(range_request)
|
|
221
208
|
|
|
222
209
|
def get_all(self, **kwargs):
|
|
223
210
|
"""
|
|
@@ -329,7 +316,7 @@ class SyncKVOperations:
|
|
|
329
316
|
:rtype: bool
|
|
330
317
|
"""
|
|
331
318
|
status, _ = self.client.transaction(
|
|
332
|
-
compare=[self.transactions.create(key) ==
|
|
319
|
+
compare=[self.transactions.create(key) == 0],
|
|
333
320
|
success=[self.transactions.put(key, value, lease=lease)],
|
|
334
321
|
failure=[],
|
|
335
322
|
)
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-etcd3-async
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: etcd3 async sdk
|
|
5
|
+
Project-URL: Repository, https://cnb.cool/owasd/python3-etcd-async
|
|
6
|
+
Project-URL: Source, https://cnb.cool/owasd/python3-etcd-async
|
|
7
|
+
Project-URL: Homepage, https://cnb.cool/owasd/python3-etcd-async
|
|
8
|
+
Project-URL: Issues, https://cnb.cool/owasd/python3-etcd-async/issues
|
|
9
|
+
Project-URL: Changelog, https://cnb.cool/owasd/python3-etcd-async/blob/main/docs/en/resources/history.md
|
|
10
|
+
Author: owasd
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
Keywords: async,etcd,etcd3,grpc,kubernetes
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Database
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
5
22
|
Requires-Python: <4,>=3.12
|
|
6
|
-
Requires-Dist: grpcio
|
|
23
|
+
Requires-Dist: grpcio>=1.76.0
|
|
7
24
|
Requires-Dist: prometheus-client>=0.24.1
|
|
8
|
-
Requires-Dist: protobuf
|
|
25
|
+
Requires-Dist: protobuf>=6.31.1
|
|
9
26
|
Description-Content-Type: text/markdown
|
|
10
27
|
|
|
11
28
|
# python-etcd3-async
|
|
@@ -23,7 +40,7 @@ Python async client for the etcd API v3, supporting both synchronous and asynchr
|
|
|
23
40
|
- **Async/Sync dual support**: Same API for both async and sync usage with feature parity
|
|
24
41
|
- **Automatic reconnection**: Auto-rebuild stale connections (e.g., after etcd server restart)
|
|
25
42
|
- **Keepalive error propagation**: Lease keepalive errors are properly propagated for connection loss detection
|
|
26
|
-
- **Endpoint failover**: Built-in support for multiple endpoints with automatic failover
|
|
43
|
+
- **Endpoint failover**: Built-in support for multiple endpoints with opt-in automatic failover (`failover=True`)
|
|
27
44
|
- **Context manager**: Clean resource management for both sync and async
|
|
28
45
|
- **Observability**: Structured logging, OpenTelemetry tracing, and Prometheus metrics
|
|
29
46
|
|
|
@@ -272,8 +289,14 @@ client = await async_client(
|
|
|
272
289
|
)
|
|
273
290
|
|
|
274
291
|
# Multiple endpoints with failover
|
|
275
|
-
|
|
276
|
-
|
|
292
|
+
# (the single-endpoint factory does not take endpoints=)
|
|
293
|
+
from etcd3 import AsyncMultiEndpointEtcd3Client, AsyncEndpoint
|
|
294
|
+
|
|
295
|
+
client = AsyncMultiEndpointEtcd3Client(
|
|
296
|
+
endpoints=[
|
|
297
|
+
AsyncEndpoint('localhost', 2379, secure=False),
|
|
298
|
+
AsyncEndpoint('localhost', 22379, secure=False),
|
|
299
|
+
],
|
|
277
300
|
failover=True, # Automatically switch to healthy endpoint
|
|
278
301
|
)
|
|
279
302
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
etcd3/__init__.py,sha256=
|
|
1
|
+
etcd3/__init__.py,sha256=Xu6id_lViZcLA1ks-5XP4MxI0Uw4bePB9r5P8oxtpjw,3433
|
|
2
2
|
etcd3/async_client.py,sha256=gHNwLMJ9ZJx6XQuAUE4PSPcG2nIeV1E3flgQBPzEqGg,2162
|
|
3
|
-
etcd3/async_client_extensions.py,sha256=
|
|
3
|
+
etcd3/async_client_extensions.py,sha256=JqrkkrGSprWrUa9EFol3i0uWdvTesYJmPytHTJw--_4,38193
|
|
4
4
|
etcd3/async_config.py,sha256=tjGR_SP25uYpC0wI9LoHxKgPuL7yaSHWtUQrcPrx0uQ,3530
|
|
5
5
|
etcd3/async_endpoint.py,sha256=ojNBRgtZ7DJQDL7WAZnO7cQ24fCFYcMRcdFxT-_2A2w,5310
|
|
6
6
|
etcd3/async_managers.py,sha256=05qIGGR4RTu8iPO5Ejrf6CRB_zzqvHa4Gd-rBXIpFD4,19378
|
|
7
7
|
etcd3/async_monitor.py,sha256=iZPac3Pqn5bI0ONguJJctQsi4b2ft4hWPknug6jj5RY,453
|
|
8
|
-
etcd3/async_multi_client.py,sha256=
|
|
8
|
+
etcd3/async_multi_client.py,sha256=O7ySYsy9ZfKhgC-jqCazI6vbf5W0N5W1qYE4Sh8GKBk,52143
|
|
9
9
|
etcd3/async_single_client.py,sha256=vCi-RhwcZ34cR_hRf2BXdcAcPEvJjknv3wkWM8zHF-I,5145
|
|
10
10
|
etcd3/async_watch.py,sha256=k6hY8yKl2H6QOYa5t1CCQ0dJ20lFPUo7s-jOHMRdMwE,16973
|
|
11
11
|
etcd3/auth.py,sha256=gWd6Cl2CMe4P6i4SsLQ_9UMv9bNSCs_QAMqyOVfKQ3k,21896
|
|
@@ -26,7 +26,7 @@ etcd3/observability.py,sha256=BLOUMs1H2icF-tEWB_RoZFKoCNoXZt1ZZERGfckesW4,14215
|
|
|
26
26
|
etcd3/shared.py,sha256=7vTHM34dlnW9D45YAKhAmoW1-EJB8GwlOjzfpeq77BU,9362
|
|
27
27
|
etcd3/single_client.py,sha256=sG3tna7i_1y5u_XuwvsH0BeMQIj6uaKL5XgOYRpratI,5014
|
|
28
28
|
etcd3/sync_cluster_operations.py,sha256=YgJGjqdSEhzp-0A1QIwXJtZNw-IbjDnxTDCiZKJydIE,4319
|
|
29
|
-
etcd3/sync_kv_operations.py,sha256=
|
|
29
|
+
etcd3/sync_kv_operations.py,sha256=KLqd_6J8oSqRpQSbONKWw7aBrQjYWsS5lLG6M_46pNQ,22895
|
|
30
30
|
etcd3/sync_lock_operations.py,sha256=XkezEM0lDGKhj3BPiMtmfFteWatm-XbZewAtjcreuwM,1091
|
|
31
31
|
etcd3/sync_maintenance_operations.py,sha256=oDXRfrMsTYvpMKbvD0K1k1nwgzyYcnjmqO8jZ3QZfUI,8397
|
|
32
32
|
etcd3/trace.py,sha256=OtOmAZ5uz7v7dqPoAaEM8d94prK5f5tSEJq7Qi2w_0c,6650
|
|
@@ -49,6 +49,6 @@ etcdrpc/rpc.proto,sha256=UD4vXbdPUNHSLSx7K_UltWqJjWswHBKSs0t9aNMA4qg,31351
|
|
|
49
49
|
etcdrpc/rpc_pb2.py,sha256=8qhnULNr8jym0qyivDVqJB86H_u1Rb_K8iY8FNT9e0s,30252
|
|
50
50
|
etcdrpc/rpc_pb2.pyi,sha256=TfPtHXIluO_D1rchVMVfoSgZTPQLk-YNhmcJBIt9XIk,35473
|
|
51
51
|
etcdrpc/rpc_pb2_grpc.py,sha256=HOifyKL0X3BmLkS1r0tWTMfyfChBiy76O6Ys12kT5n8,73810
|
|
52
|
-
python_etcd3_async-0.2.
|
|
53
|
-
python_etcd3_async-0.2.
|
|
54
|
-
python_etcd3_async-0.2.
|
|
52
|
+
python_etcd3_async-0.2.1.dist-info/METADATA,sha256=rAl7AYfE7X0qAUkbbweW3FK3rETVFa4WFi-cbyZX0aQ,9871
|
|
53
|
+
python_etcd3_async-0.2.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
54
|
+
python_etcd3_async-0.2.1.dist-info/RECORD,,
|
|
File without changes
|