[couchdb-python] 10 new revisions pushed by djc.ochtman on 2013-04-25 10:06 GMT

8 views
Skip to first unread message

couchdb...@googlecode.com

unread,
Apr 25, 2013, 6:06:48 AM4/25/13
to couchdb...@googlegroups.com
10 new revisions:

Revision: 5bce32b64026
Branch: default
Author: Dirkjan Ochtman <dir...@ochtman.nl>
Date: Thu Apr 25 00:38:14 2013
Log: Prefer assertEqual() over assertEquals() for forward
compatibility.
http://code.google.com/p/couchdb-python/source/detail?r=5bce32b64026

Revision: 0fbe7bb8572a
Branch: default
Author: Dirkjan Ochtman <dir...@ochtman.nl>
Date: Thu Apr 25 00:39:03 2013
Log: Close sockets when the connection pool goes away.
http://code.google.com/p/couchdb-python/source/detail?r=0fbe7bb8572a

Revision: af73abfdf1b3
Branch: default
Author: Dirkjan Ochtman <dir...@ochtman.nl>
Date: Thu Apr 25 00:39:25 2013
Log: Make sure to close temporary file after use.
http://code.google.com/p/couchdb-python/source/detail?r=af73abfdf1b3

Revision: 3a5f762bd41a
Branch: default
Author: Alexander Shorin <kxe...@gmail.com>
Date: Thu Apr 25 00:47:06 2013
Log: Fix credentials handling for dump tool (issue 223).
http://code.google.com/p/couchdb-python/source/detail?r=3a5f762bd41a

Revision: ce40fd77ae8d
Branch: default
Author: Alexander Shorin <kxe...@gmail.com>
Date: Thu Apr 25 00:46:55 2013
Log: Encode non-ASCII document IDs in multipart headers with RFC 2047
(issu...
http://code.google.com/p/couchdb-python/source/detail?r=ce40fd77ae8d

Revision: 4f4166f23558
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 02:43:44 2013
Log: Add basic iterview method: allow iteration over view results
(issue 16...
http://code.google.com/p/couchdb-python/source/detail?r=4f4166f23558

Revision: 96b0fa770231
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 02:50:03 2013
Log: Honour caller's 'limit' in iterview.
http://code.google.com/p/couchdb-python/source/detail?r=96b0fa770231

Revision: 452327472895
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 03:04:17 2013
Log: Fix descending in iterview.
http://code.google.com/p/couchdb-python/source/detail?r=452327472895

Revision: 1bd93eb94faf
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 02:42:30 2013
Log: Honour caller's startkey in iterview.
http://code.google.com/p/couchdb-python/source/detail?r=1bd93eb94faf

Revision: 6f5a9856716a
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 03:02:24 2013
Log: Fix iterview for views with null keys.
http://code.google.com/p/couchdb-python/source/detail?r=6f5a9856716a

==============================================================================
Revision: 5bce32b64026
Branch: default
Author: Dirkjan Ochtman <dir...@ochtman.nl>
Date: Thu Apr 25 00:38:14 2013
Log: Prefer assertEqual() over assertEquals() for forward
compatibility.
http://code.google.com/p/couchdb-python/source/detail?r=5bce32b64026

Modified:
/couchdb/tests/client.py
/couchdb/tests/http.py
/couchdb/tests/mapping.py
/couchdb/tests/view.py

=======================================
--- /couchdb/tests/client.py Mon Feb 18 01:59:37 2013
+++ /couchdb/tests/client.py Thu Apr 25 00:38:14 2013
@@ -82,21 +82,21 @@
bname, b = self.temp_db()
id, rev = a.save({'test': 'a'})
result = self.server.replicate(aname, bname)
- self.assertEquals(result['ok'], True)
- self.assertEquals(b[id]['test'], 'a')
+ self.assertEqual(result['ok'], True)
+ self.assertEqual(b[id]['test'], 'a')

doc = b[id]
doc['test'] = 'b'
b.update([doc])
self.server.replicate(bname, aname)
- self.assertEquals(a[id]['test'], 'b')
- self.assertEquals(b[id]['test'], 'b')
+ self.assertEqual(a[id]['test'], 'b')
+ self.assertEqual(b[id]['test'], 'b')

def test_replicate_continuous(self):
aname, a = self.temp_db()
bname, b = self.temp_db()
result = self.server.replicate(aname, bname, continuous=True)
- self.assertEquals(result['ok'], True)
+ self.assertEqual(result['ok'], True)
version = tuple(int(i) for i in
self.server.version().split('.')[:2])
if version >= (0, 10):
self.assertTrue('_local_id' in result)
@@ -238,7 +238,7 @@
old_rev = doc['_rev']

self.db.put_attachment(doc, 'Foo bar', 'foo.txt', 'text/plain')
- self.assertNotEquals(old_rev, doc['_rev'])
+ self.assertNotEqual(old_rev, doc['_rev'])

doc = self.db['foo']
attachment = doc['_attachments']['foo.txt']
@@ -252,7 +252,7 @@

old_rev = doc['_rev']
self.db.delete_attachment(doc, 'foo.txt')
- self.assertNotEquals(old_rev, doc['_rev'])
+ self.assertNotEqual(old_rev, doc['_rev'])
self.assertEqual(None, self.db['foo'].get('_attachments'))

def test_attachment_crud_with_files(self):
@@ -262,7 +262,7 @@
fileobj = StringIO('Foo bar baz')

self.db.put_attachment(doc, fileobj, 'foo.txt')
- self.assertNotEquals(old_rev, doc['_rev'])
+ self.assertNotEqual(old_rev, doc['_rev'])

doc = self.db['foo']
attachment = doc['_attachments']['foo.txt']
@@ -276,7 +276,7 @@

old_rev = doc['_rev']
self.db.delete_attachment(doc, 'foo.txt')
- self.assertNotEquals(old_rev, doc['_rev'])
+ self.assertNotEqual(old_rev, doc['_rev'])
self.assertEqual(None, self.db['foo'].get('_attachments'))

def test_empty_attachment(self):
@@ -285,7 +285,7 @@
old_rev = doc['_rev']

self.db.put_attachment(doc, '', 'empty.txt')
- self.assertNotEquals(old_rev, doc['_rev'])
+ self.assertNotEqual(old_rev, doc['_rev'])

doc = self.db['foo']
attachment = doc['_attachments']['empty.txt']
@@ -320,7 +320,7 @@
doc = {}
self.db['foo'] = doc
self.db.put_attachment(doc, '{}', 'test.json', 'application/json')
-
self.assertEquals(self.db.get_attachment(doc, 'test.json').read(), '{}')
+
self.assertEqual(self.db.get_attachment(doc, 'test.json').read(), '{}')

def test_include_docs(self):
doc = {'foo': 42, 'bar': 40}
@@ -587,12 +587,12 @@
def test_init_with_resource(self):
self.db['foo'] = {}
view =
client.PermanentView(self.db.resource('_all_docs').url, '_all_docs')
- self.assertEquals(len(list(view())), 1)
+ self.assertEqual(len(list(view())), 1)

def test_iter_view(self):
self.db['foo'] = {}
view =
client.PermanentView(self.db.resource('_all_docs').url, '_all_docs')
- self.assertEquals(len(list(view)), 1)
+ self.assertEqual(len(list(view)), 1)

def test_tmpview_repr(self):
mapfunc = "function(doc) {emit(null, null);}"
=======================================
--- /couchdb/tests/http.py Fri Sep 21 00:41:23 2012
+++ /couchdb/tests/http.py Thu Apr 25 00:38:14 2013
@@ -25,7 +25,7 @@
start = time.time()
status, headers, body = session.request('GET', db.resource.url
+ '/_changes?feed=longpoll&since=1000&timeout=%s' % (timeout*2*1000,))
self.assertRaises(socket.timeout, body.read)
- self.failUnless(time.time() - start < timeout * 1.3)
+ self.assertTrue(time.time() - start < timeout * 1.3)


class ResponseBodyTestCase(unittest.TestCase):
=======================================
--- /couchdb/tests/mapping.py Mon Feb 18 01:59:37 2013
+++ /couchdb/tests/mapping.py Thu Apr 25 00:38:14 2013
@@ -201,7 +201,7 @@
self.assertEqual(thing.numbers[4], Decimal('4.0'))
self.assertEqual(len(thing.numbers), 5)
del thing.numbers[3:]
- self.assertEquals(len(thing.numbers), 3)
+ self.assertEqual(len(thing.numbers), 3)

def test_mutable_fields(self):
class Thing(mapping.Document):
@@ -231,24 +231,24 @@
def test_viewfield_property(self):
self.Item().store(self.db)
results = self.Item.with_include_docs(self.db)
- self.assertEquals(type(results.rows[0]), self.Item)
+ self.assertEqual(type(results.rows[0]), self.Item)
results = self.Item.without_include_docs(self.db)
- self.assertEquals(type(results.rows[0]), self.Item)
+ self.assertEqual(type(results.rows[0]), self.Item)

def test_view(self):
self.Item().store(self.db)
results = self.Item.view(self.db, 'test/without_include_docs')
- self.assertEquals(type(results.rows[0]), self.Item)
+ self.assertEqual(type(results.rows[0]), self.Item)
results = self.Item.view(self.db, 'test/without_include_docs',
include_docs=True)
- self.assertEquals(type(results.rows[0]), self.Item)
+ self.assertEqual(type(results.rows[0]), self.Item)

def test_query(self):
self.Item().store(self.db)
results = self.Item.query(self.db, all_map_func, None)
- self.assertEquals(type(results.rows[0]), self.Item)
+ self.assertEqual(type(results.rows[0]), self.Item)
results = self.Item.query(self.db, all_map_func, None,
include_docs=True)
- self.assertEquals(type(results.rows[0]), self.Item)
+ self.assertEqual(type(results.rows[0]), self.Item)


def suite():
=======================================
--- /couchdb/tests/view.py Sat Jun 4 09:32:53 2011
+++ /couchdb/tests/view.py Thu Apr 25 00:38:14 2013
@@ -19,13 +19,13 @@
input = StringIO('["reset"]\n')
output = StringIO()
view.run(input=input, output=output)
- self.assertEquals(output.getvalue(), 'true\n')
+ self.assertEqual(output.getvalue(), 'true\n')

def test_add_fun(self):
input = StringIO('["add_fun", "def fun(doc): yield None, doc"]\n')
output = StringIO()
view.run(input=input, output=output)
- self.assertEquals(output.getvalue(), 'true\n')
+ self.assertEqual(output.getvalue(), 'true\n')

def test_map_doc(self):
input = StringIO('["add_fun", "def fun(doc): yield None, doc"]\n'

==============================================================================
Revision: 0fbe7bb8572a
Branch: default
Author: Dirkjan Ochtman <dir...@ochtman.nl>
Date: Thu Apr 25 00:39:03 2013
Log: Close sockets when the connection pool goes away.
http://code.google.com/p/couchdb-python/source/detail?r=0fbe7bb8572a

Modified:
/couchdb/http.py

=======================================
--- /couchdb/http.py Fri Oct 12 01:01:22 2012
+++ /couchdb/http.py Thu Apr 25 00:39:03 2013
@@ -479,6 +479,11 @@
finally:
self.lock.release()

+ def __del__(self):
+ for key, conns in list(self.conns.items()):
+ for conn in conns:
+ conn.close()
+

class Resource(object):


==============================================================================
Revision: af73abfdf1b3
Branch: default
Author: Dirkjan Ochtman <dir...@ochtman.nl>
Date: Thu Apr 25 00:39:25 2013
Log: Make sure to close temporary file after use.
http://code.google.com/p/couchdb-python/source/detail?r=af73abfdf1b3

Modified:
/couchdb/tests/client.py

=======================================
--- /couchdb/tests/client.py Thu Apr 25 00:38:14 2013
+++ /couchdb/tests/client.py Thu Apr 25 00:39:25 2013
@@ -306,7 +306,8 @@
f.close()
doc = {}
self.db['foo'] = doc
- self.db.put_attachment(doc, open(tmpfile))
+ with open(tmpfile) as f:
+ self.db.put_attachment(doc, f)
doc = self.db.get('foo')
self.assertTrue(doc['_attachments']['test.txt']['content_type']
== 'text/plain')
shutil.rmtree(tmpdir)

==============================================================================
Revision: 3a5f762bd41a
Branch: default
Author: Alexander Shorin <kxe...@gmail.com>
Date: Thu Apr 25 00:47:06 2013
Log: Fix credentials handling for dump tool (issue 223).
http://code.google.com/p/couchdb-python/source/detail?r=3a5f762bd41a

Modified:
/couchdb/tests/tools.py
/couchdb/tools/dump.py

=======================================
--- /couchdb/tests/tools.py Tue Oct 9 01:04:31 2012
+++ /couchdb/tests/tools.py Thu Apr 25 00:47:06 2013
@@ -11,9 +11,11 @@
import unittest
from StringIO import StringIO

-from couchdb.tools import load
+from couchdb import Unauthorized
+from couchdb.tools import load, dump
from couchdb.tests import testutil

+
class ToolLoadTestCase(testutil.TempDatabaseMixin, unittest.TestCase):

def test_handle_credentials(self):
@@ -22,6 +24,18 @@
load.load_db(StringIO(''), self.db.resource.url, 'foo', 'bar')


+class ToolDumpTestCase(testutil.TempDatabaseMixin, unittest.TestCase):
+
+ def test_handle_credentials(self):
+ # Similar to issue 194
+ # Fixing: AttributeError: 'Resource' object has no attribute 'http'
+ try:
+ dump.dump_db(self.db.resource.url, 'foo', 'bar',
output=StringIO())
+ except Unauthorized:
+ # This is ok, since we provided dummy credentials.
+ pass
+
+
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ToolLoadTestCase, 'test'))
=======================================
--- /couchdb/tools/dump.py Mon Mar 8 06:41:15 2010
+++ /couchdb/tools/dump.py Thu Apr 25 00:47:06 2013
@@ -25,7 +25,7 @@
output=sys.stdout):
db = Database(dburl)
if username is not None and password is not None:
- db.resource.http.add_credentials(username, password)
+ db.resource.credentials = username, password

envelope = write_multipart(output, boundary=boundary)
for docid in db:

==============================================================================
Revision: ce40fd77ae8d
Branch: default
Author: Alexander Shorin <kxe...@gmail.com>
Date: Thu Apr 25 00:46:55 2013
Log: Encode non-ASCII document IDs in multipart headers with RFC 2047
(issue 179).
http://code.google.com/p/couchdb-python/source/detail?r=ce40fd77ae8d

Modified:
/couchdb/multipart.py
/couchdb/tests/multipart.py

=======================================
--- /couchdb/multipart.py Thu Sep 10 07:33:23 2009
+++ /couchdb/multipart.py Thu Apr 25 00:46:55 2013
@@ -10,6 +10,7 @@

from base64 import b64encode
from cgi import parse_header
+from email import header
try:
from hashlib import md5
except ImportError:
@@ -67,8 +68,13 @@
if in_headers:
line = line.replace(CRLF, '\n')
if line != '\n':
- name, value = line.split(':', 1)
- headers[name.lower().strip()] = value.strip()
+ name, value = [item.strip() for item in line.split(':', 1)]
+ name = name.lower()
+ value, charset = header.decode_header(value)[0]
+ if charset is None:
+ headers[name] = value
+ else:
+ headers[name] = value.decode(charset)
else:
in_headers = False
mimetype, params =
parse_header(headers.get('content-type'))
@@ -170,9 +176,12 @@
def _write_headers(self, headers):
if headers:
for name in sorted(headers.keys()):
+ value = headers[name]
+ if isinstance(value, unicode):
+ value = str(header.make_header([(value, 'utf-8')]))
self.fileobj.write(name)
self.fileobj.write(': ')
- self.fileobj.write(headers[name])
+ self.fileobj.write(value)
self.fileobj.write(CRLF)
self.fileobj.write(CRLF)

=======================================
--- /couchdb/tests/multipart.py Wed Jul 1 08:44:28 2009
+++ /couchdb/tests/multipart.py Thu Apr 25 00:46:55 2013
@@ -148,6 +148,23 @@
num += 1
self.assertEqual(num, 3)

+ def test_unicode_headers(self):
+ # http://code.google.com/p/couchdb-python/issues/detail?id=179
+ dump = '''Content-Type: multipart/mixed; boundary="==123456789=="
+
+--==123456789==
+Content-ID: =?utf-8?b?5paH5qGj?=
+Content-Length: 63
+Content-MD5: Cpw3iC3xPua8YzKeWLzwvw==
+Content-Type: application/json
+
+{"_rev": "3-bc27b6930ca514527d8954c7c43e6a09", "_id": "文档"}
+'''
+ parts = multipart.read_multipart(StringIO(dump))
+ for headers, is_multipart, payload in parts:
+ self.assertEqual(headers['content-id'], u'文档')
+ break
+

class WriteMultipartTestCase(unittest.TestCase):

@@ -173,6 +190,25 @@
self.assertRaises(UnicodeEncodeError, envelope.add,
'text/plain;charset=ascii',
u'Iñtërnâtiônàlizætiøn')

+ def test_unicode_headers(self):
+ # http://code.google.com/p/couchdb-python/issues/detail?id=179
+ buf = StringIO()
+ envelope = multipart.write_multipart(buf, boundary='==123456789==')
+ envelope.add('application/json',
+ '{"_rev": "3-bc27b6930ca514527d8954c7c43e6a09",'
+ ' "_id": "文档"}',
+ headers={'Content-ID': u"文档"})
+ self.assertEqual('''Content-Type: multipart/mixed;
boundary="==123456789=="
+
+--==123456789==
+Content-ID: =?utf-8?b?5paH5qGj?=
+Content-Length: 63
+Content-MD5: Cpw3iC3xPua8YzKeWLzwvw==
+Content-Type: application/json
+
+{"_rev": "3-bc27b6930ca514527d8954c7c43e6a09", "_id": "文档"}
+''', buf.getvalue().replace('\r\n', '\n'))
+

def suite():
suite = unittest.TestSuite()

==============================================================================
Revision: 4f4166f23558
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 02:43:44 2013
Log: Add basic iterview method: allow iteration over view results
(issue 162).
http://code.google.com/p/couchdb-python/source/detail?r=4f4166f23558

Modified:
/couchdb/client.py
/couchdb/tests/client.py

=======================================
--- /couchdb/client.py Fri Sep 21 01:01:01 2012
+++ /couchdb/client.py Thu Apr 25 02:43:44 2013
@@ -826,6 +826,39 @@
return PermanentView(self.resource(*path), '/'.join(path),
wrapper=wrapper)(**options)

+ def iterview(self, name, batch, wrapper=None, **options):
+ """Iterate the rows in a view, fetching rows in batches and
yielding
+ one row at a time.
+
+ Since the view's rows are fetched in batches any rows emitted for
+ documents added, changed or deleted between requests may be missed
or
+ repeated.
+
+ :param name: the name of the view; for custom views, use the format
+ ``design_docid/viewname``, that is, the document ID
of the
+ design document and the name of the view, separated
by a
+ slash.
+ :param batch: number of rows to fetch per HTTP request.
+ :param wrapper: an optional callable that should be used to wrap
the
+ result rows
+ :param options: optional query string parameters
+ :return: row generator
+ """
+ if batch <= 0:
+ raise ValueError('batch must be 1 or more')
+ options['limit'] = batch + 1 # XXX todo: don't overwrite caller's
limit
+ startkey, startkey_docid = None, None # XXX todo: honour caller's
startkey
+ while True:
+ options.update(startkey=startkey,
startkey_docid=startkey_docid)
+ # Get a batch of rows, with one extra for start of next batch.
+ rows = list(self.view(name, wrapper, **options))
+ # Yield at most 'batch' rows.
+ for row in rows[:batch]:
+ yield row
+ if len(rows) <= batch:
+ break
+ startkey, startkey_docid = rows[-1]['key'], rows[-1]['id']
+
def show(self, name, docid=None, **options):
"""Call a 'show' function.

=======================================
--- /couchdb/tests/client.py Thu Apr 25 00:39:25 2013
+++ /couchdb/tests/client.py Thu Apr 25 02:43:44 2013
@@ -689,6 +689,7 @@
self.assertEqual(self.db.list('foo/list', 'foo/by_name',
startkey='o', endkey='p')[1].read(), '1\r\n')
self.assertEqual(self.db.list('foo/list', 'foo/by_name',
descending=True)[1].read(), '2\r\n1\r\n')

+
class UpdateHandlerTestCase(testutil.TempDatabaseMixin, unittest.TestCase):
update_func = """
function(doc, req) {
@@ -726,6 +727,43 @@
def test_update_doc(self):

self.assertEqual(self.db.update_doc('foo/bar', 'existed')[1].read(), 'hello
doc')

+
+class ViewIterationTestCase(testutil.TempDatabaseMixin, unittest.TestCase):
+
+ num_docs = 10
+
+ def docfromnum(self, num):
+ return {'_id': unicode(num), 'num': int(num / 2)}
+
+ def docfromrow(self, row):
+ return {'_id': row['id'], 'num': row['key']}
+
+ def setUp(self):
+ super(ViewIterationTestCase, self).setUp()
+ design_doc = {'_id': '_design/test',
+ 'views': {'nums': {'map': 'function(doc)
{emit(doc.num, null);}'}}}
+ self.db.save(design_doc)
+ self.db.update([self.docfromnum(num) for num in
xrange(self.num_docs)])
+
+ def test_allrows(self):
+ rows = list(self.db.iterview('test/nums', 10))
+ self.assertEqual(len(rows), self.num_docs)
+ self.assertEqual([self.docfromrow(row) for row in rows],
+ [self.docfromnum(num) for num in
xrange(self.num_docs)])
+
+ def test_batchsizes(self):
+ # Check silly _batch values.
+ self.assertRaises(ValueError, self.db.iterview('test/nums',
0).next)
+ self.assertRaises(ValueError, self.db.iterview('test/nums',
-1).next)
+ # Test various _batch sizes that are likely to cause trouble.
+ self.assertEqual(len(list(self.db.iterview('test/nums', 1))),
self.num_docs)
+ self.assertEqual(len(list(self.db.iterview('test/nums',
int(self.num_docs / 2)))), self.num_docs)
+ self.assertEqual(len(list(self.db.iterview('test/nums',
self.num_docs * 2))), self.num_docs)
+ self.assertEqual(len(list(self.db.iterview('test/nums',
self.num_docs - 1))), self.num_docs)
+ self.assertEqual(len(list(self.db.iterview('test/nums',
self.num_docs))), self.num_docs)
+ self.assertEqual(len(list(self.db.iterview('test/nums',
self.num_docs + 1))), self.num_docs)
+
+
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ServerTestCase, 'test'))
@@ -733,6 +771,7 @@
suite.addTest(unittest.makeSuite(ViewTestCase, 'test'))
suite.addTest(unittest.makeSuite(ShowListTestCase, 'test'))
suite.addTest(unittest.makeSuite(UpdateHandlerTestCase, 'test'))
+ suite.addTest(unittest.makeSuite(ViewIterationTestCase, 'test'))
suite.addTest(doctest.DocTestSuite(client))
return suite


==============================================================================
Revision: 96b0fa770231
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 02:50:03 2013
Log: Honour caller's 'limit' in iterview.
http://code.google.com/p/couchdb-python/source/detail?r=96b0fa770231

Modified:
/couchdb/client.py
/couchdb/tests/client.py

=======================================
--- /couchdb/client.py Thu Apr 25 02:43:44 2013
+++ /couchdb/client.py Thu Apr 25 02:50:03 2013
@@ -23,6 +23,7 @@
>>> del server['python-tests']
"""

+import itertools
import mimetypes
import os
from types import FunctionType
@@ -844,19 +845,30 @@
:param options: optional query string parameters
:return: row generator
"""
+ # Check sane batch size.
if batch <= 0:
raise ValueError('batch must be 1 or more')
- options['limit'] = batch + 1 # XXX todo: don't overwrite caller's
limit
+ # Save caller's limit, it must be handled manually.
+ limit = options.get('limit')
+ if limit is not None and limit <= 0:
+ raise ValueError('limit must be 1 or more')
startkey, startkey_docid = None, None # XXX todo: honour caller's
startkey
while True:
- options.update(startkey=startkey,
startkey_docid=startkey_docid)
- # Get a batch of rows, with one extra for start of next batch.
+ loop_limit = min(limit or batch, batch)
+ # Get rows in batches, with one extra for start of next batch.
+ options.update(limit=loop_limit + 1, startkey=startkey,
+ startkey_docid=startkey_docid)
rows = list(self.view(name, wrapper, **options))
- # Yield at most 'batch' rows.
- for row in rows[:batch]:
+ # Yield rows from this batch.
+ for row in itertools.islice(rows, loop_limit):
yield row
- if len(rows) <= batch:
+ # Decrement limit counter.
+ if limit is not None:
+ limit -= min(len(rows), batch)
+ # Check if there is nothing else to yield.
+ if len(rows) <= batch or (limit is not None and limit == 0):
break
+ # Save start keys for next loop.
startkey, startkey_docid = rows[-1]['key'], rows[-1]['id']

def show(self, name, docid=None, **options):
=======================================
--- /couchdb/tests/client.py Thu Apr 25 02:43:44 2013
+++ /couchdb/tests/client.py Thu Apr 25 02:50:03 2013
@@ -730,7 +730,7 @@

class ViewIterationTestCase(testutil.TempDatabaseMixin, unittest.TestCase):

- num_docs = 10
+ num_docs = 100

def docfromnum(self, num):
return {'_id': unicode(num), 'num': int(num / 2)}
@@ -763,6 +763,19 @@
self.assertEqual(len(list(self.db.iterview('test/nums',
self.num_docs))), self.num_docs)
self.assertEqual(len(list(self.db.iterview('test/nums',
self.num_docs + 1))), self.num_docs)

+ def test_limit(self):
+ # limit=0 doesn't make sense for iterview.
+ self.assertRaises(ValueError, self.db.iterview('test/nums', 10,
limit=0).next)
+ # Test various limit sizes that are likely to cause trouble.
+ for limit in [1, int(self.num_docs / 4), self.num_docs - 1,
self.num_docs,
+ self.num_docs + 1]:
+ self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', 10, limit=limit)],
+ [self.docfromnum(x) for x in
xrange(min(limit, self.num_docs))])
+ # Test limit same as batch size, in case of weird edge cases.
+ limit = int(self.num_docs / 4)
+ self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', limit, limit=limit)],
+ [self.docfromnum(x) for x in xrange(limit)])
+

def suite():
suite = unittest.TestSuite()

==============================================================================
Revision: 452327472895
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 03:04:17 2013
Log: Fix descending in iterview.
http://code.google.com/p/couchdb-python/source/detail?r=452327472895

Modified:
/couchdb/client.py
/couchdb/tests/client.py

=======================================
--- /couchdb/client.py Thu Apr 25 02:50:03 2013
+++ /couchdb/client.py Thu Apr 25 03:04:17 2013
@@ -856,8 +856,11 @@
while True:
loop_limit = min(limit or batch, batch)
# Get rows in batches, with one extra for start of next batch.
- options.update(limit=loop_limit + 1, startkey=startkey,
- startkey_docid=startkey_docid)
+ options.update(limit=loop_limit + 1)
+ # Add start keys, if any.
+ if startkey is not None: # XXX todo: None is a valid key value
+ options.update(startkey=startkey,
+ startkey_docid=startkey_docid)
rows = list(self.view(name, wrapper, **options))
# Yield rows from this batch.
for row in itertools.islice(rows, loop_limit):
=======================================
--- /couchdb/tests/client.py Thu Apr 25 02:50:03 2013
+++ /couchdb/tests/client.py Thu Apr 25 03:04:17 2013
@@ -776,6 +776,12 @@
self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', limit, limit=limit)],
[self.docfromnum(x) for x in xrange(limit)])

+ def test_descending(self):
+ self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', 10, descending=True)],
+ [self.docfromnum(x) for x in xrange(self.num_docs
- 1, -1, -1)])
+ self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', 10, limit=int(self.num_docs / 4),
descending=True)],
+ [self.docfromnum(x) for x in xrange(self.num_docs
- 1, int(self.num_docs * 3 / 4) - 1, -1)])
+

def suite():
suite = unittest.TestSuite()

==============================================================================
Revision: 1bd93eb94faf
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 02:42:30 2013
Log: Honour caller's startkey in iterview.
http://code.google.com/p/couchdb-python/source/detail?r=1bd93eb94faf

Modified:
/couchdb/client.py
/couchdb/tests/client.py

=======================================
--- /couchdb/client.py Thu Apr 25 03:04:17 2013
+++ /couchdb/client.py Thu Apr 25 02:42:30 2013
@@ -852,15 +852,17 @@
limit = options.get('limit')
if limit is not None and limit <= 0:
raise ValueError('limit must be 1 or more')
- startkey, startkey_docid = None, None # XXX todo: honour caller's
startkey
+ startkey, startkey_docid = (options.get('startkey'),
+ options.get('startkey_docid'))
while True:
loop_limit = min(limit or batch, batch)
# Get rows in batches, with one extra for start of next batch.
- options.update(limit=loop_limit + 1)
+ options['limit'] = loop_limit + 1
# Add start keys, if any.
if startkey is not None: # XXX todo: None is a valid key value
- options.update(startkey=startkey,
- startkey_docid=startkey_docid)
+ options['startkey'] = startkey
+ if startkey_docid is not None:
+ options['startkey_docid'] = startkey_docid
rows = list(self.view(name, wrapper, **options))
# Yield rows from this batch.
for row in itertools.islice(rows, loop_limit):
=======================================
--- /couchdb/tests/client.py Thu Apr 25 03:04:17 2013
+++ /couchdb/tests/client.py Thu Apr 25 02:42:30 2013
@@ -782,6 +782,12 @@
self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', 10, limit=int(self.num_docs / 4),
descending=True)],
[self.docfromnum(x) for x in xrange(self.num_docs
- 1, int(self.num_docs * 3 / 4) - 1, -1)])

+ def test_startkey(self):
+ self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', 10, startkey=int(self.num_docs / 2) - 1)],
+ [self.docfromnum(x) for x in xrange(self.num_docs
- 2, self.num_docs)])
+ self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', 10, startkey=1, descending=True)],
+ [self.docfromnum(x) for x in xrange(3, -1, -1)])
+

def suite():
suite = unittest.TestSuite()

==============================================================================
Revision: 6f5a9856716a
Branch: default
Author: Matt Goodall <matt.g...@gmail.com>
Date: Thu Apr 25 03:02:24 2013
Log: Fix iterview for views with null keys.
http://code.google.com/p/couchdb-python/source/detail?r=6f5a9856716a

Modified:
/couchdb/client.py
/couchdb/tests/client.py

=======================================
--- /couchdb/client.py Thu Apr 25 02:42:30 2013
+++ /couchdb/client.py Thu Apr 25 03:02:24 2013
@@ -852,17 +852,10 @@
limit = options.get('limit')
if limit is not None and limit <= 0:
raise ValueError('limit must be 1 or more')
- startkey, startkey_docid = (options.get('startkey'),
- options.get('startkey_docid'))
while True:
loop_limit = min(limit or batch, batch)
# Get rows in batches, with one extra for start of next batch.
options['limit'] = loop_limit + 1
- # Add start keys, if any.
- if startkey is not None: # XXX todo: None is a valid key value
- options['startkey'] = startkey
- if startkey_docid is not None:
- options['startkey_docid'] = startkey_docid
rows = list(self.view(name, wrapper, **options))
# Yield rows from this batch.
for row in itertools.islice(rows, loop_limit):
@@ -873,8 +866,8 @@
# Check if there is nothing else to yield.
if len(rows) <= batch or (limit is not None and limit == 0):
break
- # Save start keys for next loop.
- startkey, startkey_docid = rows[-1]['key'], rows[-1]['id']
+ # Update options with start keys for next loop.
+ options.update(startkey=rows[-1]['key'],
startkey_docid=rows[-1]['id'])

def show(self, name, docid=None, **options):
"""Call a 'show' function.
=======================================
--- /couchdb/tests/client.py Thu Apr 25 02:42:30 2013
+++ /couchdb/tests/client.py Thu Apr 25 03:02:24 2013
@@ -741,7 +741,8 @@
def setUp(self):
super(ViewIterationTestCase, self).setUp()
design_doc = {'_id': '_design/test',
- 'views': {'nums': {'map': 'function(doc)
{emit(doc.num, null);}'}}}
+ 'views': {'nums': {'map': 'function(doc)
{emit(doc.num, null);}'},
+ 'nulls': {'map': 'function(doc)
{emit(null, null);}'}}}
self.db.save(design_doc)
self.db.update([self.docfromnum(num) for num in
xrange(self.num_docs)])

@@ -788,6 +789,9 @@
self.assertEqual([self.docfromrow(doc) for doc in
self.db.iterview('test/nums', 10, startkey=1, descending=True)],
[self.docfromnum(x) for x in xrange(3, -1, -1)])

+ def test_nullkeys(self):
+ self.assertEqual(len(list(self.db.iterview('test/nulls', 10))),
self.num_docs)
+

def suite():
suite = unittest.TestSuite()
Reply all
Reply to author
Forward
0 new messages