summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS7
-rw-r--r--doc/src/install.rst11
-rw-r--r--lib/extras.py10
-rw-r--r--psycopg/column_type.c28
-rw-r--r--psycopg/cursor_type.c2
-rwxr-xr-xtests/test_cursor.py5
-rwxr-xr-xtests/test_extras_dictcursor.py29
7 files changed, 85 insertions, 7 deletions
diff --git a/NEWS b/NEWS
index 0ada551..a1c56d0 100644
--- a/NEWS
+++ b/NEWS
@@ -6,7 +6,12 @@ What's new in psycopg 2.8.5
- Fixed use of `!connection_factory` and `!cursor_factory` together
(:ticket:`#1019`).
-- Added AIX support (:ticket:`#1061`).`
+- Added support for `~logging.LoggerAdapter` in
+ `~psycopg2.extras.LoggingConnection` (:ticket:`#1026`).
+- `~psycopg2.extensions.Column` objects in `cursor.description` can be sliced
+ (:ticket:`#1034`).
+- Added AIX support (:ticket:`#1061`).
+- Fixed `~copy.copy()` of `~psycopg2.extras.DictCursor` rows (:ticket:`#1073`).
What's new in psycopg 2.8.4
diff --git a/doc/src/install.rst b/doc/src/install.rst
index 017d090..a18e862 100644
--- a/doc/src/install.rst
+++ b/doc/src/install.rst
@@ -11,8 +11,14 @@ wrapper for the libpq_, the official PostgreSQL client library.
The `psycopg2` package is the current mature implementation of the adapter: it
is a C extension and as such it is only compatible with CPython_. If you want
to use Psycopg on a different Python implementation (PyPy, Jython, IronPython)
-there is an experimental `porting of Psycopg for Ctypes`__, but it is not as
-mature as the C implementation yet.
+there is a couple of alternative:
+
+- a `Ctypes port`__, but it is not as mature as the C implementation yet
+ and it is not as feature-complete;
+
+- a `CFFI port`__ which is currently more used and reported more efficient on
+ PyPy, but plese be careful to its version numbers because they are not
+ aligned to the official psycopg2 ones and some features may differ.
.. _PostgreSQL: https://www.postgresql.org/
.. _Python: https://www.python.org/
@@ -20,6 +26,7 @@ mature as the C implementation yet.
.. _CPython: https://en.wikipedia.org/wiki/CPython
.. _Ctypes: https://docs.python.org/library/ctypes.html
.. __: https://github.com/mvantellingen/psycopg2-ctypes
+.. __: https://github.com/chtd/psycopg2cffi
diff --git a/lib/extras.py b/lib/extras.py
index 236a5b7..ccb8b3f 100644
--- a/lib/extras.py
+++ b/lib/extras.py
@@ -196,6 +196,10 @@ class DictRow(list):
def __contains__(self, x):
return x in self._index
+ def __reduce__(self):
+ # this is apparently useless, but it fixes #1073
+ return super(DictRow, self).__reduce__()
+
def __getstate__(self):
return self[:], self._index.copy()
@@ -392,6 +396,7 @@ class NamedTupleCursor(_cursor):
def _cached_make_nt(cls, key):
return cls._do_make_nt(key)
+
# Exposed for testability, and if someone wants to monkeypatch to tweak
# the cache size.
NamedTupleCursor._cached_make_nt = classmethod(_cached_make_nt)
@@ -406,11 +411,12 @@ class LoggingConnection(_connection):
def initialize(self, logobj):
"""Initialize the connection to log to `!logobj`.
- The `!logobj` parameter can be an open file object or a Logger
+ The `!logobj` parameter can be an open file object or a Logger/LoggerAdapter
instance from the standard logging module.
"""
self._logobj = logobj
- if _logging and isinstance(logobj, _logging.Logger):
+ if _logging and isinstance(
+ logobj, (_logging.Logger, _logging.LoggerAdapter)):
self.log = self._logtologger
else:
self.log = self._logtofile
diff --git a/psycopg/column_type.c b/psycopg/column_type.c
index 4947af2..b700706 100644
--- a/psycopg/column_type.c
+++ b/psycopg/column_type.c
@@ -233,6 +233,32 @@ column_getitem(columnObject *self, Py_ssize_t item)
}
+static PyObject*
+column_subscript(columnObject* self, PyObject* item)
+{
+ PyObject *t = NULL;
+ PyObject *rv = NULL;
+
+ /* t = tuple(self) */
+ if (!(t = PyObject_CallFunctionObjArgs(
+ (PyObject *)&PyTuple_Type, (PyObject *)self, NULL))) {
+ goto exit;
+ }
+
+ /* rv = t[item] */
+ rv = PyObject_GetItem(t, item);
+
+exit:
+ Py_XDECREF(t);
+ return rv;
+}
+
+static PyMappingMethods column_mapping = {
+ (lenfunc)column_len, /* mp_length */
+ (binaryfunc)column_subscript, /* mp_subscript */
+ 0 /* mp_ass_subscript */
+};
+
static PySequenceMethods column_sequence = {
(lenfunc)column_len, /* sq_length */
0, /* sq_concat */
@@ -346,7 +372,7 @@ PyTypeObject columnType = {
(reprfunc)column_repr, /*tp_repr*/
0, /*tp_as_number*/
&column_sequence, /*tp_as_sequence*/
- 0, /*tp_as_mapping*/
+ &column_mapping, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c
index a7bd11b..f2dd379 100644
--- a/psycopg/cursor_type.c
+++ b/psycopg/cursor_type.c
@@ -318,7 +318,7 @@ _psyco_curs_merge_query_args(cursorObject *self,
{
PyObject *fquery;
- /* if PyString_Format() return NULL an error occured: if the error is
+ /* if PyString_Format() return NULL an error occurred: if the error is
a TypeError we need to check the exception.args[0] string for the
values:
diff --git a/tests/test_cursor.py b/tests/test_cursor.py
index 4d18096..9bf9ccf 100755
--- a/tests/test_cursor.py
+++ b/tests/test_cursor.py
@@ -433,6 +433,11 @@ class CursorTests(ConnectingTestCase):
self.assertEqual(curs.description[2].table_oid, None)
self.assertEqual(curs.description[2].table_column, None)
+ def test_description_slice(self):
+ curs = self.conn.cursor()
+ curs.execute("select 1::int as a")
+ self.assertEqual(curs.description[0][0:2], ('a', 23))
+
def test_pickle_description(self):
curs = self.conn.cursor()
curs.execute('SELECT 1 AS foo')
diff --git a/tests/test_extras_dictcursor.py b/tests/test_extras_dictcursor.py
index d4bb12f..180d996 100755
--- a/tests/test_extras_dictcursor.py
+++ b/tests/test_extras_dictcursor.py
@@ -15,6 +15,7 @@
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
+import copy
import time
import pickle
import unittest
@@ -158,6 +159,20 @@ class ExtrasDictCursorTests(_DictCursorBase):
self.assertEqual(r['b'], r1['b'])
self.assertEqual(r._index, r1._index)
+ def test_copy(self):
+ curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
+ curs.execute("select 10 as foo, 'hi' as bar")
+ rv = curs.fetchone()
+ self.assertEqual(len(rv), 2)
+
+ rv2 = copy.copy(rv)
+ self.assertEqual(len(rv2), 2)
+ self.assertEqual(len(rv), 2)
+
+ rv3 = copy.deepcopy(rv)
+ self.assertEqual(len(rv3), 2)
+ self.assertEqual(len(rv), 2)
+
@skip_from_python(3)
def test_iter_methods_2(self):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
@@ -267,6 +282,20 @@ class ExtrasDictCursorRealTests(_DictCursorBase):
self.assertEqual(r['a'], r1['a'])
self.assertEqual(r['b'], r1['b'])
+ def test_copy(self):
+ curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
+ curs.execute("select 10 as foo, 'hi' as bar")
+ rv = curs.fetchone()
+ self.assertEqual(len(rv), 2)
+
+ rv2 = copy.copy(rv)
+ self.assertEqual(len(rv2), 2)
+ self.assertEqual(len(rv), 2)
+
+ rv3 = copy.deepcopy(rv)
+ self.assertEqual(len(rv3), 2)
+ self.assertEqual(len(rv), 2)
+
def testDictCursorRealWithNamedCursorFetchOne(self):
self._testWithNamedCursorReal(lambda curs: curs.fetchone())