mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-02-07 21:00:33 +03:00
Merge branch 'cursor-args-fix' into devel
This commit is contained in:
commit
4436fce4c6
8
NEWS
8
NEWS
|
@ -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
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
@ -349,7 +345,7 @@ class LoggingConnection(_connection):
|
||||||
self.log = self._logtologger
|
self.log = self._logtologger
|
||||||
else:
|
else:
|
||||||
self.log = self._logtofile
|
self.log = self._logtofile
|
||||||
|
|
||||||
def filter(self, msg, curs):
|
def filter(self, msg, curs):
|
||||||
"""Filter the query before logging it.
|
"""Filter the query before logging it.
|
||||||
|
|
||||||
|
@ -358,26 +354,24 @@ class LoggingConnection(_connection):
|
||||||
just does nothing.
|
just does nothing.
|
||||||
"""
|
"""
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def _logtofile(self, msg, curs):
|
def _logtofile(self, msg, curs):
|
||||||
msg = self.filter(msg, curs)
|
msg = self.filter(msg, curs)
|
||||||
if msg: self._logobj.write(msg + os.linesep)
|
if msg: self._logobj.write(msg + os.linesep)
|
||||||
|
|
||||||
def _logtologger(self, msg, curs):
|
def _logtologger(self, msg, curs):
|
||||||
msg = self.filter(msg, curs)
|
msg = self.filter(msg, curs)
|
||||||
if msg: self._logobj.debug(msg)
|
if msg: self._logobj.debug(msg)
|
||||||
|
|
||||||
def _check(self):
|
def _check(self):
|
||||||
if not hasattr(self, '_logobj'):
|
if not hasattr(self, '_logobj'):
|
||||||
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."""
|
||||||
|
@ -390,19 +384,19 @@ class LoggingCursor(_cursor):
|
||||||
|
|
||||||
def callproc(self, procname, vars=None):
|
def callproc(self, procname, vars=None):
|
||||||
try:
|
try:
|
||||||
return _cursor.callproc(self, procname, vars)
|
return _cursor.callproc(self, procname, vars)
|
||||||
finally:
|
finally:
|
||||||
self.connection.log(self.query, self)
|
self.connection.log(self.query, self)
|
||||||
|
|
||||||
|
|
||||||
class MinTimeLoggingConnection(LoggingConnection):
|
class MinTimeLoggingConnection(LoggingConnection):
|
||||||
"""A connection that logs queries based on execution time.
|
"""A connection that logs queries based on execution time.
|
||||||
|
|
||||||
This is just an example of how to sub-class `LoggingConnection` to
|
This is just an example of how to sub-class `LoggingConnection` to
|
||||||
provide some extra filtering for the logged queries. Both the
|
provide some extra filtering for the logged queries. Both the
|
||||||
`inizialize()` and `filter()` methods are overwritten to make sure
|
`inizialize()` and `filter()` methods are overwritten to make sure
|
||||||
that only queries executing for more than ``mintime`` ms are logged.
|
that only queries executing for more than ``mintime`` ms are logged.
|
||||||
|
|
||||||
Note that this connection uses the specialized cursor
|
Note that this connection uses the specialized cursor
|
||||||
`MinTimeLoggingCursor`.
|
`MinTimeLoggingCursor`.
|
||||||
"""
|
"""
|
||||||
|
@ -415,20 +409,17 @@ 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`."""
|
||||||
|
|
||||||
def execute(self, query, vars=None):
|
def execute(self, query, vars=None):
|
||||||
self.timestamp = time.time()
|
self.timestamp = time.time()
|
||||||
return LoggingCursor.execute(self, query, vars)
|
return LoggingCursor.execute(self, query, vars)
|
||||||
|
|
||||||
def callproc(self, procname, vars=None):
|
def callproc(self, procname, vars=None):
|
||||||
self.timestamp = time.time()
|
self.timestamp = time.time()
|
||||||
return LoggingCursor.execute(self, procname, vars)
|
return LoggingCursor.execute(self, procname, vars)
|
||||||
|
|
|
@ -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,26 +50,26 @@
|
||||||
":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);
|
||||||
|
|
||||||
if (self->status != CONN_STATUS_READY &&
|
if (self->status != CONN_STATUS_READY &&
|
||||||
|
@ -80,31 +80,28 @@ 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");
|
||||||
Py_DECREF(obj);
|
Py_DECREF(obj);
|
||||||
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 = "
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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);")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user