From 3fbff5d8484099b46aebc499ffaa2a745e510f09 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 5 Feb 2017 11:54:50 +0100 Subject: [PATCH] Give precedence to '__conform__()' over superclasses choosing adapter Close #456 --- NEWS | 2 ++ psycopg/microprotocols.c | 20 ++++++++++---------- tests/test_types_basic.py | 13 +++++++++++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 499656d2..8fc17148 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,8 @@ New features: - Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`). - Added `~connection.get_dsn_parameters()` connection method (:ticket:`#364`). - `~cursor.callproc()` now accepts a dictionary of parameters (:ticket:`#381`). +- Give precedence to `!__conform__()` over superclasses to choose an object + adapter (:ticket:`#456`). - Using Python C API decoding functions and codecs caching for faster unicode encoding/decoding (:ticket:`#473`). - `~cursor.executemany()` slowness addressed by diff --git a/psycopg/microprotocols.c b/psycopg/microprotocols.c index 3ddcc485..0e74cee1 100644 --- a/psycopg/microprotocols.c +++ b/psycopg/microprotocols.c @@ -153,15 +153,6 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) return adapted; } - /* Check if a superclass can be adapted and use the same adapter. */ - if (!(adapter = _get_superclass_adapter(obj, proto))) { - return NULL; - } - if (Py_None != adapter) { - adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); - return adapted; - } - /* try to have the protocol adapt this object*/ if ((meth = PyObject_GetAttrString(proto, "__adapt__"))) { adapted = PyObject_CallFunctionObjArgs(meth, obj, NULL); @@ -181,7 +172,7 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) PyErr_Clear(); } - /* and finally try to have the object adapt itself */ + /* then try to have the object adapt itself */ if ((meth = PyObject_GetAttrString(obj, "__conform__"))) { adapted = PyObject_CallFunctionObjArgs(meth, proto, NULL); Py_DECREF(meth); @@ -200,6 +191,15 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) PyErr_Clear(); } + /* Finally check if a superclass can be adapted and use the same adapter. */ + if (!(adapter = _get_superclass_adapter(obj, proto))) { + return NULL; + } + if (Py_None != adapter) { + adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL); + return adapted; + } + /* else set the right exception and return NULL */ PyOS_snprintf(buffer, 255, "can't adapt type '%s'", Py_TYPE(obj)->tp_name); diff --git a/tests/test_types_basic.py b/tests/test_types_basic.py index bee23d53..614d4fdd 100755 --- a/tests/test_types_basic.py +++ b/tests/test_types_basic.py @@ -422,6 +422,19 @@ class AdaptSubclassTest(unittest.TestCase): finally: del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote] + def test_conform_subclass_precedence(self): + + import psycopg2.extensions as ext + + class foo(tuple): + def __conform__(self, proto): + return self + + def getquoted(self): + return 'bar' + + self.assertEqual(ext.adapt(foo((1, 2, 3))).getquoted(), 'bar') + class ByteaParserTest(unittest.TestCase): """Unit test for our bytea format parser."""