1##############################################################################
2#
3# Copyright (c) 2003 Zope Foundation and Contributors.
4# All Rights Reserved.
5#
6# This software is subject to the provisions of the Zope Public License,
7# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11# FOR A PARTICULAR PURPOSE
12#
13##############################################################################
14"""Tools to simplify transactions within applications."""
15
16from ZODB.POSException import ReadConflictError, ConflictError
17import transaction
18
19def _commit(note):
20    t = transaction.get()
21    if note:
22        t.note(note)
23    t.commit()
24
25def transact(f, note=None, retries=5):
26    """Returns transactional version of function argument f.
27
28    Higher-order function that converts a regular function into
29    a transactional function.  The transactional function will
30    retry up to retries time before giving up.  If note, it will
31    be added to the transaction metadata when it commits.
32
33    The retries occur on ConflictErrors.  If some other
34    TransactionError occurs, the transaction will not be retried.
35    """
36
37    # TODO:  deal with ZEO disconnected errors?
38
39    def g(*args, **kwargs):
40        n = retries
41        while n:
42            n -= 1
43            try:
44                r = f(*args, **kwargs)
45            except ReadConflictError as msg:
46                transaction.abort()
47                if not n:
48                    raise
49                continue
50            try:
51                _commit(note)
52            except ConflictError as msg:
53                transaction.abort()
54                if not n:
55                    raise
56                continue
57            return r
58        raise RuntimeError("couldn't commit transaction")
59    return g
60