contextlib.py 4.02 KB
Newer Older
1 2 3
"""Utilities for with-statement contexts.  See PEP 343."""

import sys
Christian Heimes's avatar
Christian Heimes committed
4
from functools import wraps
5
from warnings import warn
6

7
__all__ = ["contextmanager", "closing", "ContextDecorator"]
8

9 10 11

class ContextDecorator(object):
    "A base class or mixin that enables context managers to work as decorators."
12 13 14

    def _recreate_cm(self):
        """Return a recreated instance of self.
15

16 17 18
        Allows otherwise one-shot context managers like
        _GeneratorContextManager to support use as
        decorators via implicit recreation.
19

20 21 22 23 24
        Note: this is a private interface just for _GCM in 3.2 but will be
        renamed and documented for third party use in 3.3
        """
        return self

25 26 27
    def __call__(self, func):
        @wraps(func)
        def inner(*args, **kwds):
28
            with self._recreate_cm():
29 30 31 32
                return func(*args, **kwds)
        return inner


33
class _GeneratorContextManager(ContextDecorator):
34 35
    """Helper for @contextmanager decorator."""

36 37 38 39 40 41 42 43 44
    def __init__(self, func, *args, **kwds):
        self.gen = func(*args, **kwds)
        self.func, self.args, self.kwds = func, args, kwds

    def _recreate_cm(self):
        # _GCM instances are one-shot context managers, so the
        # CM must be recreated each time a decorated function is
        # called
        return self.__class__(self.func, *self.args, **self.kwds)
45 46 47

    def __enter__(self):
        try:
48
            return next(self.gen)
49 50 51 52 53 54
        except StopIteration:
            raise RuntimeError("generator didn't yield")

    def __exit__(self, type, value, traceback):
        if type is None:
            try:
55
                next(self.gen)
56 57 58 59 60
            except StopIteration:
                return
            else:
                raise RuntimeError("generator didn't stop")
        else:
61 62 63 64
            if value is None:
                # Need to force instantiation so we can reliably
                # tell if we get the same exception back
                value = type()
65 66
            try:
                self.gen.throw(type, value, traceback)
67
                raise RuntimeError("generator didn't stop after throw()")
68
            except StopIteration as exc:
69 70 71 72 73 74 75 76 77 78 79 80 81 82
                # Suppress the exception *unless* it's the same exception that
                # was passed to throw().  This prevents a StopIteration
                # raised inside the "with" statement from being suppressed
                return exc is not value
            except:
                # only re-raise if it's *not* the exception that was
                # passed to throw(), because __exit__() must not raise
                # an exception unless __exit__() itself failed.  But throw()
                # has to raise the exception to signal propagation, so this
                # fixes the impedance mismatch between the throw() protocol
                # and the __exit__() protocol.
                #
                if sys.exc_info()[1] is not value:
                    raise
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112


def contextmanager(func):
    """@contextmanager decorator.

    Typical usage:

        @contextmanager
        def some_generator(<arguments>):
            <setup>
            try:
                yield <value>
            finally:
                <cleanup>

    This makes this:

        with some_generator(<arguments>) as <variable>:
            <body>

    equivalent to this:

        <setup>
        try:
            <variable> = <value>
            <body>
        finally:
            <cleanup>

    """
Christian Heimes's avatar
Christian Heimes committed
113
    @wraps(func)
114
    def helper(*args, **kwds):
115
        return _GeneratorContextManager(func, *args, **kwds)
116 117 118
    return helper


119 120
class closing(object):
    """Context to automatically close something at the end of a block.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

    Code like this:

        with closing(<module>.open(<arguments>)) as f:
            <block>

    is equivalent to this:

        f = <module>.open(<arguments>)
        try:
            <block>
        finally:
            f.close()

    """
136 137 138 139 140 141
    def __init__(self, thing):
        self.thing = thing
    def __enter__(self):
        return self.thing
    def __exit__(self, *exc_info):
        self.thing.close()