Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 366b356

Browse files
authored
Merge pull request #394 from cloudant/393-conflict-retry
Stopped raising exception on conflict retry success
2 parents f0d0010 + fa1cf06 commit 366b356

3 files changed

Lines changed: 59 additions & 1 deletion

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Unreleased
22

33
- [NEW] Add new view parameters, `stable` and `update`, as keyword arguments to `get_view_result`.
4+
- [FIXED] Case where an exception was raised after successful retry when using `doc.update_field`.
45

56
# 2.9.0 (2018-06-13)
67

src/cloudant/document.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ def _update_field(self, action, field, value, max_tries, tries=0):
259259
if tries < max_tries and ex.response.status_code == 409:
260260
self._update_field(
261261
action, field, value, max_tries, tries=tries+1)
262-
raise
262+
else:
263+
raise
263264

264265
def update_field(self, action, field, value, max_tries=10):
265266
"""

tests/unit/document_tests.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,62 @@ def test_update_field(self):
474474
self.assertTrue(doc['_rev'].startswith('2-'))
475475
self.assertEqual(doc['pets'], ['cat', 'dog', 'fish'])
476476

477+
@mock.patch('cloudant.document.Document.save')
478+
def test_update_field_maxretries(self, m_save):
479+
"""
480+
Test that conflict retries work for updating a single field.
481+
"""
482+
# Create a doc
483+
doc = Document(self.db, 'julia006')
484+
doc['name'] = 'julia'
485+
doc['age'] = 6
486+
doc.create()
487+
self.assertTrue(doc['_rev'].startswith('1-'))
488+
self.assertEqual(doc['age'], 6)
489+
# Mock conflicts when saving updates
490+
m_save.side_effect = requests.HTTPError(response=mock.Mock(status_code=409, reason='conflict'))
491+
# Tests that failing on retry eventually throws
492+
with self.assertRaises(requests.HTTPError) as cm:
493+
doc.update_field(doc.field_set, 'age', 7, max_tries=2)
494+
495+
# There is an off-by-one error for "max_tries"
496+
# It really means max_retries i.e. 1 attempt
497+
# followed by a max of 2 retries
498+
self.assertEqual(m_save.call_count, 3)
499+
self.assertEqual(cm.exception.response.status_code, 409)
500+
self.assertEqual(cm.exception.response.reason, 'conflict')
501+
# Fetch again before asserting, otherwise we assert against
502+
# the locally updated age field
503+
doc.fetch()
504+
self.assertFalse(doc['_rev'].startswith('2-'))
505+
self.assertNotEqual(doc['age'], 7)
506+
507+
def test_update_field_success_on_retry(self):
508+
"""
509+
Test that conflict retries work for updating a single field.
510+
"""
511+
# Create a doc
512+
doc = Document(self.db, 'julia006')
513+
doc['name'] = 'julia'
514+
doc['age'] = 6
515+
doc.create()
516+
self.assertTrue(doc['_rev'].startswith('1-'))
517+
self.assertEqual(doc['age'], 6)
518+
519+
# Mock when saving the document
520+
# 1st call throw a 409
521+
# 2nd call delegate to the real doc.save()
522+
with mock.patch('cloudant.document.Document.save',
523+
side_effect=[requests.HTTPError(response=mock.Mock(status_code=409, reason='conflict')),
524+
doc.save()]) as m_save:
525+
# A list of side effects containing only 1 element
526+
doc.update_field(doc.field_set, 'age', 7, max_tries=1)
527+
# Two calls to save, one with a 409 and one that succeeds
528+
self.assertEqual(m_save.call_count, 2)
529+
# Check that the _rev and age field were updated
530+
self.assertTrue(doc['_rev'].startswith('2-'))
531+
self.assertEqual(doc['age'], 7)
532+
477533
def test_delete_document_failure(self):
478534
"""
479535
Test failure condition when attempting to remove a document

0 commit comments

Comments
 (0)