/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: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Helpers for managing cleanup functions and the errors they might raise.
18
18
 
19
 
This currently just contains a copy of contextlib.ExitStack, available
20
 
even on older versions of Python.
 
19
The usual way to run cleanup code in Python is::
 
20
 
 
21
    try:
 
22
        do_something()
 
23
    finally:
 
24
        cleanup_something()
 
25
 
 
26
However if both `do_something` and `cleanup_something` raise an exception
 
27
Python will forget the original exception and propagate the one from
 
28
cleanup_something.  Unfortunately, this is almost always much less useful than
 
29
the original exception.
 
30
 
 
31
If you want to be certain that the first, and only the first, error is raised,
 
32
then use::
 
33
 
 
34
    operation = OperationWithCleanups(do_something)
 
35
    operation.add_cleanup(cleanup_something)
 
36
    operation.run_simple()
 
37
 
 
38
This is more inconvenient (because you need to make every try block a
 
39
function), but will ensure that the first error encountered is the one raised,
 
40
while also ensuring all cleanups are run.  See OperationWithCleanups for more
 
41
details.
21
42
"""
22
43
 
23
 
from __future__ import absolute_import
24
44
 
25
45
from collections import deque
26
46
import sys
27
 
 
28
 
 
29
 
try:
30
 
    from contextlib import ExitStack
31
 
except ImportError:
32
 
    # Copied from the Python standard library on Python 3.4.
33
 
    # Copyright: Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
34
 
    #   2009, 2010, 2011 Python Software Foundation
35
 
    #
36
 
    # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
37
 
    # --------------------------------------------
38
 
    # .
39
 
    # 1. This LICENSE AGREEMENT is between the Python Software Foundation
40
 
    # ("PSF"), and the Individual or Organization ("Licensee") accessing and
41
 
    # otherwise using this software ("Python") in source or binary form and
42
 
    # its associated documentation.
43
 
    # .
44
 
    # 2. Subject to the terms and conditions of this License Agreement, PSF hereby
45
 
    # grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
46
 
    # analyze, test, perform and/or display publicly, prepare derivative works,
47
 
    # distribute, and otherwise use Python alone or in any derivative version,
48
 
    # provided, however, that PSF's License Agreement and PSF's notice of copyright,
49
 
    # i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
50
 
    # 2011 Python Software Foundation; All Rights Reserved" are retained in Python
51
 
    # alone or in any derivative version prepared by Licensee.
52
 
    # .
53
 
    # 3. In the event Licensee prepares a derivative work that is based on
54
 
    # or incorporates Python or any part thereof, and wants to make
55
 
    # the derivative work available to others as provided herein, then
56
 
    # Licensee hereby agrees to include in any such work a brief summary of
57
 
    # the changes made to Python.
58
 
    # .
59
 
    # 4. PSF is making Python available to Licensee on an "AS IS"
60
 
    # basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
61
 
    # IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
62
 
    # DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
63
 
    # FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
64
 
    # INFRINGE ANY THIRD PARTY RIGHTS.
65
 
    # .
66
 
    # 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
67
 
    # FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
68
 
    # A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
69
 
    # OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
70
 
    # .
71
 
    # 6. This License Agreement will automatically terminate upon a material
72
 
    # breach of its terms and conditions.
73
 
    # .
74
 
    # 7. Nothing in this License Agreement shall be deemed to create any
75
 
    # relationship of agency, partnership, or joint venture between PSF and
76
 
    # Licensee.  This License Agreement does not grant permission to use PSF
77
 
    # trademarks or trade name in a trademark sense to endorse or promote
78
 
    # products or services of Licensee, or any third party.
79
 
    # .
80
 
    # 8. By copying, installing or otherwise using Python, Licensee
81
 
    # agrees to be bound by the terms and conditions of this License
82
 
    # Agreement.
83
 
 
84
 
    def _reraise_with_existing_context(exc_details):
85
 
        # Use 3 argument raise in Python 2,
86
 
        # but use exec to avoid SyntaxError in Python 3
87
 
        exc_type, exc_value, exc_tb = exc_details
88
 
        exec("raise exc_type, exc_value, exc_tb")
89
 
 
90
 
 
91
 
    # Inspired by discussions on http://bugs.python.org/issue13585
92
 
    class ExitStack(object):
93
 
        """Context manager for dynamic management of a stack of exit callbacks
94
 
 
95
 
        For example:
96
 
 
97
 
            with ExitStack() as stack:
98
 
                files = [stack.enter_context(open(fname)) for fname in filenames]
99
 
                # All opened files will automatically be closed at the end of
100
 
                # the with statement, even if attempts to open files later
101
 
                # in the list raise an exception
102
 
 
 
47
from bzrlib import (
 
48
    debug,
 
49
    trace,
 
50
    )
 
51
 
 
52
def _log_cleanup_error(exc):
 
53
    trace.mutter('Cleanup failed:')
 
54
    trace.log_exception_quietly()
 
55
    if 'cleanup' in debug.debug_flags:
 
56
        trace.warning('bzr: warning: Cleanup failed: %s', exc)
 
57
 
 
58
 
 
59
def _run_cleanup(func, *args, **kwargs):
 
60
    """Run func(*args, **kwargs), logging but not propagating any error it
 
61
    raises.
 
62
 
 
63
    :returns: True if func raised no errors, else False.
 
64
    """
 
65
    try:
 
66
        func(*args, **kwargs)
 
67
    except KeyboardInterrupt:
 
68
        raise
 
69
    except Exception, exc:
 
70
        _log_cleanup_error(exc)
 
71
        return False
 
72
    return True
 
73
 
 
74
 
 
75
def _run_cleanups(funcs):
 
76
    """Run a series of cleanup functions."""
 
77
    for func, args, kwargs in funcs:
 
78
        _run_cleanup(func, *args, **kwargs)
 
79
 
 
80
 
 
81
class ObjectWithCleanups(object):
 
82
    """A mixin for objects that hold a cleanup list.
 
83
 
 
84
    Subclass or client code can call add_cleanup and then later `cleanup_now`.
 
85
    """
 
86
    def __init__(self):
 
87
        self.cleanups = deque()
 
88
 
 
89
    def add_cleanup(self, cleanup_func, *args, **kwargs):
 
90
        """Add a cleanup to run.
 
91
 
 
92
        Cleanups may be added at any time.  
 
93
        Cleanups will be executed in LIFO order.
103
94
        """
104
 
        def __init__(self):
105
 
            self._exit_callbacks = deque()
106
 
 
107
 
        def pop_all(self):
108
 
            """Preserve the context stack by transferring it to a new instance"""
109
 
            new_stack = type(self)()
110
 
            new_stack._exit_callbacks = self._exit_callbacks
111
 
            self._exit_callbacks = deque()
112
 
            return new_stack
113
 
 
114
 
        def _push_cm_exit(self, cm, cm_exit):
115
 
            """Helper to correctly register callbacks to __exit__ methods"""
116
 
            def _exit_wrapper(*exc_details):
117
 
                return cm_exit(cm, *exc_details)
118
 
            _exit_wrapper.__self__ = cm
119
 
            self.push(_exit_wrapper)
120
 
 
121
 
        def push(self, exit):
122
 
            """Registers a callback with the standard __exit__ method signature
123
 
 
124
 
            Can suppress exceptions the same way __exit__ methods can.
125
 
 
126
 
            Also accepts any object with an __exit__ method (registering a call
127
 
            to the method instead of the object itself)
128
 
            """
129
 
            # We use an unbound method rather than a bound method to follow
130
 
            # the standard lookup behaviour for special methods
131
 
            _cb_type = type(exit)
132
 
            try:
133
 
                exit_method = _cb_type.__exit__
134
 
            except AttributeError:
135
 
                # Not a context manager, so assume its a callable
136
 
                self._exit_callbacks.append(exit)
137
 
            else:
138
 
                self._push_cm_exit(exit, exit_method)
139
 
            return exit # Allow use as a decorator
140
 
 
141
 
        def callback(self, callback, *args, **kwds):
142
 
            """Registers an arbitrary callback and arguments.
143
 
 
144
 
            Cannot suppress exceptions.
145
 
            """
146
 
            def _exit_wrapper(exc_type, exc, tb):
147
 
                callback(*args, **kwds)
148
 
            # We changed the signature, so using @wraps is not appropriate, but
149
 
            # setting __wrapped__ may still help with introspection
150
 
            _exit_wrapper.__wrapped__ = callback
151
 
            self.push(_exit_wrapper)
152
 
            return callback # Allow use as a decorator
153
 
 
154
 
        def enter_context(self, cm):
155
 
            """Enters the supplied context manager
156
 
 
157
 
            If successful, also pushes its __exit__ method as a callback and
158
 
            returns the result of the __enter__ method.
159
 
            """
160
 
            # We look up the special methods on the type to match the with statement
161
 
            _cm_type = type(cm)
162
 
            _exit = _cm_type.__exit__
163
 
            result = _cm_type.__enter__(cm)
164
 
            self._push_cm_exit(cm, _exit)
165
 
            return result
166
 
 
167
 
        def close(self):
168
 
            """Immediately unwind the context stack"""
169
 
            self.__exit__(None, None, None)
170
 
 
171
 
        def __enter__(self):
172
 
            return self
173
 
 
174
 
        def __exit__(self, *exc_details):
175
 
            received_exc = exc_details[0] is not None
176
 
 
177
 
            # We manipulate the exception state so it behaves as though
178
 
            # we were actually nesting multiple with statements
179
 
            frame_exc = sys.exc_info()[1]
180
 
            def _make_context_fixer(frame_exc):
181
 
                return lambda new_exc, old_exc: None
182
 
            _fix_exception_context = _make_context_fixer(frame_exc)
183
 
 
184
 
            # Callbacks are invoked in LIFO order to match the behaviour of
185
 
            # nested context managers
186
 
            suppressed_exc = False
187
 
            pending_raise = False
188
 
            while self._exit_callbacks:
189
 
                cb = self._exit_callbacks.pop()
 
95
        self.cleanups.appendleft((cleanup_func, args, kwargs))
 
96
 
 
97
    def cleanup_now(self):
 
98
        _run_cleanups(self.cleanups)
 
99
        self.cleanups.clear()
 
100
 
 
101
 
 
102
class OperationWithCleanups(ObjectWithCleanups):
 
103
    """A way to run some code with a dynamic cleanup list.
 
104
 
 
105
    This provides a way to add cleanups while the function-with-cleanups is
 
106
    running.
 
107
 
 
108
    Typical use::
 
109
 
 
110
        operation = OperationWithCleanups(some_func)
 
111
        operation.run(args...)
 
112
 
 
113
    where `some_func` is::
 
114
 
 
115
        def some_func(operation, args, ...):
 
116
            do_something()
 
117
            operation.add_cleanup(something)
 
118
            # etc
 
119
 
 
120
    Note that the first argument passed to `some_func` will be the
 
121
    OperationWithCleanups object.  To invoke `some_func` without that, use
 
122
    `run_simple` instead of `run`.
 
123
    """
 
124
 
 
125
    def __init__(self, func):
 
126
        super(OperationWithCleanups, self).__init__()
 
127
        self.func = func
 
128
 
 
129
    def run(self, *args, **kwargs):
 
130
        return _do_with_cleanups(
 
131
            self.cleanups, self.func, self, *args, **kwargs)
 
132
 
 
133
    def run_simple(self, *args, **kwargs):
 
134
        return _do_with_cleanups(
 
135
            self.cleanups, self.func, *args, **kwargs)
 
136
 
 
137
 
 
138
def _do_with_cleanups(cleanup_funcs, func, *args, **kwargs):
 
139
    """Run `func`, then call all the cleanup_funcs.
 
140
 
 
141
    All the cleanup_funcs are guaranteed to be run.  The first exception raised
 
142
    by func or any of the cleanup_funcs is the one that will be propagted by
 
143
    this function (subsequent errors are caught and logged).
 
144
 
 
145
    Conceptually similar to::
 
146
 
 
147
        try:
 
148
            return func(*args, **kwargs)
 
149
        finally:
 
150
            for cleanup, cargs, ckwargs in cleanup_funcs:
 
151
                cleanup(*cargs, **ckwargs)
 
152
 
 
153
    It avoids several problems with using try/finally directly:
 
154
     * an exception from func will not be obscured by a subsequent exception
 
155
       from a cleanup.
 
156
     * an exception from a cleanup will not prevent other cleanups from
 
157
       running (but the first exception encountered is still the one
 
158
       propagated).
 
159
 
 
160
    Unike `_run_cleanup`, `_do_with_cleanups` can propagate an exception from a
 
161
    cleanup, but only if there is no exception from func.
 
162
    """
 
163
    # As correct as Python 2.4 allows.
 
164
    try:
 
165
        result = func(*args, **kwargs)
 
166
    except:
 
167
        # We have an exception from func already, so suppress cleanup errors.
 
168
        _run_cleanups(cleanup_funcs)
 
169
        raise
 
170
    else:
 
171
        # No exception from func, so allow the first exception from
 
172
        # cleanup_funcs to propagate if one occurs (but only after running all
 
173
        # of them).
 
174
        exc_info = None
 
175
        for cleanup, c_args, c_kwargs in cleanup_funcs:
 
176
            # XXX: Hmm, if KeyboardInterrupt arrives at exactly this line, we
 
177
            # won't run all cleanups... perhaps we should temporarily install a
 
178
            # SIGINT handler?
 
179
            if exc_info is None:
190
180
                try:
191
 
                    if cb(*exc_details):
192
 
                        suppressed_exc = True
193
 
                        pending_raise = False
194
 
                        exc_details = (None, None, None)
 
181
                    cleanup(*c_args, **c_kwargs)
195
182
                except:
196
 
                    new_exc_details = sys.exc_info()
197
 
                    # simulate the stack of exceptions by setting the context
198
 
                    _fix_exception_context(new_exc_details[1], exc_details[1])
199
 
                    pending_raise = True
200
 
                    exc_details = new_exc_details
201
 
            if pending_raise:
202
 
                _reraise_with_existing_context(exc_details)
203
 
            return received_exc and suppressed_exc
 
183
                    # This is the first cleanup to fail, so remember its
 
184
                    # details.
 
185
                    exc_info = sys.exc_info()
 
186
            else:
 
187
                # We already have an exception to propagate, so log any errors
 
188
                # but don't propagate them.
 
189
                _run_cleanup(cleanup, *c_args, **kwargs)
 
190
        if exc_info is not None:
 
191
            raise exc_info[0], exc_info[1], exc_info[2]
 
192
        # No error, so we can return the result
 
193
        return result
 
194
 
 
195