1
# Copyright (C) 2009, 2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
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.
23
from __future__ import absolute_import
25
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
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()
192
suppressed_exc = True
193
pending_raise = False
194
exc_details = (None, None, None)
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