diff options
| -rw-r--r-- | MANIFEST.in | 4 | ||||
| -rw-r--r-- | NEWS | 5 | ||||
| -rw-r--r-- | doc/src/connection.rst | 2 | ||||
| -rw-r--r-- | doc/src/cursor.rst | 6 | ||||
| -rw-r--r-- | doc/src/extensions.rst | 4 | ||||
| -rw-r--r-- | doc/src/extras.rst | 2 | ||||
| -rw-r--r-- | doc/src/faq.rst | 16 | ||||
| -rw-r--r-- | doc/src/usage.rst | 30 | ||||
| -rw-r--r-- | lib/__init__.py | 7 | ||||
| -rw-r--r-- | psycopg/adapter_binary.c | 91 | ||||
| -rw-r--r-- | psycopg/adapter_list.c | 2 | ||||
| -rw-r--r-- | psycopg/psycopgmodule.c | 9 | ||||
| -rw-r--r-- | setup.py | 2 | ||||
| -rwxr-xr-x | tests/test_async.py | 4 | ||||
| -rwxr-xr-x | tests/test_cancel.py | 6 | ||||
| -rwxr-xr-x | tests/test_connection.py | 4 | ||||
| -rwxr-xr-x | tests/test_cursor.py | 5 | ||||
| -rwxr-xr-x | tests/test_transaction.py | 4 | ||||
| -rw-r--r-- | tests/testutils.py | 96 | ||||
| -rwxr-xr-x | tests/types_basic.py | 56 |
20 files changed, 233 insertions, 122 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 470b9d6..760dd06 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -recursive-include psycopg *.c *.h +recursive-include psycopg *.c *.h *.manifest recursive-include lib *.py recursive-include tests *.py recursive-include ZPsycopgDA *.py *.gif *.dtml @@ -12,5 +12,5 @@ recursive-include doc/html * prune doc/src/_build recursive-include scripts *.py *.sh include scripts/maketypes.sh scripts/buildtypes.py -include AUTHORS README INSTALL LICENSE NEWS-2.0 NEWS-2.3 ChangeLog +include AUTHORS README INSTALL LICENSE NEWS ChangeLog include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg Makefile @@ -3,6 +3,10 @@ What's new in psycopg 2.4 * New features and changes: + - Added support for Python 3.1 and 3.2. + - Adapt types 'bytearray' (from Python 2.6), 'memoryview' (from Python 2.7) + and other objects implementing the 'Revised Buffer Protocol' to bytea data + type. - Added `register_composite()` function to cast PostgreSQL composite types into Python tuples/namedtuples. - More efficient iteration on named cursors. @@ -17,7 +21,6 @@ What's new in psycopg 2.4 missing encodings: EUC_CN, EUC_JIS_2004, ISO885910, ISO885916, LATIN10, SHIFT_JIS_2004. - Dropped repeated dictionary lookups with unicode query/parameters. - - Empty lists correctly roundtrip Python -> PostgreSQL -> Python. * Bug fixes: diff --git a/doc/src/connection.rst b/doc/src/connection.rst index d0f5e1a..8534edd 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -528,7 +528,7 @@ The ``connection`` class .. versionadded:: 2.0.8 - .. versionchanged:: 2.3.3 added ``b`` and ``t`` mode and unicode + .. versionchanged:: 2.4 added ``b`` and ``t`` mode and unicode support. diff --git a/doc/src/cursor.rst b/doc/src/cursor.rst index 57fe73b..3609a87 100644 --- a/doc/src/cursor.rst +++ b/doc/src/cursor.rst @@ -174,7 +174,7 @@ The ``cursor`` class Use the most specific of the typecasters registered by `~psycopg2.extensions.register_type()`. - .. versionadded:: 2.3.3 + .. versionadded:: 2.4 .. extension:: @@ -208,7 +208,7 @@ The ``cursor`` class (2, None, 'dada') (3, 42, 'bar') - .. versionchanged:: 2.3.3 + .. versionchanged:: 2.4 iterating over a :ref:`named cursor <server-side-cursors>` fetches `~cursor.arraysize` records at time from the backend. Previously only one record was fetched per roundtrip, resulting @@ -315,7 +315,7 @@ The ``cursor`` class you really want to retrieve one record at time from the backend use `fetchone()` in a loop. - .. versionchanged:: 2.3.3 + .. versionchanged:: 2.4 `!arraysize` used in named cursor iteration. diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 87f3dd0..effdbeb 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -63,7 +63,7 @@ functionalities defined by the |DBAPI|_. `connection.encoding`) if the file was open in ``t`` mode, a bytes string for ``b`` mode. - .. versionchanged:: 2.3.3 + .. versionchanged:: 2.4 added Unicode support. .. method:: write(str) @@ -72,7 +72,7 @@ functionalities defined by the |DBAPI|_. written. Unicode strings are encoded in the `connection.encoding` before writing. - .. versionchanged:: 2.3.3 + .. versionchanged:: 2.4 added Unicode support. .. method:: export(file_name) diff --git a/doc/src/extras.rst b/doc/src/extras.rst index 05fc19b..95f4d72 100644 --- a/doc/src/extras.rst +++ b/doc/src/extras.rst @@ -165,7 +165,7 @@ can be enabled using the `register_hstore()` function. Composite types casting ^^^^^^^^^^^^^^^^^^^^^^^ -.. versionadded:: 2.3.3 +.. versionadded:: 2.4 Using `register_composite()` it is possible to cast a PostgreSQL composite type (e.g. created with |CREATE TYPE|_ command) into a Python named tuple, or diff --git a/doc/src/faq.rst b/doc/src/faq.rst index 9172e35..52c82d2 100644 --- a/doc/src/faq.rst +++ b/doc/src/faq.rst @@ -107,6 +107,7 @@ 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 + Best practices -------------- @@ -138,8 +139,8 @@ What are the advantages or disadvantages of using named cursors? little memory on the client and to skip or discard parts of the result set. -Problems compiling Psycopg from source --------------------------------------- +Problems compiling and deploying psycopg2 +----------------------------------------- .. cssclass:: faq @@ -151,3 +152,14 @@ I can't compile `!psycopg2`: the compiler says *error: libpq-fe.h: No such file You need to install the development version of the libpq: the package is usually called ``libpq-dev``. +Psycopg raises *ImportError: cannot import name tz* on import in mod_wsgi / ASP, but it works fine otherwise. + If `!psycopg2` is installed in an egg_ (e.g. because installed by + :program:`easy_install`), the user running the program may be unable to + write in the `eggs cache`__. Set the env variable + :envvar:`PYTHON_EGG_CACHE` to a writable directory. With modwsgi you can + use the WSGIPythonEggs__ directive. + + .. _egg: http://peak.telecommunity.com/DevCenter/PythonEggs + .. __: http://stackoverflow.com/questions/2192323/what-is-the-python-egg-cache-python-egg-cache + .. __: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPythonEggs + diff --git a/doc/src/usage.rst b/doc/src/usage.rst index 60a2428..9f0c5db 100644 --- a/doc/src/usage.rst +++ b/doc/src/usage.rst @@ -244,12 +244,34 @@ the SQL string that would be sent to the database. .. index:: single: Buffer; Adaptation single: bytea; Adaptation + single: bytes; Adaptation + single: bytearray; Adaptation + single: memoryview; Adaptation single: Binary string -- Binary types: Python types such as `!bytes`, `!bytearray`, `!buffer`, - `!memoryview` are converted in PostgreSQL binary string syntax, suitable for - :sql:`bytea` fields. Received data is returned as `!buffer` (in Python 2) or - `!memoryview` (in Python 3). +- Binary types: Python types representing binary objects are converted in + PostgreSQL binary string syntax, suitable for :sql:`bytea` fields. Such + types are `!buffer` (only available in Python 2), `!memoryview` (available + from Python 2.7), `!bytearray` (available from Python 2.6) and `!bytes` + (only form Python 3: the name is available from Python 2.6 but it's only an + alias for the type `!str`). Any object implementing the `Revised Buffer + Protocol`__ should be usable as binary type where the protocol is supported + (i.e. from Python 2.6). Received data is returned as `!buffer` (in Python 2) + or `!memoryview` (in Python 3). + + .. __: http://www.python.org/dev/peps/pep-3118/ + + .. versionchanged:: 2.4 + only strings were supported before. + + .. note:: + + In Python 2, if you have binary data in a `!str` object, you can pass them + to a :sql:`bytea` field using the `psycopg2.Binary` wrapper:: + + mypic = open('picture.png', 'rb').read() + curs.execute("insert into blobs (file) values (%s)", + (psycopg2.Binary(mypic),)) .. warning:: diff --git a/lib/__init__.py b/lib/__init__.py index 4b9e22a..48a9847 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -62,7 +62,9 @@ if sys.version_info[0] >= 2 and sys.version_info[1] >= 4: RuntimeWarning) del sys, warnings -from psycopg2 import tz +# Note: the first internal import should be _psycopg, otherwise the real cause +# of a failed loading of the C module may get hidden, see +# http://archives.postgresql.org/psycopg/2011-02/msg00044.php # Import the DBAPI-2.0 stuff into top-level module. @@ -78,6 +80,9 @@ from psycopg2._psycopg import NotSupportedError, OperationalError from psycopg2._psycopg import connect, apilevel, threadsafety, paramstyle from psycopg2._psycopg import __version__ +from psycopg2 import tz + + # Register default adapters. import psycopg2.extensions as _ext diff --git a/psycopg/adapter_binary.c b/psycopg/adapter_binary.c index ccfaf24..00bdb9e 100644 --- a/psycopg/adapter_binary.c +++ b/psycopg/adapter_binary.c @@ -47,54 +47,79 @@ binary_escape(unsigned char *from, size_t from_length, return PQescapeBytea(from, from_length, to_length); } +#define HAS_BUFFER (PY_MAJOR_VERSION < 3) +#define HAS_MEMORYVIEW (PY_MAJOR_VERSION > 2 || PY_MINOR_VERSION >= 6) + /* binary_quote - do the quote process on plain and unicode strings */ static PyObject * binary_quote(binaryObject *self) { - char *to; - const char *buffer; + char *to = NULL; + const char *buffer = NULL; Py_ssize_t buffer_len; size_t len = 0; + PyObject *rv = NULL; +#if HAS_MEMORYVIEW + Py_buffer view; + int got_view = 0; +#endif /* if we got a plain string or a buffer we escape it and save the buffer */ - if (Bytes_Check(self->wrapped) -#if PY_MAJOR_VERSION < 3 - || PyBuffer_Check(self->wrapped) -#else - || PyByteArray_Check(self->wrapped) - || PyMemoryView_Check(self->wrapped) + +#if HAS_MEMORYVIEW + if (PyObject_CheckBuffer(self->wrapped)) { + if (0 > PyObject_GetBuffer(self->wrapped, &view, PyBUF_CONTIG_RO)) { + goto exit; + } + got_view = 1; + buffer = (const char *)(view.buf); + buffer_len = view.len; + } #endif - ) { - /* escape and build quoted buffer */ + +#if HAS_BUFFER + if (!buffer && (Bytes_Check(self->wrapped) || PyBuffer_Check(self->wrapped))) { if (PyObject_AsReadBuffer(self->wrapped, (const void **)&buffer, - &buffer_len) < 0) - return NULL; - - to = (char *)binary_escape((unsigned char*)buffer, (size_t) buffer_len, - &len, self->conn ? ((connectionObject*)self->conn)->pgconn : NULL); - if (to == NULL) { - PyErr_NoMemory(); - return NULL; + &buffer_len) < 0) { + goto exit; } + } +#endif - if (len > 0) - self->buffer = Bytes_FromFormat( - (self->conn && ((connectionObject*)self->conn)->equote) - ? "E'%s'::bytea" : "'%s'::bytea" , to); - else - self->buffer = Bytes_FromString("''::bytea"); + if (!buffer) { + goto exit; + } - PQfreemem(to); + /* escape and build quoted buffer */ + + to = (char *)binary_escape((unsigned char*)buffer, (size_t) buffer_len, + &len, self->conn ? ((connectionObject*)self->conn)->pgconn : NULL); + if (to == NULL) { + PyErr_NoMemory(); + goto exit; } - /* if the wrapped object is not a string or a buffer, this is an error */ - else { - PyErr_SetString(PyExc_TypeError, "can't escape non-string object"); - return NULL; + if (len > 0) + rv = Bytes_FromFormat( + (self->conn && ((connectionObject*)self->conn)->equote) + ? "E'%s'::bytea" : "'%s'::bytea" , to); + else + rv = Bytes_FromString("''::bytea"); + +exit: + if (to) { PQfreemem(to); } +#if HAS_MEMORYVIEW + if (got_view) { PyBuffer_Release(&view); } +#endif + + /* if the wrapped object is not bytes or a buffer, this is an error */ + if (!rv && !PyErr_Occurred()) { + PyErr_Format(PyExc_TypeError, "can't escape %s to binary", + Py_TYPE(self->wrapped)->tp_name); } - return self->buffer; + return rv; } /* binary_str, binary_getquoted - return result of quoting */ @@ -103,11 +128,9 @@ static PyObject * binary_getquoted(binaryObject *self, PyObject *args) { if (self->buffer == NULL) { - if (!(binary_quote(self))) { - return NULL; - } + self->buffer = binary_quote(self); } - Py_INCREF(self->buffer); + Py_XINCREF(self->buffer); return self->buffer; } diff --git a/psycopg/adapter_list.c b/psycopg/adapter_list.c index b7e1f96..7bfc1c8 100644 --- a/psycopg/adapter_list.c +++ b/psycopg/adapter_list.c @@ -45,7 +45,7 @@ list_quote(listObject *self) /* empty arrays are converted to NULLs (still searching for a way to insert an empty array in postgresql */ - if (len == 0) return Bytes_FromString("'{}'::text[]"); + if (len == 0) return Bytes_FromString("'{}'"); tmp = PyTuple_New(len); diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index f91483d..7b6a334 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -315,17 +315,26 @@ psyco_adapters_init(PyObject *mod) microprotocols_add(&PyLong_Type, NULL, (PyObject*)&asisType); microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType); + /* strings */ #if PY_MAJOR_VERSION < 3 microprotocols_add(&PyString_Type, NULL, (PyObject*)&qstringType); #endif microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType); + + /* binary */ #if PY_MAJOR_VERSION < 3 microprotocols_add(&PyBuffer_Type, NULL, (PyObject*)&binaryType); #else microprotocols_add(&PyBytes_Type, NULL, (PyObject*)&binaryType); +#endif + +#if PY_MAJOR_VERSION >= 3 || PY_MINOR_VERSION >= 6 microprotocols_add(&PyByteArray_Type, NULL, (PyObject*)&binaryType); +#endif +#if PY_MAJOR_VERSION >= 3 || PY_MINOR_VERSION >= 7 microprotocols_add(&PyMemoryView_Type, NULL, (PyObject*)&binaryType); #endif + microprotocols_add(&PyList_Type, NULL, (PyObject*)&listType); if ((type = (PyTypeObject*)psyco_GetDecimalType()) != NULL) @@ -79,7 +79,7 @@ except ImportError: # Take a look at http://www.python.org/dev/peps/pep-0386/ # for a consistent versioning pattern. -PSYCOPG_VERSION = '2.4-beta2' +PSYCOPG_VERSION = '2.4-beta3' version_flags = ['dt', 'dec'] diff --git a/tests/test_async.py b/tests/test_async.py index ea5f1f1..07a3c42 100755 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -23,7 +23,7 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -from testutils import unittest, skip_if_no_pg_sleep +from testutils import unittest, skip_before_postgres import psycopg2 from psycopg2 import extensions @@ -113,7 +113,7 @@ class AsyncTests(unittest.TestCase): self.assertFalse(self.conn.isexecuting()) self.assertEquals(cur.fetchone()[0], "a") - @skip_if_no_pg_sleep('conn') + @skip_before_postgres(8, 2) def test_async_callproc(self): cur = self.conn.cursor() cur.callproc("pg_sleep", (0.1, )) diff --git a/tests/test_cancel.py b/tests/test_cancel.py index dcdbf41..6d58ddc 100755 --- a/tests/test_cancel.py +++ b/tests/test_cancel.py @@ -31,7 +31,7 @@ import psycopg2.extensions from psycopg2 import extras from testconfig import dsn -from testutils import unittest, skip_if_no_pg_sleep +from testutils import unittest, skip_before_postgres class CancelTests(unittest.TestCase): @@ -50,7 +50,7 @@ class CancelTests(unittest.TestCase): def test_empty_cancel(self): self.conn.cancel() - @skip_if_no_pg_sleep('conn') + @skip_before_postgres(8, 2) def test_cancel(self): errors = [] @@ -86,7 +86,7 @@ class CancelTests(unittest.TestCase): self.assertEqual(errors, []) - @skip_if_no_pg_sleep('conn') + @skip_before_postgres(8, 2) def test_async_cancel(self): async_conn = psycopg2.connect(dsn, async=True) self.assertRaises(psycopg2.OperationalError, async_conn.cancel) diff --git a/tests/test_connection.py b/tests/test_connection.py index 219c093..fb0c25c 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -24,7 +24,7 @@ import time import threading -from testutils import unittest, decorate_all_tests, skip_if_no_pg_sleep +from testutils import unittest, decorate_all_tests, skip_before_postgres from operator import attrgetter import psycopg2 @@ -114,7 +114,7 @@ class ConnectionTests(unittest.TestCase): self.assertRaises(psycopg2.NotSupportedError, cnn.xid, 42, "foo", "bar") - @skip_if_no_pg_sleep('conn') + @skip_before_postgres(8, 2) def test_concurrent_execution(self): def slave(): cnn = psycopg2.connect(dsn) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 0983fbb..69eca64 100755 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -23,11 +23,11 @@ # License for more details. import time -import unittest import psycopg2 import psycopg2.extensions from psycopg2.extensions import b from testconfig import dsn +from testutils import unittest, skip_before_postgres class CursorTests(unittest.TestCase): @@ -130,6 +130,7 @@ class CursorTests(unittest.TestCase): del curs self.assert_(w() is None) + @skip_before_postgres(8, 2) def test_iter_named_cursor_efficient(self): curs = self.conn.cursor('tmp') # if these records are fetched in the same roundtrip their @@ -143,6 +144,7 @@ class CursorTests(unittest.TestCase): "named cursor records fetched in 2 roundtrips (delta: %s)" % (t2 - t1)) + @skip_before_postgres(8, 0) def test_iter_named_cursor_default_arraysize(self): curs = self.conn.cursor('tmp') curs.execute('select generate_series(1,50)') @@ -150,6 +152,7 @@ class CursorTests(unittest.TestCase): # everything swallowed in one gulp self.assertEqual(rv, [(i,i) for i in range(1,51)]) + @skip_before_postgres(8, 0) def test_iter_named_cursor_arraysize(self): curs = self.conn.cursor('tmp') curs.arraysize = 30 diff --git a/tests/test_transaction.py b/tests/test_transaction.py index cab9c45..90e159a 100755 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -23,7 +23,7 @@ # License for more details. import threading -from testutils import unittest, skip_if_no_pg_sleep +from testutils import unittest, skip_before_postgres import psycopg2 from psycopg2.extensions import ( @@ -236,7 +236,7 @@ class QueryCancellationTests(unittest.TestCase): def tearDown(self): self.conn.close() - @skip_if_no_pg_sleep('conn') + @skip_before_postgres(8, 2) def test_statement_timeout(self): curs = self.conn.cursor() # Set a low statement timeout, then sleep for a longer period. diff --git a/tests/testutils.py b/tests/testutils.py index 8942945..8e99f04 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -102,31 +102,6 @@ def skip_if_no_uuid(f): return skip_if_no_uuid_ -def skip_if_no_pg_sleep(name): - """Decorator to skip a test if pg_sleep is not supported by the server. - - Pass it the name of an attribute containing a connection or of a method - returning a connection. - """ - def skip_if_no_pg_sleep_(f): - def skip_if_no_pg_sleep__(self): - cnn = getattr(self, name) - if callable(cnn): - cnn = cnn() - - if cnn.server_version < 80200: - return self.skipTest( - "server version %s doesn't support pg_sleep" - % cnn.server_version) - - return f(self) - - skip_if_no_pg_sleep__.__name__ = f.__name__ - return skip_if_no_pg_sleep__ - - return skip_if_no_pg_sleep_ - - def skip_if_tpc_disabled(f): """Skip a test if the server has tpc support disabled.""" def skip_if_tpc_disabled_(self): @@ -165,25 +140,60 @@ def skip_if_no_iobase(f): return skip_if_no_iobase_ -def skip_on_python2(f): - """Skip a test on Python 3 and following.""" - def skip_on_python2_(self): - if sys.version_info[0] < 3: - return self.skipTest("skipped because Python 2") - else: - return f(self) - - return skip_on_python2_ - -def skip_on_python3(f): - """Skip a test on Python 3 and following.""" - def skip_on_python3_(self): - if sys.version_info[0] >= 3: - return self.skipTest("skipped because Python 3") - else: - return f(self) +def skip_before_postgres(*ver): + """Skip a test on PostgreSQL before a certain version.""" + ver = ver + (0,) * (3 - len(ver)) + def skip_before_postgres_(f): + def skip_before_postgres__(self): + if self.conn.server_version < int("%d%02d%02d" % ver): + return self.skipTest("skipped because PostgreSQL %s" + % self.conn.server_version) + else: + return f(self) + + return skip_before_postgres__ + return skip_before_postgres_ + +def skip_after_postgres(*ver): + """Skip a test on PostgreSQL after (including) a certain version.""" + ver = ver + (0,) * (3 - len(ver)) + def skip_after_postgres_(f): + def skip_after_postgres__(self): + if self.conn.server_version >= int("%d%02d%02d" % ver): + return self.skipTest("skipped because PostgreSQL %s" + % self.conn.server_version) + else: + return f(self) + + return skip_after_postgres__ + return skip_after_postgres_ + +def skip_before_python(*ver): + """Skip a test on Python before a certain version.""" + def skip_before_python_(f): + def skip_before_python__(self): + if sys.version_info[:len(ver)] < ver: + return self.skipTest("skipped because Python %s" + % ".".join(map(str, sys.version_info[:len(ver)]))) + else: + return f(self) + + return skip_before_python__ + return skip_before_python_ + +def skip_from_python(*ver): + """Skip a test on Python after (including) a certain version.""" + def skip_from_python_(f): + def skip_from_python__(self): + if sys.version_info[:len(ver)] >= ver: + return self.skipTest("skipped because Python %s" + % ".".join(map(str, sys.version_info[:len(ver)]))) + else: + return f(self) + + return skip_from_python__ + return skip_from_python_ - return skip_on_python3_ def script_to_py3(script): """Convert a script to Python3 syntax if required.""" diff --git a/tests/types_basic.py b/tests/types_basic.py index 43bae93..f956f90 100755 --- a/tests/types_basic.py +++ b/tests/types_basic.py @@ -152,14 +152,26 @@ class TypesBasicTests(unittest.TestCase): self.assertEqual(s, buf2) def testArray(self): - s = self.execute("SELECT %s AS foo", ([],)) - self.failUnlessEqual(s, []) s = self.execute("SELECT %s AS foo", ([[1,2],[3,4]],)) self.failUnlessEqual(s, [[1,2],[3,4]]) s = self.execute("SELECT %s AS foo", (['one', 'two', 'three'],)) self.failUnlessEqual(s, ['one', 'two', 'three']) - @testutils.skip_on_python3 + def testEmptyArrayRegression(self): + # ticket #42 + import datetime + curs = self.conn.cursor() + curs.execute("create table array_test (id integer, col timestamp without time zone[])") + + curs.execute("insert into array_test values (%s, %s)", (1, [datetime.date(2011,2,14)])) + curs.execute("select col from array_test where id = 1") + self.assertEqual(curs.fetchone()[0], [datetime.datetime(2011, 2, 14, 0, 0)]) + + curs.execute("insert into array_test values (%s, %s)", (2, [])) + curs.execute("select col from array_test where id = 2") + self.assertEqual(curs.fetchone()[0], []) + + @testutils.skip_from_python(3) def testTypeRoundtripBuffer(self): o1 = buffer("".join(map(chr, range(256)))) o2 = self.execute("select %s;", (o1,)) @@ -170,14 +182,14 @@ class TypesBasicTests(unittest.TestCase): o2 = self.execute("select %s;", (o1,)) self.assertEqual(type(o1), type(o2)) - @testutils.skip_on_python3 + @testutils.skip_from_python(3) def testTypeRoundtripBufferArray(self): o1 = buffer("".join(map(chr, range(256)))) o1 = [o1] o2 = self.execute("select %s;", (o1,)) self.assertEqual(type(o1[0]), type(o2[0])) - @testutils.skip_on_python2 + @testutils.skip_before_python(3) def testTypeRoundtripBytes(self): o1 = bytes(range(256)) o2 = self.execute("select %s;", (o1,)) @@ -188,34 +200,46 @@ class TypesBasicTests(unittest.TestCase): o2 = self.execute("select %s;", (o1,)) self.assertEqual(memoryview, type(o2)) - @testutils.skip_on_python2 + @testutils.skip_before_python(3) def testTypeRoundtripBytesArray(self): o1 = bytes(range(256)) o1 = [o1] o2 = self.execute("select %s;", (o1,)) self.assertEqual(memoryview, type(o2[0])) - @testutils.skip_on_python2 + @testutils.skip_before_python(2, 6) def testAdaptBytearray(self): o1 = bytearray(range(256)) o2 = self.execute("select %s;", (o1,)) - self.assertEqual(memoryview, type(o2)) + if sys.version_info[0] < 3: + self.assertEqual(buffer, type(o2)) + else: + self.assertEqual(memoryview, type(o2)) # Test with an empty buffer o1 = bytearray([]) o2 = self.execute("select %s;", (o1,)) - self.assertEqual(memoryview, type(o2)) + if sys.version_info[0] < 3: + self.assertEqual(buffer, type(o2)) + else: + self.assertEqual(memoryview, type(o2)) - @testutils.skip_on_python2 + @testutils.skip_before_python(2, 7) def testAdaptMemoryview(self): - o1 = memoryview(bytes(range(256))) + o1 = memoryview(bytearray(range(256))) o2 = self.execute("select %s;", (o1,)) - self.assertEqual(memoryview, type(o2)) + if sys.version_info[0] < 3: + self.assertEqual(buffer, type(o2)) + else: + self.assertEqual(memoryview, type(o2)) # Test with an empty buffer - o1 = memoryview(bytes([])) + o1 = memoryview(bytearray([])) o2 = self.execute("select %s;", (o1,)) - self.assertEqual(memoryview, type(o2)) + if sys.version_info[0] < 3: + self.assertEqual(buffer, type(o2)) + else: + self.assertEqual(memoryview, type(o2)) class AdaptSubclassTest(unittest.TestCase): @@ -241,7 +265,7 @@ class AdaptSubclassTest(unittest.TestCase): del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] del psycopg2.extensions.adapters[B, psycopg2.extensions.ISQLQuote] - @testutils.skip_on_python3 + @testutils.skip_from_python(3) def test_no_mro_no_joy(self): from psycopg2.extensions import adapt, register_adapter, AsIs @@ -255,7 +279,7 @@ class AdaptSubclassTest(unittest.TestCase): del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] - @testutils.skip_on_python2 + @testutils.skip_before_python(3) def test_adapt_subtype_3(self): from psycopg2.extensions import adapt, register_adapter, AsIs |
