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

Commit 1ffcd54

Browse files
authored
Merge pull request #434 from cloudant/fix-document-cm-error-bug
Stop document context manager from saving if an error is raised.
2 parents 2744202 + af07c04 commit 1ffcd54

File tree

5 files changed

+80
-30
lines changed

5 files changed

+80
-30
lines changed

CHANGES.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Unreleased
22

3-
- [IMPROVED] Updated `Getting started` section with a `get_query_result` example.
3+
- [FIXED] Bug where document context manager performed remote save despite
4+
uncaught exceptions being raised inside `with` block.
45
- [FIXED] Fixed parameter type of `selector` in docstring.
6+
- [IMPROVED] Updated `Getting started` section with a `get_query_result` example.
57

68
# 2.11.0 (2019-01-21)
79

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
# documentation root, use os.path.abspath to make it absolute, like shown here.
2222
#sys.path.insert(0, os.path.abspath('.'))
2323

24+
sys.path.insert(0, os.path.abspath('../src'))
25+
2426
# -- General configuration ------------------------------------------------
2527

2628
# If your documentation needs a minimal Sphinx version, state it here.

docs/getting_started.rst

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -441,41 +441,45 @@ multiple updates to a single document. Note that we don't save to the server
441441
after each update. We only save once to the server upon exiting the ``Document``
442442
context manager.
443443

444-
.. code-block:: python
444+
.. warning:: Uncaught exceptions inside the ``with`` block will prevent your
445+
document changes being saved to the remote server. However, changes
446+
will still be applied to your local document object.
445447

446-
from cloudant import cloudant
447-
from cloudant.document import Document
448+
.. code-block:: python
448449
449-
with cloudant(USERNAME, PASSWORD, account=ACCOUNT_NAME) as client:
450+
from cloudant import cloudant
451+
from cloudant.document import Document
450452
451-
my_database = client.create_database('my_database')
453+
with cloudant(USERNAME, PASSWORD, account=ACCOUNT_NAME) as client:
452454
453-
# Upon entry into the document context, fetches the document from the
454-
# remote database, if it exists. Upon exit from the context, saves the
455-
# document to the remote database with changes made within the context
456-
# or creates a new document.
457-
with Document(database, 'julia006') as document:
458-
# If document exists, it's fetched from the remote database
459-
# Changes are made locally
460-
document['name'] = 'Julia'
461-
document['age'] = 6
462-
# The document is saved to the remote database
463-
464-
# Display a Document
465-
print(my_database['julia30'])
466-
467-
# Delete the database
468-
client.delete_database('my_database')
455+
my_database = client.create_database('my_database')
469456
470-
print('Databases: {0}'.format(client.all_dbs()))
457+
# Upon entry into the document context, fetches the document from the
458+
# remote database, if it exists. Upon exit from the context, saves the
459+
# document to the remote database with changes made within the context
460+
# or creates a new document.
461+
with Document(database, 'julia006') as document:
462+
# If document exists, it's fetched from the remote database
463+
# Changes are made locally
464+
document['name'] = 'Julia'
465+
document['age'] = 6
466+
# The document is saved to the remote database
467+
468+
# Display a Document
469+
print(my_database['julia30'])
470+
471+
# Delete the database
472+
client.delete_database('my_database')
473+
474+
print('Databases: {0}'.format(client.all_dbs()))
471475
472476
Always use the ``_deleted`` document property to delete a document from within
473477
a ``Document`` context manager. For example:
474478

475-
.. code-block:: python
479+
.. code-block:: python
476480
477-
with Document(my_database, 'julia30') as doc:
478-
doc['_deleted'] = True
481+
with Document(my_database, 'julia30') as doc:
482+
doc['_deleted'] = True
479483
480484
*You can also delete non underscore prefixed document keys to reduce the size of the request.*
481485

src/cloudant/document.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,12 +341,13 @@ def __enter__(self):
341341

342342
return self
343343

344-
def __exit__(self, *args):
344+
def __exit__(self, exc_type, exc_value, traceback):
345345
"""
346-
Support context like editing of document fields. Handles context exit
347-
logic. Executes a Document.save() upon exit.
346+
Support context like editing of document fields. Handles context exit
347+
logic. Executes a `Document.save()` upon exit if no exception occurred.
348348
"""
349-
self.save()
349+
if exc_type is None:
350+
self.save()
350351

351352
def __setitem__(self, key, value):
352353
"""

tests/unit/document_tests.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,47 @@ def test_document_context_manager_no_doc_id(self):
622622
self.assertTrue(doc['_rev'].startswith('1-'))
623623
self.assertEqual(self.db['julia006'], doc)
624624

625+
def test_document_context_manager_creation_failure_on_error(self):
626+
"""
627+
Test that the document context manager skips document creation if there
628+
is an error.
629+
"""
630+
with self.assertRaises(ZeroDivisionError), Document(self.db, 'julia006') as doc:
631+
doc['name'] = 'julia'
632+
doc['age'] = 6
633+
raise ZeroDivisionError()
634+
635+
doc = Document(self.db, 'julia006')
636+
try:
637+
doc.fetch()
638+
except requests.HTTPError as err:
639+
self.assertEqual(err.response.status_code, 404)
640+
else:
641+
self.fail('Above statement should raise a HTTPError.')
642+
643+
def test_document_context_manager_update_failure_on_error(self):
644+
"""
645+
Test that the document context manager skips document update if there
646+
is an error.
647+
"""
648+
# Create the document.
649+
doc = Document(self.db, 'julia006')
650+
doc['name'] = 'julia'
651+
doc['age'] = 6
652+
doc.save()
653+
654+
# Make a document update and then raise an error.
655+
with self.assertRaises(ZeroDivisionError), Document(self.db, 'julia006') as doc:
656+
doc['age'] = 7
657+
raise ZeroDivisionError()
658+
659+
# Assert the change persists locally.
660+
self.assertEqual(doc['age'], 7)
661+
662+
# Assert the document has not been saved to remote server.
663+
self.assertTrue(doc['_rev'].startswith('1-'))
664+
self.assertEqual(self.db['julia006']['age'], 6)
665+
625666
def test_document_context_manager_doc_create(self):
626667
"""
627668
Test that the document context manager will create a doc if it does

0 commit comments

Comments
 (0)