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::
15
state = context.set(inside='do_stuff')
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
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'):
53
And ``page`` will be set to ``'view'`` only inside that ``with``
57
from formencode.util import threadinglocal
58
from itertools import count
60
__all__ = ['Context', 'ContextRestoreError']
67
class ContextRestoreError(Exception):
69
Raised when something is restored out-of-order.
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('_'):
82
stack = self._local.stack
83
except AttributeError:
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:
90
"The attribute %s has not been set on %r"
94
def __setattr__(self, attr, value):
96
"You can only write attribute on context object with the .set() method")
99
state_id = _restore_ids.next()
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))
108
def _restore(self, state_id):
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"
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"
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]))
125
def set_default(self, **kw):
127
stack = self._local.stack
128
except AttributeError:
129
stack = self._local.stack = [({}, -1)]
130
stack[0][0].update(kw)
134
stack = self._local.stack
135
except AttributeError:
137
myid = hex(abs(id(self)))[2:]
139
return '<%s %s (empty)>' % (self.__class__.__name__, myid)
141
for vars, state_id in stack:
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
163
# @@: Should this really be allowed?
165
self.context._restore(self.state_id)