Added cursor_factory connection attribute and connect() parameter

This commit is contained in:
Daniele Varrazzo 2013-04-07 02:30:12 +01:00
parent 0e06addc9f
commit 9e15f54fe8
10 changed files with 111 additions and 23 deletions

2
NEWS
View File

@ -10,6 +10,8 @@ New features:
- Added `~psycopg2.extensions.Diagnostics` object to get extended info
from a database error. Many thanks to Matthew Woodcraft for the
implementation (:ticket:`#149`).
- Added `connection.cursor_factory` attribute to customize the default
object returned by `~connection.cursor()`.
- Added support for backward scrollable cursors. Thanks to Jon Nelson
for the initial patch (:ticket:`#108`).
- Added a simple way to :ref:`customize casting of composite types

View File

@ -27,6 +27,7 @@ More advanced topics
wait(aconn)
acurs = aconn.cursor()
.. index::
double: Subclassing; Cursor
double: Subclassing; Connection
@ -45,6 +46,16 @@ but other uses are possible. `cursor` is much more interesting, because
it is the class where query building, execution and result type-casting into
Python variables happens.
The `~psycopg2.extras` module contains several examples of :ref:`connection
and cursor sublcasses <cursor-subclasses>`.
.. note::
If you only need a customized cursor class, since Psycopg 2.5 you can use
the `~connection.cursor_factory` parameter of a regular connection instead
of creating a new `!connection` subclass.
.. index::
single: Example; Cursor subclass

View File

@ -49,7 +49,8 @@ The ``connection`` class
The *cursor_factory* argument can be used to create non-standard
cursors. The class returned must be a subclass of
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for
details.
details. A default factory for the connection can also be specified
using the `~connection.cursor_factory` attribute.
.. versionchanged:: 2.4.3 added the *withhold* argument.
.. versionchanged:: 2.5 added the *scrollable* argument.
@ -504,6 +505,15 @@ The ``connection`` class
the payload was not accessible. To keep backward compatibility,
`!Notify` objects can still be accessed as 2 items tuples.
.. attribute:: cursor_factory
The default cursor factory used by `~connection.cursor()` if the
parameter is no specified.
.. versionadded:: 2.5
.. index::
pair: Backend; PID

View File

@ -13,7 +13,7 @@ The module contains a few objects and function extending the minimum set of
functionalities defined by the |DBAPI|_.
.. class:: connection
.. class:: connection(dsn, async=False)
Is the class usually returned by the `~psycopg2.connect()` function.
It is exposed by the `extensions` module in order to allow
@ -21,11 +21,9 @@ functionalities defined by the |DBAPI|_.
`!connect()` function using the `connection_factory` parameter.
See also :ref:`subclassing-connection`.
Subclasses should have constructor signature :samp:`({dsn}, {async}=0)`.
For a complete description of the class, see `connection`.
.. class:: cursor
.. class:: cursor(conn, name=None)
It is the class usually returnded by the `connection.cursor()`
method. It is exposed by the `extensions` module in order to allow

View File

@ -16,22 +16,27 @@ This module is a generic place used to hold little helper functions and
classes until a better place in the distribution is found.
.. index::
pair: Cursor; Dictionary
.. _dict-cursor:
.. _cursor-subclasses:
Connection and cursor subclasses
--------------------------------
A few objects that change the way the results are returned by the cursor or
modify the object behavior in some other way. Typically `!connection`
subclasses are passed as *connection_factory* argument to
`~psycopg2.connect()` so that the connection will generate the matching
`!cursor` subclass. Alternatively a `!cursor` subclass can be used one-off by
passing it as the *cursor_factory* argument to the `~connection.cursor()`
method of a regular `!connection`.
modify the object behavior in some other way. Typically `!cursor` subclasses
are passed as *cursor_factory* argument to `~psycopg2.connect()` so that the
connection's `~connection.cursor()` method will generate objects of this
class. Alternatively a `!cursor` subclass can be used one-off by passing it
as the *cursor_factory* argument to the `!cursor()` method.
If you want to use a `!connection` subclass you can pass it as the
*connection_factory* argument of the `!connect()` function.
.. index::
pair: Cursor; Dictionary
.. _dict-cursor:
Dictionary-like cursor
^^^^^^^^^^^^^^^^^^^^^^
@ -61,6 +66,11 @@ The records still support indexing as the original tuple:
.. autoclass:: DictConnection
.. note::
Not very useful since Psycopg 2.5: you can use `psycopg2.connect`\
``(dsn, cursor_factory=DictCursor)`` instead of `!DictConnection`.
.. autoclass:: DictRow
@ -71,6 +81,12 @@ Real dictionary cursor
.. autoclass:: RealDictConnection
.. note::
Not very useful since Psycopg 2.5: you can use `psycopg2.connect`\
``(dsn, cursor_factory=RealDictCursor)`` instead of
`!RealDictConnection`.
.. autoclass:: RealDictRow
@ -101,6 +117,12 @@ expect it to be... ::
.. autoclass:: NamedTupleConnection
.. note::
Not very useful since Psycopg 2.5: you can use `psycopg2.connect`\
``(dsn, cursor_factory=NamedTupleCursor)`` instead of
`!NamedTupleConnection`.
.. index::
pair: Cursor; Logging

View File

@ -17,8 +17,8 @@ The module interface respects the standard defined in the |DBAPI|_.
single: DSN (Database Source Name)
.. function::
connect(dsn, connection_factory=None, async=False)
connect(\*\*kwargs, connection_factory=None, async=False)
connect(dsn, connection_factory=None, cursor_factory=None, async=False)
connect(\*\*kwargs, connection_factory=None, cursor_factory=None, async=False)
Create a new database session and return a new `connection` object.
@ -33,8 +33,9 @@ The module interface respects the standard defined in the |DBAPI|_.
The two call styles are mutually exclusive: you cannot specify connection
parameters as keyword arguments together with a connection string; only
*connection_factory* and *async* are supported together with the *dsn*
argument.
the parameters not needed for the database connection (*i.e.*
*connection_factory*, *cursor_factory*, and *async*) are supported
together with the *dsn* argument.
The basic connection parameters are:
@ -61,7 +62,9 @@ The module interface respects the standard defined in the |DBAPI|_.
Using the *connection_factory* parameter a different class or
connections factory can be specified. It should be a callable object
taking a *dsn* string argument. See :ref:`subclassing-connection` for
details.
details. If a *cursor_factory* is specified, the connection's
`~connection.cursor_factory` is set to it. If you only need customized
cursors you can use this parameter instead of subclassing a connection.
Using *async*\=\ `!True` an asynchronous connection will be created: see
:ref:`async-support` to know about advantages and limitations.
@ -70,6 +73,9 @@ The module interface respects the standard defined in the |DBAPI|_.
any keyword argument is passed to the connection. Previously only the
basic parameters (plus `!sslmode`) were supported as keywords.
.. versionchanged:: 2.5
added the *cursor_factory* parameter.
.. seealso::
- libpq `connection string syntax`__

View File

@ -101,7 +101,7 @@ del re
def connect(dsn=None,
database=None, user=None, password=None, host=None, port=None,
connection_factory=None, async=False, **kwargs):
connection_factory=None, cursor_factory=None, async=False, **kwargs):
"""
Create a new database connection.
@ -126,6 +126,9 @@ def connect(dsn=None,
factory can be specified. It should be a callable object taking a dsn
argument.
Using the *cursor_factory* parameter, a new default cursor factory will be
used by cursor().
Using *async*=True an asynchronous connection will be created.
Any other keyword parameter will be passed to the underlying client
@ -158,7 +161,11 @@ def connect(dsn=None,
dsn = " ".join(["%s=%s" % (k, _param_escape(str(v)))
for (k, v) in items])
return _connect(dsn, connection_factory=connection_factory, async=async)
conn = _connect(dsn, connection_factory=connection_factory, async=async)
if cursor_factory is not None:
conn.cursor_factory = cursor_factory
return conn
__all__ = filter(lambda k: not k.startswith('_'), locals().keys())

View File

@ -121,6 +121,7 @@ struct connectionObject {
int autocommit;
PyObject *cursor_factory; /* default cursor factory from cursor() */
};
/* map isolation level values into a numeric const */

View File

@ -64,6 +64,10 @@ psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *kwargs)
EXC_IF_CONN_CLOSED(self);
if (self->cursor_factory && self->cursor_factory != Py_None) {
factory = self->cursor_factory;
}
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "|OOOO", kwlist,
&name, &factory, &withhold, &scrollable)) {
@ -1013,6 +1017,8 @@ static struct PyMemberDef connectionObject_members[] = {
{"status", T_INT,
offsetof(connectionObject, status), READONLY,
"The current transaction status."},
{"cursor_factory", T_OBJECT, offsetof(connectionObject, cursor_factory), 0,
"Default cursor_factory for cursor()."},
{"string_types", T_OBJECT, offsetof(connectionObject, string_types), READONLY,
"A set of typecasters to convert textual values."},
{"binary_types", T_OBJECT, offsetof(connectionObject, binary_types), READONLY,

View File

@ -224,6 +224,31 @@ class ConnectionTests(ConnectingTestCase):
self.assert_(not notices, "%d notices raised" % len(notices))
def test_connect_cursor_factory(self):
import psycopg2.extras
conn = self.connect(cursor_factory=psycopg2.extras.DictCursor)
cur = conn.cursor()
cur.execute("select 1 as a")
self.assertEqual(cur.fetchone()['a'], 1)
def test_cursor_factory(self):
self.assertEqual(self.conn.cursor_factory, None)
cur = self.conn.cursor()
cur.execute("select 1 as a")
self.assertRaises(TypeError, (lambda r: r['a']), cur.fetchone())
self.conn.cursor_factory = psycopg2.extras.DictCursor
self.assertEqual(self.conn.cursor_factory, psycopg2.extras.DictCursor)
cur = self.conn.cursor()
cur.execute("select 1 as a")
self.assertEqual(cur.fetchone()['a'], 1)
self.conn.cursor_factory = None
self.assertEqual(self.conn.cursor_factory, None)
cur = self.conn.cursor()
cur.execute("select 1 as a")
self.assertRaises(TypeError, (lambda r: r['a']), cur.fetchone())
class IsolationLevelsTestCase(ConnectingTestCase):