diff --git a/NEWS b/NEWS index 065a6b2f..ca013f1b 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/doc/src/advanced.rst b/doc/src/advanced.rst index 5a4724e7..dafb1f58 100644 --- a/doc/src/advanced.rst +++ b/doc/src/advanced.rst @@ -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 `. + +.. 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 diff --git a/doc/src/connection.rst b/doc/src/connection.rst index f7ff4b19..0842aca4 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -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 diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 6df90fd2..9465789c 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -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 diff --git a/doc/src/extras.rst b/doc/src/extras.rst index d1c43030..2a82af8d 100644 --- a/doc/src/extras.rst +++ b/doc/src/extras.rst @@ -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 diff --git a/doc/src/module.rst b/doc/src/module.rst index a998015a..feaef516 100644 --- a/doc/src/module.rst +++ b/doc/src/module.rst @@ -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`__ diff --git a/lib/__init__.py b/lib/__init__.py index e9618fd0..c3f23a69 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -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()) diff --git a/psycopg/connection.h b/psycopg/connection.h index 129239c4..07dfe2e7 100644 --- a/psycopg/connection.h +++ b/psycopg/connection.h @@ -121,6 +121,7 @@ struct connectionObject { int autocommit; + PyObject *cursor_factory; /* default cursor factory from cursor() */ }; /* map isolation level values into a numeric const */ diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index 9937fdb1..679eb0c7 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -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, diff --git a/tests/test_connection.py b/tests/test_connection.py index c5d884c5..26ad9329 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -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):