diff options
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | doc/src/install.rst | 11 | ||||
-rw-r--r-- | lib/extras.py | 10 | ||||
-rw-r--r-- | psycopg/column_type.c | 28 | ||||
-rw-r--r-- | psycopg/cursor_type.c | 2 | ||||
-rwxr-xr-x | tests/test_cursor.py | 5 | ||||
-rwxr-xr-x | tests/test_extras_dictcursor.py | 29 |
7 files changed, 85 insertions, 7 deletions
@@ -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()) |