diff --git a/NEWS b/NEWS index d8be8cb5..c6635417 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ What's new in psycopg 2.4.3 --------------------------- + - Added 'new_array_type()' function for easy creation of array + typecasters. - Fixed segfault in case of transaction started with connection lost (and possibly other events). - Rollback connections in transaction or in error before putting them diff --git a/doc/src/advanced.rst b/doc/src/advanced.rst index 3ae15f05..4ccc3f15 100644 --- a/doc/src/advanced.rst +++ b/doc/src/advanced.rst @@ -216,6 +216,9 @@ read: >>> print type(point), point.x, point.y 10.2 20.3 +A typecaster created by `!new_type()` can be also used with +`~psycopg2.extensions.new_array_type()` to create a typecaster converting a +PostgreSQL array into a Python list. .. index:: diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 2fb1ae5c..ad933ef9 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -290,7 +290,7 @@ details. .. function:: new_type(oids, name, adapter) Create a new type caster to convert from a PostgreSQL type to a Python - object. The created object must be registered using + object. The object created must be registered using `register_type()` to be used. :param oids: tuple of OIDs of the PostgreSQL type to convert. @@ -309,6 +309,23 @@ details. See :ref:`type-casting-from-sql-to-python` for an usage example. +.. function:: new_array_type(oids, name, base_caster) + + Create a new type caster to convert from a PostgreSQL array type to a list + of Python object. The object created must be registered using + `register_type()` to be used. + + :param oids: tuple of OIDs of the PostgreSQL type to convert. It should + probably be the oid of the array type (e.g. the ``typarray`` field in + the ``pg_type`` table. + :param name: the name of the new type adapter. + :param base_caster: a Psycopg typecaster, e.g. created using the + `new_type()` function. The caster should be able to parse a single + item of the desired type. + + .. versionadded:: 2.4.3 + + .. function:: register_type(obj [, scope]) Register a type caster created using `new_type()`. diff --git a/lib/extensions.py b/lib/extensions.py index 10da66a0..de022ddf 100644 --- a/lib/extensions.py +++ b/lib/extensions.py @@ -57,7 +57,7 @@ except ImportError: pass from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid -from psycopg2._psycopg import string_types, binary_types, new_type, register_type +from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type from psycopg2._psycopg import ISQLQuote, Notify from psycopg2._psycopg import QueryCanceledError, TransactionRollbackError diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index f37a98e7..4dbf98d7 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -257,7 +257,7 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) " * `conn_or_curs`: A connection, cursor or None" #define typecast_from_python_doc \ -"new_type(oids, name, adapter) -> new type object\n\n" \ +"new_type(oids, name, castobj) -> new type object\n\n" \ "Create a new binding object. The object can be used with the\n" \ "`register_type()` function to bind PostgreSQL objects to python objects.\n\n" \ ":Parameters:\n" \ @@ -268,6 +268,15 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) " the string representation returned by PostgreSQL (`!None` if ``NULL``)\n" \ " and ``cur`` is the cursor from which data are read." +#define typecast_array_from_python_doc \ +"new_array_type(oids, name, baseobj) -> new type object\n\n" \ +"Create a new binding object to parse an array.\n\n" \ +"The object can be used with `register_type()`.\n\n" \ +":Parameters:\n" \ +" * `oids`: Tuple of ``oid`` of the PostgreSQL types to convert.\n" \ +" * `name`: Name for the new type\n" \ +" * `baseobj`: Adapter to perform type conversion of a single array item." + static void _psyco_register_type_set(PyObject **dict, PyObject *type) { @@ -758,6 +767,8 @@ static PyMethodDef psycopgMethods[] = { METH_VARARGS, psyco_register_type_doc}, {"new_type", (PyCFunction)typecast_from_python, METH_VARARGS|METH_KEYWORDS, typecast_from_python_doc}, + {"new_array_type", (PyCFunction)typecast_array_from_python, + METH_VARARGS|METH_KEYWORDS, typecast_array_from_python_doc}, {"AsIs", (PyCFunction)psyco_AsIs, METH_VARARGS, psyco_AsIs_doc}, diff --git a/psycopg/typecast.c b/psycopg/typecast.c index 56a203dc..8ede351c 100644 --- a/psycopg/typecast.c +++ b/psycopg/typecast.c @@ -603,6 +603,29 @@ typecast_from_python(PyObject *self, PyObject *args, PyObject *keywds) return typecast_new(name, v, cast, base); } +PyObject * +typecast_array_from_python(PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *values, *name = NULL, *base = NULL; + typecastObject *obj = NULL; + + static char *kwlist[] = {"values", "name", "baseobj", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!O!O!", kwlist, + &PyTuple_Type, &values, + &Text_Type, &name, + &typecastType, &base)) { + return NULL; + } + + if ((obj = (typecastObject *)typecast_new(name, values, NULL, base))) { + obj->ccast = typecast_GENERIC_ARRAY_cast; + obj->pcast = NULL; + } + + return (PyObject *)obj; +} + PyObject * typecast_from_c(typecastObject_initlist *type, PyObject *dict) { diff --git a/psycopg/typecast.h b/psycopg/typecast.h index cbae10a7..20def306 100644 --- a/psycopg/typecast.h +++ b/psycopg/typecast.h @@ -77,9 +77,11 @@ HIDDEN int typecast_add(PyObject *obj, PyObject *dict, int binary); /* the C callable typecastObject creator function */ HIDDEN PyObject *typecast_from_c(typecastObject_initlist *type, PyObject *d); -/* the python callable typecast creator function */ +/* the python callable typecast creator functions */ HIDDEN PyObject *typecast_from_python( PyObject *self, PyObject *args, PyObject *keywds); +HIDDEN PyObject *typecast_array_from_python( + PyObject *self, PyObject *args, PyObject *keywds); /* the function used to dispatch typecasting calls */ HIDDEN PyObject *typecast_cast( diff --git a/tests/types_basic.py b/tests/types_basic.py index 709907eb..3686dec4 100755 --- a/tests/types_basic.py +++ b/tests/types_basic.py @@ -285,6 +285,19 @@ class TypesBasicTests(unittest.TestCase): l1 = self.execute("select -%s;", (-1L,)) self.assertEqual(1, l1) + def testGenericArray(self): + def caster(s, cur): + if s is None: return "nada" + return int(s) * 2 + base = psycopg2.extensions.new_type((23,), "INT4", caster) + array = psycopg2.extensions.new_array_type((1007,), "INT4ARRAY", base) + + psycopg2.extensions.register_type(array, self.conn) + a = self.execute("select '{1,2,3}'::int4[]") + self.assertEqual(a, [2,4,6]) + a = self.execute("select '{1,2,NULL}'::int4[]") + self.assertEqual(a, [2,4,'nada']) + class AdaptSubclassTest(unittest.TestCase): def test_adapt_subtype(self):