[976] | 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_)
|
---|