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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.