Merge lp:~jderose/microfiber/db.update into lp:microfiber

Proposed by Jason Gerard DeRose
Status: Merged
Merged at revision: 183
Proposed branch: lp:~jderose/microfiber/db.update
Merge into: lp:microfiber
Diff against target: 141 lines (+109/-0)
2 files modified
microfiber/__init__.py (+16/-0)
microfiber/tests/__init__.py (+93/-0)
To merge this branch: bzr merge lp:~jderose/microfiber/db.update
Reviewer Review Type Date Requested Status
David Jordan Approve
Review via email: mp+155381@code.launchpad.net

Description of the change

For details, see this bug:

  https://bugs.launchpad.net/microfiber/+bug/1160107

Changes:

 * Adds method Database.update(doc, func, *args)

 * Adds test for same

To post a comment you must log in.
Revision history for this message
David Jordan (dmj726) wrote :

Looks good. Approved!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'microfiber/__init__.py'
2--- microfiber/__init__.py 2013-03-25 17:00:26 +0000
3+++ microfiber/__init__.py 2013-03-26 00:33:21 +0000
4@@ -58,6 +58,7 @@
5 import math
6 import platform
7 from collections import namedtuple
8+import logging
9
10 from dbase32 import RANDOM_BITS, RANDOM_BYTES, RANDOM_B32LEN
11 from dbase32.rfc3548 import random_id
12@@ -86,6 +87,7 @@
13 )
14
15 __version__ = '13.04.0'
16+log = logging.getLogger()
17 USER_AGENT = 'Microfiber/{} ({} {}; {})'.format(__version__,
18 platform.dist()[0], platform.dist()[1], platform.machine()
19 )
20@@ -1156,3 +1158,17 @@
21 separators=(',', ': '),
22 )
23
24+ def update(self, doc, func, *args):
25+ """
26+ Update *doc* with *func*, then save, with one retry after a conflict.
27+ """
28+ func(doc, *args)
29+ try:
30+ self.save(doc)
31+ return doc
32+ except Conflict:
33+ log.warning('Conflict saving %s', doc['_id'])
34+ doc = self.get(doc['_id'])
35+ func(doc, *args)
36+ self.save(doc)
37+ return doc
38
39=== modified file 'microfiber/tests/__init__.py'
40--- microfiber/tests/__init__.py 2013-03-24 21:06:04 +0000
41+++ microfiber/tests/__init__.py 2013-03-26 00:33:21 +0000
42@@ -1838,6 +1838,99 @@
43 {'reduce': True, 'include_docs': True},
44 ))
45
46+ def test_update(self):
47+ class DummyConflict(microfiber.Conflict):
48+ def __init__(self):
49+ pass
50+
51+ class DummyDatabase(microfiber.Database):
52+ def __init__(self, doc, newrev):
53+ self._calls = []
54+ self._id = doc['_id']
55+ self._rev = doc['_rev']
56+ self._doc = deepcopy(doc)
57+ self._newrev = newrev
58+
59+ def save(self, doc):
60+ self._calls.append(('save', deepcopy(doc)))
61+ if doc['_rev'] != self._rev:
62+ raise DummyConflict()
63+ doc['_rev'] = self._newrev
64+ return doc
65+
66+ def get(self, _id):
67+ assert _id == self._id
68+ self._calls.append(('get', _id))
69+ return deepcopy(self._doc)
70+
71+ def _func(self, doc, key, value):
72+ self._calls.append(('func', deepcopy(doc), key, value))
73+ doc[key] = value
74+
75+ _id = random_id()
76+ rev1 = random_id()
77+ rev2 = random_id()
78+ rev3 = random_id()
79+ key = random_id()
80+ value = random_id()
81+ doc1 = {
82+ '_id': _id,
83+ '_rev': rev1,
84+ 'hello': 'world',
85+ }
86+ doc1a = {
87+ '_id': _id,
88+ '_rev': rev1,
89+ 'hello': 'world',
90+ key: value,
91+ }
92+ doc2 = {
93+ '_id': _id,
94+ '_rev': rev2,
95+ 'stuff': 'junk',
96+ }
97+ doc2a = {
98+ '_id': _id,
99+ '_rev': rev2,
100+ 'stuff': 'junk',
101+ key: value,
102+ }
103+
104+ # Test when there is a mid-flight collision:
105+ db = DummyDatabase(deepcopy(doc2), rev3)
106+ self.assertEqual(
107+ db.update(deepcopy(doc1), db._func, key, value),
108+ {
109+ '_id': _id,
110+ '_rev': rev3,
111+ 'stuff': 'junk',
112+ key: value,
113+ }
114+ )
115+ self.assertEqual(db._calls, [
116+ ('func', doc1, key, value),
117+ ('save', doc1a),
118+ ('get', _id),
119+ ('func', doc2, key, value),
120+ ('save', doc2a),
121+ ])
122+
123+ # Test when there is no conflict:
124+ db = DummyDatabase(deepcopy(doc1), rev3)
125+ self.assertEqual(
126+ db.update(deepcopy(doc1), db._func, key, value),
127+ {
128+ '_id': _id,
129+ '_rev': rev3,
130+ 'hello': 'world',
131+ key: value,
132+ }
133+ )
134+ self.assertEqual(db._calls, [
135+ ('func', doc1, key, value),
136+ ('save', doc1a),
137+ ])
138+
139
140 class LiveTestCase(TestCase):
141 """

Subscribers

People subscribed via source and target branches