Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit bb729e6

Browse files
committed
Move create_query_index and others to CouchDatabase class, add support for 'key in db'
[NEW] Moved `create_query_index` and other query related methods to `CouchDatabase` as the `_index`/`_find` API is available in CouchDB 2.x. [NEW] Added functionality to test if a key is in a database as in `key in db`, overriding dict `__contains__` and checking in the remote database.
1 parent cbe88e7 commit bb729e6

File tree

4 files changed

+218
-162
lines changed

4 files changed

+218
-162
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
- [NEW] Moved `create_query_index` and other query related methods to `CouchDatabase` as the `_index`/`_find` API is available in CouchDB 2.x.
4+
- [NEW] Added functionality to test if a key is in a database as in `key in db`, overriding dict `__contains__` and checking in the remote database.
35
- [IMPROVED] Added support for IAM API key in `cloudant_bluemix` method.
46
- [IMPROVED] Updated Travis CI and unit tests to run against CouchDB 2.1.1.
57

docs/getting_started.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,19 @@ classes are sub-classes of ``dict``, this is accomplished through standard
270270
# Display the document
271271
print(my_document)
272272
273+
Checking if a document exists
274+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
275+
276+
You can check if a document exists in a database the same way you would check
277+
if a ``dict`` has a key-value pair by key.
278+
279+
.. code-block:: python
280+
281+
doc_exists = 'julia30' in my_database
282+
283+
if doc_exists:
284+
print('document with _id julia30 exists')
285+
273286
Retrieve all documents
274287
^^^^^^^^^^^^^^^^^^^^^^
275288

src/cloudant/database.py

Lines changed: 152 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,32 @@ def __getitem__(self, key):
608608
else:
609609
raise KeyError(key)
610610

611+
def __contains__(self, key):
612+
"""
613+
Overrides dictionary __contains__ behavior to check if a document
614+
by key exists in the current cached or remote database.
615+
616+
For example:
617+
618+
.. code-block:: python
619+
620+
if key in database:
621+
doc = database[key]
622+
# Do something with doc
623+
624+
:param str key: Document id used to check if it exists in the database.
625+
626+
:returns: True if the document exists in the local or remote
627+
database, otherwise False.
628+
"""
629+
if key in list(self.keys()):
630+
return True
631+
if key.startswith('_design/'):
632+
doc = DesignDocument(self, key)
633+
else:
634+
doc = Document(self, key)
635+
return doc.exists()
636+
611637
def __iter__(self, remote=True):
612638
"""
613639
Overrides dictionary __iter__ behavior to provide iterable Document
@@ -921,131 +947,6 @@ def update_handler_result(self, ddoc_id, handler_name, doc_id=None, data=None, *
921947
resp.raise_for_status()
922948
return resp.text
923949

924-
class CloudantDatabase(CouchDatabase):
925-
"""
926-
Encapsulates a Cloudant database. A CloudantDatabase object is
927-
instantiated with a reference to a client/session.
928-
It supports accessing the documents, and various database
929-
features such as the document indexes, changes feed, design documents, etc.
930-
931-
:param Cloudant client: Client instance used by the database.
932-
:param str database_name: Database name used to reference the database.
933-
:param int fetch_limit: Optional fetch limit used to set the max number of
934-
documents to fetch per query during iteration cycles. Defaults to 100.
935-
"""
936-
def __init__(self, client, database_name, fetch_limit=100):
937-
super(CloudantDatabase, self).__init__(
938-
client,
939-
database_name,
940-
fetch_limit=fetch_limit
941-
)
942-
943-
def security_document(self):
944-
"""
945-
Retrieves the security document for the current database
946-
containing information about the users that the database
947-
is shared with.
948-
949-
:returns: Security document as a ``dict``
950-
"""
951-
return dict(self.get_security_document())
952-
953-
@property
954-
def security_url(self):
955-
"""
956-
Constructs and returns the security document URL.
957-
958-
:returns: Security document URL
959-
"""
960-
url = '/'.join((self._database_host, '_api', 'v2', 'db',
961-
self.database_name, '_security'))
962-
return url
963-
964-
def share_database(self, username, roles=None):
965-
"""
966-
Shares the current remote database with the username provided.
967-
You can grant varying degrees of access rights,
968-
default is to share read-only, but additional
969-
roles can be added by providing the specific roles as a
970-
``list`` argument. If the user already has this database shared with
971-
them then it will modify/overwrite the existing permissions.
972-
973-
:param str username: Cloudant user to share the database with.
974-
:param list roles: A list of
975-
`roles
976-
<https://console.bluemix.net/docs/services/Cloudant/api/authorization.html#roles>`_
977-
to grant to the named user.
978-
979-
:returns: Share database status in JSON format
980-
"""
981-
if roles is None:
982-
roles = ['_reader']
983-
valid_roles = [
984-
'_reader',
985-
'_writer',
986-
'_admin',
987-
'_replicator',
988-
'_db_updates',
989-
'_design',
990-
'_shards',
991-
'_security'
992-
]
993-
doc = self.security_document()
994-
data = doc.get('cloudant', {})
995-
perms = []
996-
if all(role in valid_roles for role in roles):
997-
perms = list(set(roles))
998-
999-
if not perms:
1000-
raise CloudantArgumentError(102, roles, valid_roles)
1001-
1002-
data[username] = perms
1003-
doc['cloudant'] = data
1004-
resp = self.r_session.put(
1005-
self.security_url,
1006-
data=json.dumps(doc, cls=self.client.encoder),
1007-
headers={'Content-Type': 'application/json'}
1008-
)
1009-
resp.raise_for_status()
1010-
return resp.json()
1011-
1012-
def unshare_database(self, username):
1013-
"""
1014-
Removes all sharing with the named user for the current remote database.
1015-
This will remove the entry for the user from the security document.
1016-
To modify permissions, use the
1017-
:func:`~cloudant.database.CloudantDatabase.share_database` method
1018-
instead.
1019-
1020-
:param str username: Cloudant user to unshare the database from.
1021-
1022-
:returns: Unshare database status in JSON format
1023-
"""
1024-
doc = self.security_document()
1025-
data = doc.get('cloudant', {})
1026-
if username in data:
1027-
del data[username]
1028-
doc['cloudant'] = data
1029-
resp = self.r_session.put(
1030-
self.security_url,
1031-
data=json.dumps(doc, cls=self.client.encoder),
1032-
headers={'Content-Type': 'application/json'}
1033-
)
1034-
resp.raise_for_status()
1035-
return resp.json()
1036-
1037-
def shards(self):
1038-
"""
1039-
Retrieves information about the shards in the current remote database.
1040-
1041-
:returns: Shard information retrieval status in JSON format
1042-
"""
1043-
url = '/'.join((self.database_url, '_shards'))
1044-
resp = self.r_session.get(url)
1045-
resp.raise_for_status()
1046-
1047-
return resp.json()
1048-
1049950
def get_query_indexes(self, raw_result=False):
1050951
"""
1051952
Retrieves query indexes from the remote database.
@@ -1242,6 +1143,132 @@ def get_query_result(self, selector, fields=None, raw_result=False,
12421143

12431144
return query.result
12441145

1146+
1147+
class CloudantDatabase(CouchDatabase):
1148+
"""
1149+
Encapsulates a Cloudant database. A CloudantDatabase object is
1150+
instantiated with a reference to a client/session.
1151+
It supports accessing the documents, and various database
1152+
features such as the document indexes, changes feed, design documents, etc.
1153+
1154+
:param Cloudant client: Client instance used by the database.
1155+
:param str database_name: Database name used to reference the database.
1156+
:param int fetch_limit: Optional fetch limit used to set the max number of
1157+
documents to fetch per query during iteration cycles. Defaults to 100.
1158+
"""
1159+
def __init__(self, client, database_name, fetch_limit=100):
1160+
super(CloudantDatabase, self).__init__(
1161+
client,
1162+
database_name,
1163+
fetch_limit=fetch_limit
1164+
)
1165+
1166+
def security_document(self):
1167+
"""
1168+
Retrieves the security document for the current database
1169+
containing information about the users that the database
1170+
is shared with.
1171+
1172+
:returns: Security document as a ``dict``
1173+
"""
1174+
return dict(self.get_security_document())
1175+
1176+
@property
1177+
def security_url(self):
1178+
"""
1179+
Constructs and returns the security document URL.
1180+
1181+
:returns: Security document URL
1182+
"""
1183+
url = '/'.join((self._database_host, '_api', 'v2', 'db',
1184+
self.database_name, '_security'))
1185+
return url
1186+
1187+
def share_database(self, username, roles=None):
1188+
"""
1189+
Shares the current remote database with the username provided.
1190+
You can grant varying degrees of access rights,
1191+
default is to share read-only, but additional
1192+
roles can be added by providing the specific roles as a
1193+
``list`` argument. If the user already has this database shared with
1194+
them then it will modify/overwrite the existing permissions.
1195+
1196+
:param str username: Cloudant user to share the database with.
1197+
:param list roles: A list of
1198+
`roles
1199+
<https://console.bluemix.net/docs/services/Cloudant/api/authorization.html#roles>`_
1200+
to grant to the named user.
1201+
1202+
:returns: Share database status in JSON format
1203+
"""
1204+
if roles is None:
1205+
roles = ['_reader']
1206+
valid_roles = [
1207+
'_reader',
1208+
'_writer',
1209+
'_admin',
1210+
'_replicator',
1211+
'_db_updates',
1212+
'_design',
1213+
'_shards',
1214+
'_security'
1215+
]
1216+
doc = self.security_document()
1217+
data = doc.get('cloudant', {})
1218+
perms = []
1219+
if all(role in valid_roles for role in roles):
1220+
perms = list(set(roles))
1221+
1222+
if not perms:
1223+
raise CloudantArgumentError(102, roles, valid_roles)
1224+
1225+
data[username] = perms
1226+
doc['cloudant'] = data
1227+
resp = self.r_session.put(
1228+
self.security_url,
1229+
data=json.dumps(doc, cls=self.client.encoder),
1230+
headers={'Content-Type': 'application/json'}
1231+
)
1232+
resp.raise_for_status()
1233+
return resp.json()
1234+
1235+
def unshare_database(self, username):
1236+
"""
1237+
Removes all sharing with the named user for the current remote database.
1238+
This will remove the entry for the user from the security document.
1239+
To modify permissions, use the
1240+
:func:`~cloudant.database.CloudantDatabase.share_database` method
1241+
instead.
1242+
1243+
:param str username: Cloudant user to unshare the database from.
1244+
1245+
:returns: Unshare database status in JSON format
1246+
"""
1247+
doc = self.security_document()
1248+
data = doc.get('cloudant', {})
1249+
if username in data:
1250+
del data[username]
1251+
doc['cloudant'] = data
1252+
resp = self.r_session.put(
1253+
self.security_url,
1254+
data=json.dumps(doc, cls=self.client.encoder),
1255+
headers={'Content-Type': 'application/json'}
1256+
)
1257+
resp.raise_for_status()
1258+
return resp.json()
1259+
1260+
def shards(self):
1261+
"""
1262+
Retrieves information about the shards in the current remote database.
1263+
1264+
:returns: Shard information retrieval status in JSON format
1265+
"""
1266+
url = '/'.join((self.database_url, '_shards'))
1267+
resp = self.r_session.get(url)
1268+
resp.raise_for_status()
1269+
1270+
return resp.json()
1271+
12451272
def get_search_result(self, ddoc_id, index_name, **query_params):
12461273
"""
12471274
Retrieves the raw JSON content from the remote database based on the

0 commit comments

Comments
 (0)