summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in4
-rw-r--r--NEWS5
-rw-r--r--doc/src/connection.rst2
-rw-r--r--doc/src/cursor.rst6
-rw-r--r--doc/src/extensions.rst4
-rw-r--r--doc/src/extras.rst2
-rw-r--r--doc/src/faq.rst16
-rw-r--r--doc/src/usage.rst30
-rw-r--r--lib/__init__.py7
-rw-r--r--psycopg/adapter_binary.c91
-rw-r--r--psycopg/adapter_list.c2
-rw-r--r--psycopg/psycopgmodule.c9
-rw-r--r--setup.py2
-rwxr-xr-xtests/test_async.py4
-rwxr-xr-xtests/test_cancel.py6
-rwxr-xr-xtests/test_connection.py4
-rwxr-xr-xtests/test_cursor.py5
-rwxr-xr-xtests/test_transaction.py4
-rw-r--r--tests/testutils.py96
-rwxr-xr-xtests/types_basic.py56
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
diff --git a/NEWS b/NEWS
index 2d78c47..5c5fa69 100644
--- a/NEWS
+++ b/NEWS
@@ -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)
diff --git a/setup.py b/setup.py
index 24d5c23..a41faf3 100644
--- a/setup.py
+++ b/setup.py
@@ -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