Skip to content

Commit fc15111

Browse files
committed
Updates for changes in CouchDB trunk. Also, you can now pass options when retrieving a document, for example to retrieve an earlier revision.
--HG-- extra : convert_revision : svn%3A7a298fb0-333a-0410-83e7-658617cd9cf3/trunk%4041
1 parent 88b4ec6 commit fc15111

File tree

4 files changed

+102
-63
lines changed

4 files changed

+102
-63
lines changed

couchdb/client.py

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import simplejson as json
3030

3131
__all__ = ['ResourceNotFound', 'ResourceConflict', 'ServerError', 'Server',
32-
'Database', 'View']
32+
'Database', 'Document', 'View']
3333
__docformat__ = 'restructuredtext en'
3434

3535

@@ -46,8 +46,8 @@ class ResourceConflict(Exception):
4646

4747

4848
class ServerError(Exception):
49-
"""Exception raised when a 500 HTTP error is received in response to a
50-
request.
49+
"""Exception raised when an unexpected HTTP error is received in response
50+
to a request.
5151
"""
5252

5353

@@ -168,7 +168,7 @@ class Database(object):
168168
169169
>>> doc = db[doc_id]
170170
>>> doc #doctest: +ELLIPSIS
171-
<Row u'...'@... {...}>
171+
<Document u'...'@... {...}>
172172
173173
Documents are represented as instances of the `Row` class, which is
174174
basically just a normal dictionary with the additional attributes ``id`` and
@@ -224,7 +224,7 @@ def __contains__(self, id):
224224

225225
def __iter__(self):
226226
"""Return the IDs of all documents in the database."""
227-
return (item.id for item in self.view('_all_docs'))
227+
return iter([item.id for item in self.view('_all_docs')])
228228

229229
def __len__(self):
230230
"""Return the number of documents in the database."""
@@ -236,7 +236,7 @@ def __delitem__(self, id):
236236
:param id: the document ID
237237
"""
238238
doc = self[id] # FIXME: this should use HEAD once Etags are supported
239-
self.resource.delete(unicode_quote(id, ''), rev=doc.rev)
239+
self.resource.delete(id, rev=doc.rev)
240240

241241
def __getitem__(self, id):
242242
"""Return the document with the specified ID.
@@ -245,7 +245,7 @@ def __getitem__(self, id):
245245
:return: a `Row` object representing the requested document
246246
:rtype: `Row`
247247
"""
248-
return Row(self.resource.get(unicode_quote(id, '')))
248+
return Document(self.resource.get(id))
249249

250250
def __setitem__(self, id, content):
251251
"""Create or update a document with the specified ID.
@@ -255,9 +255,9 @@ def __setitem__(self, id, content):
255255
new documents, or a `Row` object for existing
256256
documents
257257
"""
258-
data = self.resource.put(unicode_quote(id, ''), content=content)
259-
content['_id'] = data['_id']
260-
content['_rev'] = data['_rev']
258+
result = self.resource.put(id, content=content)
259+
content['_id'] = result['id']
260+
content['_rev'] = result['rev']
261261

262262
def _get_name(self):
263263
if self._name is None:
@@ -275,10 +275,9 @@ def create(self, data):
275275
:return: the ID of the created document
276276
:rtype: `unicode`
277277
"""
278-
data = self.resource.post(content=data)
279-
return data['_id']
278+
return self.resource.post(content=data)['id']
280279

281-
def get(self, id, default=None):
280+
def get(self, id, default=None, **options):
282281
"""Return the document with the specified ID.
283282
284283
:param id: the document ID
@@ -289,9 +288,18 @@ def get(self, id, default=None):
289288
:rtype: `Row`
290289
"""
291290
try:
292-
return self[id]
291+
row = self.resource.get(id, **options)
293292
except ResourceNotFound:
294293
return default
294+
else:
295+
if 'rev' in options:
296+
for info in row['_doc_revs']:
297+
if 'ok' in info:
298+
row = info['ok']
299+
break
300+
else:
301+
return default
302+
return Document(row)
295303

296304
def query(self, code, **options):
297305
"""Execute an ad-hoc query against the database.
@@ -306,17 +314,17 @@ def query(self, code, **options):
306314
... map(doc.name, null);
307315
... }'''
308316
>>> for row in db.query(code):
309-
... print row['key']
317+
... print row.key
310318
John Doe
311319
Mary Jane
312320
313321
>>> for row in db.query(code, descending=True):
314-
... print row['key']
322+
... print row.key
315323
Mary Jane
316324
John Doe
317325
318326
>>> for row in db.query(code, key='John Doe'):
319-
... print row['key']
327+
... print row.key
320328
John Doe
321329
322330
>>> del server['python-tests']
@@ -331,7 +339,7 @@ def query(self, code, **options):
331339
options[name] = json.dumps(value)
332340
data = self.resource.post('_temp_view', content=code, **options)
333341
for row in data['rows']:
334-
yield Row(row)
342+
yield Row(row['id'], row['key'], row['value'])
335343

336344
def view(self, name, **options):
337345
"""Execute a predefined view.
@@ -354,6 +362,25 @@ def view(self, name, **options):
354362
return view(**options)
355363

356364

365+
class Document(dict):
366+
"""Representation of a document in the database.
367+
368+
This is basically just a dictionary with the two additional properties
369+
`id` and `rev`, which contain the document ID and revision, respectively.
370+
"""
371+
372+
def __init__(self, content):
373+
dict.__init__(self, content)
374+
375+
def __repr__(self):
376+
return '<%s %r@%r %r>' % (type(self).__name__, self.id, self.rev,
377+
dict([(k,v) for k,v in self.items()
378+
if k not in ('_id', '_rev')]))
379+
380+
id = property(lambda self: self['_id'])
381+
rev = property(lambda self: self['_rev'])
382+
383+
357384
class View(object):
358385
"""Representation of a permanent view on the server."""
359386

@@ -371,29 +398,27 @@ def __call__(self, **options):
371398
options[name] = json.dumps(value)
372399
data = self.resource.get(**options)
373400
for row in data['rows']:
374-
yield Row(row)
401+
yield Row(row['id'], row['key'], row['value'])
375402

376403
def __iter__(self):
377404
return self()
378405

379406

380-
class Row(dict):
407+
class Row(object):
381408
"""Representation of a row as returned by database views.
382409
383410
This is basically just a dictionary with the two additional properties
384411
`id` and `rev`, which contain the document ID and revision, respectively.
385412
"""
386413

387-
def __init__(self, content):
388-
dict.__init__(self, content)
414+
def __init__(self, id, key, value):
415+
self.id = id
416+
self.key = key
417+
self.value = value
389418

390419
def __repr__(self):
391-
return '<%s %r@%r %r>' % (type(self).__name__, self.id, self.rev,
392-
dict([(k,v) for k,v in self.items()
393-
if k not in ('_id', '_rev')]))
394-
395-
id = property(lambda self: self.get('_id'))
396-
rev = property(lambda self: self.get('_rev'))
420+
return '<%s id=%r, key=%r, value=%r>' % (type(self).__name__, self.id,
421+
self.key, self.value)
397422

398423

399424
# Internals
@@ -458,7 +483,7 @@ def _request(self, method, path=None, content=None, headers=None,
458483
elif status_code == 409:
459484
raise ResourceConflict(error)
460485
else:
461-
raise ServerError(error)
486+
raise ServerError((status_code, error))
462487

463488
return data
464489

couchdb/schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def __iter__(self):
137137
return iter(self._data)
138138

139139
def __len__(self):
140-
return len(self._data)
140+
return len(self._data or ())
141141

142142
def __delitem__(self, name):
143143
del self._data[name]

couchdb/tests/client.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ def test_doc_id_quoting(self):
3232
del self.db['foo/bar']
3333
self.assertEqual(None, self.db.get('foo/bar'))
3434

35+
def test_doc_revs(self):
36+
doc = {'bar': 42}
37+
self.db['foo'] = doc
38+
old_rev = doc['_rev']
39+
doc['bar'] = 43
40+
self.db['foo'] = doc
41+
new_rev = doc['_rev']
42+
43+
new_doc = self.db.get('foo')
44+
self.assertEqual(new_rev, new_doc['_rev'])
45+
new_doc = self.db.get('foo', rev=new_rev)
46+
self.assertEqual(new_rev, new_doc['_rev'])
47+
old_doc = self.db.get('foo', rev=old_rev)
48+
self.assertEqual(old_rev, old_doc['_rev'])
3549

3650

3751
def suite():

couchdb/tests/couch_tests.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_basics(self):
6060
result = list(self.db.query(query))
6161
self.assertEqual(1, len(result))
6262
self.assertEqual('3', result[0].id)
63-
self.assertEqual(16, result[0]['value'])
63+
self.assertEqual(16, result[0].value)
6464

6565
# modify a document, and redo the query
6666
doc = self.db['0']
@@ -114,12 +114,12 @@ def test_lots_of_docs(self):
114114
results = list(self.db.query(query))
115115
self.assertEqual(num, len(results))
116116
for idx, row in enumerate(results):
117-
self.assertEqual(idx, row['key'])
117+
self.assertEqual(idx, row.key)
118118

119119
results = list(self.db.query(query, descending=True))
120120
self.assertEqual(num, len(results))
121121
for idx, row in enumerate(results):
122-
self.assertEqual(num - idx - 1, row['key'])
122+
self.assertEqual(num - idx - 1, row.key)
123123

124124
def test_multiple_rows(self):
125125
self.db['NC'] = {'cities': ["Charlotte", "Raleigh"]}
@@ -135,37 +135,37 @@ def test_multiple_rows(self):
135135
}"""
136136
results = list(self.db.query(query))
137137
self.assertEqual(11, len(results))
138-
self.assertEqual("Boston, MA", results[0]['key']);
139-
self.assertEqual("Cambridge, MA", results[1]['key']);
140-
self.assertEqual("Charlotte, NC", results[2]['key']);
141-
self.assertEqual("Lowell, MA", results[3]['key']);
142-
self.assertEqual("Miami, FL", results[4]['key']);
143-
self.assertEqual("Orlando, FL", results[5]['key']);
144-
self.assertEqual("Raleigh, NC", results[6]['key']);
145-
self.assertEqual("Springfield, FL", results[7]['key']);
146-
self.assertEqual("Springfield, MA", results[8]['key']);
147-
self.assertEqual("Tampa, FL", results[9]['key']);
148-
self.assertEqual("Worcester, MA", results[10]['key']);
138+
self.assertEqual("Boston, MA", results[0].key);
139+
self.assertEqual("Cambridge, MA", results[1].key);
140+
self.assertEqual("Charlotte, NC", results[2].key);
141+
self.assertEqual("Lowell, MA", results[3].key);
142+
self.assertEqual("Miami, FL", results[4].key);
143+
self.assertEqual("Orlando, FL", results[5].key);
144+
self.assertEqual("Raleigh, NC", results[6].key);
145+
self.assertEqual("Springfield, FL", results[7].key);
146+
self.assertEqual("Springfield, MA", results[8].key);
147+
self.assertEqual("Tampa, FL", results[9].key);
148+
self.assertEqual("Worcester, MA", results[10].key);
149149

150150
# Add a city and rerun the query
151151
doc = self.db['NC']
152152
doc['cities'].append("Wilmington")
153153
self.db['NC'] = doc
154154
results = list(self.db.query(query))
155155
self.assertEqual(12, len(results))
156-
self.assertEqual("Wilmington, NC", results[10]['key'])
156+
self.assertEqual("Wilmington, NC", results[10].key)
157157

158158
# Remove a document and redo the query again
159159
del self.db['MA']
160160
results = list(self.db.query(query))
161161
self.assertEqual(7, len(results))
162-
self.assertEqual("Charlotte, NC", results[0]['key']);
163-
self.assertEqual("Miami, FL", results[1]['key']);
164-
self.assertEqual("Orlando, FL", results[2]['key']);
165-
self.assertEqual("Raleigh, NC", results[3]['key']);
166-
self.assertEqual("Springfield, FL", results[4]['key']);
167-
self.assertEqual("Tampa, FL", results[5]['key']);
168-
self.assertEqual("Wilmington, NC", results[6]['key'])
162+
self.assertEqual("Charlotte, NC", results[0].key);
163+
self.assertEqual("Miami, FL", results[1].key);
164+
self.assertEqual("Orlando, FL", results[2].key);
165+
self.assertEqual("Raleigh, NC", results[3].key);
166+
self.assertEqual("Springfield, FL", results[4].key);
167+
self.assertEqual("Tampa, FL", results[5].key);
168+
self.assertEqual("Wilmington, NC", results[6].key)
169169

170170
def test_large_docs(self):
171171
size = 100
@@ -199,20 +199,20 @@ def test_utf8_encoding(self):
199199
map(doc.text, null);
200200
}"""
201201
for idx, row in enumerate(self.db.query(query)):
202-
self.assertEqual(texts[idx], row['key'])
202+
self.assertEqual(texts[idx], row.key)
203203

204204
def test_design_docs(self):
205205
for i in range(50):
206206
self.db[str(i)] = {'integer': i, 'string': str(i)}
207-
self.db['_design_foo'] = {'views': {
207+
self.db['_design/foo'] = {'views': {
208208
'all_docs': 'function(doc) { map(doc.integer, null) }',
209209
'no_docs': 'function(doc) {}',
210210
'single_doc': 'function(doc) { if (doc._id == "1") map(null, 1) }'
211211
}}
212-
for idx, row in enumerate(self.db.view('_design_foo:all_docs')):
213-
self.assertEqual(idx, row['key'])
214-
self.assertEqual(0, len(list(self.db.view('_design_foo:no_docs'))))
215-
self.assertEqual(1, len(list(self.db.view('_design_foo:single_doc'))))
212+
for idx, row in enumerate(self.db.view('_design/foo/all_docs')):
213+
self.assertEqual(idx, row.key)
214+
self.assertEqual(0, len(list(self.db.view('_design/foo/no_docs'))))
215+
self.assertEqual(1, len(list(self.db.view('_design/foo/single_doc'))))
216216

217217
def test_collation(self):
218218
values = [
@@ -231,21 +231,21 @@ def test_collation(self):
231231
map(doc.foo, null);
232232
}"""
233233
rows = self.db.query(query)
234-
self.assertEqual(None, dict(rows.next())['value'])
234+
self.assertEqual(None, rows.next().value)
235235
for idx, row in enumerate(rows):
236-
self.assertEqual(values[idx + 1], row['key'])
236+
self.assertEqual(values[idx + 1], row.key)
237237

238238
rows = self.db.query(query, descending=True)
239239
for idx, row in enumerate(rows):
240240
if idx < len(values):
241-
self.assertEqual(values[len(values) - 1- idx], row['key'])
241+
self.assertEqual(values[len(values) - 1- idx], row.key)
242242
else:
243-
self.assertEqual(None, dict(row)['value'])
243+
self.assertEqual(None, row.value)
244244

245245
for value in values:
246246
rows = list(self.db.query(query, key=value))
247247
self.assertEqual(1, len(rows))
248-
self.assertEqual(value, rows[0]['key'])
248+
self.assertEqual(value, rows[0].key)
249249

250250

251251
def suite():

0 commit comments

Comments
 (0)