Merge branch 'cursor-args-fix' into devel

This commit is contained in:
Daniele Varrazzo 2012-04-11 18:12:27 +01:00
commit 4436fce4c6
6 changed files with 90 additions and 61 deletions

8
NEWS
View File

@ -1,3 +1,11 @@
What's new in psycopg 2.4.6
---------------------------
- Fixed 'cursor()' arguments propagation in connection subclasses
and overriding of the 'cursor_factory' argument. Thanks to
Corry Haines for the report and the initial patch (ticket #105).
What's new in psycopg 2.4.5 What's new in psycopg 2.4.5
--------------------------- ---------------------------

View File

@ -103,11 +103,9 @@ class DictCursorBase(_cursor):
class DictConnection(_connection): class DictConnection(_connection):
"""A connection that uses `DictCursor` automatically.""" """A connection that uses `DictCursor` automatically."""
def cursor(self, name=None): def cursor(self, *args, **kwargs):
if name is None: kwargs.setdefault('cursor_factory', DictCursor)
return _connection.cursor(self, cursor_factory=DictCursor) return _connection.cursor(self, *args, **kwargs)
else:
return _connection.cursor(self, name, cursor_factory=DictCursor)
class DictCursor(DictCursorBase): class DictCursor(DictCursorBase):
"""A cursor that keeps a list of column name -> index mappings.""" """A cursor that keeps a list of column name -> index mappings."""
@ -196,11 +194,9 @@ class DictRow(list):
class RealDictConnection(_connection): class RealDictConnection(_connection):
"""A connection that uses `RealDictCursor` automatically.""" """A connection that uses `RealDictCursor` automatically."""
def cursor(self, name=None): def cursor(self, *args, **kwargs):
if name is None: kwargs.setdefault('cursor_factory', RealDictCursor)
return _connection.cursor(self, cursor_factory=RealDictCursor) return _connection.cursor(self, *args, **kwargs)
else:
return _connection.cursor(self, name, cursor_factory=RealDictCursor)
class RealDictCursor(DictCursorBase): class RealDictCursor(DictCursorBase):
"""A cursor that uses a real dict as the base type for rows. """A cursor that uses a real dict as the base type for rows.
@ -254,7 +250,7 @@ class RealDictRow(dict):
class NamedTupleConnection(_connection): class NamedTupleConnection(_connection):
"""A connection that uses `NamedTupleCursor` automatically.""" """A connection that uses `NamedTupleCursor` automatically."""
def cursor(self, *args, **kwargs): def cursor(self, *args, **kwargs):
kwargs['cursor_factory'] = NamedTupleCursor kwargs.setdefault('cursor_factory', NamedTupleCursor)
return _connection.cursor(self, *args, **kwargs) return _connection.cursor(self, *args, **kwargs)
class NamedTupleCursor(_cursor): class NamedTupleCursor(_cursor):
@ -372,12 +368,10 @@ class LoggingConnection(_connection):
raise self.ProgrammingError( raise self.ProgrammingError(
"LoggingConnection object has not been initialize()d") "LoggingConnection object has not been initialize()d")
def cursor(self, name=None): def cursor(self, *args, **kwargs):
self._check() self._check()
if name is None: kwargs.setdefault('cursor_factory', LoggingCursor)
return _connection.cursor(self, cursor_factory=LoggingCursor) return _connection.cursor(self, *args, **kwargs)
else:
return _connection.cursor(self, name, cursor_factory=LoggingCursor)
class LoggingCursor(_cursor): class LoggingCursor(_cursor):
"""A cursor that logs queries using its connection logging facilities.""" """A cursor that logs queries using its connection logging facilities."""
@ -415,12 +409,9 @@ class MinTimeLoggingConnection(LoggingConnection):
if t > self._mintime: if t > self._mintime:
return msg + os.linesep + " (execution time: %d ms)" % t return msg + os.linesep + " (execution time: %d ms)" % t
def cursor(self, name=None): def cursor(self, *args, **kwargs):
self._check() kwargs.setdefault('cursor_factory', MinTimeLoggingCursor)
if name is None: return LoggingConnection.cursor(self, *args, **kwargs)
return _connection.cursor(self, cursor_factory=MinTimeLoggingCursor)
else:
return _connection.cursor(self, name, cursor_factory=MinTimeLoggingCursor)
class MinTimeLoggingCursor(LoggingCursor): class MinTimeLoggingCursor(LoggingCursor):
"""The cursor sub-class companion to `MinTimeLoggingConnection`.""" """The cursor sub-class companion to `MinTimeLoggingConnection`."""

View File

@ -42,7 +42,7 @@
/* cursor method - allocate a new cursor */ /* cursor method - allocate a new cursor */
#define psyco_conn_cursor_doc \ #define psyco_conn_cursor_doc \
"cursor(name=None, cursor_factory=extensions.cursor, withhold=None) -- new cursor\n\n" \ "cursor(name=None, cursor_factory=extensions.cursor, withhold=False) -- new cursor\n\n" \
"Return a new cursor.\n\nThe ``cursor_factory`` argument can be used to\n" \ "Return a new cursor.\n\nThe ``cursor_factory`` argument can be used to\n" \
"create non-standard cursors by passing a class different from the\n" \ "create non-standard cursors by passing a class different from the\n" \
"default. Note that the new class *should* be a sub-class of\n" \ "default. Note that the new class *should* be a sub-class of\n" \
@ -50,25 +50,25 @@
":rtype: `extensions.cursor`" ":rtype: `extensions.cursor`"
static PyObject * static PyObject *
psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *keywds) psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *kwargs)
{ {
const char *name = NULL; PyObject *obj;
PyObject *obj, *factory = NULL, *withhold = NULL; PyObject *name = Py_None;
PyObject *factory = (PyObject *)&cursorType;
PyObject *withhold = Py_False;
static char *kwlist[] = {"name", "cursor_factory", "withhold", NULL}; static char *kwlist[] = {"name", "cursor_factory", "withhold", NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|sOO", kwlist, if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO", kwlist,
&name, &factory, &withhold)) { &name, &factory, &withhold)) {
return NULL; return NULL;
} }
if (withhold != NULL) { if (PyObject_IsTrue(withhold) && (name == Py_None)) {
if (PyObject_IsTrue(withhold) && name == NULL) {
PyErr_SetString(ProgrammingError, PyErr_SetString(ProgrammingError,
"'withhold=True can be specified only for named cursors"); "'withhold=True can be specified only for named cursors");
return NULL; return NULL;
} }
}
EXC_IF_CONN_CLOSED(self); EXC_IF_CONN_CLOSED(self);
@ -80,23 +80,20 @@ psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *keywds)
return NULL; return NULL;
} }
if (name != NULL && self->async == 1) { if (name != Py_None && self->async == 1) {
PyErr_SetString(ProgrammingError, PyErr_SetString(ProgrammingError,
"asynchronous connections " "asynchronous connections "
"cannot produce named cursors"); "cannot produce named cursors");
return NULL; return NULL;
} }
Dprintf("psyco_conn_cursor: new cursor for connection at %p", self); Dprintf("psyco_conn_cursor: new %s cursor for connection at %p",
Dprintf("psyco_conn_cursor: parameters: name = %s", name); (name == Py_None ? "unnamed" : "named"), self);
if (factory == NULL) factory = (PyObject *)&cursorType; if (!(obj = PyObject_CallFunctionObjArgs(factory, self, name, NULL))) {
if (name) return NULL;
obj = PyObject_CallFunction(factory, "Os", self, name); }
else
obj = PyObject_CallFunctionObjArgs(factory, self, NULL);
if (obj == NULL) return NULL;
if (PyObject_IsInstance(obj, (PyObject *)&cursorType) == 0) { if (PyObject_IsInstance(obj, (PyObject *)&cursorType) == 0) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"cursor factory must be subclass of psycopg2._psycopg.cursor"); "cursor factory must be subclass of psycopg2._psycopg.cursor");
@ -104,7 +101,7 @@ psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *keywds)
return NULL; return NULL;
} }
if (withhold != NULL && PyObject_IsTrue(withhold)) if (PyObject_IsTrue(withhold))
((cursorObject*)obj)->withhold = 1; ((cursorObject*)obj)->withhold = 1;
Dprintf("psyco_conn_cursor: new cursor at %p: refcnt = " Dprintf("psyco_conn_cursor: new cursor at %p: refcnt = "

View File

@ -1724,7 +1724,7 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name)
if (name) { if (name) {
if (!(self->name = psycopg_escape_identifier_easy(name, 0))) { if (!(self->name = psycopg_escape_identifier_easy(name, 0))) {
return 1; return -1;
} }
} }
@ -1733,7 +1733,7 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name)
(PyObject *)&connectionType) == 0) { (PyObject *)&connectionType) == 0) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"argument 1 must be subclass of psycopg2._psycopg.connection"); "argument 1 must be subclass of psycopg2._psycopg.connection");
return 1; return -1;
} */ } */
Py_INCREF(conn); Py_INCREF(conn);
self->conn = conn; self->conn = conn;
@ -1808,15 +1808,28 @@ cursor_dealloc(PyObject* obj)
} }
static int static int
cursor_init(PyObject *obj, PyObject *args, PyObject *kwds) cursor_init(PyObject *obj, PyObject *args, PyObject *kwargs)
{ {
const char *name = NULL;
PyObject *conn; PyObject *conn;
PyObject *name = Py_None;
const char *cname;
if (!PyArg_ParseTuple(args, "O|s", &conn, &name)) static char *kwlist[] = {"conn", "name", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|O", kwlist,
&connectionType, &conn, &name)) {
return -1; return -1;
}
return cursor_setup((cursorObject *)obj, (connectionObject *)conn, name); if (name == Py_None) {
cname = NULL;
} else {
if (!(cname = Bytes_AsString(name))) {
return -1;
}
}
return cursor_setup((cursorObject *)obj, (connectionObject *)conn, cname);
} }
static PyObject * static PyObject *

View File

@ -165,6 +165,10 @@ class CursorTests(unittest.TestCase):
del curs del curs
self.assert_(w() is None) self.assert_(w() is None)
def test_null_name(self):
curs = self.conn.cursor(None)
self.assertEqual(curs.name, None)
def test_invalid_name(self): def test_invalid_name(self):
curs = self.conn.cursor() curs = self.conn.cursor()
curs.execute("create temp table invname (data int);") curs.execute("create temp table invname (data int);")

View File

@ -35,6 +35,16 @@ class ExtrasDictCursorTests(unittest.TestCase):
def tearDown(self): def tearDown(self):
self.conn.close() self.conn.close()
def testDictConnCursorArgs(self):
self.conn.close()
self.conn = psycopg2.connect(dsn, connection_factory=psycopg2.extras.DictConnection)
cur = self.conn.cursor()
self.assert_(isinstance(cur, psycopg2.extras.DictCursor))
self.assertEqual(cur.name, None)
# overridable
cur = self.conn.cursor('foo', cursor_factory=psycopg2.extras.NamedTupleCursor)
self.assertEqual(cur.name, 'foo')
self.assert_(isinstance(cur, psycopg2.extras.NamedTupleCursor))
def testDictCursorWithPlainCursorFetchOne(self): def testDictCursorWithPlainCursorFetchOne(self):
self._testWithPlainCursor(lambda curs: curs.fetchone()) self._testWithPlainCursor(lambda curs: curs.fetchone())
@ -219,6 +229,12 @@ class NamedTupleCursorTest(unittest.TestCase):
if self.conn is not None: if self.conn is not None:
self.conn.close() self.conn.close()
@skip_if_no_namedtuple
def test_cursor_args(self):
cur = self.conn.cursor('foo', cursor_factory=psycopg2.extras.DictCursor)
self.assertEqual(cur.name, 'foo')
self.assert_(isinstance(cur, psycopg2.extras.DictCursor))
@skip_if_no_namedtuple @skip_if_no_namedtuple
def test_fetchone(self): def test_fetchone(self):
curs = self.conn.cursor() curs = self.conn.cursor()