diff -Nru python-etcd-0.4.3/AUTHORS python-etcd-0.4.5/AUTHORS --- python-etcd-0.4.3/AUTHORS 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/AUTHORS 2018-10-15 22:40:31.000000000 +0000 @@ -2,24 +2,34 @@ ----------- Jose Plana (jplana) Giuseppe Lavagetto (lavagetto) +Shaun Crampton (fasaxc) Contributors: ------------ Aleksandar Veselinovic +Alexander Brand +Alexander Kukushkin Alex Chan Alex Ianchici +Ainlolcat Bartlomiej Biernacki Bradley Cicenas Christoph Heer +Gigi Sayfan Hogenmiller +Huangdong Jimmy Zelinskie Jim Rollenhagen John Kristensen Joshua Conner +Lars Bahner Matthias Urlichs Michal Witkowski +Mike Place Nick Bartos +Mingqing Peter Wagner +Realityone Roberto Aguilar Roy Smith Ryan Fowler @@ -31,7 +41,13 @@ Simon Gomizelj SkyLothar Spike Curtis +Stephen Milner +Taylor McKinnon +Tobe Tomas Kral Tom Denham +Toshiya Kawasaki WillPlatnick +Weizheng Xu WooParadog +Wei Tie diff -Nru python-etcd-0.4.3/debian/changelog python-etcd-0.4.5/debian/changelog --- python-etcd-0.4.3/debian/changelog 2018-10-02 18:01:15.000000000 +0000 +++ python-etcd-0.4.5/debian/changelog 2018-10-24 01:57:07.000000000 +0000 @@ -1,3 +1,10 @@ +python-etcd (0.4.5-1) unstable; urgency=medium + + * Add debian/upstream/metadata. + * New upstream release. + + -- Jelmer Vernooij Wed, 24 Oct 2018 01:57:07 +0000 + python-etcd (0.4.3-3) unstable; urgency=medium * Team upload. diff -Nru python-etcd-0.4.3/debian/control python-etcd-0.4.5/debian/control --- python-etcd-0.4.3/debian/control 2018-10-02 17:59:45.000000000 +0000 +++ python-etcd-0.4.5/debian/control 2018-10-24 01:57:07.000000000 +0000 @@ -2,8 +2,7 @@ Section: python Priority: optional Maintainer: Debian Python Modules Team -Uploaders: - Jelmer Vernooij , +Uploaders: Jelmer Vernooij Homepage: https://github.com/jplana/python-etcd Build-Depends: debhelper-compat (= 11), diff -Nru python-etcd-0.4.3/debian/upstream/metadata python-etcd-0.4.5/debian/upstream/metadata --- python-etcd-0.4.3/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ python-etcd-0.4.5/debian/upstream/metadata 2018-10-24 01:57:07.000000000 +0000 @@ -0,0 +1,3 @@ +Name: python-etcd +Repository: https://github.com/jplana/python-etcd +Bug-Database: https://github.com/jplana/python-etcd/issues diff -Nru python-etcd-0.4.3/docs-source/index.rst python-etcd-0.4.5/docs-source/index.rst --- python-etcd-0.4.3/docs-source/index.rst 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/docs-source/index.rst 2018-10-15 22:40:31.000000000 +0000 @@ -57,7 +57,7 @@ client.write('/nodes/n3', 'test2', prevValue='test1') #this fails to write client.write('/nodes/n3', 'test2', prevIndex=10) #this fails to write # mkdir - client.write('/nodes/queue', dir=True) + client.write('/nodes/queue', None, dir=True) # Append a value to a queue dir client.write('/nodes/queue', 'test', append=True) #will write i.e. /nodes/queue/11 client.write('/nodes/queue', 'test2', append=True) #will write i.e. /nodes/queue/12 @@ -88,6 +88,12 @@ client.read('/nodes/n2', wait=True) #Waits for a change in value in the key before returning. client.read('/nodes/n2', wait=True, waitIndex=10) + # raises etcd.EtcdKeyNotFound when key not found + try: + client.read('/invalid/path') + except etcd.EtcdKeyNotFound: + # do something + print "error" Delete a key @@ -99,51 +105,31 @@ client.delete('/nodes', dir=True) #spits an error if dir is not empty client.delete('/nodes', recursive=True) #this works recursively +Locking module +~~~~~~~~~~~~~~ - - -Use lock primitives -................... - -.. code-block:: python +.. code:: python # Initialize the lock object: # NOTE: this does not acquire a lock yet client = etcd.Client() - lock = client.get_lock('/customer1', ttl=60) + lock = etcd.Lock(client, 'my_lock_name') # Use the lock object: - lock.acquire() - lock.is_locked() # True - lock.renew(60) - lock.release() - lock.is_locked() # False + lock.acquire(blocking=True, # will block until the lock is acquired + lock_ttl=None) # lock will live until we release it + lock.is_acquired # True + lock.acquire(lock_ttl=60) # renew a lock + lock.release() # release an existing lock + lock.is_acquired # False # The lock object may also be used as a context manager: client = etcd.Client() - lock = client.get_lock('/customer1', ttl=60) - with lock as my_lock: + with etcd.Lock(client, 'customer1') as my_lock: do_stuff() - lock.is_locked() # True - lock.renew(60) - lock.is_locked() # False - -Use the leader election primitives -.................................. - -.. code-block:: python - - # Set a leader object with a name; if no name is given, the local hostname - # is used. - # Zero or no ttl means the leader object is persistent. - client = etcd.Client() - client.election.set('/mysql', name='foo.example.com', ttl=120) # returns the etcd index - - # Get the name - print(client.election.get('/mysql')) # 'foo.example.com' - # Delete it! - print(client.election.delete('/mysql', name='foo.example.com')) - + my_lock.is_acquired # True + my_lock.acquire(lock_ttl=60) + my_lock.is_acquired # False Get machines in the cluster diff -Nru python-etcd-0.4.3/NEWS.txt python-etcd-0.4.5/NEWS.txt --- python-etcd-0.4.3/NEWS.txt 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/NEWS.txt 2018-10-15 22:40:31.000000000 +0000 @@ -1,5 +1,34 @@ News ==== +0.4.5 +----- +*Release date: 3-Mar-2017* + +* Remove dnspython2/3 requirement +* Change property name setter in lock +* Fixed acl tests +* Added version/cluster_version properties to client +* Fixes in lock when used as context manager +* Fixed improper usage of urllib3 exceptions +* Minor fixes for error classes +* In lock return modifiedIndex to watch changes +* In lock fix context manager exception handling +* Improvments to the documentation +* Remove _base_uri only after refresh from cluster +* Avoid double update of _machines_cache + + +0.4.4 +----- +*Release date: 10-Jan-2017* + +* Fix some tests +* Use sys,version_info tuple, instead of named tuple +* Improve & fix documentation +* Fix python3 specific problem when blocking on contented lock +* Add refresh key method +* Add custom lock prefix support + 0.4.3 ----- diff -Nru python-etcd-0.4.3/README.rst python-etcd-0.4.5/README.rst --- python-etcd-0.4.3/README.rst 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/README.rst 2018-10-15 22:40:31.000000000 +0000 @@ -17,7 +17,7 @@ Pre-requirements ~~~~~~~~~~~~~~~~ -Install etcd (2.0.1 or later). This version of python-etcd will only work correctly with the etcd version 2.0.x or later. If you are running an older version of etcd, please use python-etcd 0.3.3 or earlier. +This version of python-etcd will only work correctly with the etcd server version 2.0.x or later. If you are running an older version of etcd, please use python-etcd 0.3.3 or earlier. This client is known to work with python 2.7 and with python 3.3 or above. It is not tested or expected to work in more outdated versions of python. @@ -43,13 +43,15 @@ client = etcd.Client() # this will create a client against etcd server running on localhost on port 4001 client = etcd.Client(port=4002) client = etcd.Client(host='127.0.0.1', port=4003) + client = etcd.Client(host=(('127.0.0.1', 4001), ('127.0.0.1', 4002), ('127.0.0.1', 4003))) client = etcd.Client(host='127.0.0.1', port=4003, allow_redirect=False) # wont let you run sensitive commands on non-leader machines, default is true # If you have defined a SRV record for _etcd._tcp.example.com pointing to the clients client = etcd.Client(srv_domain='example.com', protocol="https") # create a client against https://api.example.com:443/etcd client = etcd.Client(host='api.example.com', protocol='https', port=443, version_prefix='/etcd') + Write a key -~~~~~~~~~ +~~~~~~~~~~~ .. code:: python @@ -59,7 +61,7 @@ client.set('/nodes/n2', 1) # Equivalent, for compatibility reasons. Read a key -~~~~~~~~~ +~~~~~~~~~~ .. code:: python @@ -67,6 +69,14 @@ client.read('/nodes', recursive = True) #get all the values of a directory, recursively. client.get('/nodes/n2').value + # raises etcd.EtcdKeyNotFound when key not found + try: + client.read('/invalid/path') + except etcd.EtcdKeyNotFound: + # do something + print "error" + + Delete a key ~~~~~~~~~~~~ @@ -75,7 +85,7 @@ client.delete('/nodes/n1') Atomic Compare and Swap -~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python @@ -105,6 +115,20 @@ client.watch('/nodes/n1') #equivalent to client.read('/nodes/n1', wait = True) client.watch('/nodes/n1', index = 10) +Refreshing key TTL +~~~~~~~~~~~~~~~~~~ + +(Since etcd 2.3.0) Keys in etcd can be refreshed without notifying current watchers. + +This can be achieved by setting the refresh to true when updating a TTL. + +You cannot update the value of a key when refreshing it. + +.. code:: python + + client.write('/nodes/n1', 'value', ttl=30) # sets the ttl to 30 seconds + client.refresh('/nodes/n1', ttl=600) # refresh ttl to 600 seconds, without notifying current watchers + Locking module ~~~~~~~~~~~~~~ @@ -113,23 +137,25 @@ # Initialize the lock object: # NOTE: this does not acquire a lock yet client = etcd.Client() + # Or you can custom lock prefix, default is '/_locks/' if you are using HEAD + client = etcd.Client(lock_prefix='/my_etcd_root/_locks') lock = etcd.Lock(client, 'my_lock_name') # Use the lock object: lock.acquire(blocking=True, # will block until the lock is acquired lock_ttl=None) # lock will live until we release it - lock.is_acquired() # + lock.is_acquired # True lock.acquire(lock_ttl=60) # renew a lock lock.release() # release an existing lock - lock.is_acquired() # False + lock.is_acquired # False # The lock object may also be used as a context manager: client = etcd.Client() with etcd.Lock(client, 'customer1') as my_lock: do_stuff() - my_lock.is_acquired() # True - my_lock.acquire(lock_ttl = 60) - my_lock.is_acquired() # False + my_lock.is_acquired # True + my_lock.acquire(lock_ttl=60) + my_lock.is_acquired # False Get machines in the cluster @@ -147,7 +173,7 @@ client.leader Generate a sequential key in a directory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python diff -Nru python-etcd-0.4.3/setup.py python-etcd-0.4.5/setup.py --- python-etcd-0.4.3/setup.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/setup.py 2018-10-15 22:40:31.000000000 +0000 @@ -6,17 +6,11 @@ NEWS = open(os.path.join(here, 'NEWS.txt')).read() -version = '0.4.3' - -# Dnspython is two different packages depending on python version -if sys.version_info.major == 2: - dns = 'dnspython' -else: - dns = 'dnspython3' +version = '0.4.5' install_requires = [ 'urllib3>=1.7.1', - dns + 'dnspython>=1.13.0' ] test_requires = [ diff -Nru python-etcd-0.4.3/src/etcd/client.py python-etcd-0.4.5/src/etcd/client.py --- python-etcd-0.4.3/src/etcd/client.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/src/etcd/client.py 2018-10-15 22:40:31.000000000 +0000 @@ -15,7 +15,8 @@ from httplib import HTTPException import socket import urllib3 -import urllib3.util +from urllib3.exceptions import HTTPError +from urllib3.exceptions import ReadTimeoutError import json import ssl import dns.resolver @@ -41,7 +42,7 @@ _MPUT = 'PUT' _MPOST = 'POST' _MDELETE = 'DELETE' - _comparison_conditions = set(('prevValue', 'prevIndex', 'prevExist')) + _comparison_conditions = set(('prevValue', 'prevIndex', 'prevExist', 'refresh')) _read_options = set(('recursive', 'wait', 'waitIndex', 'sorted', 'quorum')) _del_conditions = set(('prevValue', 'prevIndex')) @@ -63,7 +64,8 @@ allow_reconnect=False, use_proxies=False, expected_cluster_id=None, - per_host_pool_size=10 + per_host_pool_size=10, + lock_prefix="/_locks" ): """ Initialize the client. @@ -111,6 +113,8 @@ per_host_pool_size (int): specifies maximum number of connections to pool by host. By default this will use up to 10 connections. + lock_prefix (str): Set the key prefix at etcd when client to lock object. + By default this will be use /_locks. """ # If a DNS record is provided, use it to get the hosts list @@ -143,6 +147,7 @@ self._allow_redirect = allow_redirect self._use_proxies = use_proxies self._allow_reconnect = allow_reconnect + self._lock_prefix = lock_prefix # SSL Client certificate support @@ -153,12 +158,6 @@ if self._read_timeout > 0: kw['timeout'] = self._read_timeout - if protocol == 'https': - # If we don't allow TLSv1, clients using older version of OpenSSL - # (<1.0) won't be able to connect. - _log.debug("HTTPS enabled.") - kw['ssl_version'] = ssl.PROTOCOL_TLSv1 - if cert: if isinstance(cert, tuple): # Key and cert are separate @@ -207,6 +206,23 @@ _log.debug("Machines cache initialised to %s", self._machines_cache) + # Versions set to None. They will be set upon first usage. + self._version = self._cluster_version = None + + def _set_version_info(self): + """ + Sets the version information provided by the server. + """ + # Set the version + version_info = json.loads(self.http.request( + self._MGET, + self._base_uri + '/version', + headers=self._get_headers(), + timeout=self.read_timeout, + redirect=self.allow_redirect).data.decode('utf-8')) + self._version = version_info['etcdserver'] + self._cluster_version = version_info['etcdcluster'] + def _discover(self, domain): srv_name = "_etcd._tcp.{}".format(domain) answers = dns.resolver.query(srv_name, 'SRV') @@ -259,6 +275,11 @@ return self._allow_redirect @property + def lock_prefix(self): + """Get the key prefix at etcd when client to lock object.""" + return self._lock_prefix + + @property def machines(self): """ Members of the cluster. @@ -285,9 +306,7 @@ ] _log.debug("Retrieved list of machines: %s", machines) return machines - except (urllib3.exceptions.HTTPError, - HTTPException, - socket.error) as e: + except (HTTPError, HTTPException, socket.error) as e: # We can't get the list of machines, if one server is in the # machines cache, try on it _log.error("Failed to get list of machines from %s%s: %r", @@ -373,6 +392,25 @@ raise etcd.EtcdException("Cannot parse json data in the response") @property + def version(self): + """ + Version of etcd. + """ + if not self._version: + self._set_version_info() + return self._version + + @property + def cluster_version(self): + """ + Version of the etcd cluster. + """ + if not self._cluster_version: + self._set_version_info() + + return self._cluster_version + + @property def key_endpoint(self): """ REST key endpoint. @@ -397,10 +435,9 @@ key = "/{}".format(key) return key - def write(self, key, value, ttl=None, dir=False, append=False, **kwdargs): """ - Writes the value for a key, possibly doing atomit Compare-and-Swap + Writes the value for a key, possibly doing atomic Compare-and-Swap Args: key (str): Key. @@ -422,6 +459,8 @@ prevExist (bool): If false, only create key; if true, only update key. + refresh (bool): since 2.3.0, If true, only update the ttl, prev key must existed(prevExist=True). + Returns: client.EtcdResult @@ -430,7 +469,7 @@ """ _log.debug("Writing %s to key %s ttl=%s dir=%s append=%s", - value, key, ttl, dir, append) + value, key, ttl, dir, append) key = self._sanitize_key(key) params = {} if value is not None: @@ -461,6 +500,28 @@ response = self.api_execute(path, method, params=params) return self._result_from_response(response) + def refresh(self, key, ttl, **kwdargs): + """ + (Since 2.3.0) Refresh the ttl of a key without notifying watchers. + + Keys in etcd can be refreshed without notifying watchers, + this can be achieved by setting the refresh to true when updating a TTL + + You cannot update the value of a key when refreshing it + + @see: https://github.com/coreos/etcd/blob/release-2.3/Documentation/api.md#refreshing-key-ttl + + Args: + key (str): Key. + + ttl (int): Time in seconds of expiration (optional). + + Other parameters modifying the write method are accepted as `EtcdClient.write`. + """ + # overwrite kwdargs' prevExist + kwdargs['prevExist'] = True + return self.write(key=key, value=None, ttl=ttl, refresh=True, **kwdargs) + def update(self, obj): """ Updates the value for a key atomically. Typical usage would be: @@ -693,9 +754,9 @@ client.EtcdResult Raises: - KeyValue: If the key doesn't exists. + KeyValue: If the key doesn't exist. - urllib3.exceptions.TimeoutError: If timeout is reached. + etcd.EtcdWatchTimedOut: If timeout is reached. >>> print client.watch('/key').value 'value' @@ -776,7 +837,6 @@ def _wrap_request(payload): @wraps(payload) def wrapper(self, path, method, params=None, timeout=None): - some_request_failed = False response = False if timeout is None: @@ -789,6 +849,7 @@ raise ValueError('Path does not start with /') while not response: + some_request_failed = False try: response = payload(self, path, method, params=params, timeout=timeout) @@ -802,12 +863,10 @@ _ = response.data # urllib3 doesn't wrap all httplib exceptions and earlier versions # don't wrap socket errors either. - except (urllib3.exceptions.HTTPError, - HTTPException, socket.error) as e: + except (HTTPError, HTTPException, socket.error) as e: if (isinstance(params, dict) and params.get("wait") == "true" and - isinstance(e, - urllib3.exceptions.ReadTimeoutError)): + isinstance(e, ReadTimeoutError)): _log.debug("Watch timed out.") raise etcd.EtcdWatchTimedOut( "Watch timed out: %r" % e, @@ -844,7 +903,7 @@ if not self._use_proxies: # The cluster may have changed since last invocation self._machines_cache = self.machines - self._machines_cache.remove(self._base_uri) + self._machines_cache.remove(self._base_uri) return self._handle_server_response(response) return wrapper diff -Nru python-etcd-0.4.3/src/etcd/__init__.py python-etcd-0.4.5/src/etcd/__init__.py --- python-etcd-0.4.3/src/etcd/__init__.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/src/etcd/__init__.py 2018-10-15 22:40:31.000000000 +0000 @@ -241,14 +241,16 @@ """ pass + class EtcdLockExpired(EtcdException): """ Our lock apparently expired while we were trying to acquire it. """ + pass class EtcdError(object): - # See https://github.com/coreos/etcd/blob/master/Documentation/errorcode.md + # See https://github.com/coreos/etcd/blob/master/Documentation/v2/errorcode.md error_exceptions = { 100: EtcdKeyNotFound, 101: EtcdCompareFailed, @@ -262,7 +264,7 @@ # 109: Non-public: existing peer addr. 110: EtcdInsufficientPermissions, - 200: EtcdValueError, + 200: EtcdValueError, # Not part of v2 201: EtcdValueError, 202: EtcdValueError, 203: EtcdValueError, diff -Nru python-etcd-0.4.3/src/etcd/lock.py python-etcd-0.4.5/src/etcd/lock.py --- python-etcd-0.4.3/src/etcd/lock.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/src/etcd/lock.py 2018-10-15 22:40:31.000000000 +0000 @@ -17,7 +17,7 @@ # prevent us from getting back the full path name. We prefix our # lock name with a uuid and can check for its presence on retry. self._uuid = uuid.uuid4().hex - self.path = "/_locks/{}".format(lock_name) + self.path = "{}/{}".format(client.lock_prefix, lock_name) self.is_taken = False self._sequence = None _log.debug("Initiating lock for %s with uuid %s", self.path, self._uuid) @@ -30,7 +30,7 @@ return self._uuid @uuid.setter - def set_uuid(self, value): + def uuid(self, value): old_uuid = self._uuid self._uuid = value if not self._find_lock(): @@ -54,7 +54,7 @@ self.is_taken = False return False - def acquire(self, blocking=True, lock_ttl=3600, timeout=None): + def acquire(self, blocking=True, lock_ttl=3600, timeout=0): """ Acquire the lock. @@ -94,10 +94,12 @@ """ You can use the lock as a contextmanager """ - self.acquire(blocking=True, lock_ttl=0) + self.acquire(blocking=True, lock_ttl=None) + return self def __exit__(self, type, value, traceback): self.release() + return False def _acquired(self, blocking=True, timeout=0): locker, nearest = self._get_locker() @@ -112,20 +114,21 @@ if not blocking: return False # Let's look for the lock - watch_key = nearest + watch_key = nearest.key _log.debug("Lock not acquired, now watching %s", watch_key) t = max(0, timeout) while True: try: - r = self.client.watch(watch_key, timeout=t) + r = self.client.watch(watch_key, timeout=t, index=nearest.modifiedIndex + 1) _log.debug("Detected variation for %s: %s", r.key, r.action) return self._acquired(blocking=True, timeout=timeout) except etcd.EtcdKeyNotFound: _log.debug("Key %s not present anymore, moving on", watch_key) return self._acquired(blocking=True, timeout=timeout) + except etcd.EtcdLockExpired as e: + raise e except etcd.EtcdException: - # TODO: log something... - pass + _log.exception("Unexpected exception") @property def lock_key(self): @@ -168,7 +171,7 @@ return (l[0], None) else: _log.debug("Locker: %s, key to watch: %s", l[0], l[i-1]) - return (l[0], l[i-1]) + return (l[0], next(x for x in results if x.key == l[i-1])) except ValueError: # Something very wrong is going on, most probably # our lock has expired diff -Nru python-etcd-0.4.3/src/etcd/tests/integration/test_simple.py python-etcd-0.4.5/src/etcd/tests/integration/test_simple.py --- python-etcd-0.4.3/src/etcd/tests/integration/test_simple.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/src/etcd/tests/integration/test_simple.py 2018-10-15 22:40:31.000000000 +0000 @@ -68,7 +68,8 @@ def test_leader(self): """ INTEGRATION: retrieve leader """ - self.assertEquals(self.client.leader['clientURLs'], ['http://127.0.0.1:6001']) + self.assertIn(self.client.leader['clientURLs'][0], + ['http://127.0.0.1:6001','http://127.0.0.1:6002','http://127.0.0.1:6003']) def test_get_set_delete(self): """ INTEGRATION: set a new value """ diff -Nru python-etcd-0.4.3/src/etcd/tests/test_auth.py python-etcd-0.4.5/src/etcd/tests/test_auth.py --- python-etcd-0.4.3/src/etcd/tests/test_auth.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/src/etcd/tests/test_auth.py 2018-10-15 22:40:31.000000000 +0000 @@ -127,7 +127,13 @@ except: self.fail('Reading an existing role failed') - self.assertEquals(r.acls, {'*': 'RW'}) + # XXX The ACL path result changed from '*' to '/*' at some point + # between etcd-2.2.2 and 2.2.5. They're equivalent so allow + # for both. + if '/*' in r.acls: + self.assertEquals(r.acls, {'/*': 'RW'}) + else: + self.assertEquals(r.acls, {'*': 'RW'}) # We can actually skip most other read tests as they are common # with EtcdUser diff -Nru python-etcd-0.4.3/src/etcd/tests/unit/test_client.py python-etcd-0.4.5/src/etcd/tests/unit/test_client.py --- python-etcd-0.4.3/src/etcd/tests/unit/test_client.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/src/etcd/tests/unit/test_client.py 2018-10-15 22:40:31.000000000 +0000 @@ -121,6 +121,58 @@ 'authorization': 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=' } + def test__set_version_info(self): + """Verify _set_version_info makes the proper call to the server""" + with mock.patch('urllib3.PoolManager') as _pm: + _request = _pm().request + # Return the expected data type + _request.return_value = mock.MagicMock( + data=b'{"etcdserver": "2.2.3", "etcdcluster": "2.3.0"}') + + # Create the client and make the call. + client = etcd.Client() + client._set_version_info() + + # Verify we call the proper endpoint + _request.assert_called_once_with( + client._MGET, + client._base_uri + '/version', + headers=mock.ANY, + redirect=mock.ANY, + timeout=mock.ANY) + + # Verify the properties while we are here + self.assertEquals('2.2.3', client.version) + self.assertEquals('2.3.0', client.cluster_version) + + def test_version_property(self): + """Ensure the version property is set on first access.""" + with mock.patch('urllib3.PoolManager') as _pm: + _request = _pm().request + # Return the expected data type + _request.return_value = mock.MagicMock( + data=b'{"etcdserver": "2.2.3", "etcdcluster": "2.3.0"}') + + # Create the client. + client = etcd.Client() + + # Verify the version property is set + self.assertEquals('2.2.3', client.version) + + def test_cluster_version_property(self): + """Ensure the cluster version property is set on first access.""" + with mock.patch('urllib3.PoolManager') as _pm: + _request = _pm().request + # Return the expected data type + _request.return_value = mock.MagicMock( + data=b'{"etcdserver": "2.2.3", "etcdcluster": "2.3.0"}') + + # Create the client. + client = etcd.Client() + + # Verify the cluster_version property is set + self.assertEquals('2.3.0', client.cluster_version) + def test_get_headers_without_auth(self): client = etcd.Client() assert client._get_headers() == {} diff -Nru python-etcd-0.4.3/src/etcd/tests/unit/test_lock.py python-etcd-0.4.5/src/etcd/tests/unit/test_lock.py --- python-etcd-0.4.3/src/etcd/tests/unit/test_lock.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/src/etcd/tests/unit/test_lock.py 2018-10-15 22:40:31.000000000 +0000 @@ -118,7 +118,10 @@ def test_acquired_no_timeout(self): self.locker._sequence = 4 - returns = [('/_locks/test_lock/4', None), ('/_locks/test_lock/1', '/_locks/test_lock/4')] + returns = [ + ('/_locks/test_lock/4', None), + ('/_locks/test_lock/1', etcd.EtcdResult(node={"key": '/_locks/test_lock/4', "modifiedIndex": 1})) + ] def side_effect(): return returns.pop() @@ -172,7 +175,7 @@ def test_get_locker(self): self.recursive_read() - self.assertEquals((u'/_locks/test_lock/1', u'/_locks/test_lock/1'), + self.assertEquals((u'/_locks/test_lock/1', etcd.EtcdResult(node={'newKey': False, '_children': [], 'createdIndex': 33, 'modifiedIndex': 33, 'value': u'2qwwwq', 'expiration': None, 'key': u'/_locks/test_lock/1', 'ttl': None, 'action': None, 'dir': False})), self.locker._get_locker()) with self.assertRaises(etcd.EtcdLockExpired): self.locker._sequence = '35' diff -Nru python-etcd-0.4.3/src/etcd/tests/unit/test_request.py python-etcd-0.4.5/src/etcd/tests/unit/test_request.py --- python-etcd-0.4.3/src/etcd/tests/unit/test_request.py 2015-12-14 20:48:45.000000000 +0000 +++ python-etcd-0.4.5/src/etcd/tests/unit/test_request.py 2018-10-15 22:40:31.000000000 +0000 @@ -217,7 +217,22 @@ d['node']['newKey'] = True self.assertEquals(res, etcd.EtcdResult(**d)) + def test_refresh(self): + """ Can refresh a new value """ + d = { + u'action': u'update', + u'node': { + u'expiration': u'2016-05-31T08:27:54.660337Z', + u'modifiedIndex': 183, + u'key': u'/testkey', + u'ttl': 600, + u'value': u'test' + } + } + self._mock_api(200, d) + res = self.client.refresh('/testkey', ttl=600) + self.assertEquals(res, etcd.EtcdResult(**d)) def test_not_found_response(self): """ Can handle server not found response """