31
31
If you want to be certain that the first, and only the first, error is raised,
34
do_with_cleanups(do_something, cleanups)
34
operation = OperationWithCleanups(lambda operation: do_something())
35
operation.add_cleanup(cleanup_something)
36
38
This is more inconvenient (because you need to make every try block a
37
39
function), but will ensure that the first error encountered is the one raised,
38
while also ensuring all cleanups are run.
40
while also ensuring all cleanups are run. See OperationWithCleanups for more
45
from collections import deque
43
47
from bzrlib import (
71
75
def _run_cleanups(funcs):
72
76
"""Run a series of cleanup functions."""
77
for func, args, kwargs in funcs:
78
_run_cleanup(func, *args, **kwargs)
77
81
class OperationWithCleanups(object):
78
"""A helper for using do_with_cleanups with a dynamic cleanup list.
82
"""A way to run some code with a dynamic cleanup list.
80
84
This provides a way to add cleanups while the function-with-cleanups is
92
96
operation.add_cleanup(something)
99
Note that the first argument passed to `some_func` will be the
100
OperationWithCleanups object.
96
103
def __init__(self, func):
100
def add_cleanup(self, cleanup_func):
101
"""Add a cleanup to run. Cleanups will be executed in LIFO order."""
102
self.cleanups.insert(0, cleanup_func)
105
self.cleanups = deque()
107
def add_cleanup(self, cleanup_func, *args, **kwargs):
108
"""Add a cleanup to run.
110
Cleanups may be added at any time before or during the execution of
111
self.func. Cleanups will be executed in LIFO order.
113
self.cleanups.appendleft((cleanup_func, args, kwargs))
104
115
def run(self, *args, **kwargs):
105
func = lambda: self.func(self, *args, **kwargs)
106
return do_with_cleanups(func, self.cleanups)
109
def do_with_cleanups(func, cleanup_funcs):
116
return _do_with_cleanups(
117
self.cleanups, self.func, self, *args, **kwargs)
120
def _do_with_cleanups(cleanup_funcs, func, *args, **kwargs):
110
121
"""Run `func`, then call all the cleanup_funcs.
112
123
All the cleanup_funcs are guaranteed to be run. The first exception raised
116
127
Conceptually similar to::
130
return func(*args, **kwargs)
121
for cleanup in cleanup_funcs:
132
for cleanup, cargs, ckwargs in cleanup_funcs:
133
cleanup(*cargs, **ckwargs)
124
135
It avoids several problems with using try/finally directly:
125
136
* an exception from func will not be obscured by a subsequent exception
128
139
running (but the first exception encountered is still the one
131
Unike `_run_cleanup`, `do_with_cleanups` can propagate an exception from a
142
Unike `_run_cleanup`, `_do_with_cleanups` can propagate an exception from a
132
143
cleanup, but only if there is no exception from func.
134
145
# As correct as Python 2.4 allows.
147
result = func(*args, **kwargs)
138
149
# We have an exception from func already, so suppress cleanup errors.
139
150
_run_cleanups(cleanup_funcs)
143
154
# cleanup_funcs to propagate if one occurs (but only after running all
146
for cleanup in cleanup_funcs:
157
for cleanup, c_args, c_kwargs in cleanup_funcs:
147
158
# XXX: Hmm, if KeyboardInterrupt arrives at exactly this line, we
148
159
# won't run all cleanups... perhaps we should temporarily install a
149
160
# SIGINT handler?
150
161
if exc_info is None:
163
cleanup(*c_args, **c_kwargs)
154
165
# This is the first cleanup to fail, so remember its
158
169
# We already have an exception to propagate, so log any errors
159
170
# but don't propagate them.
160
_run_cleanup(cleanup)
171
_run_cleanup(cleanup, *c_args, **kwargs)
161
172
if exc_info is not None:
162
173
raise exc_info[0], exc_info[1], exc_info[2]
163
174
# No error, so we can return the result