Support for watch()
This commit is contained in:
parent
a600eac6d2
commit
222c5b3bdc
@ -11,9 +11,11 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import threading
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from six.moves import queue
|
||||||
|
|
||||||
from etcd3gw.lease import Lease
|
from etcd3gw.lease import Lease
|
||||||
from etcd3gw.lock import Lock
|
from etcd3gw.lock import Lock
|
||||||
@ -21,6 +23,7 @@ from etcd3gw.utils import _decode
|
|||||||
from etcd3gw.utils import _encode
|
from etcd3gw.utils import _encode
|
||||||
from etcd3gw.utils import _increment_last_byte
|
from etcd3gw.utils import _increment_last_byte
|
||||||
from etcd3gw.utils import DEFAULT_TIMEOUT
|
from etcd3gw.utils import DEFAULT_TIMEOUT
|
||||||
|
from etcd3gw import watch
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
@ -263,3 +266,68 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
return self.post(self.get_url("/kv/txn"),
|
return self.post(self.get_url("/kv/txn"),
|
||||||
data=json.dumps(txn))
|
data=json.dumps(txn))
|
||||||
|
|
||||||
|
def watch(self, key, **kwargs):
|
||||||
|
"""Watch a key.
|
||||||
|
|
||||||
|
:param key: key to watch
|
||||||
|
|
||||||
|
:returns: tuple of ``events_iterator`` and ``cancel``.
|
||||||
|
Use ``events_iterator`` to get the events of key changes
|
||||||
|
and ``cancel`` to cancel the watch request
|
||||||
|
"""
|
||||||
|
event_queue = queue.Queue()
|
||||||
|
|
||||||
|
def callback(event):
|
||||||
|
event_queue.put(event)
|
||||||
|
|
||||||
|
w = watch.Watcher(self, key, callback, **kwargs)
|
||||||
|
canceled = threading.Event()
|
||||||
|
|
||||||
|
def cancel():
|
||||||
|
canceled.set()
|
||||||
|
event_queue.put(None)
|
||||||
|
w.cancel()
|
||||||
|
|
||||||
|
def iterator():
|
||||||
|
while not canceled.is_set():
|
||||||
|
event = event_queue.get()
|
||||||
|
if event is None:
|
||||||
|
canceled.set()
|
||||||
|
if not canceled.is_set():
|
||||||
|
yield event
|
||||||
|
|
||||||
|
return iterator(), cancel
|
||||||
|
|
||||||
|
def watch_prefix(self, key_prefix, **kwargs):
|
||||||
|
"""The same as ``watch``, but watches a range of keys with a prefix."""
|
||||||
|
kwargs['range_end'] = \
|
||||||
|
_increment_last_byte(_encode(key_prefix))
|
||||||
|
return self.watch(key_prefix, **kwargs)
|
||||||
|
|
||||||
|
def watch_once(self, key, timeout=None, **kwargs):
|
||||||
|
"""Watch a key and stops after the first event.
|
||||||
|
|
||||||
|
:param key: key to watch
|
||||||
|
:param timeout: (optional) timeout in seconds.
|
||||||
|
:returns: event
|
||||||
|
"""
|
||||||
|
event_queue = queue.Queue()
|
||||||
|
|
||||||
|
def callback(event):
|
||||||
|
event_queue.put(event)
|
||||||
|
|
||||||
|
w = watch.Watcher(self, key, callback, **kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return event_queue.get(timeout=timeout)
|
||||||
|
except queue.Empty:
|
||||||
|
raise watch.WatchTimedOut()
|
||||||
|
finally:
|
||||||
|
w.cancel(self)
|
||||||
|
|
||||||
|
def watch_prefix_once(self, key_prefix, timeout=None, **kwargs):
|
||||||
|
"""Watches a range of keys with a prefix, similar to watch_once"""
|
||||||
|
kwargs['range_end'] = \
|
||||||
|
_increment_last_byte(_encode(key_prefix))
|
||||||
|
return self.watch_once(key_prefix, timeout=timeout, **kwargs)
|
||||||
|
24
etcd3gw/examples/watcher.py
Normal file
24
etcd3gw/examples/watcher.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from etcd3gw.client import Client
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
client = Client()
|
||||||
|
events, cancel = client.watch('foo')
|
||||||
|
for event in events:
|
||||||
|
print(">>>> event : %r", event)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
60
etcd3gw/watch.py
Normal file
60
etcd3gw/watch.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import futurist
|
||||||
|
|
||||||
|
from etcd3gw.utils import _encode
|
||||||
|
|
||||||
|
|
||||||
|
class WatchTimedOut(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _watch(client, key, callback, **kwargs):
|
||||||
|
create_watch = {
|
||||||
|
'key': _encode(key)
|
||||||
|
}
|
||||||
|
if 'range_end' in kwargs:
|
||||||
|
create_watch['range_end'] = _encode(kwargs['range_end'])
|
||||||
|
if 'start_revision' in kwargs:
|
||||||
|
create_watch['start_revision'] = kwargs['start_revision']
|
||||||
|
if 'progress_notify' in kwargs:
|
||||||
|
create_watch['progress_notify'] = kwargs['progress_notify']
|
||||||
|
if 'filters' in kwargs:
|
||||||
|
create_watch['filters'] = kwargs['filters']
|
||||||
|
if 'prev_kv' in kwargs:
|
||||||
|
create_watch['prev_kv'] = kwargs['prev_kv']
|
||||||
|
|
||||||
|
create_request = {
|
||||||
|
"create_request": create_watch
|
||||||
|
}
|
||||||
|
resp = client.session.post(client.get_url('/watch'),
|
||||||
|
json=create_request,
|
||||||
|
stream=True)
|
||||||
|
for line in resp.iter_content(chunk_size=None, decode_unicode=True):
|
||||||
|
if line:
|
||||||
|
decoded_line = line.decode('utf-8')
|
||||||
|
callback(json.loads(decoded_line))
|
||||||
|
|
||||||
|
|
||||||
|
class Watcher(object):
|
||||||
|
def __init__(self, client, key, callback, **kwargs):
|
||||||
|
self._executor = futurist.ThreadPoolExecutor(max_workers=1)
|
||||||
|
self._executor.submit(_watch, client, key, callback, **kwargs)
|
||||||
|
|
||||||
|
def alive(self):
|
||||||
|
return self._executor.alive
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
self._executor.shutdown()
|
@ -6,3 +6,4 @@ pbr>=2.0 # Apache-2.0
|
|||||||
urllib3>=1.8.3 # MIT
|
urllib3>=1.8.3 # MIT
|
||||||
requests>=2.10.0,!=2.12.2,!=2.13.0 # Apache-2.0
|
requests>=2.10.0,!=2.12.2,!=2.13.0 # Apache-2.0
|
||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
|
futurist!=0.15.0,>=0.11.0 # Apache-2.0
|
Loading…
Reference in New Issue
Block a user