8
8
9
9
"""Python client API for CouchDB.
10
10
11
- >>> server = Server('http://localhost:5984/' )
11
+ >>> server = Server()
12
12
>>> db = server.create('python-tests')
13
13
>>> doc_id, doc_rev = db.save({'type': 'Person', 'name': 'John Doe'})
14
14
>>> doc = db[doc_id]
37
37
__docformat__ = 'restructuredtext en'
38
38
39
39
40
- DEFAULT_BASE_URL = ' http://localhost:5984/'
40
+ DEFAULT_BASE_URL = os . environ . get ( 'COUCHDB_URL' , ' http://localhost:5984/')
41
41
42
42
43
43
class Server (object ):
44
44
"""Representation of a CouchDB server.
45
45
46
- >>> server = Server('http://localhost:5984/' )
46
+ >>> server = Server()
47
47
48
48
This class behaves like a dictionary of databases. For example, to get a
49
49
list of database names on the server, you can simply iterate over the
@@ -169,6 +169,19 @@ def tasks(self):
169
169
status , headers , data = self .resource .get_json ('_active_tasks' )
170
170
return data
171
171
172
+ def uuids (self , count = None ):
173
+ """Retrieve a batch of uuids
174
+
175
+ :param count: a number of uuids to fetch
176
+ (None -- get as many as the server sends)
177
+ :return: a list of uuids
178
+ """
179
+ if count is None :
180
+ _ , _ , data = self .resource .get_json ('_uuids' )
181
+ else :
182
+ _ , _ , data = self .resource .get_json ('_uuids' , count = count )
183
+ return data ['uuids' ]
184
+
172
185
def create (self , name ):
173
186
"""Create a new database with the given name.
174
187
@@ -205,7 +218,7 @@ def replicate(self, source, target, **options):
205
218
class Database (object ):
206
219
"""Representation of a database on a CouchDB server.
207
220
208
- >>> server = Server('http://localhost:5984/' )
221
+ >>> server = Server()
209
222
>>> db = server.create('python-tests')
210
223
211
224
New documents can be added to the database using the `save()` method:
@@ -387,7 +400,11 @@ def save(self, doc, **options):
387
400
:return: (id, rev) tuple of the save document
388
401
:rtype: `tuple`
389
402
"""
390
- _ , _ , data = self .resource .post_json (body = doc , ** options )
403
+ if '_id' in doc :
404
+ func = self .resource (doc ['_id' ]).put_json
405
+ else :
406
+ func = self .resource .post_json
407
+ _ , _ , data = func (body = doc , ** options )
391
408
id , rev = data ['id' ], data .get ('rev' )
392
409
doc ['_id' ] = id
393
410
if rev is not None : # Not present for batch='ok'
@@ -400,7 +417,9 @@ def commit(self):
400
417
immediate commits, this method can be used to ensure that any
401
418
non-committed changes are committed to physical storage.
402
419
"""
403
- _ , _ , data = self .resource .post_json ('_ensure_full_commit' )
420
+ _ , _ , data = self .resource .post_json (
421
+ '_ensure_full_commit' ,
422
+ headers = {'Content-Type' : 'application/json' })
404
423
return data
405
424
406
425
def compact (self , ddoc = None ):
@@ -415,9 +434,11 @@ def compact(self, ddoc=None):
415
434
:rtype: `bool`
416
435
"""
417
436
if ddoc :
418
- _ , _ , data = self .resource ('_compact' ). post_json ( ddoc )
437
+ resource = self .resource ('_compact' , ddoc )
419
438
else :
420
- _ , _ , data = self .resource .post_json ('_compact' )
439
+ resource = self .resource ('_compact' )
440
+ _ , _ , data = resource .post_json (
441
+ headers = {'Content-Type' : 'application/json' })
421
442
return data ['ok' ]
422
443
423
444
def copy (self , src , dest ):
@@ -467,7 +488,7 @@ def delete(self, doc):
467
488
the document has been updated since it was retrieved, this method will
468
489
raise a `ResourceConflict` exception.
469
490
470
- >>> server = Server('http://localhost:5984/' )
491
+ >>> server = Server()
471
492
>>> db = server.create('python-tests')
472
493
473
494
>>> doc = dict(type='Person', name='John Doe')
@@ -568,8 +589,8 @@ def get_attachment(self, id_or_doc, filename, default=None):
568
589
:param filename: the name of the attachment file
569
590
:param default: default value to return when the document or attachment
570
591
is not found
571
- :return: the content of the attachment as a string , or the value of the
572
- `default` argument if the attachment is not found
592
+ :return: a file-like object with read and close methods , or the value
593
+ of the `default` argument if the attachment is not found
573
594
:since: 0.4.1
574
595
"""
575
596
if isinstance (id_or_doc , basestring ):
@@ -621,7 +642,7 @@ def query(self, map_fun, reduce_fun=None, language='javascript',
621
642
wrapper = None , ** options ):
622
643
"""Execute an ad-hoc query (a "temp view") against the database.
623
644
624
- >>> server = Server('http://localhost:5984/' )
645
+ >>> server = Server()
625
646
>>> db = server.create('python-tests')
626
647
>>> db['johndoe'] = dict(type='Person', name='John Doe')
627
648
>>> db['maryjane'] = dict(type='Person', name='Mary Jane')
@@ -664,7 +685,7 @@ def update(self, documents, **options):
664
685
"""Perform a bulk update or insertion of the given documents using a
665
686
single HTTP request.
666
687
667
- >>> server = Server('http://localhost:5984/' )
688
+ >>> server = Server()
668
689
>>> db = server.create('python-tests')
669
690
>>> for doc in db.update([
670
691
... Document(type='Person', name='John Doe'),
@@ -732,7 +753,7 @@ def update(self, documents, **options):
732
753
def view (self , name , wrapper = None , ** options ):
733
754
"""Execute a predefined view.
734
755
735
- >>> server = Server('http://localhost:5984/' )
756
+ >>> server = Server()
736
757
>>> db = server.create('python-tests')
737
758
>>> db['gotham'] = dict(type='City', name='Gotham City')
738
759
@@ -901,7 +922,7 @@ class ViewResults(object):
901
922
This class allows the specification of ``key``, ``startkey``, and
902
923
``endkey`` options using Python slice notation.
903
924
904
- >>> server = Server('http://localhost:5984/' )
925
+ >>> server = Server()
905
926
>>> db = server.create('python-tests')
906
927
>>> db['johndoe'] = dict(type='Person', name='John Doe')
907
928
>>> db['maryjane'] = dict(type='Person', name='Mary Jane')
@@ -960,19 +981,15 @@ def __getitem__(self, key):
960
981
return ViewResults (self .view , options )
961
982
962
983
def __iter__ (self ):
963
- wrapper = self .view .wrapper
964
- for row in self .rows :
965
- if wrapper is not None :
966
- yield wrapper (row )
967
- else :
968
- yield row
984
+ return iter (self .rows )
969
985
970
986
def __len__ (self ):
971
987
return len (self .rows )
972
988
973
989
def _fetch (self ):
974
990
data = self .view ._exec (self .options )
975
- self ._rows = [Row (row ) for row in data ['rows' ]]
991
+ wrapper = self .view .wrapper or Row
992
+ self ._rows = [wrapper (row ) for row in data ['rows' ]]
976
993
self ._total_rows = data .get ('total_rows' )
977
994
self ._offset = data .get ('offset' , 0 )
978
995
@@ -1049,8 +1066,11 @@ def doc(self):
1049
1066
return Document (doc )
1050
1067
1051
1068
1069
+ SPECIAL_DB_NAMES = set (['_users' ])
1052
1070
VALID_DB_NAME = re .compile (r'^[a-z][a-z0-9_$()+-/]*$' )
1053
1071
def validate_dbname (name ):
1072
+ if name in SPECIAL_DB_NAMES :
1073
+ return name
1054
1074
if not VALID_DB_NAME .match (name ):
1055
1075
raise ValueError ('Invalid database name' )
1056
1076
return name
0 commit comments