diff --git a/.travis.yml b/.travis.yml index 37094ee1..84f5360a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: before_install: - ./build_etcd.sh v0.3.0 + - pip install --upgrade setuptools # command to install dependencies install: diff --git a/src/etcd/__init__.py b/src/etcd/__init__.py index 600a60cc..9da10492 100644 --- a/src/etcd/__init__.py +++ b/src/etcd/__init__.py @@ -105,19 +105,56 @@ class EtcdException(Exception): """ Generic Etcd Exception. """ + def __init__(self, message=None, payload=None): + super(Exception, self).__init__(message) + self.payload=payload + +class EtcdKeyError(EtcdException): + """ + Etcd Generic KeyError Exception + """ + pass + +class EtcdKeyNotFound(EtcdKeyError): + """ + Etcd key not found exception (100) + """ + pass + +class EtcdNotFile(EtcdKeyError): + """ + Etcd not a file exception (102) + """ pass +class EtcdNotDir(EtcdKeyError): + """ + Etcd not a directory exception (104) + """ + pass + +class EtcdAlreadyExist(EtcdKeyError): + """ + Etcd already exist exception (105) + """ + pass + +class EtcdEventIndexCleared(EtcdException): + """ + Etcd event index is outdated and cleared exception (401) + """ + pass class EtcdError(object): # See https://github.com/coreos/etcd/blob/master/Documentation/errorcode.md error_exceptions = { - 100: KeyError, + 100: EtcdKeyNotFound, 101: ValueError, - 102: KeyError, + 102: EtcdNotFile, 103: Exception, - 104: KeyError, - 105: KeyError, + 104: EtcdNotDir, + 105: EtcdAlreadyExist, 106: KeyError, 200: ValueError, 201: ValueError, @@ -126,7 +163,7 @@ class EtcdError(object): 300: Exception, 301: Exception, 400: Exception, - 401: EtcdException, + 401: EtcdEventIndexCleared, 500: EtcdException } @@ -134,12 +171,19 @@ class EtcdError(object): def handle(cls, errorCode=None, message=None, cause=None, **kwdargs): """ Decodes the error and throws the appropriate error message""" try: - msg = "{} : {}".format(message, cause) + msg = '{} : {}'.format(message, cause) + payload={'errorCode': errorCode, 'message': message, 'cause': cause} + if len(kwdargs) > 0: + for key in kwdargs: + payload[key]=kwdargs[key] exc = cls.error_exceptions[errorCode] except: msg = "Unable to decode server response" exc = EtcdException - raise exc(msg) + if exc in [EtcdException, EtcdKeyNotFound, EtcdNotFile, EtcdNotDir, EtcdAlreadyExist, EtcdEventIndexCleared]: + raise exc(msg, payload) + else: + raise exc(msg) # Attempt to enable urllib3's SNI support, if possible diff --git a/src/etcd/client.py b/src/etcd/client.py index 271d85ed..516e335e 100644 --- a/src/etcd/client.py +++ b/src/etcd/client.py @@ -202,7 +202,7 @@ def __contains__(self, key): try: self.get(key) return True - except KeyError: + except etcd.EtcdKeyNotFound: return False def _sanitize_key(self, key): diff --git a/src/etcd/tests/integration/test_election.py b/src/etcd/tests/integration/test_election.py index 76f397bc..46ea2bb0 100644 --- a/src/etcd/tests/integration/test_election.py +++ b/src/etcd/tests/integration/test_election.py @@ -31,4 +31,4 @@ def test_get_delete_after_ttl_expired_raises(self): e.set('/mysql', name='foo', ttl=1) time.sleep(2) self.assertRaises(etcd.EtcdException, e.get, '/mysql') - self.assertRaises(KeyError, e.delete, '/mysql', name='foo') + self.assertRaises(etcd.EtcdKeyNotFound, e.delete, '/mysql', name='foo') diff --git a/src/etcd/tests/integration/test_simple.py b/src/etcd/tests/integration/test_simple.py index 50530e9f..9283cc59 100644 --- a/src/etcd/tests/integration/test_simple.py +++ b/src/etcd/tests/integration/test_simple.py @@ -74,7 +74,7 @@ def test_get_set_delete(self): try: get_result = self.client.get('/test_set') assert False - except KeyError as e: + except etcd.EtcdKeyNotFound as e: pass self.assertFalse('/test_set' in self.client) @@ -100,7 +100,7 @@ def test_get_set_delete(self): try: get_result = self.client.get('/test_set') assert False - except KeyError as e: + except etcd.EtcdKeyNotFound as e: pass def test_update(self): @@ -120,7 +120,7 @@ def test_retrieve_subkeys(self): set_result = self.client.write('/subtree/test_set2', 'test-key3') get_result = self.client.read('/subtree', recursive=True) result = [subkey.value for subkey in get_result.leaves] - self.assertEquals(['test-key1', 'test-key2', 'test-key3'], result) + self.assertEquals(['test-key1', 'test-key2', 'test-key3'].sort(), result.sort()) def test_directory_ttl_update(self): """ INTEGRATION: should be able to update a dir TTL """ @@ -140,7 +140,7 @@ def test_is_not_a_file(self): """ INTEGRATION: try to write value to an existing directory """ self.client.set('/directory/test-key', 'test-value') - self.assertRaises(KeyError, self.client.set, '/directory', 'test-value') + self.assertRaises(etcd.EtcdNotFile, self.client.set, '/directory', 'test-value') def test_test_and_set(self): """ INTEGRATION: try test_and_set operation """ @@ -159,7 +159,8 @@ def test_creating_already_existing_directory(self): `prevExist=True` should fail """ self.client.write('/mydir', None, dir=True) - self.assertRaises(KeyError, self.client.write, '/mydir', None, dir=True) + self.assertRaises(etcd.EtcdNotFile, self.client.write, '/mydir', None, dir=True) + self.assertRaises(etcd.EtcdAlreadyExist, self.client.write, '/mydir', None, dir=True, prevExist=False) class TestClusterFunctions(EtcdIntegrationTest): diff --git a/src/etcd/tests/unit/test_old_request.py b/src/etcd/tests/unit/test_old_request.py index e449b7df..9367ebd5 100644 --- a/src/etcd/tests/unit/test_old_request.py +++ b/src/etcd/tests/unit/test_old_request.py @@ -171,7 +171,7 @@ def test_get_subdirs(self): def test_not_in(self): """ Can check if key is not in client """ client = etcd.Client() - client.get = mock.Mock(side_effect=KeyError()) + client.get = mock.Mock(side_effect=etcd.EtcdKeyNotFound()) result = '/testkey' not in client self.assertEquals(True, result) @@ -307,8 +307,8 @@ def test_get_error(self): try: client.api_execute('/v2/keys/testkey', client._MGET) assert False - except KeyError as e: - self.assertEquals(str(e), "'message : cause'") + except etcd.EtcdKeyNotFound as e: + self.assertEquals(str(e), 'message : cause') def test_put(self): """ http put request """ @@ -357,8 +357,8 @@ def test_set_error(self): try: client.api_execute('/v2/keys/testkey', client._MPUT, payload) self.fail() - except KeyError as e: - self.assertEquals("'message : cause'", str(e)) + except etcd.EtcdNotFile as e: + self.assertEquals('message : cause', str(e)) def test_get_error_unknown(self): """ http get error request unknown """ diff --git a/src/etcd/tests/unit/test_request.py b/src/etcd/tests/unit/test_request.py index 7103fa15..df1ae575 100644 --- a/src/etcd/tests/unit/test_request.py +++ b/src/etcd/tests/unit/test_request.py @@ -282,7 +282,7 @@ def test_get_dir(self): def test_not_in(self): """ Can check if key is not in client """ - self._mock_exception(KeyError, 'Key not Found : /testKey') + self._mock_exception(etcd.EtcdKeyNotFound, 'Key not Found : /testKey') self.assertTrue('/testey' not in self.client) def test_in(self):