row_factory and DictCursor
"Joel Nothman" <[email protected]>
| Newsgroups | gmane.comp.python.db.psycopg.devel |
|---|---|
| Message-ID | <[email protected]> |
Hi again,
I've been looking at the code for DictCursor, etc. It seems illogical that
the row_factory property of the cursor requires the factory to be a type
which can have its values set using __setitem__. Surely passing it an
iterator or tuple of data as an argument to the constructor (like sqlite3
does) is more sensible. It would simplify both the C and the Python code
required.
It would also mean that something like (lambda cursor, data: set(data))
would be a valid factory to make a set, whereas currently one would need
to define a class with a bogus __setitem__ to make a set factory. Same
goes for tuples, namedtuples (see below), some string/file-based
row_factory, etc. which would be even harder as they are immutable types.
Also, the code in extras.py for handling DictRow, etc, can be hugely
simplified (removing things like cursor._prefetch and
cursor._query_executed) by calling _build_fields (was _build_index) from
within the constructor of the DictRow object, instead of in each fetch
method. (Code untested)
class DictCursorBase(_cursor):
"""Base class for all dict-like cursors."""
__slots__ = ('_fields',)
def __init__(self, row_factory, *args, **kwargs)
_cursor.__init__(self, *args, **kwargs)
self._fields = None
self.row_factory = row_factory
def execute(self, *args):
self._fields = None
return _cursor.execute(self, *args)
def callproc(self, *args):
self._fields = None
return _cursor.execute(self, *args)
def _build_fields(self):
if self._fields:
return
self._fields = tuple(f[0] for f in self.description)
Anything inheriting from DictCursorBase can now optionally reimplement
_build_fields, but otherwise need not override methods. A DictRow would
call cursor._build_fields() on initialisation, and use cursor._index
immediately; we can assume when the row is constructed with fetched data
that cursor.description is available.
Implementing both these changes means we can make a namedtuple row factory
very easily (given Python >=2.6; code untested):
from collections import namedtuple
def NamedTupleRow(cursor, data):
cursor._build_index()
return cursor._fields(data)
class NamedTupleCursor(DictCursorBase):
def __init__(self, *args, **kwargs):
DictCursorBase.__init__(self, NamedTupleRow, *args, **kwargs)
def _build_fields(self):
if self._fields:
return
self._fields = namedtuple('NamedTupleRow',
(f[0] for f in self.description))
What do you think of these proposed changes? Unfortunately, it would be
hard to make them backwards compatible, but if few people have written
their own row factories according to the old interface, then the available
ones (DictRow, RealDictRow) will be easy to modify.
[Also note it may be sensible to move _build_fields to the row factory, as
this is the only place it is called from. Although this would make the
XXXXCursor class definitions even simpler, _build_fields' aim is to store
datic that is static between rows in the cursor, and so belongs more in
the cursor than in the row constructor.]
- Joel