Skip to content

Commit 86fd3b5

Browse files
committed
Add basic iterview method: allow iteration over view results (issue 162).
1 parent dcf4073 commit 86fd3b5

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

couchdb/client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,39 @@ def view(self, name, wrapper=None, **options):
826826
return PermanentView(self.resource(*path), '/'.join(path),
827827
wrapper=wrapper)(**options)
828828

829+
def iterview(self, name, batch, wrapper=None, **options):
830+
"""Iterate the rows in a view, fetching rows in batches and yielding
831+
one row at a time.
832+
833+
Since the view's rows are fetched in batches any rows emitted for
834+
documents added, changed or deleted between requests may be missed or
835+
repeated.
836+
837+
:param name: the name of the view; for custom views, use the format
838+
``design_docid/viewname``, that is, the document ID of the
839+
design document and the name of the view, separated by a
840+
slash.
841+
:param batch: number of rows to fetch per HTTP request.
842+
:param wrapper: an optional callable that should be used to wrap the
843+
result rows
844+
:param options: optional query string parameters
845+
:return: row generator
846+
"""
847+
if batch <= 0:
848+
raise ValueError('batch must be 1 or more')
849+
options['limit'] = batch + 1 # XXX todo: don't overwrite caller's limit
850+
startkey, startkey_docid = None, None # XXX todo: honour caller's startkey
851+
while True:
852+
options.update(startkey=startkey, startkey_docid=startkey_docid)
853+
# Get a batch of rows, with one extra for start of next batch.
854+
rows = list(self.view(name, wrapper, **options))
855+
# Yield at most 'batch' rows.
856+
for row in rows[:batch]:
857+
yield row
858+
if len(rows) <= batch:
859+
break
860+
startkey, startkey_docid = rows[-1]['key'], rows[-1]['id']
861+
829862
def show(self, name, docid=None, **options):
830863
"""Call a 'show' function.
831864

couchdb/tests/client.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,7 @@ def test_list_view_params(self):
689689
self.assertEqual(self.db.list('foo/list', 'foo/by_name', startkey='o', endkey='p')[1].read(), '1\r\n')
690690
self.assertEqual(self.db.list('foo/list', 'foo/by_name', descending=True)[1].read(), '2\r\n1\r\n')
691691

692+
692693
class UpdateHandlerTestCase(testutil.TempDatabaseMixin, unittest.TestCase):
693694
update_func = """
694695
function(doc, req) {
@@ -726,13 +727,51 @@ def test_new_doc(self):
726727
def test_update_doc(self):
727728
self.assertEqual(self.db.update_doc('foo/bar', 'existed')[1].read(), 'hello doc')
728729

730+
731+
class ViewIterationTestCase(testutil.TempDatabaseMixin, unittest.TestCase):
732+
733+
num_docs = 10
734+
735+
def docfromnum(self, num):
736+
return {'_id': unicode(num), 'num': int(num / 2)}
737+
738+
def docfromrow(self, row):
739+
return {'_id': row['id'], 'num': row['key']}
740+
741+
def setUp(self):
742+
super(ViewIterationTestCase, self).setUp()
743+
design_doc = {'_id': '_design/test',
744+
'views': {'nums': {'map': 'function(doc) {emit(doc.num, null);}'}}}
745+
self.db.save(design_doc)
746+
self.db.update([self.docfromnum(num) for num in xrange(self.num_docs)])
747+
748+
def test_allrows(self):
749+
rows = list(self.db.iterview('test/nums', 10))
750+
self.assertEqual(len(rows), self.num_docs)
751+
self.assertEqual([self.docfromrow(row) for row in rows],
752+
[self.docfromnum(num) for num in xrange(self.num_docs)])
753+
754+
def test_batchsizes(self):
755+
# Check silly _batch values.
756+
self.assertRaises(ValueError, self.db.iterview('test/nums', 0).next)
757+
self.assertRaises(ValueError, self.db.iterview('test/nums', -1).next)
758+
# Test various _batch sizes that are likely to cause trouble.
759+
self.assertEqual(len(list(self.db.iterview('test/nums', 1))), self.num_docs)
760+
self.assertEqual(len(list(self.db.iterview('test/nums', int(self.num_docs / 2)))), self.num_docs)
761+
self.assertEqual(len(list(self.db.iterview('test/nums', self.num_docs * 2))), self.num_docs)
762+
self.assertEqual(len(list(self.db.iterview('test/nums', self.num_docs - 1))), self.num_docs)
763+
self.assertEqual(len(list(self.db.iterview('test/nums', self.num_docs))), self.num_docs)
764+
self.assertEqual(len(list(self.db.iterview('test/nums', self.num_docs + 1))), self.num_docs)
765+
766+
729767
def suite():
730768
suite = unittest.TestSuite()
731769
suite.addTest(unittest.makeSuite(ServerTestCase, 'test'))
732770
suite.addTest(unittest.makeSuite(DatabaseTestCase, 'test'))
733771
suite.addTest(unittest.makeSuite(ViewTestCase, 'test'))
734772
suite.addTest(unittest.makeSuite(ShowListTestCase, 'test'))
735773
suite.addTest(unittest.makeSuite(UpdateHandlerTestCase, 'test'))
774+
suite.addTest(unittest.makeSuite(ViewIterationTestCase, 'test'))
736775
suite.addTest(doctest.DocTestSuite(client))
737776
return suite
738777

0 commit comments

Comments
 (0)