Dict Cursor Der DictCursor, also ein Datenbankcursor, der eine Liste aus dicts zurückgibt, ist nicht im DB-API-Standard verankert. Deswegen haben nicht alle Datenbankmodule so einen Cursor, und die wenigsten haben überhaupt eine Methode, den Cursortyp zu ändern.

Also was macht man in solchen Fällen? Der Trick heißt "wrappen", also eine Datenbankverbindung und den Cursor in eine Klasse stecken und Methoden überschreiben. Und wenn wir schon dabei sind, werden wir auch gleichzeitig eine Methode prepare_sql einbauen, damit der Datenbankzugriff einfacher wird.

Dann sollte folgendes funktionieren:

   1 from MySQLdb import Connect
   2 con = WrappedConnection(Connect(db='mydb', user='test', passwd='password'), '%s', 'myprefix_')
   3 cur = con.cursor()
   4 cur.execute('select * from $$users where id > ? and id < ?', (200, 300))
   5 for user in cur:
   6     print 'user %(username)s hat %(posts)s geschrieben' % user

WrappedConnection

Der erste Weg ist es, die komplette Datenbank in eine Klasse zu stecken und die Methodenaufrufe 1:1 weiterzureichen, aber die cursor Methode so zu überschreiben, dass sie einen IterableDictCursor zurückgibt. Also frisch ans Werk:

   1 class WrappedConnection(object):
   2 
   3     def __init__(self, cnx, placeholder, prefix=''):
   4         self.cnx = cnx
   5         self.placeholder = placeholder
   6         self.prefix = prefix
   7 
   8     def cursor(self):
   9         return IterableDictCursor(self.cnx, self.placeholder, self.prefix)
  10 
  11     def __getattr__(self, attr):
  12         "Zugriff auf Attribute/Methoden an das eigentliche Connection-Objekt durchreichen"
  13         return getattr(self.cnx, attr)

Das sollte schon ausreichen. Jetzt brauchen wir noch die Cursorklasse:

   1 class IterableDictCursor(object):
   2 
   3     def __init__(self, cnx, placeholder, prefix):
   4         self._cursor = cnx.cursor()
   5         self._placeholder = placeholder
   6         self._prefix = prefix
   7 
   8     def __getattr__(self, attr):
   9         "Zugriff auf Attribute/Methoden an das eigentliche Cursor-Objekt durchreichen"
  10         return getattr(self._cursor, attr)
  11 
  12     def prepare_sql(self, sql):
  13         return sql.replace('$$', self._prefix) \
  14                   .replace('?', self._placeholder)
  15 
  16     def execute(self, sql, values=None):
  17         args = [self.prepare_sql(sql)]
  18         if values:
  19             args.append(values)
  20         self._cursor.execute(*tuple(args))
  21 
  22     def fetchone(self):
  23         row = self._cursor.fetchone()
  24         if not row:
  25             return ()
  26         result = {}
  27         for idx, col in enumerate(self._cursor.description):
  28             result[col[0]] = row[idx]
  29         return result
  30 
  31     def fetchall(self):
  32         rows = self._cursor.fetchall()
  33         result = []
  34         for row in rows:
  35             tmp = {}
  36             for idx, col in enumerate(self._cursor.description):
  37                 tmp[col[0]] = row[idx]
  38             result.append(tmp)
  39         return result
  40 
  41     def __iter__(self):
  42         while True:
  43             row = self.fetchone()
  44             if not row:
  45                 return
  46             yield row

In fetchone und fetchall geschieht einfach die Umwandlung in ein dict, execute ruft vorher die Funktion prepare_sql auf, die $$ in das eingestellte Prefix umwandelt und ? in das übergebene Platzhaltersymbol, ergo funktioniert es nicht mit named, was aber keine Datenbank verwendet.

Die Methode __iter__ ruft hier solange fetchone auf, bis das nichts mehr zurückliefert. Vorteil ist, dass Python schon arbeitet, wärend die Datenbank noch Daten abholt.


Angepasste Version des Pocoo Datenbank-Wrappers.

Tags: Codesnippets | Db

Dict Cursor (zuletzt geändert am 2009-06-17 16:14:26 durch anonym)