formencode.context
Covered: 113 lines
Missed: 22 lines
Skipped 33 lines
Percent: 83 %
  1
"""
  2
A dynamic-scope-like system, aka fluid variables.
  4
The idea behind dynamic scoped variables is for when, at one level,
  5
you want to change the behavior of something you call.  Except you
  6
can't pass in any new arguments (e.g., there's some function or object
  7
inbetween you and the thing you want to change), or you can't predict
  8
exactly what you will want to change.
 10
You should use it like::
 12
    context = Context()
 14
    def do_stuff():
 15
        state = context.set(inside='do_stuff')
 16
        try:
 17
            do stuff...
 18
        finally:
 19
            state.restore()
 21
Then ``context.inside`` will be set to ``'do_stuff'`` inside that try
 22
block.  If a value isn't set, you'll get an attribute error.
 24
Note that all values are thread local; this means you cannot use a
 25
context object to pass information to another thread.  In a
 26
single-thread environment it doesn't really matter.
 28
Typically you will create ``Context`` instances for your application,
 29
environment, etc.  These should be global module-level variables, that
 30
may be imported by any interested module; each instance is a namespace
 31
of its own.
 33
Sometimes it's nice to have default values, instead of getting
 34
attribute errors.  This makes it easier to put in new variables that
 35
are intended to be used elsewhere, without having to use
 36
``getattr(context, 'var', default)`` to avoid AttributeErrors.  There
 37
are two ways (that can be used together) to do this.
 39
First, when instantiating a ``Context`` object, you can give it a
 40
``default`` value.  If given, then all variables will default to that
 41
value.  ``None`` is a typical value for that.
 43
Another is ``context.set_default(**vars)``, which will set only those
 44
variables to default values.  This will not effect the stack of
 45
scopes, but will only add defaults.
 48
When Python 2.5 comes out, this syntax would certainly be useful::
 50
    with context(page='view'):
 51
        do stuff...
 53
And ``page`` will be set to ``'view'`` only inside that ``with``
 54
block.
 55
"""
 57
from formencode.util import threadinglocal
 58
from itertools import count
 60
__all__ = ['Context', 'ContextRestoreError']
 62
_restore_ids = count()
 64
class _NoDefault:
 65
    pass
 67
class ContextRestoreError(Exception):
 68
    """
 69
    Raised when something is restored out-of-order.
 70
    """
 72
class Context(object):
 74
    def __init__(self, default=_NoDefault):
 75
        self.__dict__['_local'] = threadinglocal.local()
 76
        self.__dict__['_default'] = default
 78
    def __getattr__(self, attr):
 79
        if attr.startswith('_'):
 80
            raise AttributeError
 81
        try:
 82
            stack = self._local.stack
 83
        except AttributeError:
 84
            stack = []
 85
        for i in range(len(stack)-1, -1, -1):
 86
            if attr in stack[i][0]:
 87
                return stack[i][0][attr]
 88
        if self._default is _NoDefault:
 89
            raise AttributeError(
 90
                "The attribute %s has not been set on %r"
 91
                % (attr, self))
 92
        return self._default
 94
    def __setattr__(self, attr, value):
 95
        raise AttributeError(
 96
            "You can only write attribute on context object with the .set() method")
 98
    def set(self, **kw):
 99
        state_id = _restore_ids.next()
100
        try:
101
            stack = self._local.stack
102
        except AttributeError:
103
            stack = self._local.stack = [({}, -1)]
104
        restorer = RestoreState(self, state_id)
105
        stack.append((kw, state_id))
106
        return restorer
108
    def _restore(self, state_id):
109
        try:
110
            stack = self._local.stack
111
        except AttributeError:
112
            raise ContextRestoreError(
113
                "Tried to restore context %r (to state ID %s) but no variables have been set in context"
114
                % (self, state_id))
115
        if stack[-1][1] == -1:
116
            raise ContextRestoreError(
117
                "Out of order restoration of context %r (to state ID %s); the stack state is empty"
118
                % (self, state_id))
119
        if stack[-1][1] != state_id:
120
            raise ContextRestoreError(
121
                "Out of order restoration of context %r (to state ID %s) when last state is %s"
122
                % (self, state_id, stack[-1][1]))
123
        stack.pop()
125
    def set_default(self, **kw):
126
        try:
127
            stack = self._local.stack
128
        except AttributeError:
129
            stack = self._local.stack = [({}, -1)]
130
        stack[0][0].update(kw)
132
    def __repr__(self):
133
        try:
134
            stack = self._local.stack
135
        except AttributeError:
136
            stack = []
137
        myid = hex(abs(id(self)))[2:]
138
        if not stack:
139
            return '<%s %s (empty)>' % (self.__class__.__name__, myid)
140
        cur = {}
141
        for vars, state_id in stack:
142
            cur.update(vars)
143
        keys = cur.keys()
144
        keys.sort()
145
        varlist = []
146
        for key in keys:
147
            rep = repr(cur[key])
148
            if len(rep) > 10:
149
                rep = rep[:9]+'...'+rep[-1]
150
            varlist.append('%s=%s' % (key, rep))
151
        return '<%s %s %s>' % (
152
            self.__class__.__name__, myid, ' '.join(varlist))
154
class RestoreState(object):
156
    def __init__(self, context, state_id):
157
        self.state_id = state_id
158
        self.context = context
159
        self.restored = False
161
    def restore(self):
162
        if self.restored:
164
            return
165
        self.context._restore(self.state_id)
166
        self.restored = True