1
2
3
4
5
6 """
7 Client implementation for CouchDB access. It allows you to manage a CouchDB
8 server, databases, documents and views. All objects mostly reflect python
9 objects for convenience. Server and Database objects for example, can be
10 used as easy as a dict.
11
12 Example:
13
14 >>> from couchdbkit import Server
15 >>> server = Server()
16 >>> db = server.create_db('couchdbkit_test')
17 >>> doc = { 'string': 'test', 'number': 4 }
18 >>> db.save_doc(doc)
19 >>> docid = doc['_id']
20 >>> doc2 = db.get(docid)
21 >>> doc['string']
22 u'test'
23 >>> del db[docid]
24 >>> docid in db
25 False
26 >>> del server['simplecouchdb_test']
27
28 """
29
30 UNKOWN_INFO = {}
31
32
33 from collections import deque
34 from itertools import groupby
35 from mimetypes import guess_type
36 import time
37
38 from restkit.util import url_quote
39
40 from .exceptions import InvalidAttachment, NoResultFound, \
41 ResourceNotFound, ResourceConflict, BulkSaveError, MultipleResultsFound
42 from . import resource
43 from .utils import validate_dbname
44
45 from .schema.util import maybe_schema_wrapper
46
47
48 DEFAULT_UUID_BATCH_COUNT = 1000
51 if hasattr(doc, "to_json"):
52
53 try:
54 doc.validate()
55 except AttributeError:
56 pass
57
58 return doc.to_json(), True
59 elif isinstance(doc, dict):
60 return doc.copy(), False
61
62 return doc, False
63
65 """ Server object that allows you to access and manage a couchdb node.
66 A Server object can be used like any `dict` object.
67 """
68
69 resource_class = resource.CouchdbResource
70
71 - def __init__(self, uri='http://127.0.0.1:5984',
72 uuid_batch_count=DEFAULT_UUID_BATCH_COUNT,
73 resource_class=None, resource_instance=None,
74 **client_opts):
75
76 """ constructor for Server object
77
78 @param uri: uri of CouchDb host
79 @param uuid_batch_count: max of uuids to get in one time
80 @param resource_instance: `restkit.resource.CouchdbDBResource` instance.
81 It alows you to set a resource class with custom parameters.
82 """
83
84 if not uri or uri is None:
85 raise ValueError("Server uri is missing")
86
87 if uri.endswith("/"):
88 uri = uri[:-1]
89
90 self.uri = uri
91 self.uuid_batch_count = uuid_batch_count
92 self._uuid_batch_count = uuid_batch_count
93
94 if resource_class is not None:
95 self.resource_class = resource_class
96
97 if resource_instance and isinstance(resource_instance,
98 resource.CouchdbResource):
99 resource_instance.initial['uri'] = uri
100 self.res = resource_instance.clone()
101 if client_opts:
102 self.res.client_opts.update(client_opts)
103 else:
104 self.res = self.resource_class(uri, **client_opts)
105 self._uuids = deque()
106
108 """ info of server
109
110 @return: dict
111
112 """
113 try:
114 resp = self.res.get()
115 except Exception:
116 return UNKOWN_INFO
117
118 return resp.json_body
119
121 """ get list of databases in CouchDb host
122
123 """
124 return self.res.get('/_all_dbs').json_body
125
126 - def get_db(self, dbname, **params):
127 """
128 Try to return a Database object for dbname.
129
130 """
131 return Database(self._db_uri(dbname), server=self, **params)
132
134 """ Create a database on CouchDb host
135
136 @param dname: str, name of db
137 @param param: custom parameters to pass to create a db. For
138 example if you use couchdbkit to access to cloudant or bigcouch:
139
140 Ex: q=12 or n=4
141
142 See https://github.com/cloudant/bigcouch for more info.
143
144 @return: Database instance if it's ok or dict message
145 """
146 return self.get_db(dbname, create=True, **params)
147
148 get_or_create_db = create_db
149 get_or_create_db.__doc__ = """
150 Try to return a Database object for dbname. If
151 database doest't exist, it will be created.
152
153 """
154
156 """
157 Delete database
158 """
159 del self[dbname]
160
161
162 - def replicate(self, source, target, **params):
163 """
164 simple handler for replication
165
166 @param source: str, URI or dbname of the source
167 @param target: str, URI or dbname of the target
168 @param params: replication options
169
170 More info about replication here :
171 http://wiki.apache.org/couchdb/Replication
172
173 """
174 payload = {
175 "source": source,
176 "target": target,
177 }
178 payload.update(params)
179 resp = self.res.post('/_replicate', payload=payload)
180 return resp.json_body
181
183 """ return active tasks """
184 resp = self.res.get('/_active_tasks')
185 return resp.json_body
186
187 - def uuids(self, count=1):
189
191 """
192 return an available uuid from couchdbkit
193 """
194 if count is not None:
195 self._uuid_batch_count = count
196 else:
197 self._uuid_batch_count = self.uuid_batch_count
198
199 try:
200 return self._uuids.pop()
201 except IndexError:
202 self._uuids.extend(self.uuids(count=self._uuid_batch_count)["uuids"])
203 return self._uuids.pop()
204
207
209 ret = self.res.delete('/%s/' % url_quote(dbname,
210 safe=":")).json_body
211 return ret
212
214 try:
215 self.res.head('/%s/' % url_quote(dbname, safe=":"))
216 except:
217 return False
218 return True
219
223
226
228 return (len(self) > 0)
229
231 if dbname.startswith("/"):
232 dbname = dbname[1:]
233
234 dbname = url_quote(dbname, safe=":")
235 return "/".join([self.uri, dbname])
236
238 """ Object that abstract access to a CouchDB database
239 A Database object can act as a Dict object.
240 """
241
242 - def __init__(self, uri, create=False, server=None, **params):
243 """Constructor for Database
244
245 @param uri: str, Database uri
246 @param create: boolean, False by default,
247 if True try to create the database.
248 @param server: Server instance
249
250 """
251 self.uri = uri
252 self.server_uri, self.dbname = uri.rsplit("/", 1)
253
254 if server is not None:
255 if not hasattr(server, 'next_uuid'):
256 raise TypeError('%s is not a couchdbkit.Server instance' %
257 server.__class__.__name__)
258 self.server = server
259 else:
260 self.server = server = Server(self.server_uri, **params)
261
262 validate_dbname(self.dbname)
263 if create:
264 try:
265 self.server.res.head('/%s/' % self.dbname)
266 except ResourceNotFound:
267 self.server.res.put('/%s/' % self.dbname, **params).json_body
268
269 self.res = server.res(self.dbname)
270
272 return "<%s %s>" % (self.__class__.__name__, self.dbname)
273
275 """
276 Get database information
277
278 @return: dict
279 """
280 return self.res.get().json_body
281
282
284 """ compact database
285 @param dname: string, name of design doc. Usefull to
286 compact a view.
287 """
288 path = "/_compact"
289 if dname is not None:
290 path = "%s/%s" % (path, resource.escape_docid(dname))
291 res = self.res.post(path, headers={"Content-Type":
292 "application/json"})
293 return res.json_body
294
296 res = self.res.post('/_view_cleanup', headers={"Content-Type":
297 "application/json"})
298 return res.json_body
299
301 """ Remove all docs from a database
302 except design docs."""
303
304 all_ddocs = self.all_docs(startkey="_design",
305 endkey="_design/"+u"\u9999",
306 include_docs=True)
307 ddocs = []
308 for ddoc in all_ddocs:
309 ddoc['doc'].pop('_rev')
310 ddocs.append(ddoc['doc'])
311
312
313 self.server.delete_db(self.dbname)
314
315
316 time.sleep(0.2)
317
318
319 self.server.create_db(self.dbname)
320 self.bulk_save(ddocs)
321
323 """Test if document exists in a database
324
325 @param docid: str, document id
326 @return: boolean, True if document exist
327 """
328
329 try:
330 self.res.head(resource.escape_docid(docid))
331 except ResourceNotFound:
332 return False
333 return True
334
336 """Get document from database
337
338 Args:
339 @param docid: str, document id to retrieve
340 @param wrapper: callable. function that takes dict as a param.
341 Used to wrap an object.
342 @param **params: See doc api for parameters to use:
343 http://wiki.apache.org/couchdb/HTTP_Document_API
344
345 @return: dict, representation of CouchDB document as
346 a dict.
347 """
348 wrapper = None
349 if "wrapper" in params:
350 wrapper = params.pop("wrapper")
351 elif "schema" in params:
352 schema = params.pop("schema")
353 if not hasattr(schema, "wrap"):
354 raise TypeError("invalid schema")
355 wrapper = schema.wrap
356
357 docid = resource.escape_docid(docid)
358 doc = self.res.get(docid, **params).json_body
359 if wrapper is not None:
360 if not callable(wrapper):
361 raise TypeError("wrapper isn't a callable")
362
363 return wrapper(doc)
364
365 return doc
366 get = open_doc
367
368 - def list(self, list_name, view_name, **params):
369 """ Execute a list function on the server and return the response.
370 If the response is json it will be deserialized, otherwise the string
371 will be returned.
372
373 Args:
374 @param list_name: should be 'designname/listname'
375 @param view_name: name of the view to run through the list document
376 @param params: params of the list
377 """
378 list_name = list_name.split('/')
379 dname = list_name.pop(0)
380 vname = '/'.join(list_name)
381 list_path = '_design/%s/_list/%s/%s' % (dname, vname, view_name)
382
383 return self.res.get(list_path, **params).json_body
384
385 - def show(self, show_name, doc_id, **params):
386 """ Execute a show function on the server and return the response.
387 If the response is json it will be deserialized, otherwise the string
388 will be returned.
389
390 Args:
391 @param show_name: should be 'designname/showname'
392 @param doc_id: id of the document to pass into the show document
393 @param params: params of the show
394 """
395 show_name = show_name.split('/')
396 dname = show_name.pop(0)
397 vname = '/'.join(show_name)
398 show_path = '_design/%s/_show/%s/%s' % (dname, vname, doc_id)
399
400 return self.res.get(show_path, **params).json_body
401
402 - def all_docs(self, by_seq=False, **params):
403 """Get all documents from a database
404
405 This method has the same behavior as a view.
406
407 `all_docs( **params )` is the same as `view('_all_docs', **params)`
408 and `all_docs( by_seq=True, **params)` is the same as
409 `view('_all_docs_by_seq', **params)`
410
411 You can use all(), one(), first() just like views
412
413 Args:
414 @param by_seq: bool, if True the "_all_docs_by_seq" is passed to
415 couchdb. It will return an updated list of all documents.
416
417 @return: list, results of the view
418 """
419 if by_seq:
420 try:
421 return self.view('_all_docs_by_seq', **params)
422 except ResourceNotFound:
423
424 raise AttributeError("_all_docs_by_seq isn't supported on Couchdb %s" % self.server.info()[1])
425
426 return self.view('_all_docs', **params)
427
429 """ Get last revision from docid (the '_rev' member)
430 @param docid: str, undecoded document id.
431
432 @return rev: str, the last revision of document.
433 """
434 response = self.res.head(resource.escape_docid(docid))
435 return response['etag'].strip('"')
436
437 - def save_doc(self, doc, encode_attachments=True, force_update=False,
438 **params):
439 """ Save a document. It will use the `_id` member of the document
440 or request a new uuid from CouchDB. IDs are attached to
441 documents on the client side because POST has the curious property of
442 being automatically retried by proxies in the event of network
443 segmentation and lost responses. (Idee from `Couchrest <http://github.com/jchris/couchrest/>`)
444
445 @param doc: dict. doc is updated
446 with doc '_id' and '_rev' properties returned
447 by CouchDB server when you save.
448 @param force_update: boolean, if there is conlict, try to update
449 with latest revision
450 @param params, list of optionnal params, like batch="ok"
451
452 @return res: result of save. doc is updated in the mean time
453 """
454 if doc is None:
455 doc1 = {}
456 else:
457 doc1, schema = _maybe_serialize(doc)
458
459 if '_attachments' in doc1 and encode_attachments:
460 doc1['_attachments'] = resource.encode_attachments(doc['_attachments'])
461
462 if '_id' in doc:
463 docid = doc1['_id']
464 docid1 = resource.escape_docid(doc1['_id'])
465 try:
466 res = self.res.put(docid1, payload=doc1,
467 **params).json_body
468 except ResourceConflict:
469 if force_update:
470 doc1['_rev'] = self.get_rev(docid)
471 res =self.res.put(docid1, payload=doc1,
472 **params).json_body
473 else:
474 raise
475 else:
476 try:
477 doc['_id'] = self.server.next_uuid()
478 res = self.res.put(doc['_id'], payload=doc1,
479 **params).json_body
480 except:
481 res = self.res.post(payload=doc1, **params).json_body
482
483 if 'batch' in params and 'id' in res:
484 doc1.update({ '_id': res['id']})
485 else:
486 doc1.update({'_id': res['id'], '_rev': res['rev']})
487
488
489 if schema:
490 doc._doc = doc1
491 else:
492 doc.update(doc1)
493 return res
494
495 - def save_docs(self, docs, use_uuids=True, all_or_nothing=False,
496 **params):
497 """ bulk save. Modify Multiple Documents With a Single Request
498
499 @param docs: list of docs
500 @param use_uuids: add _id in doc who don't have it already set.
501 @param all_or_nothing: In the case of a power failure, when the database
502 restarts either all the changes will have been saved or none of them.
503 However, it does not do conflict checking, so the documents will
504
505 .. seealso:: `HTTP Bulk Document API <http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API>`
506
507 """
508
509 docs1 = []
510 docs_schema = []
511 for doc in docs:
512 doc1, schema = _maybe_serialize(doc)
513 docs1.append(doc1)
514 docs_schema.append(schema)
515
516 def is_id(doc):
517 return '_id' in doc
518
519 if use_uuids:
520 noids = []
521 for k, g in groupby(docs1, is_id):
522 if not k:
523 noids = list(g)
524
525 uuid_count = max(len(noids), self.server.uuid_batch_count)
526 for doc in noids:
527 nextid = self.server.next_uuid(count=uuid_count)
528 if nextid:
529 doc['_id'] = nextid
530
531 payload = { "docs": docs1 }
532 if all_or_nothing:
533 payload["all_or_nothing"] = True
534
535
536 results = self.res.post('/_bulk_docs',
537 payload=payload, **params).json_body
538
539 errors = []
540 for i, res in enumerate(results):
541 if 'error' in res:
542 errors.append(res)
543 else:
544 if docs_schema[i]:
545 docs[i]._doc.update({
546 '_id': res['id'],
547 '_rev': res['rev']
548 })
549 else:
550 docs[i].update({
551 '_id': res['id'],
552 '_rev': res['rev']
553 })
554 if errors:
555 raise BulkSaveError(errors, results)
556 return results
557 bulk_save = save_docs
558
559 - def delete_docs(self, docs, all_or_nothing=False,
560 empty_on_delete=False, **params):
561 """ bulk delete.
562 It adds '_deleted' member to doc then uses bulk_save to save them.
563
564 @param empty_on_delete: default is False if you want to make
565 sure the doc is emptied and will not be stored as is in Apache
566 CouchDB.
567 @param all_or_nothing: In the case of a power failure, when the database
568 restarts either all the changes will have been saved or none of them.
569 However, it does not do conflict checking, so the documents will
570
571 .. seealso:: `HTTP Bulk Document API <http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API>`
572
573
574 """
575
576 if empty_on_delete:
577 for doc in docs:
578 new_doc = {"_id": doc["_id"],
579 "_rev": doc["_rev"],
580 "_deleted": True}
581 doc.clear()
582 doc.update(new_doc)
583 else:
584 for doc in docs:
585 doc['_deleted'] = True
586
587 return self.bulk_save(docs, use_uuids=False,
588 all_or_nothing=all_or_nothing, **params)
589
590 bulk_delete = delete_docs
591
593 """ delete a document or a list of documents
594 @param doc: str or dict, document id or full doc.
595 @return: dict like:
596
597 .. code-block:: python
598
599 {"ok":true,"rev":"2839830636"}
600 """
601 result = { 'ok': False }
602
603 doc1, schema = _maybe_serialize(doc)
604 if isinstance(doc1, dict):
605 if not '_id' or not '_rev' in doc1:
606 raise KeyError('_id and _rev are required to delete a doc')
607
608 docid = resource.escape_docid(doc1['_id'])
609 result = self.res.delete(docid, rev=doc1['_rev'], **params).json_body
610 elif isinstance(doc1, basestring):
611 rev = self.get_rev(doc1)
612 docid = resource.escape_docid(doc1)
613 result = self.res.delete(docid, rev=rev, **params).json_body
614
615 if schema:
616 doc._doc.update({
617 "_rev": result['rev'],
618 "_deleted": True
619 })
620 elif isinstance(doc, dict):
621 doc.update({
622 "_rev": result['rev'],
623 "_deleted": True
624 })
625 return result
626
627 - def copy_doc(self, doc, dest=None, headers=None):
628 """ copy an existing document to a new id. If dest is None, a new uuid will be requested
629 @param doc: dict or string, document or document id
630 @param dest: basestring or dict. if _rev is specified in dict it will override the doc
631 """
632
633 if not headers:
634 headers = {}
635
636 doc1, schema = _maybe_serialize(doc)
637 if isinstance(doc1, basestring):
638 docid = doc1
639 else:
640 if not '_id' in doc1:
641 raise KeyError('_id is required to copy a doc')
642 docid = doc1['_id']
643
644 if dest is None:
645 destination = self.server.next_uuid(count=1)
646 elif isinstance(dest, basestring):
647 if dest in self:
648 dest = self.get(dest)
649 destination = "%s?rev=%s" % (dest['_id'], dest['_rev'])
650 else:
651 destination = dest
652 elif isinstance(dest, dict):
653 if '_id' in dest and '_rev' in dest and dest['_id'] in self:
654 destination = "%s?rev=%s" % (dest['_id'], dest['_rev'])
655 else:
656 raise KeyError("dest doesn't exist or this not a document ('_id' or '_rev' missig).")
657
658 if destination:
659 headers.update({"Destination": str(destination)})
660 result = self.res.copy('/%s' % docid, headers=headers).json_body
661 return result
662
663 return { 'ok': False }
664
666 if 'keys' in params:
667 keys = params.pop('keys')
668 return self.res.post(view_path, payload={ 'keys': keys }, **params)
669 else:
670 return self.res.get(view_path, **params)
671
673 return db.res.post('_temp_view', payload=design,
674 headers={"Content-Type": "application/json"}, **params)
675
676 - def view(self, view_name, schema=None, wrapper=None, **params):
677 """ get view results from database. viewname is generally
678 a string like `designname/viewname". It return an ViewResults
679 object on which you could iterate, list, ... . You could wrap
680 results in wrapper function, a wrapper function take a row
681 as argument. Wrapping could be also done by passing an Object
682 in obj arguments. This Object should have a `wrap` method
683 that work like a simple wrapper function.
684
685 @param view_name, string could be '_all_docs', '_all_docs_by_seq',
686 'designname/viewname' if view_name start with a "/" it won't be parsed
687 and beginning slash will be removed. Usefull with c-l for example.
688 @param schema, Object with a wrapper function
689 @param wrapper: function used to wrap results
690 @param params: params of the view
691
692 """
693
694 if view_name.startswith('/'):
695 view_name = view_name[1:]
696 if view_name == '_all_docs':
697 view_path = view_name
698 elif view_name == '_all_docs_by_seq':
699 view_path = view_name
700 else:
701 view_name = view_name.split('/')
702 dname = view_name.pop(0)
703 vname = '/'.join(view_name)
704 view_path = '_design/%s/_view/%s' % (dname, vname)
705
706 return ViewResults(self.raw_view, view_path, wrapper, schema, params)
707
708 - def temp_view(self, design, schema=None, wrapper=None, **params):
709 """ get adhoc view results. Like view it reeturn a ViewResult object."""
710 return ViewResults(self.raw_temp_view, design, wrapper, schema, params)
711
712 - def search( self, view_name, handler='_fti/_design', wrapper=None, schema=None, **params):
713 """ Search. Return results from search. Use couchdb-lucene
714 with its default settings by default."""
715 return ViewResults(self, self.raw_view,
716 "/%s/%s" % (handler, view_name),
717 wrapper=wrapper, schema=schema, params=params)
718
719 - def documents(self, schema=None, wrapper=None, **params):
720 """ return a ViewResults objects containing all documents.
721 This is a shorthand to view function.
722 """
723 return ViewResults(self.raw_view, '_all_docs',
724 wrapper=wrapper, schema=schema, params=params)
725 iterdocuments = documents
726
727
728
729 - def put_attachment(self, doc, content, name=None, content_type=None,
730 content_length=None, headers=None):
731 """ Add attachement to a document. All attachments are streamed.
732
733 @param doc: dict, document object
734 @param content: string or :obj:`File` object.
735 @param name: name or attachment (file name).
736 @param content_type: string, mimetype of attachment.
737 If you don't set it, it will be autodetected.
738 @param content_lenght: int, size of attachment.
739
740 @return: bool, True if everything was ok.
741
742
743 Example:
744
745 >>> from simplecouchdb import server
746 >>> server = server()
747 >>> db = server.create_db('couchdbkit_test')
748 >>> doc = { 'string': 'test', 'number': 4 }
749 >>> db.save(doc)
750 >>> text_attachment = u'un texte attaché'
751 >>> db.put_attachment(doc, text_attachment, "test", "text/plain")
752 True
753 >>> file = db.fetch_attachment(doc, 'test')
754 >>> result = db.delete_attachment(doc, 'test')
755 >>> result['ok']
756 True
757 >>> db.fetch_attachment(doc, 'test')
758 >>> del server['couchdbkit_test']
759 {u'ok': True}
760 """
761
762 if not headers:
763 headers = {}
764
765 if not content:
766 content = ""
767 content_length = 0
768 if name is None:
769 if hasattr(content, "name"):
770 name = content.name
771 else:
772 raise InvalidAttachment('You should provide a valid attachment name')
773 name = url_quote(name, safe="")
774 if content_type is None:
775 content_type = ';'.join(filter(None, guess_type(name)))
776
777 if content_type:
778 headers['Content-Type'] = content_type
779
780
781 if content_length and content_length is not None:
782 headers['Content-Length'] = content_length
783
784 doc1, schema = _maybe_serialize(doc)
785
786 docid = resource.escape_docid(doc1['_id'])
787 res = self.res(docid).put(name, payload=content,
788 headers=headers, rev=doc1['_rev']).json_body
789
790 if res['ok']:
791 new_doc = self.get(doc1['_id'], rev=res['rev'])
792 doc.update(new_doc)
793 return res['ok']
794
796 """ delete attachement to the document
797
798 @param doc: dict, document object in python
799 @param name: name of attachement
800
801 @return: dict, with member ok set to True if delete was ok.
802 """
803 doc1, schema = _maybe_serialize(doc)
804
805 docid = resource.escape_docid(doc1['_id'])
806 name = url_quote(name, safe="")
807
808 res = self.res(docid).delete(name, rev=doc1['_rev'],
809 headers=headers).json_body
810 if res['ok']:
811 new_doc = self.get(doc1['_id'], rev=res['rev'])
812 doc.update(new_doc)
813 return res['ok']
814
815
818 """ get attachment in a document
819
820 @param id_or_doc: str or dict, doc id or document dict
821 @param name: name of attachment default: default result
822 @param stream: boolean, if True return a file object
823 @return: `restkit.httpc.Response` object
824 """
825
826 if isinstance(id_or_doc, basestring):
827 docid = id_or_doc
828 else:
829 doc, schema = _maybe_serialize(id_or_doc)
830 docid = doc['_id']
831
832 docid = resource.escape_docid(docid)
833 name = url_quote(name, safe="")
834
835 resp = self.res(docid).get(name, headers=headers)
836 if stream:
837 return resp.body_stream()
838 return resp.body_string(charset="utf-8")
839
840
842 """ commit all docs in memory """
843 return self.res.post('_ensure_full_commit', headers={
844 "Content-Type": "application/json"
845 }).json_body
846
848 return self.info()['doc_count']
849
852
854 return self.get(docid)
855
859
860
863
866
868 return (len(self) > 0)
869
871 """
872 Object to retrieve view results.
873 """
874
875 - def __init__(self, fetch, arg, wrapper, schema, params):
876 """
877 Constructor of ViewResults object
878
879 @param view: Object inherited from :mod:`couchdbkit.client.view.ViewInterface
880 @param params: params to apply when fetching view.
881
882 """
883 assert not (wrapper and schema)
884 wrap_doc = params.get('wrap_doc', schema is not None)
885 if schema:
886 schema = maybe_schema_wrapper(None, schema, params)
887 def row_wrapper(row):
888 data = row.get('value')
889 docid = row.get('id')
890 doc = row.get('doc')
891 if doc is not None and wrap_doc:
892 return schema(doc)
893 elif not data or data is None:
894 return row
895 elif not isinstance(data, dict) or not docid:
896 return row
897 else:
898 data['_id'] = docid
899 if 'rev' in data:
900 data['_rev'] = data.pop('rev')
901 return schema(data)
902 else:
903 def row_wrapper(row):
904 return row
905
906 self._fetch = fetch
907 self._arg = arg
908 self.wrapper = wrapper or row_wrapper
909 self.params = params or {}
910 self._result_cache = None
911 self._total_rows = None
912 self._offset = 0
913 self._dynamic_keys = []
914
916 self._fetch_if_needed()
917 rows = self._result_cache.get('rows', [])
918 wrapper = self.wrapper
919 for row in rows:
920 yield wrapper(row)
921
923 """
924 Return the first result of this query or None if the result doesn’t contain any row.
925
926 This results in an execution of the underlying query.
927 """
928
929 try:
930 return list(self)[0]
931 except IndexError:
932 return None
933
934 - def one(self, except_all=False):
935 """
936 Return exactly one result or raise an exception.
937
938
939 Raises `couchdbkit.exceptions.MultipleResultsFound` if multiple rows are returned.
940 If except_all is True, raises `couchdbkit.exceptions.NoResultFound`
941 if the query selects no rows.
942
943 This results in an execution of the underlying query.
944 """
945
946 length = len(self)
947 if length > 1:
948 raise MultipleResultsFound("%s results found." % length)
949
950 result = self.first()
951 if result is None and except_all:
952 raise NoResultFound
953 return result
954
956 """ return list of all results """
957 return list(self.iterator())
958
960 """ return number of returned results """
961 self._fetch_if_needed()
962 return len(self._result_cache.get('rows', []))
963
965 """ fetch results and cache them """
966
967 for key in self._dynamic_keys:
968 try:
969 delattr(self, key)
970 except:
971 pass
972 self._dynamic_keys = []
973
974 self._result_cache = self.fetch_raw().json_body
975 self._total_rows = self._result_cache.get('total_rows')
976 self._offset = self._result_cache.get('offset', 0)
977
978
979
980 for key in self._result_cache.keys():
981 if key not in ["total_rows", "offset", "rows"]:
982 self._dynamic_keys.append(key)
983 setattr(self, key, self._result_cache[key])
984
985
987 """ retrive the raw result """
988 return self._fetch(self._arg, self.params)
989
991 if not self._result_cache:
992 self.fetch()
993
994 @property
996 """ return number of total rows in the view """
997 self._fetch_if_needed()
998
999 if self._total_rows is None:
1000 return self.count()
1001 return self._total_rows
1002
1003 @property
1005 """ current position in the view """
1006 self._fetch_if_needed()
1007 return self._offset
1008
1010 params = self.params.copy()
1011 if type(key) is slice:
1012 if key.start is not None:
1013 params['startkey'] = key.start
1014 if key.stop is not None:
1015 params['endkey'] = key.stop
1016 elif isinstance(key, (list, tuple,)):
1017 params['keys'] = key
1018 else:
1019 params['key'] = key
1020
1021 return ViewResults(self._fetch, self._arg, wrapper=wrapper, params=params)
1022
1025
1028
1030 return bool(len(self))
1031