1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# Runvar implementations
import attr
 
from . import _run
 
from .._util import Final
 
 
@attr.s(eq=False, hash=False, slots=True)
class _RunVarToken:
    _no_value = object()
 
    _var = attr.ib()
    previous_value = attr.ib(default=_no_value)
    redeemed = attr.ib(default=False, init=False)
 
    @classmethod
    def empty(cls, var):
        return cls(var)
 
 
@attr.s(eq=False, hash=False, slots=True)
class RunVar(metaclass=Final):
    """The run-local variant of a context variable.
 
    :class:`RunVar` objects are similar to context variable objects,
    except that they are shared across a single call to :func:`trio.run`
    rather than a single task.
 
    """
 
    _NO_DEFAULT = object()
    _name = attr.ib()
    _default = attr.ib(default=_NO_DEFAULT)
 
    def get(self, default=_NO_DEFAULT):
        """Gets the value of this :class:`RunVar` for the current run call."""
        try:
            return _run.GLOBAL_RUN_CONTEXT.runner._locals[self]
        except AttributeError:
            raise RuntimeError("Cannot be used outside of a run context") from None
        except KeyError:
            # contextvars consistency
            if default is not self._NO_DEFAULT:
                return default
 
            if self._default is not self._NO_DEFAULT:
                return self._default
 
            raise LookupError(self) from None
 
    def set(self, value):
        """Sets the value of this :class:`RunVar` for this current run
        call.
 
        """
        try:
            old_value = self.get()
        except LookupError:
            token = _RunVarToken.empty(self)
        else:
            token = _RunVarToken(self, old_value)
 
        # This can't fail, because if we weren't in Trio context then the
        # get() above would have failed.
        _run.GLOBAL_RUN_CONTEXT.runner._locals[self] = value
        return token
 
    def reset(self, token):
        """Resets the value of this :class:`RunVar` to what it was
        previously specified by the token.
 
        """
        if token is None:
            raise TypeError("token must not be none")
 
        if token.redeemed:
            raise ValueError("token has already been used")
 
        if token._var is not self:
            raise ValueError("token is not for us")
 
        previous = token.previous_value
        try:
            if previous is _RunVarToken._no_value:
                _run.GLOBAL_RUN_CONTEXT.runner._locals.pop(self)
            else:
                _run.GLOBAL_RUN_CONTEXT.runner._locals[self] = previous
        except AttributeError:
            raise RuntimeError("Cannot be used outside of a run context")
 
        token.redeemed = True
 
    def __repr__(self):
        return "<RunVar name={!r}>".format(self._name)