Skip to content

Commit 3eabd19

Browse files
committed
Updates for changes in CouchDB trunk. Also fixes issues djc#5 and djc#8.
--HG-- extra : convert_revision : svn%3A7a298fb0-333a-0410-83e7-658617cd9cf3/trunk%4068
1 parent 629c698 commit 3eabd19

File tree

6 files changed

+78
-51
lines changed

6 files changed

+78
-51
lines changed

ChangeLog.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ http://couchdb-python.googlecode.com/svn/tags/0.4.0
33
(?, from branches/0.4.x)
44

55
* Add command-line scripts for importing/exporting databases.
6+
* The `Database.update()` function will now actually perform the `POST`
7+
request even when you do not iterate over the results (issue 5).
68

79

810
Version 0.3

couchdb/client.py

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,18 @@
2828
import re
2929
import simplejson as json
3030

31-
__all__ = ['ResourceNotFound', 'ResourceConflict', 'ServerError', 'Server',
32-
'Database', 'Document', 'ViewResults', 'Row']
31+
__all__ = ['PreconditionFailed', 'ResourceNotFound', 'ResourceConflict',
32+
'ServerError', 'Server', 'Database', 'Document', 'ViewResults',
33+
'Row']
3334
__docformat__ = 'restructuredtext en'
3435

3536

37+
class PreconditionFailed(Exception):
38+
"""Exception raised when a 412 HTTP error is received in response to a
39+
request.
40+
"""
41+
42+
3643
class ResourceNotFound(Exception):
3744
"""Exception raised when a 404 HTTP error is received in response to a
3845
request.
@@ -196,8 +203,6 @@ class Database(object):
196203
True
197204
>>> len(db)
198205
2
199-
>>> list(db) #doctest: +ELLIPSIS
200-
[u'...', u'JohnDoe']
201206
202207
>>> del server['python-tests']
203208
"""
@@ -291,43 +296,47 @@ def get(self, id, default=None, **options):
291296
except ResourceNotFound:
292297
return default
293298

294-
def query(self, code, content_type='text/javascript', wrapper=None,
295-
**options):
299+
def query(self, map_fun, reduce_fun=None, language='javascript',
300+
wrapper=None, **options):
296301
"""Execute an ad-hoc query (a "temp view") against the database.
297302
298303
>>> server = Server('http://localhost:5984/')
299304
>>> db = server.create('python-tests')
300305
>>> db['johndoe'] = dict(type='Person', name='John Doe')
301306
>>> db['maryjane'] = dict(type='Person', name='Mary Jane')
302307
>>> db['gotham'] = dict(type='City', name='Gotham City')
303-
>>> code = '''function(doc) {
304-
... if (doc.type=='Person')
305-
... map(doc.name, null);
308+
>>> map_fun = '''function(doc) {
309+
... if (doc.type == 'Person')
310+
... emit(doc.name, null);
306311
... }'''
307-
>>> for row in db.query(code):
312+
>>> for row in db.query(map_fun):
308313
... print row.key
309314
John Doe
310315
Mary Jane
311316
312-
>>> for row in db.query(code, descending=True):
317+
>>> for row in db.query(map_fun, descending=True):
313318
... print row.key
314319
Mary Jane
315320
John Doe
316321
317-
>>> for row in db.query(code, key='John Doe'):
322+
>>> for row in db.query(map_fun, key='John Doe'):
318323
... print row.key
319324
John Doe
320325
321326
>>> del server['python-tests']
322327
323-
:param code: the code of the view function
324-
:param content_type: the MIME type of the code, which determines the
325-
language the view function is written in
328+
:param map_fun: the code of the map function
329+
:param reduce_fun: the code of the reduce function (optional)
330+
:param language: the language of the functions, to determine which view
331+
server to use
332+
:param wrapper: an optional callable that should be used to wrap the
333+
result rows
334+
:param options: optional query string parameters
326335
:return: the view reults
327336
:rtype: `ViewResults`
328337
"""
329-
return TemporaryView(uri(self.resource.uri, '_temp_view'), code,
330-
content_type=content_type, wrapper=wrapper,
338+
return TemporaryView(uri(self.resource.uri, '_temp_view'), map_fun,
339+
reduce_fun, language=language, wrapper=wrapper,
331340
http=self.resource.http)(**options)
332341

333342
def update(self, documents):
@@ -355,12 +364,14 @@ def update(self, documents):
355364
:since: version 0.2
356365
"""
357366
documents = list(documents)
358-
data = self.resource.post('_bulk_docs', content=documents)
359-
for idx, result in enumerate(data['results']):
360-
assert 'ok' in result # FIXME: how should error handling work here?
361-
doc = documents[idx]
362-
doc.update({'_id': result['id'], '_rev': result['rev']})
363-
yield doc
367+
data = self.resource.post('_bulk_docs', content={'docs': documents})
368+
assert data['ok'] # FIXME: Should probably raise a proper exception
369+
def _update():
370+
for idx, result in enumerate(data['new_revs']):
371+
doc = documents[idx]
372+
doc.update({'_id': result['id'], '_rev': result['rev']})
373+
yield doc
374+
return _update()
364375

365376
def view(self, name, wrapper=None, **options):
366377
"""Execute a predefined view.
@@ -375,6 +386,11 @@ def view(self, name, wrapper=None, **options):
375386
376387
>>> del server['python-tests']
377388
389+
:param name: the name of the view, including the `_view/design_docid`
390+
prefix for custom views
391+
:param wrapper: an optional callable that should be used to wrap the
392+
result rows
393+
:param options: optional query string parameters
378394
:return: the view results
379395
:rtype: `ViewResults`
380396
"""
@@ -443,21 +459,24 @@ def _exec(self, options):
443459
class TemporaryView(View):
444460
"""Representation of a temporary view."""
445461

446-
def __init__(self, uri, code=None, content_type='text/javascript',
447-
wrapper=None, http=None):
462+
def __init__(self, uri, map_fun=None, reduce_fun=None,
463+
language='javascript', wrapper=None, http=None):
448464
View.__init__(self, uri, wrapper=wrapper, http=http)
449465
self.resource = Resource(http, uri)
450-
self.code = code
451-
self.content_type = content_type
466+
self.map_fun = map_fun
467+
self.reduce_fun = reduce_fun
468+
self.language = language
452469

453470
def __repr__(self):
454-
return '<%s %r>' % (type(self).__name__, self.code)
471+
return '<%s %r %r>' % (type(self).__name__, self.map_fun,
472+
self.reduce_fun)
455473

456474
def _exec(self, options):
457-
headers = {}
458-
if self.content_type:
459-
headers['Content-Type'] = self.content_type
460-
return self.resource.post(content=self.code, headers=headers,
475+
body = {'map': self.map_fun, 'language': self.language}
476+
if self.reduce_fun:
477+
body['reduce'] = self.reduce_fun
478+
return self.resource.post(content=json.dumps(body),
479+
headers={'Content-Type': 'application/json'},
461480
**self._encode_options(options))
462481

463482

@@ -473,10 +492,10 @@ class ViewResults(object):
473492
>>> db['johndoe'] = dict(type='Person', name='John Doe')
474493
>>> db['maryjane'] = dict(type='Person', name='Mary Jane')
475494
>>> db['gotham'] = dict(type='City', name='Gotham City')
476-
>>> code = '''function(doc) {
477-
... map([doc.type, doc.name], doc.name);
495+
>>> map_fun = '''function(doc) {
496+
... emit([doc.type, doc.name], doc.name);
478497
... }'''
479-
>>> results = db.query(code)
498+
>>> results = db.query(map_fun)
480499
481500
At this point, the view has not actually been accessed yet. It is accessed
482501
as soon as it is iterated over, its length is requested, or one of its
@@ -646,6 +665,8 @@ def _request(self, method, path=None, content=None, headers=None,
646665
raise ResourceNotFound(error)
647666
elif status_code == 409:
648667
raise ResourceConflict(error)
668+
elif status_code == 412:
669+
raise PreconditionFailed(error)
649670
else:
650671
raise ServerError((status_code, error))
651672

couchdb/schema.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,8 @@ def store(self, db):
204204
db[self._data.id] = self._data
205205
return self
206206

207-
def query(cls, db, code, content_type='text/javascript', eager=False,
208-
**options):
207+
def query(cls, db, map_fun, reduce_fun, language='javascript',
208+
eager=False, **options):
209209
"""Execute a CouchDB temporary view and map the result values back to
210210
objects of this schema.
211211
@@ -221,8 +221,8 @@ def _wrapper(row):
221221
data = row.value
222222
data['_id'] = row.id
223223
return cls.wrap(data)
224-
return db.query(code, content_type=content_type, wrapper=_wrapper,
225-
**options)
224+
return db.query(map_fun, reduce_fun=reduce_fun, language=language,
225+
wrapper=_wrapper, **options)
226226
query = classmethod(query)
227227

228228
def view(cls, db, viewname, eager=False, **options):

couchdb/tests/couch_tests.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import os
1111
import unittest
12-
from couchdb import ResourceConflict, ResourceNotFound, Server
12+
from couchdb import PreconditionFailed, ResourceNotFound, Server
1313

1414

1515
class CouchTests(unittest.TestCase):
@@ -55,7 +55,7 @@ def test_basics(self):
5555
# test a simple query
5656
query = """function(doc) {
5757
if (doc.a==4)
58-
map(null, doc.b);
58+
emit(null, doc.b);
5959
}"""
6060
result = list(self.db.query(query))
6161
self.assertEqual(1, len(result))
@@ -93,11 +93,11 @@ def test_conflict_detection(self):
9393
doc1['a'] = 2
9494
doc2['a'] = 3
9595
self.db['foo'] = doc1
96-
self.assertRaises(ResourceConflict, self.db.__setitem__, 'foo', doc2)
96+
self.assertRaises(PreconditionFailed, self.db.__setitem__, 'foo', doc2)
9797

9898
# try submitting without the revision info
9999
data = {'_id': 'foo', 'a': 3, 'b': 1}
100-
self.assertRaises(ResourceConflict, self.db.__setitem__, 'foo', data)
100+
self.assertRaises(PreconditionFailed, self.db.__setitem__, 'foo', data)
101101

102102
del self.db['foo']
103103
self.db['foo'] = data
@@ -109,7 +109,7 @@ def test_lots_of_docs(self):
109109
self.assertEqual(num, len(self.db))
110110

111111
query = """function(doc) {
112-
map(doc.integer, null);
112+
emit(doc.integer, null);
113113
}"""
114114
results = list(self.db.query(query))
115115
self.assertEqual(num, len(results))
@@ -130,7 +130,7 @@ def test_multiple_rows(self):
130130

131131
query = """function(doc){
132132
for (var i = 0; i < doc.cities.length; i++) {
133-
map(doc.cities[i] + ", " + doc._id, null);
133+
emit(doc.cities[i] + ", " + doc._id, null);
134134
}
135135
}"""
136136
results = list(self.db.query(query))
@@ -176,7 +176,7 @@ def test_large_docs(self):
176176
self.db.create({'longtext': longtext})
177177

178178
query = """function(doc) {
179-
map(null, doc.longtext);
179+
emit(null, doc.longtext);
180180
}"""
181181
results = list(self.db.query(query))
182182
self.assertEqual(4, len(results))
@@ -196,7 +196,7 @@ def test_utf8_encoding(self):
196196
self.assertEqual(text, doc['text'])
197197

198198
query = """function(doc) {
199-
map(doc.text, null);
199+
emit(doc.text, null);
200200
}"""
201201
for idx, row in enumerate(self.db.query(query)):
202202
self.assertEqual(texts[idx], row.key)
@@ -205,9 +205,9 @@ def test_design_docs(self):
205205
for i in range(50):
206206
self.db[str(i)] = {'integer': i, 'string': str(i)}
207207
self.db['_design/test'] = {'views': {
208-
'all_docs': 'function(doc) { map(doc.integer, null) }',
209-
'no_docs': 'function(doc) {}',
210-
'single_doc': 'function(doc) { if (doc._id == "1") map(null, 1) }'
208+
'all_docs': {'map': 'function(doc) { emit(doc.integer, null) }'},
209+
'no_docs': {'map': 'function(doc) {}'},
210+
'single_doc': {'map': 'function(doc) { if (doc._id == "1") emit(null, 1) }'}
211211
}}
212212
for idx, row in enumerate(self.db.view('_view/test/all_docs')):
213213
self.assertEqual(idx, row.key)
@@ -228,7 +228,7 @@ def test_collation(self):
228228
self.db[str(idx + 1)] = {'foo': value}
229229

230230
query = """function(doc) {
231-
map(doc.foo, null);
231+
emit(doc.foo, null);
232232
}"""
233233
rows = iter(self.db.query(query))
234234
self.assertEqual(None, rows.next().value)

couchdb/tools/dump.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def dump_db(dburl, username=None, password=None, boundary=None):
2323
db.resource.http.add_credentials(username, password)
2424
for docid in db:
2525
doc = db[docid]
26+
if '_attachments' in doc:
27+
del doc['_attachments']
2628
print>>sys.stderr, 'Dumping document %r' % doc.id
2729
part = MIMEBase('application', 'json')
2830
part['Content-ID'] = doc.id

couchdb/tools/load.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def load_db(fileobj, dburl, username=None, password=None):
2424
docid = part['Content-ID']
2525
doc = json.loads(part.get_payload())
2626
del doc['_rev']
27+
if '_attachments' in doc:
28+
del doc['_attachments']
2729
print>>sys.stderr, 'Loading document %r' % docid
2830
db[docid] = doc
2931

0 commit comments

Comments
 (0)