Author: cito
Date: Tue Oct 16 15:16:05 2012
New Revision: 447

Log:
Added a namedresult() method to the classic API.

Modified:
   trunk/docs/changelog.txt
   trunk/docs/pg.txt
   trunk/module/pg.py
   trunk/module/pgmodule.c
   trunk/module/test_pg.py

Modified: trunk/docs/changelog.txt
==============================================================================
--- trunk/docs/changelog.txt    Tue Oct  2 12:12:19 2012        (r446)
+++ trunk/docs/changelog.txt    Tue Oct 16 15:16:05 2012        (r447)
@@ -9,6 +9,9 @@
 - The query method of the classic API now supports positional parameters.
   This an effective way to pass arbitrary or unknown data without worrying
   about SQL injection or syntax errors (contribution by Patrick TJ McPhee).
+- The classic API now supports a method namedresult() in addition to
+  getresult() and dictresult(), which returns the rows of the result
+  as named tuples if these are supported (Python 2.6 or higher).
 - The execute() and executemany() methods now return the cursor object,
   so you can now write statements like "for row in cursor.execute(...)"
   (as suggested by Adam Frederick).

Modified: trunk/docs/pg.txt
==============================================================================
--- trunk/docs/pg.txt   Tue Oct  2 12:12:19 2012        (r446)
+++ trunk/docs/pg.txt   Tue Oct 16 15:16:05 2012        (r447)
@@ -74,7 +74,7 @@
   :pgobject: If successful, the `pgobject` handling the connection
 
 Exceptions raised:
-  :Type: bad argument type, or too many arguments
+  :TypeError: bad argument type, or too many arguments
   :SyntaxError: duplicate argument definition
   :pg.InternalError: some error occurred during pg connection definition
 
@@ -384,6 +384,19 @@
   This function can be used to specify the Python class that shall be
   used by PyGreSQL to hold PostgreSQL numeric values. The default class
   is decimal.Decimal if available, otherwise the float type is used.
+  
+set_namedresult -- set a function that will convert to named tuples
+-------------------------------------------------------------------
+Syntax::
+
+  set_namedresult(func)
+
+Parameters:
+  :func: the function to be used to convert results to named tuples
+
+Description:
+  You can use this if you want to create different kinds of named tuples.
+  
 
 Module constants
 ----------------
@@ -447,8 +460,9 @@
   row in a table with OIDs, then the numer of rows affected is returned as a
   string. If it is a statement that returns rows as a result (usually a select
   statement, but maybe also an "insert/update ... returning" statement), this
-  method returns a `pgqueryobject` that can be accessed via the `getresult()`
-  or `dictresult()` method or simply printed. Otherwise, it returns `None`.
+  method returns a `pgqueryobject` that can be accessed via the `getresult()`,
+  `dictresult()` or `namedresult()` methods or simply printed. Otherwise, it
+  returns `None`.
 
   The query may optionally contain positional parameters of the form `$1`,
   `$2`, etc instead of literal data, and the values supplied as a tuple.
@@ -1158,7 +1172,7 @@
   :list: result values as a list of tuples
 
 Exceptions raised:
-  :TypeError: too many parameters
+  :TypeError: too many (any) parameters
   :MemoryError: internal memory error
 
 Description:
@@ -1179,7 +1193,7 @@
   :list: result values as a list of dictionaries
 
 Exceptions raised:
-  :TypeError: too many parameters
+  :TypeError: too many (any) parameters
   :MemoryError: internal memory error
 
 Description:
@@ -1187,6 +1201,26 @@
   with each tuple returned as a dictionary with the field names
   used as the dictionary index.
 
+namedresult - get query values as list of named tuples
+------------------------------------------------------
+Syntax::
+
+  namedresult()
+
+Parameters:
+  None
+
+Return type:
+  :list: result values as a list of named tuples
+
+Exceptions raised:
+  :TypeError: too many (any) parameters
+  :TypeError: named tuples not supported
+  :MemoryError: internal memory error
+
+Description:
+  This method returns the list of the values returned by the query
+  with each row returned as a named tuple with proper field names.
 
 listfields - lists fields names of previous query result
 --------------------------------------------------------

Modified: trunk/module/pg.py
==============================================================================
--- trunk/module/pg.py  Tue Oct  2 12:12:19 2012        (r446)
+++ trunk/module/pg.py  Tue Oct 16 15:16:05 2012        (r447)
@@ -12,7 +12,7 @@
 
 This pg module implements some basic database management stuff.
 It includes the _pg module and builds on it, providing the higher
-level wrapper class named DB with addtional functionality.
+level wrapper class named DB with additional functionality.
 This is known as the "classic" ("old style") PyGreSQL interface.
 For a DB-API 2 compliant interface use the newer pgdb module.
 
@@ -26,8 +26,12 @@
 try:
     from decimal import Decimal
     set_decimal(Decimal)
-except ImportError:
-    pass  # Python < 2.4
+except ImportError:  # Python < 2.4
+    pass
+try:
+    from collections import namedtuple
+except ImportError:  # Python < 2.6
+    namedtuple = None
 
 
 # Auxiliary functions which are independent from a DB connection:
@@ -96,6 +100,16 @@
     return 'oid(%s)' % qcl
 
 
+if namedtuple:
+
+    def _namedresult(q):
+        """Get query result as named tuples."""
+        row = namedtuple('Row', q.listfields())
+        return [row(*r) for r in q.getresult()]
+
+    set_namedresult(_namedresult)
+
+
 def _db_error(msg, cls=DatabaseError):
     """Returns DatabaseError with empty sqlstate attribute."""
     error = cls(msg)

Modified: trunk/module/pgmodule.c
==============================================================================
--- trunk/module/pgmodule.c     Tue Oct  2 12:12:19 2012        (r446)
+++ trunk/module/pgmodule.c     Tue Oct 16 15:16:05 2012        (r447)
@@ -116,7 +116,9 @@
 DL_EXPORT(void) init_pg(void);
 int *get_type_array(PGresult *result, int nfields);
 
-static PyObject *decimal = NULL; /* decimal type */
+static PyObject *decimal = NULL, /* decimal type */
+                               *namedresult = NULL; /* function for getting 
named results */
+
 
 /* --------------------------------------------------------------------- */
 /* OBJECTS DECLARATION */
@@ -2092,7 +2094,7 @@
 /* retrieves last result */
 static char pgquery_getresult__doc__[] =
 "getresult() -- Gets the result of a query.  The result is returned "
-"as a list of rows, each one a list of fields in the order returned "
+"as a list of rows, each one a tuple of fields in the order returned "
 "by the server.";
 
 static PyObject *
@@ -2236,7 +2238,7 @@
        if (args && !PyArg_ParseTuple(args, ""))
        {
                PyErr_SetString(PyExc_TypeError,
-                       "method getresult() takes no parameters.");
+                       "method dictresult() takes no parameters.");
                return NULL;
        }
 
@@ -2340,6 +2342,43 @@
        return reslist;
 }
 
+/* retrieves last result as named tuples */
+static char pgquery_namedresult__doc__[] =
+"namedresult() -- Gets the result of a query.  The result is returned "
+"as a list of rows, each one a tuple of fields in the order returned "
+"by the server.";
+
+static PyObject *
+pgquery_namedresult(pgqueryobject *self, PyObject *args)
+{
+       PyObject   *arglist,
+                          *ret;
+
+       /* checks args (args == NULL for an internal call) */
+       if (args && !PyArg_ParseTuple(args, ""))
+       {
+               PyErr_SetString(PyExc_TypeError,
+                       "method namedresult() takes no parameters.");
+               return NULL;
+       }
+
+       if (!namedresult)
+       {
+               PyErr_SetString(PyExc_TypeError,
+                       "named tuples are not supported.");
+               return NULL;
+       }
+
+       arglist = Py_BuildValue("(O)", self);
+       ret = PyObject_CallObject(namedresult, arglist);
+       Py_DECREF(arglist);
+
+       if (ret == NULL)
+           return NULL;
+
+       return ret;
+}
+
 /* gets asynchronous notify */
 static char pg_getnotify__doc__[] =
 "getnotify() -- get database notify for this connection.";
@@ -3493,6 +3532,8 @@
                        pgquery_getresult__doc__},
        {"dictresult", (PyCFunction) pgquery_dictresult, METH_VARARGS,
                        pgquery_dictresult__doc__},
+       {"namedresult", (PyCFunction) pgquery_namedresult, METH_VARARGS,
+                       pgquery_namedresult__doc__},
        {"fieldname", (PyCFunction) pgquery_fieldname, METH_VARARGS,
                         pgquery_fieldname__doc__},
        {"fieldnum", (PyCFunction) pgquery_fieldnum, METH_VARARGS,
@@ -3641,6 +3682,28 @@
        return ret;
 }
 
+/* set named result */
+static char set_namedresult__doc__[] =
+"set_namedresult(cls) -- set a function to be used for getting named results.";
+
+static PyObject *
+set_namedresult(PyObject *self, PyObject *args)
+{
+    PyObject *ret = NULL;
+    PyObject *func;
+
+    if (PyArg_ParseTuple(args, "O", &func))
+    {
+        if (PyCallable_Check(func)) {
+            Py_XINCREF(func); Py_XDECREF(namedresult); namedresult = func;
+            Py_INCREF(Py_None); ret = Py_None;
+        }
+        else
+            PyErr_SetString(PyExc_TypeError, "parameter must be callable");
+    }
+    return ret;
+}
+
 #ifdef DEFAULT_VARS
 
 /* gets default host */
@@ -3998,6 +4061,8 @@
                        unescape_bytea__doc__},
        {"set_decimal", (PyCFunction) set_decimal, METH_VARARGS,
                        set_decimal__doc__},
+       {"set_namedresult", (PyCFunction) set_namedresult, METH_VARARGS,
+                       set_namedresult__doc__},
 
 #ifdef DEFAULT_VARS
        {"get_defhost", pggetdefhost, METH_VARARGS, getdefhost__doc__},

Modified: trunk/module/test_pg.py
==============================================================================
--- trunk/module/test_pg.py     Tue Oct  2 12:12:19 2012        (r446)
+++ trunk/module/test_pg.py     Tue Oct 16 15:16:05 2012        (r447)
@@ -52,6 +52,10 @@
     from decimal import Decimal
 except ImportError:  # Python < 2.4
     Decimal = float
+try:
+    from collections import namedtuple
+except ImportError:  # Python < 2.6
+    namedtuple = None
 
 
 def smart_ddl(conn, cmd):
@@ -485,6 +489,16 @@
         r = self.c.query(q).dictresult()
         self.assertEqual(r, result)
 
+    def testNamedresult(self):
+        if namedtuple:
+            q = "select 0 as alias0"
+            result = [(0,)]
+            r = self.c.query(q).namedresult()
+            self.assertEqual(r, result)
+            v = r[0]
+            self.assertEqual(v._fields, ('alias0',))
+            self.assertEqual(v.alias0, 0)
+
     def testGet3Cols(self):
         q = "select 1,2,3"
         result = [(1, 2, 3)]
@@ -497,6 +511,16 @@
         r = self.c.query(q).dictresult()
         self.assertEqual(r, result)
 
+    def testGet3NamedCols(self):
+        if namedtuple:
+            q = "select 1 as a,2 as b,3 as c"
+            result = [(1, 2, 3)]
+            r = self.c.query(q).namedresult()
+            self.assertEqual(r, result)
+            v = r[0]
+            self.assertEqual(v._fields, ('a', 'b', 'c'))
+            self.assertEqual(v.b, 2)
+
     def testGet3Rows(self):
         q = "select 3 union select 1 union select 2 order by 1"
         result = [(1,), (2,), (3,)]
@@ -510,6 +534,16 @@
         r = self.c.query(q).dictresult()
         self.assertEqual(r, result)
 
+    def testGet3NamedRows(self):
+        if namedtuple:
+            q = "select 3 as alias3" \
+                " union select 1 union select 2 order by 1"
+            result = [(1,), (2,), (3,)]
+            r = self.c.query(q).namedresult()
+            self.assertEqual(r, result)
+            for v in r:
+                self.assertEqual(v._fields, ('alias3',))
+
     def testDictresultNames(self):
         q = "select 'MixedCase' as MixedCaseAlias"
         result = [{'mixedcasealias': 'MixedCase'}]
@@ -520,6 +554,22 @@
         r = self.c.query(q).dictresult()
         self.assertEqual(r, result)
 
+    def testNamedresultNames(self):
+        if namedtuple:
+            q = "select 'MixedCase' as MixedCaseAlias"
+            result = [('MixedCase',)]
+            r = self.c.query(q).namedresult()
+            self.assertEqual(r, result)
+            v = r[0]
+            self.assertEqual(v._fields, ('mixedcasealias',))
+            self.assertEqual(v.mixedcasealias, 'MixedCase')
+            q = "select 'MixedCase' as \"MixedCaseAlias\""
+            r = self.c.query(q).namedresult()
+            self.assertEqual(r, result)
+            v = r[0]
+            self.assertEqual(v._fields, ('MixedCaseAlias',))
+            self.assertEqual(v.MixedCaseAlias, 'MixedCase')
+
     def testBigGetresult(self):
         num_cols = 100
         num_rows = 100
_______________________________________________
PyGreSQL mailing list
[email protected]
https://mail.vex.net/mailman/listinfo.cgi/pygresql

Reply via email to