Author: cito
Date: Sun Jan 31 17:57:22 2016
New Revision: 804
Log:
Make the automatic conversion to arrays configurable
The automatic conversion of arrays to lists can now be
disabled with the set_array() method.
Modified:
trunk/docs/contents/changelog.rst
trunk/docs/contents/pg/module.rst
trunk/pgmodule.c
trunk/tests/test_classic_dbwrapper.py
Modified: trunk/docs/contents/changelog.rst
==============================================================================
--- trunk/docs/contents/changelog.rst Sun Jan 31 16:39:40 2016 (r803)
+++ trunk/docs/contents/changelog.rst Sun Jan 31 17:57:22 2016 (r804)
@@ -51,7 +51,8 @@
- Conversely, when the query method returns a PostgreSQL array, it is
passed
to Python as a list. PostgreSQL records are converted to named tuples as
well, but only if you use one of the get/insert/update/delete() methods.
- PyGreSQL uses a new fast built-in parser to achieve this.
+ PyGreSQL uses a new fast built-in parser to achieve this. The automatic
+ conversion of arrays to lists can be disabled with set_array(False).
- The pkey() method of the classic interface now returns tuples instead
of frozenset. The order of the tuples is like in the primary key index.
- Like the DB-API 2 module, the classic module now also returns bytea
Modified: trunk/docs/contents/pg/module.rst
==============================================================================
--- trunk/docs/contents/pg/module.rst Sun Jan 31 16:39:40 2016 (r803)
+++ trunk/docs/contents/pg/module.rst Sun Jan 31 17:57:22 2016 (r804)
@@ -428,6 +428,39 @@
.. versionadded:: 4.2
+get/set_array -- whether arrays are returned as list objects
+-------------------------------------------------------------
+
+.. function:: get_array()
+
+ Check whether arrays are returned as list objects
+
+ :returns: whether or not list objects will be returned
+ :rtype: bool
+
+This function checks whether PyGreSQL returns PostgreSQL arrays converted
+to Python list objects, or simply as text in the internal special output
+syntax of PostgreSQL. By default, conversion to list objects is activated,
+but you can disable this with the :func:`set_array` function.
+
+.. versionadded:: 5.0
+
+.. function:: set_array(on)
+
+ Set whether arrays are returned as list objects
+
+ :param on: whether or not list objects shall be returned
+
+This function can be used to specify whether PyGreSQL shall return PostgreSQL
+arrays converted to Python list objects, or simply as text in the internal
+special output syntax of PostgreSQL. By default, conversion to list objects
+is activated, but you can disable this by calling ``set_array(False)``.
+
+.. versionadded:: 5.0
+
+.. versionchanged:: 5.0
+ Arrays had been returned as text strings only in earlier versions.
+
get/set_bytea_escaped -- whether bytea data is returned escaped
---------------------------------------------------------------
Modified: trunk/pgmodule.c
==============================================================================
--- trunk/pgmodule.c Sun Jan 31 16:39:40 2016 (r803)
+++ trunk/pgmodule.c Sun Jan 31 17:57:22 2016 (r804)
@@ -96,7 +96,8 @@
*namedresult = NULL, /* function for getting
named results */
*jsondecode = NULL; /* function for decoding
json strings */
static char decimal_point = '.'; /* decimal point used in money values */
-static int use_bool = 0; /* whether or not bool objects shall be returned */
+static int bool_as_text = 1; /* whether bool shall be returned as text */
+static int array_as_text = 0; /* whether arrays shall be returned as text */
static int bytea_escaped = 0; /* whether bytea shall be returned escaped */
static int pg_encoding_utf8 = 0;
@@ -314,38 +315,40 @@
case CIDARRAYOID:
case OIDARRAYOID:
case XIDARRAYOID:
- t = PYGRES_INT | PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : (PYGRES_INT |
PYGRES_ARRAY);
break;
case INT8ARRAYOID:
- t = PYGRES_LONG | PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : (PYGRES_LONG |
PYGRES_ARRAY);
break;
case FLOAT4ARRAYOID:
case FLOAT8ARRAYOID:
- t = PYGRES_FLOAT | PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : (PYGRES_FLOAT |
PYGRES_ARRAY);
break;
case NUMERICARRAYOID:
- t = PYGRES_DECIMAL | PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : (PYGRES_DECIMAL |
PYGRES_ARRAY);
break;
case CASHARRAYOID:
- t = (decimal_point ?
- PYGRES_MONEY : PYGRES_TEXT) | PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : ((decimal_point ?
+ PYGRES_MONEY : PYGRES_TEXT) | PYGRES_ARRAY);
break;
case BOOLARRAYOID:
- t = PYGRES_BOOL | PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : (PYGRES_BOOL |
PYGRES_ARRAY);
break;
case BYTEAARRAYOID:
- t = (bytea_escaped ? PYGRES_TEXT : PYGRES_BYTEA) |
PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : ((bytea_escaped ?
+ PYGRES_TEXT : PYGRES_BYTEA) | PYGRES_ARRAY);
break;
case JSONARRAYOID:
case JSONBARRAYOID:
- t = (jsondecode ? PYGRES_JSON : PYGRES_TEXT) |
PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : ((jsondecode ?
+ PYGRES_JSON : PYGRES_TEXT) | PYGRES_ARRAY);
break;
case BPCHARARRAYOID:
@@ -360,7 +363,7 @@
case TIMESTAMPARRAYOID:
case TIMESTAMPTZARRAYOID:
case REGTYPEARRAYOID:
- t = PYGRES_TEXT | PYGRES_ARRAY;
+ t = array_as_text ? PYGRES_TEXT : (PYGRES_TEXT |
PYGRES_ARRAY);
break;
default:
@@ -537,15 +540,15 @@
break;
case PYGRES_BOOL:
- /* convert to bool only if use_bool is set */
- if (use_bool)
+ /* convert to bool only if bool_as_text is not set */
+ if (bool_as_text)
{
- obj = *s == 't' ? Py_True : Py_False;
- Py_INCREF(obj);
+ obj = PyStr_FromString(*s == 't' ? "t" : "f");
}
else
{
- obj = PyStr_FromString(*s == 't' ? "t" : "f");
+ obj = *s == 't' ? Py_True : Py_False;
+ Py_INCREF(obj);
}
break;
@@ -611,15 +614,15 @@
break;
case PYGRES_BOOL:
- /* convert to bool only if use_bool is set */
- if (use_bool)
+ /* convert to bool only if bool_as_text is not set */
+ if (bool_as_text)
{
- obj = *s == 't' ? Py_True : Py_False;
- Py_INCREF(obj);
+ obj = PyStr_FromString(*s == 't' ? "t" : "f");
}
else
{
- obj = PyStr_FromString(*s == 't' ? "t" : "f");
+ obj = *s == 't' ? Py_True : Py_False;
+ Py_INCREF(obj);
}
break;
@@ -5101,7 +5104,7 @@
if (PyArg_ParseTuple(args, ""))
{
- ret = use_bool ? Py_True : Py_False;
+ ret = bool_as_text ? Py_False : Py_True;
Py_INCREF(ret);
}
else
@@ -5124,7 +5127,7 @@
/* gets arguments */
if (PyArg_ParseTuple(args, "i", &i))
{
- use_bool = i ? 1 : 0;
+ bool_as_text = i ? 0 : 1;
Py_INCREF(Py_None); ret = Py_None;
}
else
@@ -5134,6 +5137,50 @@
return ret;
}
+/* get conversion of arrays to lists */
+static char pgGetArray__doc__[] =
+"get_array() -- check whether arrays are converted as lists";
+
+static PyObject *
+pgGetArray(PyObject *self, PyObject * args)
+{
+ PyObject *ret = NULL;
+
+ if (PyArg_ParseTuple(args, ""))
+ {
+ ret = array_as_text ? Py_False : Py_True;
+ Py_INCREF(ret);
+ }
+ else
+ PyErr_SetString(PyExc_TypeError,
+ "Function get_array() takes no arguments");
+
+ return ret;
+}
+
+/* set conversion of arrays to lists */
+static char pgSetArray__doc__[] =
+"set_array(on) -- set whether arrays should be converted to lists";
+
+static PyObject *
+pgSetArray(PyObject *self, PyObject * args)
+{
+ PyObject *ret = NULL;
+ int i;
+
+ /* gets arguments */
+ if (PyArg_ParseTuple(args, "i", &i))
+ {
+ array_as_text = i ? 0 : 1;
+ Py_INCREF(Py_None); ret = Py_None;
+ }
+ else
+ PyErr_SetString(PyExc_TypeError,
+ "Function set_array() expects a boolean value as
argument");
+
+ return ret;
+}
+
/* check whether bytea values are unescaped */
static char pgGetByteaEscaped__doc__[] =
"get_bytea_escaped() -- check whether bytea will be returned escaped";
@@ -5722,6 +5769,8 @@
pgSetDecimal__doc__},
{"get_bool", (PyCFunction) pgGetBool, METH_VARARGS, pgGetBool__doc__},
{"set_bool", (PyCFunction) pgSetBool, METH_VARARGS, pgSetBool__doc__},
+ {"get_array", (PyCFunction) pgGetArray, METH_VARARGS,
pgGetArray__doc__},
+ {"set_array", (PyCFunction) pgSetArray, METH_VARARGS,
pgSetArray__doc__},
{"get_bytea_escaped", (PyCFunction) pgGetByteaEscaped, METH_VARARGS,
pgGetByteaEscaped__doc__},
{"set_bytea_escaped", (PyCFunction) pgSetByteaEscaped, METH_VARARGS,
Modified: trunk/tests/test_classic_dbwrapper.py
==============================================================================
--- trunk/tests/test_classic_dbwrapper.py Sun Jan 31 16:39:40 2016
(r803)
+++ trunk/tests/test_classic_dbwrapper.py Sun Jan 31 17:57:22 2016
(r804)
@@ -3077,6 +3077,7 @@
self.assertIsInstance(r['stock'], dict)
def testArray(self):
+ returns_arrays = pg.get_array()
self.createTable('arraytest',
'id smallint, i2 smallint[], i4 integer[], i8 bigint[],'
' d numeric[], f4 real[], f8 double precision[], m money[],'
@@ -3118,33 +3119,56 @@
t=['abc', 'Hello, World!', '"Hello, World!"', '', None])
r = data.copy()
self.db.insert('arraytest', r)
- self.assertEqual(r, data)
+ if returns_arrays:
+ self.assertEqual(r, data)
+ else:
+ self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
self.db.insert('arraytest', r)
r = self.db.get('arraytest', 42, 'id')
- self.assertEqual(r, data)
+ if returns_arrays:
+ self.assertEqual(r, data)
+ else:
+ self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
r = self.db.query('select * from arraytest limit 1').dictresult()[0]
- self.assertEqual(r, data)
+ if returns_arrays:
+ self.assertEqual(r, data)
+ else:
+ self.assertEqual(r['i4'], '{42,123456789,NULL,0,1,-1}')
def testArrayLiteral(self):
insert = self.db.insert
+ returns_arrays = pg.get_array()
self.createTable('arraytest', 'i int[], t text[]', oids=True)
r = dict(i=[1, 2, 3], t=['a', 'b', 'c'])
insert('arraytest', r)
- self.assertEqual(r['i'], [1, 2, 3])
- self.assertEqual(r['t'], ['a', 'b', 'c'])
+ if returns_arrays:
+ self.assertEqual(r['i'], [1, 2, 3])
+ self.assertEqual(r['t'], ['a', 'b', 'c'])
+ else:
+ self.assertEqual(r['i'], '{1,2,3}')
+ self.assertEqual(r['t'], '{a,b,c}')
r = dict(i='{1,2,3}', t='{a,b,c}')
self.db.insert('arraytest', r)
- self.assertEqual(r['i'], [1, 2, 3])
- self.assertEqual(r['t'], ['a', 'b', 'c'])
+ if returns_arrays:
+ self.assertEqual(r['i'], [1, 2, 3])
+ self.assertEqual(r['t'], ['a', 'b', 'c'])
+ else:
+ self.assertEqual(r['i'], '{1,2,3}')
+ self.assertEqual(r['t'], '{a,b,c}')
L = pg.Literal
r = dict(i=L("ARRAY[1, 2, 3]"), t=L("ARRAY['a', 'b', 'c']"))
self.db.insert('arraytest', r)
- self.assertEqual(r['i'], [1, 2, 3])
- self.assertEqual(r['t'], ['a', 'b', 'c'])
+ if returns_arrays:
+ self.assertEqual(r['i'], [1, 2, 3])
+ self.assertEqual(r['t'], ['a', 'b', 'c'])
+ else:
+ self.assertEqual(r['i'], '{1,2,3}')
+ self.assertEqual(r['t'], '{a,b,c}')
r = dict(i="1, 2, 3", t="'a', 'b', 'c'")
self.assertRaises(pg.ProgrammingError, self.db.insert, 'arraytest', r)
def testArrayOfIds(self):
+ array_on = pg.get_array()
self.createTable('arraytest', 'c cid[], o oid[], x xid[]', oids=True)
r = self.db.get_attnames('arraytest')
if self.regtypes:
@@ -3158,13 +3182,20 @@
self.db.insert('arraytest', r)
qoid = 'oid(arraytest)'
oid = r.pop(qoid)
- self.assertEqual(r, data)
+ if array_on:
+ self.assertEqual(r, data)
+ else:
+ self.assertEqual(r['o'], '{21,22,23}')
r = {qoid: oid}
self.db.get('arraytest', r)
self.assertEqual(oid, r.pop(qoid))
- self.assertEqual(r, data)
+ if array_on:
+ self.assertEqual(r, data)
+ else:
+ self.assertEqual(r['o'], '{21,22,23}')
def testArrayOfText(self):
+ array_on = pg.get_array()
self.createTable('arraytest', 'data text[]', oids=True)
r = self.db.get_attnames('arraytest')
self.assertEqual(r['data'], 'text[]')
@@ -3173,17 +3204,22 @@
"It's all \\ kinds of\r nasty stuff!\n"]
r = dict(data=data)
self.db.insert('arraytest', r)
+ if not array_on:
+ r['data'] = pg.cast_array(r['data'])
self.assertEqual(r['data'], data)
self.assertIsInstance(r['data'][1], str)
self.assertIsNone(r['data'][2])
r['data'] = None
self.db.get('arraytest', r)
+ if not array_on:
+ r['data'] = pg.cast_array(r['data'])
self.assertEqual(r['data'], data)
self.assertIsInstance(r['data'][1], str)
self.assertIsNone(r['data'][2])
def testArrayOfBytea(self):
- unescape = pg.unescape_bytea if pg.get_bytea_escaped() else None
+ array_on = pg.get_array()
+ bytea_escaped = pg.get_bytea_escaped()
self.createTable('arraytest', 'data bytea[]', oids=True)
r = self.db.get_attnames('arraytest')
self.assertEqual(r['data'], 'bytea[]')
@@ -3191,20 +3227,24 @@
b"It's all \\ kinds \x00 of\r nasty \xff stuff!\n"]
r = dict(data=data)
self.db.insert('arraytest', r)
- if unescape:
+ if array_on:
+ self.assertIsInstance(r['data'], list)
+ if array_on and not bytea_escaped:
+ self.assertEqual(r['data'], data)
+ self.assertIsInstance(r['data'][1], bytes)
+ self.assertIsNone(r['data'][2])
+ else:
self.assertNotEqual(r['data'], data)
- r['data'] = [unescape(v) if v else v for v in r['data']]
- self.assertEqual(r['data'], data)
- self.assertIsInstance(r['data'][1], bytes)
- self.assertIsNone(r['data'][2])
r['data'] = None
self.db.get('arraytest', r)
- if unescape:
+ if array_on:
+ self.assertIsInstance(r['data'], list)
+ if array_on and not bytea_escaped:
+ self.assertEqual(r['data'], data)
+ self.assertIsInstance(r['data'][1], bytes)
+ self.assertIsNone(r['data'][2])
+ else:
self.assertNotEqual(r['data'], data)
- r['data'] = [unescape(v) if v else v for v in r['data']]
- self.assertEqual(r['data'], data)
- self.assertIsInstance(r['data'][1], bytes)
- self.assertIsNone(r['data'][2])
def testArrayOfJson(self):
try:
@@ -3216,19 +3256,26 @@
r = self.db.get_attnames('arraytest')
self.assertEqual(r['data'], 'json[]')
data = [dict(id=815, name='John Doe'), dict(id=816, name='Jane Roe')]
+ array_on = pg.get_array()
jsondecode = pg.get_jsondecode()
r = dict(data=data)
self.db.insert('arraytest', r)
+ if not array_on:
+ r['data'] = pg.cast_array(r['data'], jsondecode)
if jsondecode is None:
r['data'] = [json.loads(d) for d in r['data']]
self.assertEqual(r['data'], data)
r['data'] = None
self.db.get('arraytest', r)
+ if not array_on:
+ r['data'] = pg.cast_array(r['data'], jsondecode)
if jsondecode is None:
r['data'] = [json.loads(d) for d in r['data']]
self.assertEqual(r['data'], data)
r = dict(data=[json.dumps(d) for d in data])
self.db.insert('arraytest', r)
+ if not array_on:
+ r['data'] = pg.cast_array(r['data'], jsondecode)
if jsondecode is None:
r['data'] = [json.loads(d) for d in r['data']]
self.assertEqual(r['data'], data)
@@ -3238,10 +3285,13 @@
r = dict(data=['', None])
self.db.insert('arraytest', r)
r = r['data']
- self.assertIsInstance(r, list)
- self.assertEqual(len(r), 2)
- self.assertIsNone(r[0])
- self.assertIsNone(r[1])
+ if array_on:
+ self.assertIsInstance(r, list)
+ self.assertEqual(len(r), 2)
+ self.assertIsNone(r[0])
+ self.assertIsNone(r[1])
+ else:
+ self.assertEqual(r, '{NULL,NULL}')
def testArrayOfJsonb(self):
try:
@@ -3253,19 +3303,26 @@
r = self.db.get_attnames('arraytest')
self.assertEqual(r['data'], 'jsonb[]' if self.regtypes else 'json[]')
data = [dict(id=815, name='John Doe'), dict(id=816, name='Jane Roe')]
+ array_on = pg.get_array()
jsondecode = pg.get_jsondecode()
r = dict(data=data)
self.db.insert('arraytest', r)
+ if not array_on:
+ r['data'] = pg.cast_array(r['data'], jsondecode)
if jsondecode is None:
r['data'] = [json.loads(d) for d in r['data']]
self.assertEqual(r['data'], data)
r['data'] = None
self.db.get('arraytest', r)
+ if not array_on:
+ r['data'] = pg.cast_array(r['data'], jsondecode)
if jsondecode is None:
r['data'] = [json.loads(d) for d in r['data']]
self.assertEqual(r['data'], data)
r = dict(data=[json.dumps(d) for d in data])
self.db.insert('arraytest', r)
+ if not array_on:
+ r['data'] = pg.cast_array(r['data'], jsondecode)
if jsondecode is None:
r['data'] = [json.loads(d) for d in r['data']]
self.assertEqual(r['data'], data)
@@ -3275,22 +3332,32 @@
r = dict(data=['', None])
self.db.insert('arraytest', r)
r = r['data']
- self.assertIsInstance(r, list)
- self.assertEqual(len(r), 2)
- self.assertIsNone(r[0])
- self.assertIsNone(r[1])
+ if array_on:
+ self.assertIsInstance(r, list)
+ self.assertEqual(len(r), 2)
+ self.assertIsNone(r[0])
+ self.assertIsNone(r[1])
+ else:
+ self.assertEqual(r, '{NULL,NULL}')
def testDeepArray(self):
+ array_on = pg.get_array()
self.createTable('arraytest', 'data text[][][]', oids=True)
r = self.db.get_attnames('arraytest')
self.assertEqual(r['data'], 'text[]')
data = [[['Hello, World!', '{a,b,c}', 'back\\slash']]]
r = dict(data=data)
self.db.insert('arraytest', r)
- self.assertEqual(r['data'], data)
+ if array_on:
+ self.assertEqual(r['data'], data)
+ else:
+ self.assertTrue(r['data'].startswith('{{{"Hello,'))
r['data'] = None
self.db.get('arraytest', r)
- self.assertEqual(r['data'], data)
+ if array_on:
+ self.assertEqual(r['data'], data)
+ else:
+ self.assertTrue(r['data'].startswith('{{{"Hello,'))
def testInsertUpdateGetRecord(self):
query = self.db.query
@@ -3635,6 +3702,8 @@
cls.set_option('decimal', float)
not_bool = not pg.get_bool()
cls.set_option('bool', not_bool)
+ not_array = not pg.get_array()
+ cls.set_option('array', not_array)
not_bytea_escaped = not pg.get_bytea_escaped()
cls.set_option('bytea_escaped', not_bytea_escaped)
cls.set_option('namedresult', None)
@@ -3648,6 +3717,7 @@
cls.reset_option('jsondecode')
cls.reset_option('namedresult')
cls.reset_option('bool')
+ cls.reset_option('array')
cls.reset_option('bytea_escaped')
cls.reset_option('decimal')
_______________________________________________
PyGreSQL mailing list
[email protected]
https://mail.vex.net/mailman/listinfo.cgi/pygresql