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 CHANGED
@@ -51,7 +51,7 @@ from etcd3.async_single_client import async_client
51
51
 
52
52
  __author__ = "owasd"
53
53
  __email__ = ""
54
- __version__ = "0.2.0"
54
+ __version__ = "0.2.1"
55
55
 
56
56
  __all__ = (
57
57
  "etcdrpc",
@@ -176,20 +176,20 @@ class AsyncKVOperations:
176
176
  channel = await self.client._get_channel()
177
177
  return etcdrpc.KVStub(channel)
178
178
 
179
- @_async_handle_errors
180
- async def get_response(self, key, **kwargs):
181
- """Get the value of a key from etcd.
179
+ async def _range(self, range_request):
180
+ """Run a range request.
182
181
 
183
- :param key: key in etcd to get
184
- :type key: str or bytes
185
- :param kwargs: Additional arguments (revision, limit, sort_order, etc.)
186
- :returns: RangeResponse from etcd
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 _range():
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
- # Reads are idempotent, so a transient failure can be retried.
202
- response = await self.client._retry_with_backoff(_range)
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,
@@ -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: a response containing a header and the prev_kv
844
- :rtype: :class:`.rpc_pb2.PutResponse`
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: True if the key has been deleted when
1030
- ``return_response`` is False and a response containing
1031
- a header, the number of deleted keys and prev_kvs when
1032
- ``return_response`` is True
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
@@ -83,16 +83,20 @@ class SyncKVOperations:
83
83
  max_create_revision=max_create_revision,
84
84
  )
85
85
 
86
- @_sync_handle_errors
87
- @monitor_request("kv.get_response")
88
- @log_operation("kv.get")
89
- def get_response(self, key, **kwargs):
90
- """Get the value of a key from etcd."""
91
- range_request = self._build_get_range_request(key, **kwargs)
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
- def _range():
94
- # The stub is created per attempt so that a retry picks up a
95
- # freshly built channel.
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
- # Reads are idempotent, so a transient failure can be retried.
105
- return self.client._retry_with_backoff(_range)
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) == "0"],
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.0
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==1.76.0
23
+ Requires-Dist: grpcio>=1.76.0
7
24
  Requires-Dist: prometheus-client>=0.24.1
8
- Requires-Dist: protobuf==6.31.1
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
- client = await async_client(
276
- endpoints=['localhost:2379', 'localhost:22379'],
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=Evxr9VwSTbdwE5F5mwmTz9blukSTIwlz6_yRLjeX3tw,3433
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=S1ZjJubMYtQf0itJ_cOesbvlmmJ2aK3Lp_ARxR6UUHE,37704
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=fwvU_6_fd9ZJ7rwne170g8pJgA7blnvWEcL4Yq-tAGw,52044
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=L8ZWfzuY3fqBiIw7moTA-UOATfZHtzmP81q4hICgpog,23252
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.0.dist-info/METADATA,sha256=a4feA5oLLPypoy9h62zNbPBwhsZ879k4bpp5EepXFHM,8723
53
- python_etcd3_async-0.2.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
54
- python_etcd3_async-0.2.0.dist-info/RECORD,,
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,,