/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/cleanup.py

  • Committer: Andrew Bennetts
  • Date: 2009-10-26 06:23:14 UTC
  • mto: This revision was merged to the branch mainline in revision 4775.
  • Revision ID: andrew.bennetts@canonical.com-20091026062314-gc80ohde1kgp0fp8
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
If you want to be certain that the first, and only the first, error is raised,
32
32
then use::
33
33
 
34
 
    do_with_cleanups(do_something, cleanups)
 
34
    operation = OperationWithCleanups(lambda operation: do_something())
 
35
    operation.add_cleanup(cleanup_something)
 
36
    operation.run()
35
37
 
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
 
41
details.
39
42
"""
40
43
 
41
44
 
 
45
from collections import deque
42
46
import sys
43
47
from bzrlib import (
44
48
    debug,
70
74
 
71
75
def _run_cleanups(funcs):
72
76
    """Run a series of cleanup functions."""
73
 
    for func in funcs:
74
 
        _run_cleanup(func)
 
77
    for func, args, kwargs in funcs:
 
78
        _run_cleanup(func, *args, **kwargs)
75
79
 
76
80
 
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.
79
83
 
80
84
    This provides a way to add cleanups while the function-with-cleanups is
81
85
    running.
91
95
            do_something()
92
96
            operation.add_cleanup(something)
93
97
            # etc
 
98
 
 
99
    Note that the first argument passed to `some_func` will be the
 
100
    OperationWithCleanups object.
94
101
    """
95
102
 
96
103
    def __init__(self, func):
97
104
        self.func = func
98
 
        self.cleanups = []
99
 
 
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()
 
106
 
 
107
    def add_cleanup(self, cleanup_func, *args, **kwargs):
 
108
        """Add a cleanup to run.
 
109
 
 
110
        Cleanups may be added at any time before or during the execution of
 
111
        self.func.  Cleanups will be executed in LIFO order.
 
112
        """
 
113
        self.cleanups.appendleft((cleanup_func, args, kwargs))
103
114
 
104
115
    def run(self, *args, **kwargs):
105
 
        func = lambda: self.func(self, *args, **kwargs)
106
 
        return do_with_cleanups(func, self.cleanups)
107
 
 
108
 
 
109
 
def do_with_cleanups(func, cleanup_funcs):
 
116
        return _do_with_cleanups(
 
117
            self.cleanups, self.func, self, *args, **kwargs)
 
118
 
 
119
 
 
120
def _do_with_cleanups(cleanup_funcs, func, *args, **kwargs):
110
121
    """Run `func`, then call all the cleanup_funcs.
111
122
 
112
123
    All the cleanup_funcs are guaranteed to be run.  The first exception raised
116
127
    Conceptually similar to::
117
128
 
118
129
        try:
119
 
            return func()
 
130
            return func(*args, **kwargs)
120
131
        finally:
121
 
            for cleanup in cleanup_funcs:
122
 
                cleanup()
 
132
            for cleanup, cargs, ckwargs in cleanup_funcs:
 
133
                cleanup(*cargs, **ckwargs)
123
134
 
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
129
140
       propagated).
130
141
 
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.
133
144
    """
134
145
    # As correct as Python 2.4 allows.
135
146
    try:
136
 
        result = func()
 
147
        result = func(*args, **kwargs)
137
148
    except:
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
144
155
        # of them).
145
156
        exc_info = None
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:
151
162
                try:
152
 
                    cleanup()
 
163
                    cleanup(*c_args, **c_kwargs)
153
164
                except:
154
165
                    # This is the first cleanup to fail, so remember its
155
166
                    # details.
157
168
            else:
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