smartdict.py revision 1606
1# The SmartDict class fixes a couple of issues with using the content
2# of os.environ or similar dicts of strings as Python variables:
3#
4# 1) Undefined variables should return False rather than raising KeyError.
5#
6# 2) String values of 'False', '0', etc., should evaluate to False
7#    (not just the empty string).
8#
9# #1 is solved by overriding __getitem__, and #2 is solved by using a
10# proxy class for values and overriding __nonzero__ on the proxy.
11# Everything else is just to (a) make proxies behave like normal
12# values otherwise, (b) make sure any dict operation returns a proxy
13# rather than a normal value, and (c) coerce values written to the
14# dict to be strings.
15
16
17from convert import *
18
19class Variable(str):
20    """Intelligent proxy class for SmartDict.  Variable will use the
21    various convert functions to attempt to convert values to useable
22    types"""
23    def __int__(self):
24        return toInteger(str(self))
25    def __long__(self):
26        return toLong(str(self))
27    def __float__(self):
28        return toFloat(str(self))
29    def __nonzero__(self):
30        return toBool(str(self))
31    def convert(self, other):
32        t = type(other)
33        if t == bool:
34            return bool(self)
35        if t == int:
36            return int(self)
37        if t == long:
38            return long(self)
39        if t == float:
40            return float(self)
41        return str(self)
42    def __lt__(self, other):
43        return self.convert(other) < other
44    def __le__(self, other):
45        return self.convert(other) <= other
46    def __eq__(self, other):
47        return self.convert(other) == other
48    def __ne__(self, other):
49        return self.convert(other) != other
50    def __gt__(self, other):
51        return self.convert(other) > other
52    def __ge__(self, other):
53        return self.convert(other) >= other
54
55    def __add__(self, other):
56        return self.convert(other) + other
57    def __sub__(self, other):
58        return self.convert(other) - other
59    def __mul__(self, other):
60        return self.convert(other) * other
61    def __div__(self, other):
62        return self.convert(other) / other
63    def __truediv__(self, other):
64        return self.convert(other) / other
65
66    def __radd__(self, other):
67        return other + self.convert(other)
68    def __rsub__(self, other):
69        return other - self.convert(other)
70    def __rmul__(self, other):
71        return other * self.convert(other)
72    def __rdiv__(self, other):
73        return other / self.convert(other)
74    def __rtruediv__(self, other):
75        return other / self.convert(other)
76
77class UndefinedVariable(object):
78    """Placeholder class to represent undefined variables.  Will
79    generally cause an exception whenever it is used, but evaluates to
80    zero for boolean truth testing such as in an if statement"""
81    def __nonzero__(self):
82        return False
83
84class SmartDict(dict):
85    """Dictionary class that holds strings, but intelligently converts
86    those strings to other types depending on their usage"""
87
88    def __getitem__(self, key):
89        """returns a Variable proxy if the values exists in the database and
90        returns an UndefinedVariable otherwise"""
91
92        if key in self:
93            return Variable(dict.get(self, key))
94        else:
95            # Note that this does *not* change the contents of the dict,
96            # so that even after we call env['foo'] we still get a
97            # meaningful answer from "'foo' in env" (which
98            # calls dict.__contains__, which we do not override).
99            return UndefinedVariable()
100
101    def __setitem__(self, key, item):
102        """intercept the setting of any variable so that we always
103        store strings in the dict"""
104        dict.__setitem__(self, key, str(item))
105
106    def values(self):
107        return [ Variable(v) for v in dict.values(self) ]
108
109    def itervalues(self):
110        for value in dict.itervalues(self):
111            yield Variable(value)
112
113    def items(self):
114        return [ (k, Variable(v)) for k,v in dict.items(self) ]
115
116    def iteritems(self):
117        for key,value in dict.iteritems(self):
118            yield key, Variable(value)
119
120    def get(self, key, default='False'):
121        return Variable(dict.get(self, key, str(default)))
122
123    def setdefault(self, key, default='False'):
124        return Variable(dict.setdefault(self, key, str(default)))
125
126__all__ = [ 'SmartDict' ]
127