1 | #
|
---|
2 | # dtuple.py: Database Tuple handling
|
---|
3 | #
|
---|
4 | # Written by Greg Stein. Public Domain.
|
---|
5 | # No Copyright, no Rights Reserved, and no Warranties.
|
---|
6 | #
|
---|
7 | # This module is maintained by Greg and is available at:
|
---|
8 | # http://www.lyra.org/greg/python/dtuple.py
|
---|
9 | #
|
---|
10 | # Some discussion/usage of this module can be found at:
|
---|
11 | # http://www.python.org/pipermail/db-sig/1996-January/000000.html
|
---|
12 | # http://www.python.org/pipermail/db-sig/1997-January/000152.html
|
---|
13 | # (and various other posts around these time frames)
|
---|
14 | #
|
---|
15 | # Since this isn't in any Python distribution yet, we'll use the CVS ID for
|
---|
16 | # tracking:
|
---|
17 | # $Id: dtuple.py,v 1.1 2004/09/27 16:43:51 pbuschman Exp $
|
---|
18 | #
|
---|
19 |
|
---|
20 | class TupleDescriptor:
|
---|
21 | """Describes a return tuple from a DB-API fetch*() method.
|
---|
22 |
|
---|
23 | Instances of this class are used to describe database tuples (which are
|
---|
24 | typically instances of DatabaseTuple or one of its derivative classes).
|
---|
25 | These instances specify the column names, formats, lengths, and other
|
---|
26 | relevant information about the items in a particular tuple. An instance
|
---|
27 | is typically shared between many database tuples (such as those returned
|
---|
28 | by a single query).
|
---|
29 |
|
---|
30 | Note: the term database tuple is rather specific; in actuality the tuple
|
---|
31 | may have come from non-database sources and/or generated by a process
|
---|
32 | wholly unrelated to databases.
|
---|
33 |
|
---|
34 | Note again: I'm open for new names for this and the DatabaseTuple class
|
---|
35 | and concept :-)
|
---|
36 | """
|
---|
37 |
|
---|
38 | def __init__(self, desc):
|
---|
39 | """TupleDescriptor constructor.
|
---|
40 |
|
---|
41 | An instance is created by passing a "descriptor" to fully specify the
|
---|
42 | information about the related database tuple. This descriptor takes the
|
---|
43 | form of a tuple or list where each element is a tuple. The first element
|
---|
44 | of this tuple is the name of the column. The following elements of the
|
---|
45 | tuple are used to describe the column (such as length, format,
|
---|
46 | significant
|
---|
47 | digits, etc).
|
---|
48 | """
|
---|
49 | self.desc = tuple(desc)
|
---|
50 | ### validate the names?
|
---|
51 | self.names = map(lambda x: x[0], desc)
|
---|
52 | self.namemap = { }
|
---|
53 | for i in range(len(self.names)):
|
---|
54 | self.namemap[self.names[i]] = i
|
---|
55 |
|
---|
56 | def __len__(self):
|
---|
57 | """Returns the number of elements in the data object.
|
---|
58 |
|
---|
59 | A tuple descriptor responds to __len__ to simplify some processing by
|
---|
60 | allowing the use of the len() builtin function.
|
---|
61 | """
|
---|
62 | return len(self.names)
|
---|
63 |
|
---|
64 | def __repr__(self):
|
---|
65 | return '%s(%s)' % (self.__class__.__name__, repr(self.desc))
|
---|
66 | def __str__(self):
|
---|
67 | return str(self.desc)
|
---|
68 |
|
---|
69 |
|
---|
70 |
|
---|
71 | class DatabaseTuple:
|
---|
72 | """Wraps the return data from a DB-API fetch*() method.
|
---|
73 |
|
---|
74 | Instances of this class are used to represent tuples of information,
|
---|
75 | typically returned by a database query. A TupleDescriptor is used as
|
---|
76 | a means of describing the information for a variety of access methods.
|
---|
77 | The tuple's information can be accessed via simple indexing, slices,
|
---|
78 | as a mapping where the keys are the column names (as defined by the
|
---|
79 | descriptor), or via attribute-based access (where the attribute names
|
---|
80 | are equivalent to the column names).
|
---|
81 |
|
---|
82 | This object acts as a tuple, a list, a mapping, and an instance. To
|
---|
83 | retrieve "pure" tuples, lists, or mappings, the asTuple(), asList(),
|
---|
84 | and asMapping() methods may be used, each returning a value equal to
|
---|
85 | what this object pretends to be.
|
---|
86 |
|
---|
87 | There exists a potential ambiguity between attempting to act as a list
|
---|
88 | or mapping and the attribute-based access to the data. In particular,
|
---|
89 | if the column names are 'index', 'count', 'keys', 'items', 'values', or
|
---|
90 | 'has_key', then the attribute-based access will have precedence over
|
---|
91 | their related methods for lists and mappings. To actually use these
|
---|
92 | methods, simply apply them to the result of the asList() or asMapping()
|
---|
93 | methods.
|
---|
94 |
|
---|
95 | Note that column names with leading underscores may interfere with
|
---|
96 | the implementation of this class, and as a result may not be accessible
|
---|
97 | via the attribute-access scheme. Also, column names of asTuple, asList,
|
---|
98 | and asMapping will be inaccessible via the attribute-access scheme
|
---|
99 | since those will always represent the methods. To access these columns,
|
---|
100 | the mapping interface can be used with the column name as the mapping
|
---|
101 | key.
|
---|
102 |
|
---|
103 | Note that a database tuple acts as a tuple with respect to sub-scripted
|
---|
104 | assignment. TypeError exceptions will be raised for several situations,
|
---|
105 | and AttributeError may be raised for some methods that are intended
|
---|
106 | to mutate the data (list's 'sort' method) as these methods have not
|
---|
107 | been implemented.
|
---|
108 | """
|
---|
109 |
|
---|
110 | def __init__(self, desc, data):
|
---|
111 | """DatabaseTuple constructor.
|
---|
112 |
|
---|
113 | A DatabaseTuple is initialized with a TupleDescriptor and a tuple or
|
---|
114 | list specifying the data elements.
|
---|
115 | """
|
---|
116 | if len(desc) != len(data):
|
---|
117 | raise ValueError # descriptor does not seem to describe tuple
|
---|
118 | if type(desc) == type(()) or type(desc) == type([]):
|
---|
119 | desc = TupleDescriptor(desc)
|
---|
120 | self.__dict__['_desc_'] = desc
|
---|
121 | self.__dict__['_data_'] = tuple(data)
|
---|
122 |
|
---|
123 | def __str__(self):
|
---|
124 | return str(self._data_)
|
---|
125 | def __repr__(self):
|
---|
126 | return '%s(%s,%s)' % (self.__class__.__name__,
|
---|
127 | repr(self._desc_),
|
---|
128 | repr(self._data_))
|
---|
129 |
|
---|
130 | def __cmp__(self, other):
|
---|
131 | if type(self._data_) == type(other):
|
---|
132 | return cmp(self._data_, other)
|
---|
133 | if type(self._data_) == type( {} ):
|
---|
134 | return cmp(self.asMapping(), other)
|
---|
135 | if type(self._data_) == type( () ):
|
---|
136 | return cmp(self.asTuple(), other)
|
---|
137 | if type(self) == type(other): ### fix this: need to verify equal classes
|
---|
138 | return cmp(self._data_, other._data_)
|
---|
139 | return cmp(self._data_, other)
|
---|
140 |
|
---|
141 | def __getattr__(self, name):
|
---|
142 | 'Simulate attribute-access via column names'
|
---|
143 | return self._getvalue_(name)
|
---|
144 |
|
---|
145 | def __setattr__(self, name, value):
|
---|
146 | 'Simulate attribute-access via column names'
|
---|
147 | ### need to redirect into a db update
|
---|
148 | raise TypeError, "can't assign to this subscripted object"
|
---|
149 |
|
---|
150 | def __getitem__(self, key):
|
---|
151 | 'Simulate indexed (tuple/list) and mapping-style access'
|
---|
152 | if type(key) == type(1):
|
---|
153 | return self._data_[key]
|
---|
154 | return self._getvalue_(key)
|
---|
155 |
|
---|
156 | def __setitem__(self, key, value):
|
---|
157 | 'Simulate indexed (tuple/list) and mapping-style access'
|
---|
158 | if type(key) == type(1):
|
---|
159 | ### need to redirect into a db update of elem #key
|
---|
160 | raise TypeError, "can't assign to this subscripted object"
|
---|
161 | ### need to redirect into a db update of elem named key
|
---|
162 | raise TypeError, "can't assign to this subscripted object"
|
---|
163 |
|
---|
164 | def __len__(self):
|
---|
165 | return len(self._data_)
|
---|
166 |
|
---|
167 | def __getslice__(self, i, j):
|
---|
168 | 'Simulate list/tuple slicing access'
|
---|
169 | return self._data_[i:j]
|
---|
170 |
|
---|
171 | def __setslice__(self, i, j, list):
|
---|
172 | 'Simulate list/tuple slicing access'
|
---|
173 | ### need to redirect into a db update of elems
|
---|
174 | raise TypeError, "can't assign to this subscripted object"
|
---|
175 |
|
---|
176 | def _keys_(self):
|
---|
177 | "Simulate mapping's methods"
|
---|
178 | return self._desc_.names
|
---|
179 |
|
---|
180 | def _has_key_(self, key):
|
---|
181 | "Simulate mapping's methods"
|
---|
182 | return key in self._desc_.names
|
---|
183 |
|
---|
184 | def _items_(self):
|
---|
185 | "Simulate mapping's methods"
|
---|
186 | return self.asMapping().items()
|
---|
187 |
|
---|
188 | def _count_(self, item):
|
---|
189 | "Simulate list's methods"
|
---|
190 | return self.asList().count(item)
|
---|
191 |
|
---|
192 | def _index_(self, item):
|
---|
193 | "Simulate list's methods"
|
---|
194 | return self.asList().index(item)
|
---|
195 |
|
---|
196 | def _getvalue_(self,name):
|
---|
197 | 'Internal method for named-based value retrieval'
|
---|
198 | if name not in self._desc_.names:
|
---|
199 | if name == 'keys':
|
---|
200 | return self._keys_
|
---|
201 | if name == 'items':
|
---|
202 | return self._items_
|
---|
203 | if name == 'values':
|
---|
204 | return self.asList
|
---|
205 | if name == 'has_key':
|
---|
206 | return self._has_key_
|
---|
207 | if name == 'count':
|
---|
208 | return self._count_
|
---|
209 | if name == 'index':
|
---|
210 | return self._index_
|
---|
211 | raise AttributeError
|
---|
212 | return self._data_[self._desc_.namemap[name]]
|
---|
213 |
|
---|
214 | def asMapping(self):
|
---|
215 | 'Return the "tuple" as a real mapping'
|
---|
216 | value = { }
|
---|
217 | for name, idx in self._desc_.namemap.items():
|
---|
218 | value[name] = self._data_[idx]
|
---|
219 | return value
|
---|
220 |
|
---|
221 | def asTuple(self):
|
---|
222 | 'Return the "tuple" as a real tuple'
|
---|
223 | return self._data_
|
---|
224 |
|
---|
225 | def asList(self):
|
---|
226 | 'Return the "list" as a real mapping'
|
---|
227 | return map(None, self._data_)
|
---|