launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01224
[Merge] lp:~julian-edwards/launchpad/move-write-transaction into lp:launchpad/devel
Jonathan Lange has proposed merging lp:~julian-edwards/launchpad/move-write-transaction into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
This moves the write_transaction and read_transaction methods to lp.services, where they belong.
--
https://code.launchpad.net/~julian-edwards/launchpad/move-write-transaction/+merge/36625
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~julian-edwards/launchpad/move-write-transaction into lp:launchpad/devel.
=== modified file 'lib/canonical/librarian/db.py'
--- lib/canonical/librarian/db.py 2010-09-06 09:48:41 +0000
+++ lib/canonical/librarian/db.py 2010-09-25 09:13:49 +0000
@@ -10,15 +10,10 @@
'write_transaction',
]
-from psycopg2.extensions import TransactionRollbackError
from sqlobject.sqlbuilder import AND
-from storm.exceptions import DisconnectionError, IntegrityError
from storm.expr import SQL
-import transaction
-from twisted.python.util import mergeFunctionMetadata
from canonical.database.sqlbase import (
- reset_store,
session_store,
)
from canonical.launchpad.database.librarian import (
@@ -28,65 +23,6 @@
)
-RETRY_ATTEMPTS = 3
-
-
-def retry_transaction(func):
- """Decorator used to retry database transaction failures.
-
- The function being decorated should not have side effects outside
- of the transaction.
- """
- def retry_transaction_decorator(*args, **kwargs):
- attempt = 0
- while True:
- attempt += 1
- try:
- return func(*args, **kwargs)
- except (DisconnectionError, IntegrityError,
- TransactionRollbackError), exc:
- if attempt >= RETRY_ATTEMPTS:
- raise # tried too many times
- return mergeFunctionMetadata(func, retry_transaction_decorator)
-
-
-def read_transaction(func):
- """Decorator used to run the function inside a read only transaction.
-
- The transaction will be aborted on successful completion of the
- function. The transaction will be retried if appropriate.
- """
- @reset_store
- def read_transaction_decorator(*args, **kwargs):
- transaction.begin()
- try:
- return func(*args, **kwargs)
- finally:
- transaction.abort()
- return retry_transaction(mergeFunctionMetadata(
- func, read_transaction_decorator))
-
-
-def write_transaction(func):
- """Decorator used to run the function inside a write transaction.
-
- The transaction will be committed on successful completion of the
- function, and aborted on failure. The transaction will be retried
- if appropriate.
- """
- @reset_store
- def write_transaction_decorator(*args, **kwargs):
- transaction.begin()
- try:
- ret = func(*args, **kwargs)
- except:
- transaction.abort()
- raise
- transaction.commit()
- return ret
- return retry_transaction(mergeFunctionMetadata(
- func, write_transaction_decorator))
-
class Library:
"""Class that encapsulates the database interface for the librarian."""
=== modified file 'lib/canonical/librarian/ftests/test_db.py'
--- lib/canonical/librarian/ftests/test_db.py 2010-09-06 14:45:01 +0000
+++ lib/canonical/librarian/ftests/test_db.py 2010-09-25 09:13:49 +0000
@@ -44,8 +44,8 @@
self.assertEqual('text/unknown', alias.mimetype)
-class TestTransactionDecorators(unittest.TestCase):
- """Tests for the transaction decorators used by the librarian."""
+class TestLibrarianStuff(unittest.TestCase):
+ """Tests for the librarian."""
layer = LaunchpadZopelessLayer
@@ -150,59 +150,6 @@
]
self.assertEqual(expected_aliases, aliases)
- def test_read_transaction_reset_store(self):
- """Make sure that the store is reset after the transaction."""
- @db.read_transaction
- def no_op():
- pass
- no_op()
- self.failIf(
- self.file_content is self._getTestFileContent(),
- "Store wasn't reset properly.")
-
- def test_write_transaction_reset_store(self):
- """Make sure that the store is reset after the transaction."""
- @db.write_transaction
- def no_op():
- pass
- no_op()
- self.failIf(
- self.file_content is self._getTestFileContent(),
- "Store wasn't reset properly.")
-
- def test_write_transaction_reset_store_with_raise(self):
- """Make sure that the store is reset after the transaction."""
- @db.write_transaction
- def no_op():
- raise RuntimeError('an error occured')
- self.assertRaises(RuntimeError, no_op)
- self.failIf(
- self.file_content is self._getTestFileContent(),
- "Store wasn't reset properly.")
-
- def test_writing_transaction_reset_store_on_commit_failure(self):
- """The store should be reset even if committing the transaction fails.
- """
- class TransactionAborter:
- """Make the next commit() fails."""
- def newTransaction(self, txn):
- pass
-
- def beforeCompletion(self, txn):
- raise RuntimeError('the commit will fail')
- aborter = TransactionAborter()
- transaction.manager.registerSynch(aborter)
- try:
- @db.write_transaction
- def no_op():
- pass
- self.assertRaises(RuntimeError, no_op)
- self.failIf(
- self.file_content is self._getTestFileContent(),
- "Store wasn't reset properly.")
- finally:
- transaction.manager.unregisterSynch(aborter)
-
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
=== modified file 'lib/canonical/librarian/storage.py'
--- lib/canonical/librarian/storage.py 2010-09-05 21:53:21 +0000
+++ lib/canonical/librarian/storage.py 2010-09-25 09:13:49 +0000
@@ -13,7 +13,7 @@
from canonical.launchpad.webapp.interfaces import (
IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
-from canonical.librarian.db import write_transaction
+from lp.services.database import write_transaction
__all__ = [
'DigestMismatchError',
=== modified file 'lib/canonical/librarian/web.py'
--- lib/canonical/librarian/web.py 2010-09-06 08:33:15 +0000
+++ lib/canonical/librarian/web.py 2010-09-25 09:13:49 +0000
@@ -12,7 +12,10 @@
from canonical.config import config
from canonical.librarian.client import url_path_quote
-from canonical.librarian.db import read_transaction, write_transaction
+from lp.services.database import (
+ read_transaction,
+ write_transaction,
+ )
from canonical.librarian.utils import guess_librarian_encoding
=== modified file 'lib/lp/buildmaster/manager.py'
--- lib/lp/buildmaster/manager.py 2010-09-20 10:21:32 +0000
+++ lib/lp/buildmaster/manager.py 2010-09-25 09:13:49 +0000
@@ -32,7 +32,7 @@
from canonical.config import config
from canonical.launchpad.webapp import urlappend
-from canonical.librarian.db import write_transaction
+from lp.services.database import write_transaction
from lp.buildmaster.enums import BuildStatus
from lp.services.twistedsupport.processmonitor import ProcessWithTimeout
=== modified file 'lib/lp/services/database/__init__.py'
--- lib/lp/services/database/__init__.py 2009-07-17 02:25:09 +0000
+++ lib/lp/services/database/__init__.py 2010-09-25 09:13:49 +0000
@@ -4,5 +4,77 @@
"""The lp.services.database package."""
__metaclass__ = type
-__all__ = []
+__all__ = [
+ 'read_transaction',
+ 'write_transaction',
+ ]
+
+from psycopg2.extensions import TransactionRollbackError
+from storm.exceptions import DisconnectionError, IntegrityError
+import transaction
+from twisted.python.util import mergeFunctionMetadata
+
+from canonical.database.sqlbase import (
+ reset_store,
+ )
+
+
+RETRY_ATTEMPTS = 3
+
+
+def retry_transaction(func):
+ """Decorator used to retry database transaction failures.
+
+ The function being decorated should not have side effects outside
+ of the transaction.
+ """
+ def retry_transaction_decorator(*args, **kwargs):
+ attempt = 0
+ while True:
+ attempt += 1
+ try:
+ return func(*args, **kwargs)
+ except (DisconnectionError, IntegrityError,
+ TransactionRollbackError), exc:
+ if attempt >= RETRY_ATTEMPTS:
+ raise # tried too many times
+ return mergeFunctionMetadata(func, retry_transaction_decorator)
+
+
+def read_transaction(func):
+ """Decorator used to run the function inside a read only transaction.
+
+ The transaction will be aborted on successful completion of the
+ function. The transaction will be retried if appropriate.
+ """
+ @reset_store
+ def read_transaction_decorator(*args, **kwargs):
+ transaction.begin()
+ try:
+ return func(*args, **kwargs)
+ finally:
+ transaction.abort()
+ return retry_transaction(mergeFunctionMetadata(
+ func, read_transaction_decorator))
+
+
+def write_transaction(func):
+ """Decorator used to run the function inside a write transaction.
+
+ The transaction will be committed on successful completion of the
+ function, and aborted on failure. The transaction will be retried
+ if appropriate.
+ """
+ @reset_store
+ def write_transaction_decorator(*args, **kwargs):
+ transaction.begin()
+ try:
+ ret = func(*args, **kwargs)
+ except:
+ transaction.abort()
+ raise
+ transaction.commit()
+ return ret
+ return retry_transaction(mergeFunctionMetadata(
+ func, write_transaction_decorator))
=== added file 'lib/lp/services/database/tests/test_transaction_decorators.py'
--- lib/lp/services/database/tests/test_transaction_decorators.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/database/tests/test_transaction_decorators.py 2010-09-25 09:13:49 +0000
@@ -0,0 +1,92 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+import unittest
+
+import transaction
+from zope.component import getUtility
+
+from lp.services.database import (
+ read_transaction,
+ write_transaction,
+ )
+from canonical.launchpad.database.librarian import LibraryFileContent
+from canonical.launchpad.webapp.interfaces import (
+ IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
+from canonical.librarian import db
+from canonical.testing import LaunchpadZopelessLayer
+
+
+class TestTransactionDecorators(unittest.TestCase):
+ """Tests for the transaction decorators used by the librarian."""
+
+ layer = LaunchpadZopelessLayer
+
+ def setUp(self):
+ self.layer.switchDbUser('librarian')
+ self.store = getUtility(IStoreSelector).get(
+ MAIN_STORE, DEFAULT_FLAVOR)
+ self.content_id = db.Library().add('deadbeef', 1234, 'abababab')
+ self.file_content = self._getTestFileContent()
+ transaction.commit()
+
+ def _getTestFileContent(self):
+ """Return the file content object that created."""
+ return self.store.find(LibraryFileContent, id=self.content_id).one()
+
+ def test_read_transaction_reset_store(self):
+ """Make sure that the store is reset after the transaction."""
+ @read_transaction
+ def no_op():
+ pass
+ no_op()
+ self.failIf(
+ self.file_content is self._getTestFileContent(),
+ "Store wasn't reset properly.")
+
+ def test_write_transaction_reset_store(self):
+ """Make sure that the store is reset after the transaction."""
+ @write_transaction
+ def no_op():
+ pass
+ no_op()
+ self.failIf(
+ self.file_content is self._getTestFileContent(),
+ "Store wasn't reset properly.")
+
+ def test_write_transaction_reset_store_with_raise(self):
+ """Make sure that the store is reset after the transaction."""
+ @write_transaction
+ def no_op():
+ raise RuntimeError('an error occured')
+ self.assertRaises(RuntimeError, no_op)
+ self.failIf(
+ self.file_content is self._getTestFileContent(),
+ "Store wasn't reset properly.")
+
+ def test_writing_transaction_reset_store_on_commit_failure(self):
+ """The store should be reset even if committing the transaction fails.
+ """
+ class TransactionAborter:
+ """Make the next commit() fails."""
+ def newTransaction(self, txn):
+ pass
+
+ def beforeCompletion(self, txn):
+ raise RuntimeError('the commit will fail')
+ aborter = TransactionAborter()
+ transaction.manager.registerSynch(aborter)
+ try:
+ @write_transaction
+ def no_op():
+ pass
+ self.assertRaises(RuntimeError, no_op)
+ self.failIf(
+ self.file_content is self._getTestFileContent(),
+ "Store wasn't reset properly.")
+ finally:
+ transaction.manager.unregisterSynch(aborter)
+
+
+def test_suite():
+ return unittest.TestLoader().loadTestsFromName(__name__)
Follow ups