blob: e37fde8a020900b1a398122b7caac1d2daa734e6 [file] [log] [blame]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00001"""Utilities for with-statement contexts. See PEP 343."""
2
3import sys
Christian Heimes81ee3ef2008-05-04 22:42:01 +00004from functools import wraps
Raymond Hettinger91e3b9d2009-05-28 22:20:03 +00005from warnings import warn
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00006
Michael Foordb3a89842010-06-30 12:17:50 +00007__all__ = ["contextmanager", "closing", "ContextDecorator"]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00008
Michael Foordb3a89842010-06-30 12:17:50 +00009
10class ContextDecorator(object):
11 "A base class or mixin that enables context managers to work as decorators."
12 def __call__(self, func):
13 @wraps(func)
14 def inner(*args, **kwds):
15 with self:
16 return func(*args, **kwds)
17 return inner
18
19
20class GeneratorContextManager(ContextDecorator):
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000021 """Helper for @contextmanager decorator."""
22
23 def __init__(self, gen):
24 self.gen = gen
25
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000026 def __enter__(self):
27 try:
Georg Brandla18af4e2007-04-21 15:47:16 +000028 return next(self.gen)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000029 except StopIteration:
30 raise RuntimeError("generator didn't yield")
31
32 def __exit__(self, type, value, traceback):
33 if type is None:
34 try:
Georg Brandla18af4e2007-04-21 15:47:16 +000035 next(self.gen)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000036 except StopIteration:
37 return
38 else:
39 raise RuntimeError("generator didn't stop")
40 else:
Guido van Rossum2cc30da2007-11-02 23:46:40 +000041 if value is None:
42 # Need to force instantiation so we can reliably
43 # tell if we get the same exception back
44 value = type()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000045 try:
46 self.gen.throw(type, value, traceback)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000047 raise RuntimeError("generator didn't stop after throw()")
Guido van Rossumb940e112007-01-10 16:19:56 +000048 except StopIteration as exc:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000049 # Suppress the exception *unless* it's the same exception that
50 # was passed to throw(). This prevents a StopIteration
51 # raised inside the "with" statement from being suppressed
52 return exc is not value
53 except:
54 # only re-raise if it's *not* the exception that was
55 # passed to throw(), because __exit__() must not raise
56 # an exception unless __exit__() itself failed. But throw()
57 # has to raise the exception to signal propagation, so this
58 # fixes the impedance mismatch between the throw() protocol
59 # and the __exit__() protocol.
60 #
61 if sys.exc_info()[1] is not value:
62 raise
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000063
64
65def contextmanager(func):
66 """@contextmanager decorator.
67
68 Typical usage:
69
70 @contextmanager
71 def some_generator(<arguments>):
72 <setup>
73 try:
74 yield <value>
75 finally:
76 <cleanup>
77
78 This makes this:
79
80 with some_generator(<arguments>) as <variable>:
81 <body>
82
83 equivalent to this:
84
85 <setup>
86 try:
87 <variable> = <value>
88 <body>
89 finally:
90 <cleanup>
91
92 """
Christian Heimes81ee3ef2008-05-04 22:42:01 +000093 @wraps(func)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000094 def helper(*args, **kwds):
95 return GeneratorContextManager(func(*args, **kwds))
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000096 return helper
97
98
Thomas Wouters477c8d52006-05-27 19:21:47 +000099class closing(object):
100 """Context to automatically close something at the end of a block.
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000101
102 Code like this:
103
104 with closing(<module>.open(<arguments>)) as f:
105 <block>
106
107 is equivalent to this:
108
109 f = <module>.open(<arguments>)
110 try:
111 <block>
112 finally:
113 f.close()
114
115 """
Thomas Wouters477c8d52006-05-27 19:21:47 +0000116 def __init__(self, thing):
117 self.thing = thing
118 def __enter__(self):
119 return self.thing
120 def __exit__(self, *exc_info):
121 self.thing.close()