Commits

Max Noel  committed 4eb4c22

* Transactions are now only saved (and switched to "running") if the first getter succeeded -- that is, failing transactions are only saved is they failed partway through.

  • Participants
  • Parent commits 522e497

Comments (0)

Files changed (2)

File dynamodb_mapper/tests/test_transactions.py

 
         self.assertRaises(TargetNotFoundError, t.commit)
 
+        # The transaction fails at the first step: no save
         self.assertEquals(m_user_save.call_count, 0)
-        m_transaction_save.assert_called()
-        self.assertEquals(t.status, "running")
+        self.assertFalse(m_transaction_save.called)
+        self.assertEquals(t.status, "pending")
 
     @mock.patch("dynamodb_mapper.transactions.Transaction.save")
     @mock.patch.object(User, "save")
 
         self.assertEquals(m_user_instance.energy, ENERGY)
         self.assertEquals(m_user_save.call_count, 0)
-        m_transaction_save.assert_called()
-        self.assertEquals(t.status, "running")
+        self.assertFalse(m_transaction_save.called)
+        self.assertEquals(t.status, "pending")
 
     @mock.patch("dynamodb_mapper.transactions.Transaction.save")
     @mock.patch.object(User, "save")

File dynamodb_mapper/transactions.py

     # database, and are as a result write-only. This value is defined on the
     # class level bu may be redefined on a per instance basis.
     transient = False
+
     # Maximum attempts. Each attempt consumes write credits
     MAX_RETRIES = 100
 
+    STATUSES_TO_SAVE = frozenset(["running", "done"])
+
     def _setup(self):
         """Set up preconditions and parameters for the transaction.
 
 
         # edit and attempt to save it
         setter(target)
+
+        # If we've reached this point, at least the transaction's primary
+        # target exists, and will have been modified/saved even if the rest
+        # of the transaction fails.
+
+        # So if anything fails beyond this point, we must save the transaction.
+        self.status = "running"
         target.save(expected_values=old_values)
 
     def _assign_datetime_and_save(self):
             self._setup()
             transactors = self._get_transactors()
 
-            self.status = "running"
-
             for getter, setter in transactors:
                 self._retry(
                     lambda: self._apply_and_save_target(getter, setter),
 
             self.status = "done"
         finally:
-            # Always (attempt to) save transaction status
-            self._retry(self._assign_datetime_and_save, OverwriteError)
+            if self.status in self.STATUSES_TO_SAVE:
+                # Save the transaction if it succeeded,
+                # or if it failed partway through.
+                self._retry(self._assign_datetime_and_save, OverwriteError)
 
     def save(self, allow_overwrite=True, expected_values=None):
         """If the transaction is transient (``transient = True``),