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