1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
=================================
Transaction convenience support
=================================
with support
============
We can now use the with statement to define transaction boundaries.
.. doctest::
>>> import transaction.tests.savepointsample
>>> dm = transaction.tests.savepointsample.SampleSavepointDataManager()
>>> list(dm.keys())
[]
We can use it with a manager:
.. doctest::
>>> with transaction.manager as t:
... dm['z'] = 3
... t.note(u'test 3')
>>> dm['z']
3
>>> dm.last_note == 'test 3'
True
>>> with transaction.manager: #doctest ELLIPSIS
... dm['z'] = 4
... xxx
Traceback (most recent call last):
...
NameError: ... name 'xxx' is not defined
>>> dm['z']
3
On Python 2, you can also abbreviate ``with transaction.manager:`` as ``with
transaction:``. This does not work on Python 3 (see
http://bugs.python.org/issue12022).
Retries
=======
Commits can fail for transient reasons, especially conflicts.
Applications will often retry transactions some number of times to
overcome transient failures. This typically looks something like::
for i in range(3):
try:
with transaction.manager:
... some something ...
except SomeTransientException:
continue
else:
break
This is rather ugly and easy to get wrong.
Transaction managers provide two helpers for this case.
Running and retrying functions as transactions
----------------------------------------------
The first helper runs a function as a transaction::
def do_somthing():
"Do something"
... some something ...
transaction.manager.run(do_somthing)
You can also use this as a decorator, which executes the decorated
function immediately [#decorator-executes]_::
@transaction.manager.run
def _():
"Do something"
... some something ...
The transaction manager ``run`` method will run the function and
return the results. If the function raises a ``TransientError``, the
function will be retried a configurable number of times, 3 by
default. Any other exceptions will be raised.
The function name (if it isn't ``'_'``) and docstring, if any, are
added to the transaction description.
You can pass an integer number of times to try to the ``run`` method::
transaction.manager.run(do_somthing, 9)
@transaction.manager.run(9)
def _():
"Do something"
... some something ...
The default number of times to try is 3.
Retrying code blocks using a attempt iterator
---------------------------------------------
An older helper for running transactions uses an iterator of attempts::
for attempt in transaction.manager.attempts():
with attempt as t:
... some something ...
This runs the code block until it runs without a transient error or
until the number of attempts is exceeded. By default, it tries 3
times, but you can pass a number of attempts::
for attempt in transaction.manager.attempts(9):
with attempt as t:
... some something ...
.. [#decorator-executes] Some people find this easier to read, even
though the result isn't a decorated function, but rather the result of
calling it in a transaction. The function name ``_`` is used here to
emphasize that the function is essentially being used as an anonymous
function.
|