diff --git a/NEWS b/NEWS index 5149cf74..cfb88d9b 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ What's new in psycopg 2.4.5 to Menno Smits (tickets #94, #95). - Fixed 'rownumber' during iteration on cursor subclasses. Regression introduced in 2.4.4 (ticket #100). + - Added support for 'inet' arrays. What's new in psycopg 2.4.4 diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 1ad88eda..820468dd 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -325,6 +325,20 @@ details. .. versionadded:: 2.4.3 + .. _cast-array-unknown: + + .. note:: + + The function can be used to create a generic array typecaster, + returning a list of strings: just use the `~psycopg2.STRING` as base + typecaster. For instance, if you want to receive from the database an + array of :sql:`macaddr`, each address represented by string, you can + use:: + + psycopg2.extensions.register_type( + psycopg2.extensions.new_array_type( + (1040,), 'MACADDR[]', psycopg2.STRING)) + .. function:: register_type(obj [, scope]) diff --git a/doc/src/extras.rst b/doc/src/extras.rst index 710e9b5c..1e0f12f2 100644 --- a/doc/src/extras.rst +++ b/doc/src/extras.rst @@ -250,6 +250,7 @@ UUID data type ^^^^^^^^^^^^^^^^^^^^^^ .. versionadded:: 2.0.9 +.. versionchanged:: 2.4.5 added inet array support. .. doctest:: @@ -264,7 +265,7 @@ UUID data type '192.168.0.1/24' -.. autofunction:: register_inet() +.. autofunction:: register_inet .. autoclass:: Inet diff --git a/doc/src/faq.rst b/doc/src/faq.rst index 3296cd84..8afc478f 100644 --- a/doc/src/faq.rst +++ b/doc/src/faq.rst @@ -109,6 +109,13 @@ Transferring binary data from PostgreSQL 9.0 doesn't work. .. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html .. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT +Arrays of *TYPE* are not casted to list. + Arrays are only casted to list when their oid is known, and an array + typecaster is registered for them. If there is no typecaster, the array is + returned unparsed from PostgreSQL (e.g. ``{a,b,c}``). It is easy to create + a generic arrays typecaster, returning a list of array: an example is + provided in the `~psycopg2.extensions.new_array_type()` documentation. + Best practices -------------- diff --git a/doc/src/usage.rst b/doc/src/usage.rst index d480adef..a1b51de7 100644 --- a/doc/src/usage.rst +++ b/doc/src/usage.rst @@ -294,7 +294,7 @@ the SQL string that would be sent to the database. `bytea_output`__ configuration parameter to ``escape``, either in the server configuration file or in the client session (using a query such as ``SET bytea_output TO escape;``) before receiving binary data. - + .. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html .. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT @@ -334,6 +334,14 @@ the SQL string that would be sent to the database. >>> cur.mogrify("SELECT %s;", ([10, 20, 30], )) 'SELECT ARRAY[10, 20, 30];' + .. note:: + + Reading back from PostgreSQL, arrays are converted to list of Python + objects as expected, but only if the types are known one. Arrays of + unknown types are returned as represented by the database (e.g. + ``{a,b,c}``). You can easily create a typecaster for :ref:`array of + unknown types `. + .. _adapt-tuple: .. index:: diff --git a/lib/extras.py b/lib/extras.py index 870b5ca7..f53561d2 100644 --- a/lib/extras.py +++ b/lib/extras.py @@ -455,14 +455,21 @@ class UUID_adapter(object): __str__ = getquoted def register_uuid(oids=None, conn_or_curs=None): - """Create the UUID type and an uuid.UUID adapter.""" + """Create the UUID type and an uuid.UUID adapter. + + :param oids: oid for the PostgreSQL :sql:`uuid` type, or 2-items sequence + with oids of the type and the array. If not specified, use PostgreSQL + standard oids. + :param conn_or_curs: where to register the typecaster. If not specified, + register it globally. + """ import uuid if not oids: oid1 = 2950 oid2 = 2951 - elif type(oids) == list: + elif isinstance(oids, (list, tuple)): oid1, oid2 = oids else: oid1 = oids @@ -491,13 +498,13 @@ class Inet(object): """ def __init__(self, addr): self.addr = addr - + def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.addr) def prepare(self, conn): self._conn = conn - + def getquoted(self): obj = _A(self.addr) if hasattr(obj, 'prepare'): @@ -510,13 +517,32 @@ class Inet(object): def __str__(self): return str(self.addr) - + def register_inet(oid=None, conn_or_curs=None): - """Create the INET type and an Inet adapter.""" - if not oid: oid = 869 - _ext.INET = _ext.new_type((oid, ), "INET", + """Create the INET type and an Inet adapter. + + :param oid: oid for the PostgreSQL :sql:`inet` type, or 2-items sequence + with oids of the type and the array. If not specified, use PostgreSQL + standard oids. + :param conn_or_curs: where to register the typecaster. If not specified, + register it globally. + """ + if not oid: + oid1 = 869 + oid2 = 1041 + elif isinstance(oid, (list, tuple)): + oid1, oid2 = oid + else: + oid1 = oid + oid2 = 1041 + + _ext.INET = _ext.new_type((oid1, ), "INET", lambda data, cursor: data and Inet(data) or None) + _ext.INETARRAY = _ext.new_array_type((oid2, ), "INETARRAY", _ext.INET) + _ext.register_type(_ext.INET, conn_or_curs) + _ext.register_type(_ext.INETARRAY, conn_or_curs) + return _ext.INET diff --git a/tests/test_types_extras.py b/tests/test_types_extras.py index 29a935ee..36dd77ed 100755 --- a/tests/test_types_extras.py +++ b/tests/test_types_extras.py @@ -82,13 +82,22 @@ class TypesExtrasTests(unittest.TestCase): def testINET(self): psycopg2.extras.register_inet() - i = "192.168.1.0/24"; + i = psycopg2.extras.Inet("192.168.1.0/24") s = self.execute("SELECT %s AS foo", (i,)) - self.failUnless(i == s) + self.failUnless(i.addr == s.addr) # must survive NULL cast to inet s = self.execute("SELECT NULL::inet AS foo") self.failUnless(s is None) + def testINETARRAY(self): + psycopg2.extras.register_inet() + i = psycopg2.extras.Inet("192.168.1.0/24") + s = self.execute("SELECT %s AS foo", ([i],)) + self.failUnless(i.addr == s[0].addr) + # must survive NULL cast to inet + s = self.execute("SELECT NULL::inet[] AS foo") + self.failUnless(s is None) + def test_inet_conform(self): from psycopg2.extras import Inet i = Inet("192.168.1.0/24")