Use the adapter of an object superclass if available.

This commit is contained in:
Daniele Varrazzo 2010-11-08 01:35:06 +00:00
parent 225b276de5
commit 62d3a1533b
4 changed files with 92 additions and 4 deletions

View File

@ -4,6 +4,9 @@
* psycopg/microprotocols.c: fixed refcount bug.
* psycopg/microprotocols.c: use the adapter of an object superclass if
available.
2010-11-06 Daniele Varrazzo <daniele.varrazzo@gmail.com>
* lib/extras.py: added NamedTupleCursor.

View File

@ -14,6 +14,7 @@ psycopg 2.3 aims to expose some new features introduced in PostgreSQL 9.0.
* Other features and changes:
- `mogrify()` now supports unicode queries.
- subclasses of a type that can be adapted are adapted as the superclass.
- `errorcodes` knows a couple of new codes introduced in PostgreSQL 9.0.
- Dropped deprecated Psycopg "own quoting".
- Never issue a ROLLBACK on close/GC. This behaviour was introduced as a bug

View File

@ -75,12 +75,61 @@ microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast)
return 0;
}
/* Check if one of `obj` superclasses has an adapter for `proto`.
*
* If it does, return a *borrowed reference* to the adapter, else NULL.
*/
static PyObject *
_get_superclass_adapter(PyObject *obj, PyObject *proto)
{
PyTypeObject *type;
PyObject *mro, *st;
PyObject *key, *adapter;
Py_ssize_t i, ii;
type = (PyTypeObject *)Py_TYPE(obj);
if (!(Py_TPFLAGS_HAVE_CLASS & type->tp_flags)) {
/* has no mro */
return NULL;
}
/* Walk the mro from the most specific subclass. */
mro = type->tp_mro;
for (i = 1, ii = PyTuple_GET_SIZE(mro); i < ii; ++i) {
st = PyTuple_GET_ITEM(mro, i);
key = PyTuple_Pack(2, st, proto);
adapter = PyDict_GetItem(psyco_adapters, key);
Py_DECREF(key);
if (adapter) {
Dprintf(
"microprotocols_adapt: using '%s' adapter to adapt '%s'",
((PyTypeObject *)st)->tp_name, type->tp_name);
/* register this adapter as good for the subclass too,
* so that the next time it will be found in the fast path */
/* Well, no, maybe this is not a good idea.
* It would become a leak in case of dynamic
* classes generated in a loop (think namedtuples). */
/* key = PyTuple_Pack(2, (PyObject*)type, proto);
* PyDict_SetItem(psyco_adapters, key, adapter);
* Py_DECREF(key);
*/
return adapter;
}
}
return NULL;
}
/* microprotocols_adapt - adapt an object to the built-in protocol */
PyObject *
microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt)
{
PyObject *adapter, *key;
PyObject *adapter, *adapted, *key;
char buffer[256];
/* we don't check for exact type conformance as specified in PEP 246
@ -99,13 +148,19 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt)
adapter = PyDict_GetItem(psyco_adapters, key);
Py_DECREF(key);
if (adapter) {
PyObject *adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
return adapted;
}
/* Check if a superclass can be adapted and use the same adapter. */
if (NULL != (adapter = _get_superclass_adapter(obj, proto))) {
adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
return adapted;
}
/* try to have the protocol adapt this object*/
if (PyObject_HasAttrString(proto, "__adapt__")) {
PyObject *adapted = PyObject_CallMethod(proto, "__adapt__", "O", obj);
adapted = PyObject_CallMethod(proto, "__adapt__", "O", obj);
if (adapted && adapted != Py_None) return adapted;
Py_XDECREF(adapted);
if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError))
@ -114,7 +169,7 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt)
/* and finally try to have the object adapt itself */
if (PyObject_HasAttrString(obj, "__conform__")) {
PyObject *adapted = PyObject_CallMethod(obj, "__conform__","O", proto);
adapted = PyObject_CallMethod(obj, "__conform__","O", proto);
if (adapted && adapted != Py_None) return adapted;
Py_XDECREF(adapted);
if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError))

View File

@ -129,6 +129,35 @@ class TypesBasicTests(unittest.TestCase):
o2 = self.execute("select %s;", (o1,))
self.assertEqual(type(o1[0]), type(o2[0]))
class AdaptSubclassTest(unittest.TestCase):
def test_adapt_subtype(self):
from psycopg2.extensions import adapt
class Sub(str): pass
s1 = "hel'lo"
s2 = Sub(s1)
self.assertEqual(adapt(s1).getquoted(), adapt(s2).getquoted())
def test_adapt_most_specific(self):
from psycopg2.extensions import adapt, register_adapter, AsIs
class A(object): pass
class B(A): pass
class C(B): pass
register_adapter(A, lambda a: AsIs("a"))
register_adapter(B, lambda b: AsIs("b"))
self.assertEqual('b', adapt(C()).getquoted())
def test_no_mro_no_joy(self):
from psycopg2.extensions import adapt, register_adapter, AsIs
class A: pass
class B(A): pass
register_adapter(A, lambda a: AsIs("a"))
self.assertRaises(psycopg2.ProgrammingError, adapt, B())
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)