smartdict.py revision 1736
1# Copyright (c) 2005 The Regents of The University of Michigan
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27# The SmartDict class fixes a couple of issues with using the content
28# of os.environ or similar dicts of strings as Python variables:
29#
30# 1) Undefined variables should return False rather than raising KeyError.
31#
32# 2) String values of 'False', '0', etc., should evaluate to False
33#    (not just the empty string).
34#
35# #1 is solved by overriding __getitem__, and #2 is solved by using a
36# proxy class for values and overriding __nonzero__ on the proxy.
37# Everything else is just to (a) make proxies behave like normal
38# values otherwise, (b) make sure any dict operation returns a proxy
39# rather than a normal value, and (c) coerce values written to the
40# dict to be strings.
41
42
43from convert import *
44
45class Variable(str):
46    """Intelligent proxy class for SmartDict.  Variable will use the
47    various convert functions to attempt to convert values to useable
48    types"""
49    def __int__(self):
50        return toInteger(str(self))
51    def __long__(self):
52        return toLong(str(self))
53    def __float__(self):
54        return toFloat(str(self))
55    def __nonzero__(self):
56        return toBool(str(self))
57    def convert(self, other):
58        t = type(other)
59        if t == bool:
60            return bool(self)
61        if t == int:
62            return int(self)
63        if t == long:
64            return long(self)
65        if t == float:
66            return float(self)
67        return str(self)
68    def __lt__(self, other):
69        return self.convert(other) < other
70    def __le__(self, other):
71        return self.convert(other) <= other
72    def __eq__(self, other):
73        return self.convert(other) == other
74    def __ne__(self, other):
75        return self.convert(other) != other
76    def __gt__(self, other):
77        return self.convert(other) > other
78    def __ge__(self, other):
79        return self.convert(other) >= other
80
81    def __add__(self, other):
82        return self.convert(other) + other
83    def __sub__(self, other):
84        return self.convert(other) - other
85    def __mul__(self, other):
86        return self.convert(other) * other
87    def __div__(self, other):
88        return self.convert(other) / other
89    def __truediv__(self, other):
90        return self.convert(other) / other
91
92    def __radd__(self, other):
93        return other + self.convert(other)
94    def __rsub__(self, other):
95        return other - self.convert(other)
96    def __rmul__(self, other):
97        return other * self.convert(other)
98    def __rdiv__(self, other):
99        return other / self.convert(other)
100    def __rtruediv__(self, other):
101        return other / self.convert(other)
102
103class UndefinedVariable(object):
104    """Placeholder class to represent undefined variables.  Will
105    generally cause an exception whenever it is used, but evaluates to
106    zero for boolean truth testing such as in an if statement"""
107    def __nonzero__(self):
108        return False
109
110class SmartDict(dict):
111    """Dictionary class that holds strings, but intelligently converts
112    those strings to other types depending on their usage"""
113
114    def __getitem__(self, key):
115        """returns a Variable proxy if the values exists in the database and
116        returns an UndefinedVariable otherwise"""
117
118        if key in self:
119            return Variable(dict.get(self, key))
120        else:
121            # Note that this does *not* change the contents of the dict,
122            # so that even after we call env['foo'] we still get a
123            # meaningful answer from "'foo' in env" (which
124            # calls dict.__contains__, which we do not override).
125            return UndefinedVariable()
126
127    def __setitem__(self, key, item):
128        """intercept the setting of any variable so that we always
129        store strings in the dict"""
130        dict.__setitem__(self, key, str(item))
131
132    def values(self):
133        return [ Variable(v) for v in dict.values(self) ]
134
135    def itervalues(self):
136        for value in dict.itervalues(self):
137            yield Variable(value)
138
139    def items(self):
140        return [ (k, Variable(v)) for k,v in dict.items(self) ]
141
142    def iteritems(self):
143        for key,value in dict.iteritems(self):
144            yield key, Variable(value)
145
146    def get(self, key, default='False'):
147        return Variable(dict.get(self, key, str(default)))
148
149    def setdefault(self, key, default='False'):
150        return Variable(dict.setdefault(self, key, str(default)))
151
152__all__ = [ 'SmartDict' ]
153