This repository has been archived by the owner on Mar 11, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 55
/
database.py
1590 lines (1366 loc) · 66.2 KB
/
database.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# Copyright (C) 2015, 2019 IBM Corp. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License a
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
API module that maps to a Cloudant or CouchDB database instance.
"""
import json
import contextlib
from requests.exceptions import HTTPError
from ._2to3 import url_quote_plus, iteritems_
from ._common_util import (
JSON_INDEX_TYPE,
SEARCH_INDEX_ARGS,
SPECIAL_INDEX_TYPE,
TEXT_INDEX_TYPE,
TYPE_CONVERTERS,
get_docs,
response_to_json_dict)
from .document import Document
from .design_document import DesignDocument
from .security_document import SecurityDocument
from .view import View
from .index import Index, TextIndex, SpecialIndex
from .query import Query
from .error import CloudantArgumentError, CloudantDatabaseException
from .result import Result, QueryResult
from .feed import Feed, InfiniteFeed
class CouchDatabase(dict):
"""
Encapsulates a CouchDB database. A CouchDatabase object is
instantiated with a reference to a client/session.
It supports accessing the documents, and various database
features such as the document indexes, changes feed, design documents, etc.
:param CouchDB client: Client instance used by the database.
:param str database_name: Database name used to reference the database.
:param int fetch_limit: Optional fetch limit used to set the max number of
documents to fetch per query during iteration cycles. Defaults to 100.
:param bool partitioned: Create as a partitioned database. Defaults to
``False``.
"""
def __init__(self, client, database_name, fetch_limit=100,
partitioned=False):
super(CouchDatabase, self).__init__()
self.client = client
self._database_host = client.server_url
self.database_name = database_name
self._fetch_limit = fetch_limit
self._partitioned = partitioned
self.result = Result(self.all_docs)
@property
def r_session(self):
"""
Returns the ``r_session`` from the client instance used by the database.
:returns: Client ``r_session``
"""
return self.client.r_session
@property
def admin_party(self):
"""
Returns the CouchDB Admin Party status. ``True`` if using Admin Party
``False`` otherwise.
:returns: CouchDB Admin Party mode status
"""
return self.client.admin_party
@property
def database_url(self):
"""
Constructs and returns the database URL.
:returns: Database URL
"""
return '/'.join((
self._database_host, url_quote_plus(self.database_name)))
@property
def creds(self):
"""
Retrieves a dictionary of useful authentication information
that can be used to authenticate against this database.
:returns: Dictionary containing authentication information
"""
session = self.client.session()
if session is None:
return None
return {
"basic_auth": self.client.basic_auth_str(),
"user_ctx": session.get('userCtx')
}
def database_partition_url(self, partition_key):
"""
Get the URL of the database partition.
:param str partition_key: Partition key.
:return: URL of the database partition.
:rtype: str
"""
return '/'.join((self.database_url,
'_partition',
url_quote_plus(partition_key)))
def exists(self):
"""
Performs an existence check on the remote database.
:returns: Boolean True if the database exists, False otherwise
"""
resp = self.r_session.head(self.database_url)
if resp.status_code not in [200, 404]:
resp.raise_for_status()
return resp.status_code == 200
def metadata(self):
"""
Retrieves the remote database metadata dictionary.
:returns: Dictionary containing database metadata details
"""
resp = self.r_session.get(self.database_url)
resp.raise_for_status()
return response_to_json_dict(resp)
def partition_metadata(self, partition_key):
"""
Retrieves the metadata dictionary for the remote database partition.
:param str partition_key: Partition key.
:returns: Metadata dictionary for the database partition.
:rtype: dict
"""
resp = self.r_session.get(self.database_partition_url(partition_key))
resp.raise_for_status()
return response_to_json_dict(resp)
def doc_count(self):
"""
Retrieves the number of documents in the remote database
:returns: Database document count
"""
return self.metadata().get('doc_count')
def create_document(self, data, throw_on_exists=False):
"""
Creates a new document in the remote and locally cached database, using
the data provided. If an _id is included in the data then depending on
that _id either a :class:`~cloudant.document.Document` or a
:class:`~cloudant.design_document.DesignDocument`
object will be added to the locally cached database and returned by this
method.
:param dict data: Dictionary of document JSON data, containing _id.
:param bool throw_on_exists: Optional flag dictating whether to raise
an exception if the document already exists in the database.
:returns: A :class:`~cloudant.document.Document` or
:class:`~cloudant.design_document.DesignDocument` instance
corresponding to the new document in the database.
"""
docid = data.get('_id', None)
doc = None
if docid and docid.startswith('_design/'):
doc = DesignDocument(self, docid)
else:
doc = Document(self, docid)
doc.update(data)
try:
doc.create()
except HTTPError as error:
if error.response.status_code == 409:
if throw_on_exists:
raise CloudantDatabaseException(409, docid)
else:
raise
super(CouchDatabase, self).__setitem__(doc['_id'], doc)
return doc
def new_document(self):
"""
Creates a new, empty document in the remote and locally cached database,
auto-generating the _id.
:returns: Document instance corresponding to the new document in the
database
"""
doc = Document(self, None)
doc.create()
super(CouchDatabase, self).__setitem__(doc['_id'], doc)
return doc
def design_documents(self):
"""
Retrieve the JSON content for all design documents in this database.
Performs a remote call to retrieve the content.
:returns: All design documents found in this database in JSON format
"""
url = '/'.join((self.database_url, '_all_docs'))
query = "startkey=\"_design\"&endkey=\"_design0\"&include_docs=true"
resp = self.r_session.get(url, params=query)
resp.raise_for_status()
data = response_to_json_dict(resp)
return data['rows']
def list_design_documents(self):
"""
Retrieves a list of design document names in this database.
Performs a remote call to retrieve the content.
:returns: List of names for all design documents in this database
"""
url = '/'.join((self.database_url, '_all_docs'))
query = "startkey=\"_design\"&endkey=\"_design0\""
resp = self.r_session.get(url, params=query)
resp.raise_for_status()
data = response_to_json_dict(resp)
return [x.get('key') for x in data.get('rows', [])]
def get_design_document(self, ddoc_id):
"""
Retrieves a design document. If a design document exists remotely
then that content is wrapped in a DesignDocument object and returned
to the caller. Otherwise a "shell" DesignDocument object is returned.
:param str ddoc_id: Design document id
:returns: A DesignDocument instance, if exists remotely then it will
be populated accordingly
"""
ddoc = DesignDocument(self, ddoc_id)
try:
ddoc.fetch()
except HTTPError as error:
if error.response.status_code != 404:
raise
return ddoc
def get_security_document(self):
"""
Retrieves the database security document as a SecurityDocument object.
The returned object is useful for viewing as well as updating the
the database's security document.
:returns: A SecurityDocument instance representing the database
security document
"""
sdoc = SecurityDocument(self)
sdoc.fetch()
return sdoc
def get_partitioned_view_result(self, partition_key, ddoc_id, view_name,
raw_result=False, **kwargs):
"""
Retrieves the partitioned view result based on the design document and
view name.
See :func:`~cloudant.database.CouchDatabase.get_view_result` method for
further details.
:param str partition_key: Partition key.
:param str ddoc_id: Design document id used to get result.
:param str view_name: Name of the view used to get result.
:param bool raw_result: Dictates whether the view result is returned
as a default Result object or a raw JSON response.
Defaults to False.
:param kwargs: See
:func:`~cloudant.database.CouchDatabase.get_view_result` method for
available keyword arguments.
:returns: The result content either wrapped in a QueryResult or
as the raw response JSON content.
:rtype: QueryResult, dict
"""
ddoc = DesignDocument(self, ddoc_id)
view = View(ddoc, view_name, partition_key=partition_key)
return self._get_view_result(view, raw_result, **kwargs)
def get_view_result(self, ddoc_id, view_name, raw_result=False, **kwargs):
"""
Retrieves the view result based on the design document and view name.
By default the result is returned as a
:class:`~cloudant.result.Result` object which provides a key
accessible, sliceable, and iterable interface to the result collection.
Depending on how you are accessing, slicing or iterating through your
result collection certain query parameters are not permitted. See
:class:`~cloudant.result.Result` for additional details.
However, by setting ``raw_result=True``, the result will be returned as
the raw JSON response content for the view requested. With this setting
there are no restrictions on the query parameters used but it also
means that the result collection key access, slicing, and iteration is
the responsibility of the developer.
For example:
.. code-block:: python
# get Result based on a design document view
result = db.get_view_result('_design/ddoc_id_001', 'view_001')
# get a customized Result based on a design document view
result = db.get_view_result('_design/ddoc_id_001', 'view_001',
include_docs=True, reduce=False)
# get raw response content based on a design document view
result = db.get_view_result('_design/ddoc_id_001', 'view_001',
raw_result=True)
# get customized raw response content for a design document view
db.get_view_result('_design/ddoc_id_001', 'view_001',
raw_result=True, include_docs=True, skip=100, limit=100)
For more detail on key access, slicing and iteration, refer to the
:class:`~cloudant.result.Result` documentation.
:param str ddoc_id: Design document id used to get result.
:param str view_name: Name of the view used to get result.
:param bool raw_result: Dictates whether the view result is returned
as a default Result object or a raw JSON response.
Defaults to False.
:param bool descending: Return documents in descending key order.
:param endkey: Stop returning records at this specified key.
Not valid when used with :class:`~cloudant.result.Result` key
access and key slicing.
:param str endkey_docid: Stop returning records when the specified
document id is reached.
:param bool group: Using the reduce function, group the results to a
group or single row.
:param group_level: Only applicable if the view uses complex keys: keys
that are lists. Groups reduce results for the specified number
of list fields.
:param bool include_docs: Include the full content of the documents.
:param bool inclusive_end: Include rows with the specified endkey.
:param key: Return only documents that match the specified key.
Not valid when used with :class:`~cloudant.result.Result` key
access and key slicing.
:param list keys: Return only documents that match the specified keys.
Not valid when used with :class:`~cloudant.result.Result` key
access and key slicing.
:param int limit: Limit the number of returned documents to the
specified count. Not valid when used with
:class:`~cloudant.result.Result` iteration.
:param int page_size: Sets the page size for result iteration.
Only valid if used with ``raw_result=False``.
:param bool reduce: True to use the reduce function, false otherwise.
:param int skip: Skip this number of rows from the start.
Not valid when used with :class:`~cloudant.result.Result` iteration.
:param bool stable: Whether or not the view results should be returned
from a "stable" set of shards.
:param str stale: Allow the results from a stale view to be used. This
makes the request return immediately, even if the view has not been
completely built yet. If this parameter is not given, a response is
returned only after the view has been built. Note that this
parameter is deprecated and the appropriate combination of `stable`
and `update` should be used instead.
:param startkey: Return records starting with the specified key.
Not valid when used with :class:`~cloudant.result.Result` key
access and key slicing.
:param str startkey_docid: Return records starting with the specified
document ID.
:param str update: Determine whether the view in question should be
updated prior to or after responding to the user. Valid values are:
false: return results before updating the view; true: Return results
after updating the view; lazy: Return the view results without
waiting for an update, but update them immediately after the request.
:returns: The result content either wrapped in a QueryResult or
as the raw response JSON content
"""
ddoc = DesignDocument(self, ddoc_id)
view = View(ddoc, view_name)
return self._get_view_result(view, raw_result, **kwargs)
@staticmethod
def _get_view_result(view, raw_result, **kwargs):
""" Get view results helper. """
if raw_result:
return view(**kwargs)
if kwargs:
return Result(view, **kwargs)
return view.result
def create(self, throw_on_exists=False):
"""
Creates a database defined by the current database object, if it
does not already exist and raises a CloudantException if the operation
fails. If the database already exists then this method call is a no-op.
:param bool throw_on_exists: Boolean flag dictating whether or
not to throw a CloudantDatabaseException when attempting to
create a database that already exists.
:returns: The database object
"""
if not throw_on_exists and self.exists():
return self
resp = self.r_session.put(self.database_url, params={
'partitioned': TYPE_CONVERTERS.get(bool)(self._partitioned)
})
if resp.status_code == 201 or resp.status_code == 202:
return self
raise CloudantDatabaseException(
resp.status_code, self.database_url, resp.text
)
def delete(self):
"""
Deletes the current database from the remote instance.
"""
resp = self.r_session.delete(self.database_url)
resp.raise_for_status()
def all_docs(self, **kwargs):
"""
Wraps the _all_docs primary index on the database, and returns the
results by value. This can be used as a direct query to the _all_docs
endpoint. More convenient/efficient access using keys, slicing
and iteration can be done through the ``result`` attribute.
Keyword arguments supported are those of the view/index access API.
:param bool descending: Return documents in descending key order.
:param endkey: Stop returning records at this specified key.
:param str endkey_docid: Stop returning records when the specified
document id is reached.
:param bool include_docs: Include the full content of the documents.
:param bool inclusive_end: Include rows with the specified endkey.
:param key: Return only documents that match the specified key.
:param list keys: Return only documents that match the specified keys.
:param int limit: Limit the number of returned documents to the
specified count.
:param int skip: Skip this number of rows from the start.
:param startkey: Return records starting with the specified key.
:param str startkey_docid: Return records starting with the specified
document ID.
:returns: Raw JSON response content from ``_all_docs`` endpoint
"""
resp = get_docs(self.r_session,
'/'.join([self.database_url, '_all_docs']),
self.client.encoder,
**kwargs)
return response_to_json_dict(resp)
def partitioned_all_docs(self, partition_key, **kwargs):
"""
Wraps the _all_docs primary index on the database partition, and returns
the results by value.
See :func:`~cloudant.database.CouchDatabase.all_docs` method for further
details.
:param str partition_key: Partition key.
:param kwargs: See :func:`~cloudant.database.CouchDatabase.all_docs`
method for available keyword arguments.
:returns: Raw JSON response content from ``_all_docs`` endpoint.
:rtype: dict
"""
resp = get_docs(self.r_session,
'/'.join([
self.database_partition_url(partition_key),
'_all_docs'
]),
self.client.encoder,
**kwargs)
return response_to_json_dict(resp)
@contextlib.contextmanager
def custom_result(self, **options):
"""
Provides a context manager that can be used to customize the
``_all_docs`` behavior and wrap the output as a
:class:`~cloudant.result.Result`.
:param bool descending: Return documents in descending key order.
:param endkey: Stop returning records at this specified key.
Not valid when used with :class:`~cloudant.result.Result` key
access and key slicing.
:param str endkey_docid: Stop returning records when the specified
document id is reached.
:param bool include_docs: Include the full content of the documents.
:param bool inclusive_end: Include rows with the specified endkey.
:param key: Return only documents that match the specified key.
Not valid when used with :class:`~cloudant.result.Result` key
access and key slicing.
:param list keys: Return only documents that match the specified keys.
Not valid when used with :class:`~cloudant.result.Result` key
access and key slicing.
:param int page_size: Sets the page size for result iteration.
:param startkey: Return records starting with the specified key.
Not valid when used with :class:`~cloudant.result.Result` key
access and key slicing.
:param str startkey_docid: Return records starting with the specified
document ID.
For example:
.. code-block:: python
with database.custom_result(include_docs=True) as rslt:
data = rslt[100: 200]
"""
rslt = Result(self.all_docs, **options)
yield rslt
del rslt
def keys(self, remote=False):
"""
Retrieves the list of document ids in the database. Default is
to return only the locally cached document ids, specify remote=True
to make a remote request to include all document ids from the remote
database instance.
:param bool remote: Dictates whether the list of locally cached
document ids are returned or a remote request is made to include
an up to date list of document ids from the server.
Defaults to False.
:returns: List of document ids
"""
if not remote:
return list(super(CouchDatabase, self).keys())
docs = self.all_docs()
return [row['id'] for row in docs.get('rows', [])]
def changes(self, raw_data=False, **kwargs):
"""
Returns the ``_changes`` feed iterator. The ``_changes`` feed can be
iterated over and once complete can also provide the last sequence
identifier of the feed. If necessary, the iteration can be stopped by
issuing a call to the ``stop()`` method on the returned iterator object.
For example:
.. code-block:: python
# Iterate over a "normal" _changes feed
changes = db.changes()
for change in changes:
print(change)
print(changes.last_seq)
# Iterate over a "continuous" _changes feed with additional options
changes = db.changes(feed='continuous', since='now', descending=True)
for change in changes:
if some_condition:
changes.stop()
print(change)
:param bool raw_data: If set to True then the raw response data will be
streamed otherwise if set to False then JSON formatted data will be
streamed. Default is False.
:param bool conflicts: Can only be set if include_docs is True. Adds
information about conflicts to each document. Default is False.
:param bool descending: Changes appear in sequential order. Default is
False.
:param list doc_ids: To be used only when ``filter`` is set to
``_doc_ids``. Filters the feed so that only changes to the
specified documents are sent.
:param str feed: Type of feed. Valid values are ``continuous``,
``longpoll``, and ``normal``. Default is ``normal``.
:param str filter: Name of filter function from a design document to get
updates. Default is no filter.
:param int heartbeat: Time in milliseconds after which an empty line is
sent during ``longpoll`` or ``continuous`` if there have been no
changes. Must be a positive number. Default is no heartbeat.
:param bool include_docs: Include the document with the result. The
document will not be returned as a
:class:`~cloudant.document.Document` but instead will be returned as
either formated JSON or as raw response content. Default is False.
:param int limit: Maximum number of rows to return. Must be a positive
number. Default is no limit.
:param since: Start the results from changes after the specified
sequence identifier. In other words, using since excludes from the
list all changes up to and including the specified sequence
identifier. If since is 0 (the default), or omitted, the request
returns all changes. If it is ``now``, only changes made after the
time of the request will be emitted.
:param str style: Specifies how many revisions are returned in the
changes array. The default, ``main_only``, only returns the current
"winning" revision; ``all_docs`` returns all leaf revisions,
including conflicts and deleted former conflicts.
:param int timeout: Number of milliseconds to wait for data before
terminating the response. ``heartbeat`` supersedes ``timeout`` if
both are supplied.
:param int chunk_size: The HTTP response stream chunk size. Defaults to
512.
:returns: Feed object that can be iterated over as a ``_changes`` feed.
"""
return Feed(self, raw_data, **kwargs)
def infinite_changes(self, **kwargs):
"""
Returns an infinite (perpetually refreshed) ``_changes`` feed iterator.
If necessary, the iteration can be stopped by issuing a call to the
``stop()`` method on the returned iterator object.
For example:
.. code-block:: python
# Iterate over an infinite _changes feed
changes = db.infinite_changes()
for change in changes:
if some_condition:
changes.stop()
print(change)
:param bool conflicts: Can only be set if include_docs is True. Adds
information about conflicts to each document. Default is False.
:param bool descending: Changes appear in sequential order. Default is
False.
:param list doc_ids: To be used only when ``filter`` is set to
``_doc_ids``. Filters the feed so that only changes to the
specified documents are sent.
:param str filter: Name of filter function from a design document to get
updates. Default is no filter.
:param int heartbeat: Time in milliseconds after which an empty line is
sent if there have been no changes. Must be a positive number.
Default is no heartbeat.
:param bool include_docs: Include the document with the result. The
document will not be returned as a
:class:`~cloudant.document.Document` but instead will be returned as
either formated JSON or as raw response content. Default is False.
:param since: Start the results from changes after the specified
sequence identifier. In other words, using since excludes from the
list all changes up to and including the specified sequence
identifier. If since is 0 (the default), or omitted, the request
returns all changes. If it is ``now``, only changes made after the
time of the request will be emitted.
:param str style: Specifies how many revisions are returned in the
changes array. The default, ``main_only``, only returns the current
"winning" revision; ``all_docs`` returns all leaf revisions,
including conflicts and deleted former conflicts.
:param int timeout: Number of milliseconds to wait for data before
terminating the response. ``heartbeat`` supersedes ``timeout`` if
both are supplied.
:param int chunk_size: The HTTP response stream chunk size. Defaults to
512.
:returns: Feed object that can be iterated over as a ``_changes`` feed.
"""
return InfiniteFeed(self, **kwargs)
def __getitem__(self, key):
"""
Overrides dictionary __getitem__ behavior to provide a document
instance for the specified key from the current database.
If the document instance does not exist locally, then a remote request
is made and the document is subsequently added to the local cache and
returned to the caller.
If the document instance already exists locally then it is returned and
a remote request is not performed.
A KeyError will result if the document does not exist locally or in the
remote database.
:param str key: Document id used to retrieve the document from the
database.
:returns: A Document or DesignDocument object depending on the
specified document id (key)
"""
if key in list(self.keys()):
return super(CouchDatabase, self).__getitem__(key)
if key.startswith('_design/'):
doc = DesignDocument(self, key)
else:
doc = Document(self, key)
if doc.exists():
doc.fetch()
super(CouchDatabase, self).__setitem__(key, doc)
else:
raise KeyError(key)
return doc
def get(self, key, remote=False):
"""
Overrides dict's get method. This gets an item from the database or cache
like __getitem__, but instead of throwing an exception if the item is not
found, it simply returns None.
:param bool remote: Dictates whether a remote request is made to
retrieve the doc, if it is not present in the local cache.
Defaults to False.
"""
if remote:
try:
return self.__getitem__(key)
except KeyError:
return None
else:
return super(CouchDatabase, self).get(key)
def __contains__(self, key):
"""
Overrides dictionary __contains__ behavior to check if a document
by key exists in the current cached or remote database.
For example:
.. code-block:: python
if key in database:
doc = database[key]
# Do something with doc
:param str key: Document id used to check if it exists in the database.
:returns: True if the document exists in the local or remote
database, otherwise False.
"""
if key in list(self.keys()):
return True
if key.startswith('_design/'):
doc = DesignDocument(self, key)
else:
doc = Document(self, key)
return doc.exists()
def __iter__(self, remote=True):
"""
Overrides dictionary __iter__ behavior to provide iterable Document
results. By default, Documents are fetched from the remote database,
in batches equal to the database object's defined ``fetch_limit``,
yielding Document/DesignDocument objects.
If ``remote=False`` then the locally cached Document objects are
iterated over with no attempt to retrieve documents from the remote
database.
:param bool remote: Dictates whether the locally cached
Document objects are returned or a remote request is made to
retrieve Document objects from the remote database.
Defaults to True.
:returns: Iterable of Document and/or DesignDocument objects
"""
if not remote:
super(CouchDatabase, self).__iter__()
else:
# Use unicode Null U+0000 as the initial lower bound to ensure any
# document id could exist in the results set.
next_startkey = u'\u0000'
while next_startkey is not None:
docs = self.all_docs(
limit=self._fetch_limit,
include_docs=True,
startkey=next_startkey
).get('rows', [])
if len(docs) >= self._fetch_limit:
# Ensure the next document batch contains ids that sort
# strictly higher than the previous document id fetched.
next_startkey = docs[-1]['id'] + u'\u0000'
else:
# This is the last batch of docs, so we set
# ourselves up to break out of the while loop
# after this pass.
next_startkey = None
for doc in docs:
# Wrap the doc dictionary as the appropriate
# document object before yielding it.
if doc['id'].startswith('_design/'):
document = DesignDocument(self, doc['id'])
else:
document = Document(self, doc['id'])
document.update(doc['doc'])
super(CouchDatabase, self).__setitem__(doc['id'], document)
yield document
return
def bulk_docs(self, docs):
"""
Performs multiple document inserts and/or updates through a single
request. Each document must either be or extend a dict as
is the case with Document and DesignDocument objects. A document
must contain the ``_id`` and ``_rev`` fields if the document
is meant to be updated.
:param list docs: List of Documents to be created/updated.
:returns: Bulk document creation/update status in JSON format
"""
url = '/'.join((self.database_url, '_bulk_docs'))
data = {'docs': docs}
headers = {'Content-Type': 'application/json'}
resp = self.r_session.post(
url,
data=json.dumps(data, cls=self.client.encoder),
headers=headers
)
resp.raise_for_status()
return response_to_json_dict(resp)
def missing_revisions(self, doc_id, *revisions):
"""
Returns a list of document revision values that do not exist in the
current remote database for the specified document id and specified
list of revision values.
:param str doc_id: Document id to check for missing revisions against.
:param list revisions: List of document revisions values to check
against.
:returns: List of missing document revision values
"""
url = '/'.join((self.database_url, '_missing_revs'))
data = {doc_id: list(revisions)}
resp = self.r_session.post(
url,
headers={'Content-Type': 'application/json'},
data=json.dumps(data, cls=self.client.encoder)
)
resp.raise_for_status()
resp_json = response_to_json_dict(resp)
missing_revs = resp_json['missing_revs'].get(doc_id)
if missing_revs is None:
missing_revs = []
return missing_revs
def revisions_diff(self, doc_id, *revisions):
"""
Returns the differences in the current remote database for the specified
document id and specified list of revision values.
:param str doc_id: Document id to check for revision differences
against.
:param list revisions: List of document revisions values to check
against.
:returns: The revision differences in JSON format
"""
url = '/'.join((self.database_url, '_revs_diff'))
data = {doc_id: list(revisions)}
resp = self.r_session.post(
url,
headers={'Content-Type': 'application/json'},
data=json.dumps(data, cls=self.client.encoder)
)
resp.raise_for_status()
return response_to_json_dict(resp)
def get_revision_limit(self):
"""
Retrieves the limit of historical revisions to store for any single
document in the current remote database.
:returns: Revision limit value for the current remote database
"""
url = '/'.join((self.database_url, '_revs_limit'))
resp = self.r_session.get(url)
resp.raise_for_status()
try:
ret = int(resp.text)
except ValueError:
raise CloudantDatabaseException(400, response_to_json_dict(resp))
return ret
def set_revision_limit(self, limit):
"""
Sets the limit of historical revisions to store for any single document
in the current remote database.
:param int limit: Number of revisions to store for any single document
in the current remote database.
:returns: Revision limit set operation status in JSON format
"""
url = '/'.join((self.database_url, '_revs_limit'))
resp = self.r_session.put(url, data=json.dumps(limit, cls=self.client.encoder))
resp.raise_for_status()
return response_to_json_dict(resp)
def view_cleanup(self):
"""
Removes view files that are not used by any design document in the
remote database.
:returns: View cleanup status in JSON format
"""
url = '/'.join((self.database_url, '_view_cleanup'))
resp = self.r_session.post(
url,
headers={'Content-Type': 'application/json'}
)
resp.raise_for_status()
return response_to_json_dict(resp)
def get_list_function_result(self, ddoc_id, list_name, view_name, **kwargs):
"""
Retrieves a customized MapReduce view result from the specified
database based on the list function provided. List functions are
used, for example, when you want to access Cloudant directly
from a browser, and need data to be returned in a different
format, such as HTML.
Note: All query parameters for View requests are supported.
See :class:`~cloudant.database.get_view_result` for
all supported query parameters.
For example:
.. code-block:: python
# Assuming that 'view001' exists as part of the
# 'ddoc001' design document in the remote database...
# Retrieve documents where the list function is 'list1'
resp = db.get_list_function_result('ddoc001', 'list1', 'view001', limit=10)
for row in resp['rows']:
# Process data (in text format).
For more detail on list functions, refer to the
`Cloudant list documentation <https://console.bluemix.net/docs/services/Cloudant/api/
design_documents.html#list-functions>`_.
:param str ddoc_id: Design document id used to get result.
:param str list_name: Name used in part to identify the
list function.
:param str view_name: Name used in part to identify the view.
:return: Formatted view result data in text format
"""
ddoc = DesignDocument(self, ddoc_id)
headers = {'Content-Type': 'application/json'}
resp = get_docs(self.r_session,
'/'.join([ddoc.document_url, '_list', list_name, view_name]),
self.client.encoder,
headers,
**kwargs)
return resp.text
def get_show_function_result(self, ddoc_id, show_name, doc_id):
"""
Retrieves a formatted document from the specified database
based on the show function provided. Show functions, for example,
are used when you want to access Cloudant directly from a browser,
and need data to be returned in a different format, such as HTML.
For example:
.. code-block:: python
# Assuming that 'view001' exists as part of the
# 'ddoc001' design document in the remote database...
# Retrieve a formatted 'doc001' document where the show function is 'show001'
resp = db.get_show_function_result('ddoc001', 'show001', 'doc001')
for row in resp['rows']:
# Process data (in text format).
For more detail on show functions, refer to the
`Cloudant show documentation <https://console.bluemix.net/docs/services/Cloudant/api/
design_documents.html#show-functions>`_.
:param str ddoc_id: Design document id used to get the result.
:param str show_name: Name used in part to identify the