66
@contextlib.contextmanager
67
def StackContext(context_factory):
86
class StackContext(object):
68
87
'''Establishes the given context as a StackContext that will be transferred.
70
89
Note that the parameter is a callable that returns a context
71
90
manager, not the context itself. That is, where for a
72
non-transferable context manager you would say
91
non-transferable context manager you would say::
74
StackContext takes the function itself rather than its result:
95
StackContext takes the function itself rather than its result::
75
97
with StackContext(my_context):
77
old_contexts = _state.contexts
79
_state.contexts = old_contexts + (context_factory,)
80
with context_factory():
83
_state.contexts = old_contexts
85
@contextlib.contextmanager
99
def __init__(self, context_factory):
100
self.context_factory = context_factory
102
# Note that some of this code is duplicated in ExceptionStackContext
103
# below. ExceptionStackContext is more common and doesn't need
104
# the full generality of this class.
106
self.old_contexts = _state.contexts
107
# _state.contexts is a tuple of (class, arg) pairs
108
_state.contexts = (self.old_contexts +
109
((StackContext, self.context_factory),))
111
self.context = self.context_factory()
112
self.context.__enter__()
114
_state.contexts = self.old_contexts
117
def __exit__(self, type, value, traceback):
119
return self.context.__exit__(type, value, traceback)
121
_state.contexts = self.old_contexts
124
class ExceptionStackContext(object):
125
'''Specialization of StackContext for exception handling.
127
The supplied exception_handler function will be called in the
128
event of an uncaught exception in this context. The semantics are
129
similar to a try/finally clause, and intended use cases are to log
130
an error, close a socket, or similar cleanup actions. The
131
exc_info triple (type, value, traceback) will be passed to the
132
exception_handler function.
134
If the exception handler returns true, the exception will be
135
consumed and will not be propagated to other exception handlers.
137
def __init__(self, exception_handler):
138
self.exception_handler = exception_handler
141
self.old_contexts = _state.contexts
142
_state.contexts = (self.old_contexts +
143
((ExceptionStackContext, self.exception_handler),))
145
def __exit__(self, type, value, traceback):
148
return self.exception_handler(type, value, traceback)
150
_state.contexts = self.old_contexts
153
class NullContext(object):
87
154
'''Resets the StackContext.
89
156
Useful when creating a shared resource on demand (e.g. an AsyncHTTPClient)
90
157
where the stack that caused the creating is not relevant to future
93
old_contexts = _state.contexts
161
self.old_contexts = _state.contexts
95
162
_state.contexts = ()
98
_state.contexts = old_contexts
164
def __exit__(self, type, value, traceback):
165
_state.contexts = self.old_contexts
168
class _StackContextWrapper(functools.partial):
101
'''Returns a callable object that will resore the current StackContext
173
'''Returns a callable object that will restore the current StackContext
104
176
Use this whenever saving a callback to be executed later in a
105
177
different execution context (either in a different thread or
106
178
asynchronously in the same thread).
180
if fn is None or fn.__class__ is _StackContextWrapper:
110
182
# functools.wraps doesn't appear to work on functools.partial objects
111
183
#@functools.wraps(fn)
112
185
def wrapped(callback, contexts, *args, **kwargs):
186
if contexts is _state.contexts or not contexts:
187
callback(*args, **kwargs)
189
if not _state.contexts:
190
new_contexts = [cls(arg) for (cls, arg) in contexts]
113
191
# If we're moving down the stack, _state.contexts is a prefix
114
192
# of contexts. For each element of contexts not in that prefix,
115
193
# create a new StackContext object.
116
194
# If we're moving up the stack (or to an entirely different stack),
117
195
# _state.contexts will have elements not in contexts. Use
118
196
# NullContext to clear the state and then recreate from contexts.
119
if (len(_state.contexts) > len(contexts) or
197
elif (len(_state.contexts) > len(contexts) or
121
199
for a, b in izip(_state.contexts, contexts))):
122
200
# contexts have been removed or changed, so start over
123
201
new_contexts = ([NullContext()] +
124
[StackContext(c) for c in contexts])
202
[cls(arg) for (cls,arg) in contexts])
126
new_contexts = [StackContext(c)
127
for c in contexts[len(_state.contexts):]]
129
with contextlib.nested(*new_contexts):
204
new_contexts = [cls(arg)
205
for (cls, arg) in contexts[len(_state.contexts):]]
206
if len(new_contexts) > 1:
207
with _nested(*new_contexts):
208
callback(*args, **kwargs)
210
with new_contexts[0]:
130
211
callback(*args, **kwargs)
132
213
callback(*args, **kwargs)
133
if getattr(fn, 'stack_context_wrapped', False):
135
contexts = _state.contexts
136
result = functools.partial(wrapped, fn, contexts)
137
result.stack_context_wrapped = True
215
return _StackContextWrapper(wrapped, fn, _state.contexts)
217
return _StackContextWrapper(fn)
220
@contextlib.contextmanager
221
def _nested(*managers):
222
"""Support multiple context managers in a single with-statement.
224
Copied from the python 2.6 standard library. It's no longer present
225
in python 3 because the with statement natively supports multiple
226
context managers, but that doesn't help if the list of context
227
managers is not known until runtime.
231
exc = (None, None, None)
235
enter = mgr.__enter__
246
exc = (None, None, None)
249
if exc != (None, None, None):
250
# Don't rely on sys.exc_info() still containing
251
# the right information. Another exception may
252
# have been raised and caught by an exit method