python-etcd3-async 0.1.0__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/utils.py ADDED
@@ -0,0 +1,158 @@
1
+ """Utility functions for etcd3 operations."""
2
+
3
+ from enum import Enum
4
+ from typing import Any, AsyncIterator, Iterator, Union
5
+
6
+ import grpc
7
+
8
+
9
+ # Watch filter types - matching etcd protobuf FilterType
10
+ # Use these constants with the filters parameter in watch operations
11
+ #
12
+ # Example usage:
13
+ # events, cancel = await etcd.watch('/key', filters=[FILTER_DELETE])
14
+ # # Only PUT events will be received, DELETE events are filtered out
15
+ #
16
+ FILTER_PUT = 0 # Filter out PUT events (only DELETE events will be received)
17
+ FILTER_DELETE = 1 # Filter out DELETE events (only PUT events will be received)
18
+ FILTER_COMMIT = 2 # Filter out all events (no events will be received)
19
+
20
+
21
+ class LoadBalancerStrategy(Enum):
22
+ """Load balancing strategies for multi-endpoint clients.
23
+
24
+ Attributes:
25
+ PRIORITY: Priority-based routing - prefer healthy nodes, failover on failure
26
+ ROUND_ROBIN: Round-robin - cycle through endpoints sequentially
27
+ RANDOM: Random - select random healthy endpoint
28
+ STICKY: Sticky - maintain connection to the same endpoint
29
+ """
30
+ PRIORITY = "priority"
31
+ ROUND_ROBIN = "round_robin"
32
+ RANDOM = "random"
33
+ STICKY = "sticky"
34
+
35
+
36
+ def is_connection_canceled(error: grpc.RpcError) -> bool:
37
+ """Check if a connection error indicates the connection was canceled.
38
+
39
+ This is similar to Go's clientv3.IsConnCanceled function.
40
+
41
+ :param error: The gRPC error to check
42
+ :type error: grpc.RpcError
43
+ :returns: True if the connection was canceled
44
+ :rtype: bool
45
+ """
46
+ code = error.code()
47
+ if code == grpc.StatusCode.UNAVAILABLE:
48
+ return True
49
+ if code == grpc.StatusCode.INTERNAL:
50
+ return True
51
+ return False
52
+
53
+
54
+ def is_channel_ready(channel) -> bool:
55
+ """Check if a gRPC channel is ready.
56
+
57
+ :param channel: The gRPC channel to check
58
+ :type channel: grpc.aio.Channel
59
+ :returns: True if the channel is ready
60
+ :rtype: bool
61
+ """
62
+ try:
63
+ state = channel.get_state()
64
+ return state == grpc.ChannelConnectivity.READY
65
+ except Exception:
66
+ return False
67
+
68
+
69
+ def prefix_range_end(prefix: bytes) -> bytes:
70
+ """Create a bytestring that can be used as a range_end for a prefix query.
71
+
72
+ This function calculates the end of a key range that includes all keys
73
+ starting with the given prefix.
74
+
75
+ :param prefix: The key prefix to match
76
+ :type prefix: bytes
77
+ :returns: The range end bytes for prefix matching
78
+ :rtype: bytes
79
+
80
+ Example:
81
+ >>> prefix_range_end(b'/foo/')
82
+ b'/foo0' # All keys starting with /foo/ will match
83
+ """
84
+ if not prefix:
85
+ # Empty prefix returns empty range_end (matches nothing)
86
+ return b""
87
+ s = bytearray(prefix)
88
+ for i in reversed(range(len(s))):
89
+ if s[i] < 0xFF:
90
+ s[i] = s[i] + 1
91
+ break
92
+ return bytes(s)
93
+
94
+
95
+ def to_bytes(maybe_bytestring: Union[str, bytes]) -> bytes:
96
+ """Encode string to bytes.
97
+
98
+ Convenience function to do a simple encode('utf-8') if the input is not
99
+ already bytes. Returns the data unmodified if the input is bytes.
100
+
101
+ :param maybe_bytestring: The string or bytes to convert
102
+ :type maybe_bytestring: str or bytes
103
+ :returns: The input encoded as bytes
104
+ :rtype: bytes
105
+ """
106
+ if isinstance(maybe_bytestring, bytes):
107
+ return maybe_bytestring
108
+ else:
109
+ return maybe_bytestring.encode("utf-8")
110
+
111
+
112
+ def lease_to_id(lease: Any) -> int:
113
+ """Extract lease ID from a Lease object or return the ID directly.
114
+
115
+ :param lease: A Lease object with an 'id' attribute, or a lease ID
116
+ :type lease: Any (with 'id' attribute) or int
117
+ :returns: The lease ID as an integer
118
+ :rtype: int
119
+
120
+ Example:
121
+ >>> lease_to_id(my_lease) # Lease object
122
+ 12345
123
+ >>> lease_to_id(12345) # Direct ID
124
+ 12345
125
+ """
126
+ lease_id = 0
127
+ if hasattr(lease, "id"):
128
+ lease_id = lease.id
129
+ else:
130
+ try:
131
+ lease_id = int(lease)
132
+ except TypeError:
133
+ pass
134
+ return lease_id
135
+
136
+
137
+ def response_to_event_iterator(response_iterator: Iterator[Any]) -> Iterator[Any]:
138
+ """Convert a watch response iterator to an event iterator.
139
+
140
+ :param response_iterator: Iterator of watch responses
141
+ :type response_iterator: Iterator
142
+ :yields: Individual events from the watch responses
143
+ :rtype: Any
144
+ """
145
+ for response in response_iterator:
146
+ for event in response.events:
147
+ yield event
148
+
149
+
150
+ async def async_response_to_event_iterator(response_iterator) -> AsyncIterator[Any]:
151
+ """Convert an async watch response iterator to an async event iterator.
152
+
153
+ :param response_iterator: Async iterator of watch responses
154
+ :yields: Individual events from the watch responses
155
+ """
156
+ async for response in response_iterator:
157
+ for event in response.events:
158
+ yield event
etcd3/watch.py ADDED
@@ -0,0 +1,270 @@
1
+ import logging
2
+ import threading
3
+ import time
4
+
5
+ import grpc
6
+ import queue
7
+
8
+ import etcdrpc
9
+ import etcd3.events as events
10
+ import etcd3.exceptions as exceptions
11
+ import etcd3.utils as utils
12
+
13
+
14
+ _log = logging.getLogger(__name__)
15
+
16
+
17
+ class Watch(object):
18
+ def __init__(self, watch_id, iterator=None, etcd_client=None):
19
+ self.watch_id = watch_id
20
+ self.etcd_client = etcd_client
21
+ self.iterator = iterator
22
+
23
+ def cancel(self):
24
+ self.etcd_client.cancel_watch(self.watch_id)
25
+
26
+ def iterator(self):
27
+ if self.iterator is not None:
28
+ return self.iterator
29
+
30
+ raise ValueError("Undefined iterator")
31
+
32
+
33
+ class Watcher(object):
34
+ def __init__(self, watchstub, timeout=None, call_credentials=None, metadata=None):
35
+ self.timeout = timeout
36
+ self._watch_stub = watchstub
37
+ self._credentials = call_credentials
38
+ self._metadata = metadata
39
+
40
+ self._lock = threading.Lock()
41
+ self._request_queue = queue.Queue(maxsize=10)
42
+ self._callbacks = {}
43
+ self._callback_thread = None
44
+ self._new_watch_cond = threading.Condition(lock=self._lock)
45
+ self._new_watch = None
46
+ self._stopping = False
47
+
48
+ def _create_watch_request(
49
+ self,
50
+ key,
51
+ range_end=None,
52
+ start_revision=None,
53
+ progress_notify=False,
54
+ filters=None,
55
+ prev_kv=False,
56
+ ):
57
+ create_watch = etcdrpc.WatchCreateRequest()
58
+ create_watch.key = utils.to_bytes(key)
59
+ if range_end is not None:
60
+ create_watch.range_end = utils.to_bytes(range_end)
61
+ if start_revision is not None:
62
+ create_watch.start_revision = start_revision
63
+ if progress_notify:
64
+ create_watch.progress_notify = progress_notify
65
+ if filters is not None:
66
+ create_watch.filters.extend(filters)
67
+ if prev_kv:
68
+ create_watch.prev_kv = prev_kv
69
+ return etcdrpc.WatchRequest(create_request=create_watch)
70
+
71
+ def add_callback(
72
+ self,
73
+ key,
74
+ callback,
75
+ range_end=None,
76
+ start_revision=None,
77
+ progress_notify=False,
78
+ filters=None,
79
+ prev_kv=False,
80
+ ):
81
+ rq = self._create_watch_request(
82
+ key,
83
+ range_end=range_end,
84
+ start_revision=start_revision,
85
+ progress_notify=progress_notify,
86
+ filters=filters,
87
+ prev_kv=prev_kv,
88
+ )
89
+
90
+ with self._lock:
91
+ # Wait for exiting thread to close
92
+ if self._stopping:
93
+ self._callback_thread.join()
94
+ self._callback_thread = None
95
+ self._stopping = False
96
+
97
+ # Start the callback thread if it is not yet running.
98
+ if not self._callback_thread:
99
+ thread_name = "etcd3_watch_%x" % (id(self),)
100
+ self._callback_thread = threading.Thread(
101
+ name=thread_name, target=self._run
102
+ )
103
+ self._callback_thread.daemon = True
104
+ self._callback_thread.start()
105
+
106
+ # Only one create watch request can be pending at a time, so if
107
+ # there one already, then wait for it to complete first.
108
+ while self._new_watch:
109
+ self._new_watch_cond.wait()
110
+
111
+ # Submit a create watch request.
112
+ new_watch = _NewWatch(callback)
113
+ self._request_queue.put(rq)
114
+ self._new_watch = new_watch
115
+
116
+ try:
117
+ # Wait for the request to be completed, or timeout.
118
+ # Use a loop to handle spurious wakeups (Mesa semantics)
119
+ wait_deadline = time.time() + self.timeout if self.timeout else None
120
+ while new_watch.id is None and new_watch.err is None:
121
+ if wait_deadline is not None:
122
+ remaining = wait_deadline - time.time()
123
+ if remaining <= 0:
124
+ break
125
+ self._new_watch_cond.wait(timeout=remaining)
126
+ else:
127
+ self._new_watch_cond.wait()
128
+
129
+ # If the request not completed yet, then raise a timeout
130
+ # exception.
131
+ if new_watch.id is None and new_watch.err is None:
132
+ raise exceptions.WatchTimedOut()
133
+
134
+ # Raise an exception if the watch request failed.
135
+ if new_watch.err:
136
+ raise new_watch.err
137
+ finally:
138
+ # Wake up threads stuck on add_callback call if any.
139
+ self._new_watch = None
140
+ self._new_watch_cond.notify_all()
141
+
142
+ return new_watch.id
143
+
144
+ def cancel(self, watch_id):
145
+ with self._lock:
146
+ callback = self._callbacks.pop(watch_id, None)
147
+ if not callback:
148
+ return
149
+
150
+ self._cancel_no_lock(watch_id)
151
+
152
+ def _run(self):
153
+ callback_err = None
154
+ try:
155
+ response_iter = self._watch_stub.Watch(
156
+ _new_request_iter(self._request_queue),
157
+ credentials=self._credentials,
158
+ metadata=self._metadata,
159
+ )
160
+ for rs in response_iter:
161
+ self._handle_response(rs)
162
+
163
+ except grpc.RpcError as err:
164
+ callback_err = err
165
+
166
+ finally:
167
+ with self._lock:
168
+ self._stopping = True
169
+ if self._new_watch:
170
+ self._new_watch.err = callback_err
171
+ self._new_watch_cond.notify_all()
172
+
173
+ callbacks = self._callbacks
174
+ self._callbacks = {}
175
+
176
+ # Rotate request queue. This way we can terminate one gRPC
177
+ # stream and initiate another one whilst avoiding a race
178
+ # between them over requests in the queue.
179
+ self._request_queue.put(None)
180
+ self._request_queue = queue.Queue(maxsize=10)
181
+
182
+ for callback in callbacks.values():
183
+ _safe_callback(callback, callback_err)
184
+
185
+ def _handle_response(self, rs):
186
+ with self._lock:
187
+ if rs.created:
188
+ # Store reference locally to avoid race with other threads
189
+ new_watch = self._new_watch
190
+
191
+ # If the new watch request has already expired then cancel the
192
+ # created watch right away.
193
+ if not new_watch:
194
+ self._cancel_no_lock(rs.watch_id)
195
+ return
196
+
197
+ if rs.compact_revision != 0:
198
+ new_watch.err = exceptions.RevisionCompactedError(
199
+ rs.compact_revision
200
+ )
201
+ return
202
+
203
+ self._callbacks[rs.watch_id] = new_watch.callback
204
+ new_watch.id = rs.watch_id
205
+ self._new_watch_cond.notify_all()
206
+
207
+ callback = self._callbacks.get(rs.watch_id)
208
+
209
+ # Ignore leftovers from canceled watches.
210
+ if not callback:
211
+ return
212
+
213
+ # The watcher can be safely reused, but adding a new event
214
+ # to indicate that the revision is already compacted
215
+ # requires api change which would break all users of this
216
+ # module. So, raising an exception if a watcher is still
217
+ # alive.
218
+ if rs.compact_revision != 0:
219
+ err = exceptions.RevisionCompactedError(rs.compact_revision)
220
+ _safe_callback(callback, err)
221
+ self.cancel(rs.watch_id)
222
+ return
223
+
224
+ # Call the callback even when there are no events in the watch
225
+ # response so as not to ignore progress notify responses.
226
+ if rs.events or not (rs.created or rs.canceled):
227
+ new_events = [events.new_event(event) for event in rs.events]
228
+ response = WatchResponse(rs.header, new_events)
229
+ _safe_callback(callback, response)
230
+
231
+ def _cancel_no_lock(self, watch_id):
232
+ cancel_watch = etcdrpc.WatchCancelRequest()
233
+ cancel_watch.watch_id = watch_id
234
+ rq = etcdrpc.WatchRequest(cancel_request=cancel_watch)
235
+ self._request_queue.put(rq)
236
+
237
+ def close(self):
238
+ with self._lock:
239
+ if self._callback_thread and not self._stopping:
240
+ self._request_queue.put(None)
241
+
242
+
243
+ class WatchResponse(object):
244
+ def __init__(self, header, events):
245
+ self.header = header
246
+ self.events = events
247
+
248
+
249
+ class _NewWatch(object):
250
+ def __init__(self, callback):
251
+ self.callback = callback
252
+ self.id = None
253
+ self.err = None
254
+
255
+
256
+ def _new_request_iter(_request_queue):
257
+ while True:
258
+ rq = _request_queue.get()
259
+ if rq is None:
260
+ return
261
+
262
+ yield rq
263
+
264
+
265
+ def _safe_callback(callback, response_or_err):
266
+ try:
267
+ callback(response_or_err)
268
+
269
+ except Exception:
270
+ _log.exception("Watch callback failed")
etcdrpc/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """etcdrpc package - uses pre-generated protobuf files."""
2
+
3
+ # 简单的检查,如果文件不存在则报错提示(防御性编程)
4
+ from pathlib import Path
5
+ _current_dir = Path(__file__).parent
6
+ if not (_current_dir / "rpc_pb2.py").exists():
7
+ raise ImportError(
8
+ "Protobuf modules not found. This package might be incorrectly installed."
9
+ )
10
+
11
+ # 扁平化导出
12
+ from etcdrpc.rpc_pb2 import *
13
+ from etcdrpc.rpc_pb2_grpc import *
14
+
15
+ # 获取所有非私有成员
16
+ __all__ = [name for name in globals().keys() if not name.startswith('_')]
etcdrpc/auth.proto ADDED
@@ -0,0 +1,49 @@
1
+ //
2
+ // Copyright 2016-2021 The jetcd authors
3
+ //
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // you may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ //
8
+ // http://www.apache.org/licenses/LICENSE-2.0
9
+ //
10
+ // Unless required by applicable law or agreed to in writing, software
11
+ // distributed under the License is distributed on an "AS IS" BASIS,
12
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ // See the License for the specific language governing permissions and
14
+ // limitations under the License.
15
+ //
16
+
17
+ syntax = "proto3";
18
+
19
+ package authpb;
20
+
21
+ option java_multiple_files = true;
22
+ option java_package = "io.etcd.jetcd.api";
23
+
24
+ // User is a single entry in the bucket authUsers
25
+ message User {
26
+ bytes name = 1;
27
+ bytes password = 2;
28
+ repeated string roles = 3;
29
+ }
30
+
31
+ // Permission is a single entity
32
+ message Permission {
33
+ enum Type {
34
+ READ = 0;
35
+ WRITE = 1;
36
+ READWRITE = 2;
37
+ }
38
+ Type permType = 1;
39
+
40
+ bytes key = 2;
41
+ bytes range_end = 3;
42
+ }
43
+
44
+ // Role is a single entry in the bucket authRoles
45
+ message Role {
46
+ bytes name = 1;
47
+
48
+ repeated Permission keyPermission = 2;
49
+ }
etcdrpc/auth_pb2.py ADDED
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: etcdrpc/auth.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'etcdrpc/auth.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x65tcdrpc/auth.proto\x12\x06\x61uthpb\"5\n\x04User\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x10\n\x08password\x18\x02 \x01(\x0c\x12\r\n\x05roles\x18\x03 \x03(\t\"\x83\x01\n\nPermission\x12)\n\x08permType\x18\x01 \x01(\x0e\x32\x17.authpb.Permission.Type\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x11\n\trange_end\x18\x03 \x01(\x0c\"*\n\x04Type\x12\x08\n\x04READ\x10\x00\x12\t\n\x05WRITE\x10\x01\x12\r\n\tREADWRITE\x10\x02\"?\n\x04Role\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12)\n\rkeyPermission\x18\x02 \x03(\x0b\x32\x12.authpb.PermissionB\x15\n\x11io.etcd.jetcd.apiP\x01\x62\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'etcdrpc.auth_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ _globals['DESCRIPTOR']._loaded_options = None
34
+ _globals['DESCRIPTOR']._serialized_options = b'\n\021io.etcd.jetcd.apiP\001'
35
+ _globals['_USER']._serialized_start=30
36
+ _globals['_USER']._serialized_end=83
37
+ _globals['_PERMISSION']._serialized_start=86
38
+ _globals['_PERMISSION']._serialized_end=217
39
+ _globals['_PERMISSION_TYPE']._serialized_start=175
40
+ _globals['_PERMISSION_TYPE']._serialized_end=217
41
+ _globals['_ROLE']._serialized_start=219
42
+ _globals['_ROLE']._serialized_end=282
43
+ # @@protoc_insertion_point(module_scope)
etcdrpc/auth_pb2.pyi ADDED
@@ -0,0 +1,44 @@
1
+ from google.protobuf.internal import containers as _containers
2
+ from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
3
+ from google.protobuf import descriptor as _descriptor
4
+ from google.protobuf import message as _message
5
+ from collections.abc import Iterable as _Iterable, Mapping as _Mapping
6
+ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
7
+
8
+ DESCRIPTOR: _descriptor.FileDescriptor
9
+
10
+ class User(_message.Message):
11
+ __slots__ = ("name", "password", "roles")
12
+ NAME_FIELD_NUMBER: _ClassVar[int]
13
+ PASSWORD_FIELD_NUMBER: _ClassVar[int]
14
+ ROLES_FIELD_NUMBER: _ClassVar[int]
15
+ name: bytes
16
+ password: bytes
17
+ roles: _containers.RepeatedScalarFieldContainer[str]
18
+ def __init__(self, name: _Optional[bytes] = ..., password: _Optional[bytes] = ..., roles: _Optional[_Iterable[str]] = ...) -> None: ...
19
+
20
+ class Permission(_message.Message):
21
+ __slots__ = ("permType", "key", "range_end")
22
+ class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
23
+ __slots__ = ()
24
+ READ: _ClassVar[Permission.Type]
25
+ WRITE: _ClassVar[Permission.Type]
26
+ READWRITE: _ClassVar[Permission.Type]
27
+ READ: Permission.Type
28
+ WRITE: Permission.Type
29
+ READWRITE: Permission.Type
30
+ PERMTYPE_FIELD_NUMBER: _ClassVar[int]
31
+ KEY_FIELD_NUMBER: _ClassVar[int]
32
+ RANGE_END_FIELD_NUMBER: _ClassVar[int]
33
+ permType: Permission.Type
34
+ key: bytes
35
+ range_end: bytes
36
+ def __init__(self, permType: _Optional[_Union[Permission.Type, str]] = ..., key: _Optional[bytes] = ..., range_end: _Optional[bytes] = ...) -> None: ...
37
+
38
+ class Role(_message.Message):
39
+ __slots__ = ("name", "keyPermission")
40
+ NAME_FIELD_NUMBER: _ClassVar[int]
41
+ KEYPERMISSION_FIELD_NUMBER: _ClassVar[int]
42
+ name: bytes
43
+ keyPermission: _containers.RepeatedCompositeFieldContainer[Permission]
44
+ def __init__(self, name: _Optional[bytes] = ..., keyPermission: _Optional[_Iterable[_Union[Permission, _Mapping]]] = ...) -> None: ...
@@ -0,0 +1,24 @@
1
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2
+ """Client and server classes corresponding to protobuf-defined services."""
3
+ import grpc
4
+ import warnings
5
+
6
+
7
+ GRPC_GENERATED_VERSION = '1.76.0'
8
+ GRPC_VERSION = grpc.__version__
9
+ _version_not_supported = False
10
+
11
+ try:
12
+ from grpc._utilities import first_version_is_lower
13
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
14
+ except ImportError:
15
+ _version_not_supported = True
16
+
17
+ if _version_not_supported:
18
+ raise RuntimeError(
19
+ f'The grpc package installed is at version {GRPC_VERSION},'
20
+ + ' but the generated code in etcdrpc/auth_pb2_grpc.py depends on'
21
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
22
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
23
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
24
+ )
etcdrpc/kv.proto ADDED
@@ -0,0 +1,43 @@
1
+ syntax = "proto3";
2
+
3
+ package mvccpb;
4
+
5
+
6
+ message KeyValue {
7
+ // key is the key in bytes. An empty key is not allowed.
8
+ bytes key = 1;
9
+ // create_revision is the revision of last creation on this key.
10
+ int64 create_revision = 2;
11
+ // mod_revision is the revision of last modification on this key.
12
+ int64 mod_revision = 3;
13
+ // version is the version of the key. A deletion resets
14
+ // the version to zero and any modification of the key
15
+ // increases its version.
16
+ int64 version = 4;
17
+ // value is the value held by the key, in bytes.
18
+ bytes value = 5;
19
+ // lease is the ID of the lease that attached to key.
20
+ // When the attached lease expires, the key will be deleted.
21
+ // If lease is 0, then no lease is attached to the key.
22
+ int64 lease = 6;
23
+ }
24
+
25
+ message Event {
26
+ enum EventType {
27
+ PUT = 0;
28
+ DELETE = 1;
29
+ }
30
+ // type is the kind of event. If type is a PUT, it indicates
31
+ // new data has been stored to the key. If type is a DELETE,
32
+ // it indicates the key was deleted.
33
+ EventType type = 1;
34
+ // kv holds the KeyValue for the event.
35
+ // A PUT event contains current kv pair.
36
+ // A PUT event with kv.Version=1 indicates the creation of a key.
37
+ // A DELETE/EXPIRE event contains the deleted key with
38
+ // its modification revision set to the revision of deletion.
39
+ KeyValue kv = 2;
40
+
41
+ // prev_kv holds the key-value pair before the event happens.
42
+ KeyValue prev_kv = 3;
43
+ }
etcdrpc/kv_pb2.py ADDED
@@ -0,0 +1,40 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: etcdrpc/kv.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'etcdrpc/kv.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x65tcdrpc/kv.proto\x12\x06mvccpb\"u\n\x08KeyValue\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\x17\n\x0f\x63reate_revision\x18\x02 \x01(\x03\x12\x14\n\x0cmod_revision\x18\x03 \x01(\x03\x12\x0f\n\x07version\x18\x04 \x01(\x03\x12\r\n\x05value\x18\x05 \x01(\x0c\x12\r\n\x05lease\x18\x06 \x01(\x03\"\x91\x01\n\x05\x45vent\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.mvccpb.Event.EventType\x12\x1c\n\x02kv\x18\x02 \x01(\x0b\x32\x10.mvccpb.KeyValue\x12!\n\x07prev_kv\x18\x03 \x01(\x0b\x32\x10.mvccpb.KeyValue\" \n\tEventType\x12\x07\n\x03PUT\x10\x00\x12\n\n\x06\x44\x45LETE\x10\x01\x62\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'etcdrpc.kv_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ DESCRIPTOR._loaded_options = None
34
+ _globals['_KEYVALUE']._serialized_start=28
35
+ _globals['_KEYVALUE']._serialized_end=145
36
+ _globals['_EVENT']._serialized_start=148
37
+ _globals['_EVENT']._serialized_end=293
38
+ _globals['_EVENT_EVENTTYPE']._serialized_start=261
39
+ _globals['_EVENT_EVENTTYPE']._serialized_end=293
40
+ # @@protoc_insertion_point(module_scope)