@@ -69,9 +69,6 @@ def die_on_error():
69
69
70
70
from __future__ import absolute_import , division , print_function , with_statement
71
71
72
- import contextlib
73
- import functools
74
- import operator
75
72
import sys
76
73
import threading
77
74
@@ -84,7 +81,7 @@ class StackContextInconsistentError(Exception):
84
81
85
82
class _State (threading .local ):
86
83
def __init__ (self ):
87
- self .contexts = ()
84
+ self .contexts = (tuple (), None )
88
85
_state = _State ()
89
86
90
87
@@ -108,34 +105,41 @@ class StackContext(object):
108
105
context that are currently pending). This is an advanced feature
109
106
and not necessary in most applications.
110
107
"""
111
- def __init__ (self , context_factory , _active_cell = None ):
108
+ def __init__ (self , context_factory ):
112
109
self .context_factory = context_factory
113
- self .active_cell = _active_cell or [True ]
110
+ self .contexts = []
111
+
112
+ # StackContext protocol
113
+ def enter (self ):
114
+ context = self .context_factory ()
115
+ self .contexts .append (context )
116
+ context .__enter__ ()
117
+
118
+ def exit (self , type , value , traceback ):
119
+ context = self .contexts .pop ()
120
+ context .__exit__ (type , value , traceback )
114
121
115
122
# Note that some of this code is duplicated in ExceptionStackContext
116
123
# below. ExceptionStackContext is more common and doesn't need
117
124
# the full generality of this class.
118
125
def __enter__ (self ):
119
126
self .old_contexts = _state .contexts
120
- # _state.contexts is a tuple of (class, arg, active_cell) tuples
121
- self .new_contexts = (self .old_contexts +
122
- ((StackContext , self .context_factory ,
123
- self .active_cell ),))
127
+ self .new_contexts = (self .old_contexts [0 ] + (self ,), self )
124
128
_state .contexts = self .new_contexts
129
+
125
130
try :
126
- self .context = self .context_factory ()
127
- self .context .__enter__ ()
128
- except Exception :
131
+ self .enter ()
132
+ except :
129
133
_state .contexts = self .old_contexts
130
134
raise
131
- return lambda : operator .setitem (self .active_cell , 0 , False )
132
135
133
136
def __exit__ (self , type , value , traceback ):
134
137
try :
135
- return self .context . __exit__ (type , value , traceback )
138
+ self .exit (type , value , traceback )
136
139
finally :
137
140
final_contexts = _state .contexts
138
141
_state .contexts = self .old_contexts
142
+
139
143
# Generator coroutines and with-statements with non-local
140
144
# effects interact badly. Check here for signs of
141
145
# the stack getting out of sync.
@@ -146,7 +150,6 @@ def __exit__(self, type, value, traceback):
146
150
raise StackContextInconsistentError (
147
151
'stack_context inconsistency (may be caused by yield '
148
152
'within a "with StackContext" block)' )
149
- self .old_contexts = self .new_contexts = None
150
153
151
154
152
155
class ExceptionStackContext (object ):
@@ -162,17 +165,17 @@ class ExceptionStackContext(object):
162
165
If the exception handler returns true, the exception will be
163
166
consumed and will not be propagated to other exception handlers.
164
167
"""
165
- def __init__ (self , exception_handler , _active_cell = None ):
168
+ def __init__ (self , exception_handler ):
166
169
self .exception_handler = exception_handler
167
- self .active_cell = _active_cell or [True ]
170
+
171
+ def exit (self , type , value , traceback ):
172
+ if type is not None :
173
+ return self .exception_handler (type , value , traceback )
168
174
169
175
def __enter__ (self ):
170
176
self .old_contexts = _state .contexts
171
- self .new_contexts = (self .old_contexts +
172
- ((ExceptionStackContext , self .exception_handler ,
173
- self .active_cell ),))
177
+ self .new_contexts = (self .old_contexts [0 ], self )
174
178
_state .contexts = self .new_contexts
175
- return lambda : operator .setitem (self .active_cell , 0 , False )
176
179
177
180
def __exit__ (self , type , value , traceback ):
178
181
try :
@@ -181,11 +184,11 @@ def __exit__(self, type, value, traceback):
181
184
finally :
182
185
final_contexts = _state .contexts
183
186
_state .contexts = self .old_contexts
187
+
184
188
if final_contexts is not self .new_contexts :
185
189
raise StackContextInconsistentError (
186
190
'stack_context inconsistency (may be caused by yield '
187
191
'within a "with StackContext" block)' )
188
- self .old_contexts = self .new_contexts = None
189
192
190
193
191
194
class NullContext (object ):
@@ -197,16 +200,12 @@ class NullContext(object):
197
200
"""
198
201
def __enter__ (self ):
199
202
self .old_contexts = _state .contexts
200
- _state .contexts = ()
203
+ _state .contexts = (tuple (), None )
201
204
202
205
def __exit__ (self , type , value , traceback ):
203
206
_state .contexts = self .old_contexts
204
207
205
208
206
- class _StackContextWrapper (functools .partial ):
207
- pass
208
-
209
-
210
209
def wrap (fn ):
211
210
"""Returns a callable object that will restore the current `StackContext`
212
211
when executed.
@@ -215,64 +214,86 @@ def wrap(fn):
215
214
different execution context (either in a different thread or
216
215
asynchronously in the same thread).
217
216
"""
218
- if fn is None or fn .__class__ is _StackContextWrapper :
217
+ # Check if function is already wrapped
218
+ if fn is None or hasattr (fn , '_wrapped' ):
219
219
return fn
220
- # functools.wraps doesn't appear to work on functools.partial objects
221
- #@functools.wraps(fn)
222
220
221
+ # Capture current stack head
222
+ contexts = _state .contexts
223
+
224
+ #@functools.wraps
223
225
def wrapped (* args , ** kwargs ):
224
- callback , contexts , args = args [0 ], args [1 ], args [2 :]
225
-
226
- if _state .contexts :
227
- new_contexts = [NullContext ()]
228
- else :
229
- new_contexts = []
230
- if contexts :
231
- new_contexts .extend (cls (arg , active_cell )
232
- for (cls , arg , active_cell ) in contexts
233
- if active_cell [0 ])
234
- if len (new_contexts ) > 1 :
235
- with _nested (* new_contexts ):
236
- callback (* args , ** kwargs )
237
- elif new_contexts :
238
- with new_contexts [0 ]:
239
- callback (* args , ** kwargs )
240
- else :
241
- callback (* args , ** kwargs )
242
- return _StackContextWrapper (wrapped , fn , _state .contexts )
243
-
244
-
245
- @contextlib .contextmanager
246
- def _nested (* managers ):
247
- """Support multiple context managers in a single with-statement.
248
-
249
- Copied from the python 2.6 standard library. It's no longer present
250
- in python 3 because the with statement natively supports multiple
251
- context managers, but that doesn't help if the list of context
252
- managers is not known until runtime.
253
- """
254
- exits = []
255
- vars = []
256
- exc = (None , None , None )
257
- try :
258
- for mgr in managers :
259
- exit = mgr .__exit__
260
- enter = mgr .__enter__
261
- vars .append (enter ())
262
- exits .append (exit )
263
- yield vars
264
- except :
265
- exc = sys .exc_info ()
266
- finally :
267
- while exits :
268
- exit = exits .pop ()
269
- try :
270
- if exit (* exc ):
271
- exc = (None , None , None )
272
- except :
273
- exc = sys .exc_info ()
274
- if exc != (None , None , None ):
275
- # Don't rely on sys.exc_info() still containing
276
- # the right information. Another exception may
277
- # have been raised and caught by an exit method
278
- raise_exc_info (exc )
226
+ try :
227
+ # Force local state - switch to new stack chain
228
+ current_state = _state .contexts
229
+ _state .contexts = contexts
230
+
231
+ # Current exception
232
+ exc = (None , None , None )
233
+ top = None
234
+
235
+ # Apply stack contexts
236
+ last_ctx = 0
237
+ stack = contexts [0 ]
238
+
239
+ # Apply state
240
+ for n in stack :
241
+ try :
242
+ n .enter ()
243
+ last_ctx += 1
244
+ except :
245
+ # Exception happened. Record exception info and store top-most handler
246
+ exc = sys .exc_info ()
247
+ top = n .old_contexts [1 ]
248
+
249
+ # Execute callback if no exception happened while restoring state
250
+ if top is None :
251
+ try :
252
+ fn (* args , ** kwargs )
253
+ except :
254
+ exc = sys .exc_info ()
255
+ top = contexts [1 ]
256
+
257
+ # If there was exception, try to handle it by going through the exception chain
258
+ if top is not None :
259
+ exc = _handle_exception (top , exc )
260
+ else :
261
+ # Otherwise take shorter path and run stack contexts in reverse order
262
+ while last_ctx > 0 :
263
+ last_ctx -= 1
264
+ c = stack [last_ctx ]
265
+
266
+ try :
267
+ c .exit (* exc )
268
+ except :
269
+ exc = sys .exc_info ()
270
+ top = c .old_contexts [1 ]
271
+ break
272
+ else :
273
+ top = None
274
+
275
+ # If if exception happened while unrolling, take longer exception handler path
276
+ if top is not None :
277
+ exc = _handle_exception (top , exc )
278
+
279
+ # If exception was not handled, raise it
280
+ if exc != (None , None , None ):
281
+ raise_exc_info (exc )
282
+ finally :
283
+ _state .contexts = current_state
284
+
285
+ wrapped ._wrapped = True
286
+ return wrapped
287
+
288
+
289
+ def _handle_exception (tail , exc ):
290
+ while tail is not None :
291
+ try :
292
+ if tail .exit (* exc ):
293
+ exc = (None , None , None )
294
+ except :
295
+ exc = sys .exc_info ()
296
+
297
+ tail = tail .old_contexts [1 ]
298
+
299
+ return exc
0 commit comments