Skip to content

Commit eee2785

Browse files
committed
Adding the update() method that allows atomic updates on results
1 parent 2efc46b commit eee2785

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

src/etcd/client.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def __init__(
5050
read_timeout (int): max seconds to wait for a read.
5151
5252
allow_redirect (bool): allow the client to connect to other nodes.
53-
53+
+
5454
protocol (str): Protocol used to connect to etcd.
5555
5656
cert (mixed): If a string, the whole ssl client certificate;
@@ -207,6 +207,7 @@ def _sanitize_key(self, key):
207207
key = "/{}".format(key)
208208
return key
209209

210+
210211
def write(self, key, value, ttl=None, dir=False, append=False, **kwdargs):
211212
"""
212213
Writes the value for a key, possibly doing atomit Compare-and-Swap
@@ -268,6 +269,29 @@ def write(self, key, value, ttl=None, dir=False, append=False, **kwdargs):
268269
response = self.api_execute(path, method, params=params)
269270
return self._result_from_response(response)
270271

272+
def update(self, obj):
273+
"""
274+
Updates the value for a key atomically. Typical usage would be:
275+
276+
c = etcd.Client()
277+
o = c.read("/somekey")
278+
o.value += 1
279+
c.update(o)
280+
281+
Args:
282+
obj (etcd.EtcdResult): The object that needs updating.
283+
284+
"""
285+
return self.write(
286+
obj.key,
287+
obj.value,
288+
ttl=obj.ttl,
289+
dir=obj.dir,
290+
prevExist=True,
291+
prevIndex=obj.modifiedIndex)
292+
293+
294+
271295
def read(self, key, **kwdargs):
272296
"""
273297
Returns the value of the key 'key'.

src/etcd/tests/integration/test_simple.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ def test_get_set_delete(self):
103103
except KeyError as e:
104104
pass
105105

106+
def test_update(self):
107+
"""INTEGRATION: update a value"""
108+
self.client.set('/foo', 3)
109+
c = self.client.get('/foo')
110+
c.value = int(c.value) + 3
111+
self.client.update(c)
112+
newres = self.client.get('/foo')
113+
self.assertEquals(newres.value, u'6')
114+
self.assertRaises(ValueError, self.client.update, c)
115+
106116
def test_retrieve_subkeys(self):
107117
""" INTEGRATION: retrieve multiple subkeys """
108118
set_result = self.client.write('/subtree/test_set', 'test-key1')

src/etcd/tests/unit/test_request.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,26 @@ def test_set_plain(self):
113113
res = self.client.write('/testkey', 'test')
114114
self.assertEquals(res, etcd.EtcdResult(**d))
115115

116+
117+
def test_update(self):
118+
"""Can update a result."""
119+
d = {u'action': u'set',
120+
u'node': {
121+
u'expiration': u'2013-09-14T00:56:59.316195568+02:00',
122+
u'modifiedIndex': 6,
123+
u'key': u'/testkey',
124+
u'ttl': 19,
125+
u'value': u'test'
126+
}
127+
}
128+
self._mock_api(200,d)
129+
res = self.client.get('/testkey')
130+
res.value = 'ciao'
131+
d['node']['value'] = 'ciao'
132+
self._mock_api(200,d)
133+
newres = self.client.update(res)
134+
self.assertEquals(newres.value, 'ciao')
135+
116136
def test_newkey(self):
117137
""" Can set a new value """
118138
d = {
@@ -130,6 +150,8 @@ def test_newkey(self):
130150
d['node']['newKey'] = True
131151
self.assertEquals(res, etcd.EtcdResult(**d))
132152

153+
154+
133155
def test_not_found_response(self):
134156
""" Can handle server not found response """
135157
self._mock_api(404, 'Not found')
@@ -444,3 +466,25 @@ def test_not_in(self):
444466

445467
def test_in(self):
446468
pass
469+
470+
def test_update_fails(self):
471+
""" Non-atomic updates fail """
472+
d = {u'action': u'set',
473+
u'node': {
474+
u'expiration': u'2013-09-14T00:56:59.316195568+02:00',
475+
u'modifiedIndex': 6,
476+
u'key': u'/testkey',
477+
u'ttl': 19,
478+
u'value': u'test'
479+
}
480+
}
481+
res = etcd.EtcdResult(**d)
482+
483+
error = {
484+
"errorCode":101,
485+
"message":"Compare failed",
486+
"cause":"[ != bar] [7 != 6]",
487+
"index":6}
488+
self._mock_api(412, error)
489+
res.value = 'bar'
490+
self.assertRaises(ValueError, self.client.update, res)

0 commit comments

Comments
 (0)