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

Commit 3c809fd

Browse files
authored
Merge pull request #199 from cloudant/193-update-handlers
Added support for update handlers
2 parents 6960cba + e39fc8d commit 3c809fd

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- [NEW] Added support for Cloudant Search index management.
55
- [NEW] Added support for managing and querying list functions.
66
- [NEW] Added support for managing and querying show functions.
7+
- [NEW] Added support for querying update handlers.
78
- [NEW] Added ``rewrites`` accessor property for URL rewriting.
89
- [NEW] Added ``st_indexes`` accessor property for Cloudant Geospatial indexes.
910
- [NEW] Added support for DesignDocument ``_info`` and ``_search_info`` endpoints.

src/cloudant/database.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,57 @@ def get_show_function_result(self, ddoc_id, show_name, doc_id):
842842
headers)
843843
return resp.text
844844

845+
def update_handler_result(self, ddoc_id, handler_name, doc_id=None, data=None, **params):
846+
"""
847+
Creates or updates a document from the specified database based on the
848+
update handler function provided. Update handlers are used, for
849+
example, to provide server-side modification timestamps, and document
850+
updates to individual fields without the latest revision. You can
851+
provide query parameters needed by the update handler function using
852+
the ``params`` argument.
853+
854+
Create a document with a generated ID:
855+
856+
.. code-block:: python
857+
858+
# Assuming that 'update001' update handler exists as part of the
859+
# 'ddoc001' design document in the remote database...
860+
# Execute 'update001' to create a new document
861+
resp = db.update_handler_result('ddoc001', 'update001', data={'name': 'John',
862+
'message': 'hello'})
863+
864+
Create or update a document with the specified ID:
865+
866+
.. code-block:: python
867+
868+
# Assuming that 'update001' update handler exists as part of the
869+
# 'ddoc001' design document in the remote database...
870+
# Execute 'update001' to update document 'doc001' in the database
871+
resp = db.update_handler_result('ddoc001', 'update001', 'doc001',
872+
data={'month': 'July'})
873+
874+
For more details, see the `update handlers documentation
875+
<https://docs.cloudant.com/design_documents.html#update-handlers>`_.
876+
877+
:param str ddoc_id: Design document id used to get result.
878+
:param str handler_name: Name used in part to identify the
879+
update handler function.
880+
:param str doc_id: Optional document id used to specify the
881+
document to be handled.
882+
883+
:returns: Result of update handler function in text format
884+
"""
885+
ddoc = DesignDocument(self, ddoc_id)
886+
if doc_id:
887+
resp = self.r_session.put(
888+
'/'.join([ddoc.document_url, '_update', handler_name, doc_id]),
889+
params=params, data=data)
890+
else:
891+
resp = self.r_session.post(
892+
'/'.join([ddoc.document_url, '_update', handler_name]),
893+
params=params, data=data)
894+
return resp.text
895+
845896
class CloudantDatabase(CouchDatabase):
846897
"""
847898
Encapsulates a Cloudant database. A CloudantDatabase object is

src/cloudant/design_document.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,44 @@ def __init__(self, database, document_id=None):
4848
for prop in self._nested_object_names:
4949
self.setdefault(prop, dict())
5050

51+
@property
52+
def updates(self):
53+
"""
54+
Provides an accessor property to the updates dictionary in the locally
55+
cached DesignDocument. Update handlers are custom functions stored on
56+
Cloudant's server that will create or update a document.
57+
To execute the update handler function, see
58+
:func:`~cloudant.database.CouchDatabase.update_handler_result`.
59+
60+
Update handlers receive two arguments: ``doc`` and ``req``. If a document ID is
61+
provided in the request to the update handler, then ``doc`` will be the
62+
document corresponding with that ID.
63+
If no ID was provided, ``doc`` will be null.
64+
65+
Update handler example:
66+
67+
.. code-block:: python
68+
69+
# Add the update handler to ``updates`` and save the design document
70+
ddoc = DesignDocument(self.db, '_design/ddoc001')
71+
ddoc001['updates'] = {
72+
'update001': 'function(doc, req) { if (!doc) '
73+
'{ if ('id' in req && req.id){ return [{_id: req.id}, '
74+
'\"New World\"] } return [null, \"Empty World\"] } '
75+
'doc.world = \'hello\'; '
76+
'return [doc, \"Added world.hello!\"]} '
77+
}
78+
ddoc.save()
79+
80+
Note: Update handler functions must return an array of two elements,
81+
the first being the document to save (or null, if you don't want to
82+
save anything), and the second being the response body.
83+
84+
:returns: Dictionary containing update handler names and objects
85+
as key/value
86+
"""
87+
return self.get('updates')
88+
5189
@property
5290
def st_indexes(self):
5391
"""

tests/unit/database_tests.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,61 @@ def test_get_show_result(self):
692692
'Hello from doc001!'
693693
)
694694

695+
def test_create_doc_with_update_handler(self):
696+
"""
697+
Test update_handler_result executes an update handler function
698+
that creates a new document
699+
"""
700+
self.populate_db_with_documents()
701+
ddoc = DesignDocument(self.db, '_design/ddoc001')
702+
ddoc['updates'] = {
703+
'update001': 'function(doc, req) { if (!doc) { var new_doc = req.form; '
704+
'new_doc._id = \'testDoc\'; return [new_doc, '
705+
'\'Created new doc: \' + JSON.stringify(new_doc)]; }} '
706+
}
707+
708+
ddoc.save()
709+
resp = self.db.update_handler_result('ddoc001', 'update001', data={'message': 'hello'})
710+
self.assertEqual(
711+
resp,
712+
'Created new doc: {"message":"hello","_id":"testDoc"}'
713+
)
714+
715+
def test_update_doc_with_update_handler(self):
716+
"""
717+
Test update_handler_result executes an update handler function
718+
that updates a document with query parameters
719+
"""
720+
self.populate_db_with_documents()
721+
ddoc = DesignDocument(self.db, '_design/ddoc001')
722+
ddoc['updates'] = {
723+
'update001': 'function(doc, req) { '
724+
'var field = req.query.field; '
725+
'var value = req.query.value; '
726+
'var new_doc = doc; '
727+
'doc[field] = value; '
728+
'for(var key in req.form) doc[key]=req.form[key]; '
729+
'var message = \'set \'+field+\' to \'+value'
730+
'+\' and add data \'+ JSON.stringify(req.form); '
731+
'return [doc, message]; } '
732+
}
733+
ddoc.save()
734+
resp = self.db.update_handler_result('ddoc001', 'update001', 'julia001',
735+
field='new_field', value='new_value',
736+
data={'message': 'hello'})
737+
self.assertEqual(
738+
resp,
739+
'set new_field to new_value and add data {"message":"hello"}'
740+
)
741+
ddoc_remote = Document(self.db, 'julia001')
742+
ddoc_remote.fetch()
743+
self.assertEqual(
744+
ddoc_remote,
745+
{'age': 1, 'name': 'julia', 'new_field': 'new_value',
746+
'_rev': ddoc_remote['_rev'], '_id': 'julia001',
747+
'message': 'hello'}
748+
)
749+
695750
@unittest.skipUnless(
696751
os.environ.get('RUN_CLOUDANT_TESTS') is not None,
697752
'Skipping Cloudant specific Database tests'

0 commit comments

Comments
 (0)