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