17
17
"""Helpers for managing cleanup functions and the errors they might raise.
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::
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.
31
If you want to be certain that the first, and only the first, error is raised,
34
operation = OperationWithCleanups(do_something)
35
operation.add_cleanup(cleanup_something)
36
operation.run_simple()
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
23
from __future__ import absolute_import
25
45
from collections import deque
30
from contextlib import ExitStack
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
36
# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
37
# --------------------------------------------
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.
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.
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.
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.
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.
71
# 6. This License Agreement will automatically terminate upon a material
72
# breach of its terms and conditions.
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.
80
# 8. By copying, installing or otherwise using Python, Licensee
81
# agrees to be bound by the terms and conditions of this License
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")
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
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
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)
59
def _run_cleanup(func, *args, **kwargs):
60
"""Run func(*args, **kwargs), logging but not propagating any error it
63
:returns: True if func raised no errors, else False.
67
except KeyboardInterrupt:
69
except Exception, exc:
70
_log_cleanup_error(exc)
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)
81
class OperationWithCleanups(object):
82
"""A way to run some code with a dynamic cleanup list.
84
This provides a way to add cleanups while the function-with-cleanups is
89
operation = OperationWithCleanups(some_func)
90
operation.run(args...)
92
where `some_func` is::
94
def some_func(operation, args, ...):
96
operation.add_cleanup(something)
99
Note that the first argument passed to `some_func` will be the
100
OperationWithCleanups object. To invoke `some_func` without that, use
101
`run_simple` instead of `run`.
104
def __init__(self, func):
106
self.cleanups = deque()
108
def add_cleanup(self, cleanup_func, *args, **kwargs):
109
"""Add a cleanup to run.
111
Cleanups may be added at any time before or during the execution of
112
self.func. Cleanups will be executed in LIFO order.
105
self._exit_callbacks = deque()
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()
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)
121
def push(self, exit):
122
"""Registers a callback with the standard __exit__ method signature
124
Can suppress exceptions the same way __exit__ methods can.
126
Also accepts any object with an __exit__ method (registering a call
127
to the method instead of the object itself)
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)
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)
138
self._push_cm_exit(exit, exit_method)
139
return exit # Allow use as a decorator
141
def callback(self, callback, *args, **kwds):
142
"""Registers an arbitrary callback and arguments.
144
Cannot suppress exceptions.
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
154
def enter_context(self, cm):
155
"""Enters the supplied context manager
157
If successful, also pushes its __exit__ method as a callback and
158
returns the result of the __enter__ method.
160
# We look up the special methods on the type to match the with statement
162
_exit = _cm_type.__exit__
163
result = _cm_type.__enter__(cm)
164
self._push_cm_exit(cm, _exit)
168
"""Immediately unwind the context stack"""
169
self.__exit__(None, None, None)
174
def __exit__(self, *exc_details):
175
received_exc = exc_details[0] is not None
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)
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()
114
self.cleanups.appendleft((cleanup_func, args, kwargs))
116
def run(self, *args, **kwargs):
117
return _do_with_cleanups(
118
self.cleanups, self.func, self, *args, **kwargs)
120
def run_simple(self, *args, **kwargs):
121
return _do_with_cleanups(
122
self.cleanups, self.func, *args, **kwargs)
124
def cleanup_now(self):
125
_run_cleanups(self.cleanups)
126
self.cleanups.clear()
129
def _do_with_cleanups(cleanup_funcs, func, *args, **kwargs):
130
"""Run `func`, then call all the cleanup_funcs.
132
All the cleanup_funcs are guaranteed to be run. The first exception raised
133
by func or any of the cleanup_funcs is the one that will be propagted by
134
this function (subsequent errors are caught and logged).
136
Conceptually similar to::
139
return func(*args, **kwargs)
141
for cleanup, cargs, ckwargs in cleanup_funcs:
142
cleanup(*cargs, **ckwargs)
144
It avoids several problems with using try/finally directly:
145
* an exception from func will not be obscured by a subsequent exception
147
* an exception from a cleanup will not prevent other cleanups from
148
running (but the first exception encountered is still the one
151
Unike `_run_cleanup`, `_do_with_cleanups` can propagate an exception from a
152
cleanup, but only if there is no exception from func.
154
# As correct as Python 2.4 allows.
156
result = func(*args, **kwargs)
158
# We have an exception from func already, so suppress cleanup errors.
159
_run_cleanups(cleanup_funcs)
162
# No exception from func, so allow the first exception from
163
# cleanup_funcs to propagate if one occurs (but only after running all
166
for cleanup, c_args, c_kwargs in cleanup_funcs:
167
# XXX: Hmm, if KeyboardInterrupt arrives at exactly this line, we
168
# won't run all cleanups... perhaps we should temporarily install a
192
suppressed_exc = True
193
pending_raise = False
194
exc_details = (None, None, None)
172
cleanup(*c_args, **c_kwargs)
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])
200
exc_details = new_exc_details
202
_reraise_with_existing_context(exc_details)
203
return received_exc and suppressed_exc
174
# This is the first cleanup to fail, so remember its
176
exc_info = sys.exc_info()
178
# We already have an exception to propagate, so log any errors
179
# but don't propagate them.
180
_run_cleanup(cleanup, *c_args, **kwargs)
181
if exc_info is not None:
182
raise exc_info[0], exc_info[1], exc_info[2]
183
# No error, so we can return the result