From e985babcc5c23fe2a7893500e4bdc76f4740249c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 15 Nov 2022 12:42:45 +0100 Subject: [PATCH 01/16] gh-96168: Add sqlite3 row factory how-to --- Doc/library/sqlite3.rst | 115 ++++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 33 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 1681fc49e9f1e0..886b6ca2ab20cd 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1320,27 +1320,11 @@ Connection objects a :class:`Cursor` object and the raw row results as a :class:`tuple`, and returns a custom object representing an SQLite row. - Example: - - .. doctest:: - - >>> def dict_factory(cursor, row): - ... col_names = [col[0] for col in cursor.description] - ... return {key: value for key, value in zip(col_names, row)} - >>> con = sqlite3.connect(":memory:") - >>> con.row_factory = dict_factory - >>> for row in con.execute("SELECT 1 AS a, 2 AS b"): - ... print(row) - {'a': 1, 'b': 2} - If returning a tuple doesn't suffice and you want name-based access to columns, you should consider setting :attr:`row_factory` to the - highly optimized :class:`sqlite3.Row` type. :class:`Row` provides both - index-based and case-insensitive name-based access to columns with almost no - memory overhead. It will probably be better than your own custom - dictionary-based approach or even a db_row based solution. + highly optimized :class:`sqlite3.Row` - .. XXX what's a db_row-based solution? + See :ref:`sqlite3-row-factory-how-to` for more details. .. attribute:: text_factory @@ -1611,6 +1595,8 @@ Row objects Two row objects compare equal if have equal columns and equal members. + See :ref:`sqlite3-row-factory-how-to` for more details. + .. method:: keys Return a :class:`list` of column names as :class:`strings `. @@ -1620,21 +1606,6 @@ Row objects .. versionchanged:: 3.5 Added support of slicing. - Example: - - .. doctest:: - - >>> con = sqlite3.connect(":memory:") - >>> con.row_factory = sqlite3.Row - >>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius") - >>> row = res.fetchone() - >>> row.keys() - ['name', 'radius'] - >>> row[0], row["name"] # Access by index and name. - ('Earth', 'Earth') - >>> row["RADIUS"] # Column names are case-insensitive. - 6378 - .. _sqlite3-blob-objects: @@ -2358,6 +2329,84 @@ can be found in the `SQLite URI documentation`_. .. _SQLite URI documentation: https://www.sqlite.org/uri.html +.. _sqlite3-row-factory-how-to: + +How to work with row factories +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, :mod:`!sqlite3` represent fetched rows as :class:`tuples `. +If a :class:`!tuple` does not suit your needs, +use the built-in :class:`Row` type or a custom :attr:`~Connection.row_factory`. +The former provides both indexed and case-insensitive named access to columns, +with low memory overhead and minimal performance impact. +Example use: + +.. doctest:: + + >>> con = sqlite3.connect(":memory:") + >>> con.row_factory = sqlite3.Row + >>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius") + >>> row = res.fetchone() + >>> row.keys() + ['name', 'radius'] + >>> row[0], row["name"] # Access by index and name. + ('Earth', 'Earth') + >>> row["RADIUS"] # Column names are case-insensitive. + 6378 + +If you need more flexibility, you can design your own row factory. +Here's an example of a :class:`dict` row factory: + +.. doctest:: + + >>> def dict_factory(cursor, row): + ... col_names = [col[0] for col in cursor.description] + ... return {key: value for key, value in zip(col_names, row)} + + >>> con = sqlite3.connect(":memory:") + >>> con.row_factory = dict_factory + >>> for row in con.execute("SELECT 1 AS a, 2 AS b"): + ... print(row) + {'a': 1, 'b': 2} + +Here's an example of a optimised :class:`~collections.namedtuple` factory: + +.. testcode:: + + from collections import namedtuple + from functools import lru_cache + + def _fields(cursor): + return [col[0] for col in cursor.description] + + @lru_cache + def _make_cls(fields): + return namedtuple("Row", fields) + + def NamedTupleFactory(cursor, row): + cls = _make_cls(_fields(cursor)) + return cls._make(row) + +Example use: + +.. doctest:: + + >>> con = sqlite3.connect(":memory:") + >>> con.row_factory = NamedTupleRow + >>> cur = con.execute("SELECT 1 AS a, 2 AS b") + >>> row = cur.fetchone() + >>> row + Row(a='1', b='2') + >>> row[0] # Indexed access. + 1 + >>> row.b # Attribute access. + 2 + +With some adjustments, the above recipe can be adapted to use a +:class:`~dataclasses.dataclass`, or any other custom class, +instead of a :class:`~collections.namedtuple`. + + .. _sqlite3-explanation: Explanation From 15d4b4e6459f7bca48a9b13f2130830b65cad7c2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 15 Nov 2022 12:50:23 +0100 Subject: [PATCH 02/16] Suggest reading the row factory how-to at the end of the tutorial --- Doc/library/sqlite3.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 886b6ca2ab20cd..fcd312287e2b3c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -239,6 +239,7 @@ inserted data and retrieved values from it in multiple ways. * :ref:`sqlite3-adapters` * :ref:`sqlite3-converters` * :ref:`sqlite3-connection-context-manager` + * :ref:`sqlite3-row-factory-how-to` * :ref:`sqlite3-explanation` for in-depth background on transaction control. From f5f24b4fd63f251783d3a276aca0e8721fe7e32d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 15 Nov 2022 13:00:13 +0100 Subject: [PATCH 03/16] Fix named tuple example usage --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index fcd312287e2b3c..c754d28db14d5e 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2393,7 +2393,7 @@ Example use: .. doctest:: >>> con = sqlite3.connect(":memory:") - >>> con.row_factory = NamedTupleRow + >>> con.row_factory = NamedTupleFactory >>> cur = con.execute("SELECT 1 AS a, 2 AS b") >>> row = cur.fetchone() >>> row From 85c34bc7677743a23014eb88ee5bd785b093450b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 15 Nov 2022 13:10:49 +0100 Subject: [PATCH 04/16] Fix _fields helper --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c754d28db14d5e..8af254e1461920 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2378,7 +2378,7 @@ Here's an example of a optimised :class:`~collections.namedtuple` factory: from functools import lru_cache def _fields(cursor): - return [col[0] for col in cursor.description] + return (col[0] for col in cursor.description) @lru_cache def _make_cls(fields): From 0e7ea3b5c79f265fc58637bff97ff5ccdd8ef4ac Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 15 Nov 2022 13:17:11 +0100 Subject: [PATCH 05/16] Fix expected output in namedtuple row factory doctest --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 8af254e1461920..76e3d8717acfd7 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2397,7 +2397,7 @@ Example use: >>> cur = con.execute("SELECT 1 AS a, 2 AS b") >>> row = cur.fetchone() >>> row - Row(a='1', b='2') + Row(a=1, b=2) >>> row[0] # Indexed access. 1 >>> row.b # Attribute access. From 413dc243f0d922ee069f9bbe406f88baea58ef9d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 16 Nov 2022 11:08:56 +0100 Subject: [PATCH 06/16] Address reviews --- Doc/library/sqlite3.rst | 52 +++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 76e3d8717acfd7..7f906483b43c89 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -239,7 +239,7 @@ inserted data and retrieved values from it in multiple ways. * :ref:`sqlite3-adapters` * :ref:`sqlite3-converters` * :ref:`sqlite3-connection-context-manager` - * :ref:`sqlite3-row-factory-how-to` + * :ref:`sqlite3-howto-row-factory` * :ref:`sqlite3-explanation` for in-depth background on transaction control. @@ -1321,11 +1321,12 @@ Connection objects a :class:`Cursor` object and the raw row results as a :class:`tuple`, and returns a custom object representing an SQLite row. - If returning a tuple doesn't suffice and you want name-based access to - columns, you should consider setting :attr:`row_factory` to the - highly optimized :class:`sqlite3.Row` + If returning a tuple doesn't suffice + and name-based access to columns is needed, + :attr:`row_factory` can be set to the + highly optimized :class:`sqlite3.Row` row factory. - See :ref:`sqlite3-row-factory-how-to` for more details. + See :ref:`sqlite3-howto-row-factory` for more details. .. attribute:: text_factory @@ -1594,9 +1595,9 @@ Row objects It supports iteration, equality testing, :func:`len`, and :term:`mapping` access by column name and index. - Two row objects compare equal if have equal columns and equal members. + Two row objects compare equal if they have equal columns and equal members. - See :ref:`sqlite3-row-factory-how-to` for more details. + See :ref:`sqlite3-howto-row-factory` for more details. .. method:: keys @@ -2330,17 +2331,18 @@ can be found in the `SQLite URI documentation`_. .. _SQLite URI documentation: https://www.sqlite.org/uri.html -.. _sqlite3-row-factory-how-to: +.. _sqlite3-howto-row-factory: -How to work with row factories -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +How to create and use row factories +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ By default, :mod:`!sqlite3` represent fetched rows as :class:`tuples `. If a :class:`!tuple` does not suit your needs, -use the built-in :class:`Row` type or a custom :attr:`~Connection.row_factory`. -The former provides both indexed and case-insensitive named access to columns, +use the :class:`sqlite3.Row` class or a custom :attr:`~Connection.row_factory`. + +:class:`!Row` provides indexed and case-insensitive named access to columns, with low memory overhead and minimal performance impact. -Example use: +For example: .. doctest:: @@ -2350,13 +2352,15 @@ Example use: >>> row = res.fetchone() >>> row.keys() ['name', 'radius'] - >>> row[0], row["name"] # Access by index and name. - ('Earth', 'Earth') + >>> row[0] # Access by index. + 'Earth' + >>> row["name"] # Access by name. + 'Earth' >>> row["RADIUS"] # Column names are case-insensitive. 6378 -If you need more flexibility, you can design your own row factory. -Here's an example of a :class:`dict` row factory: +If you need more flexibility, you can implement your own row factory. +Here's an example of one returning a :class:`dict`: .. doctest:: @@ -2370,30 +2374,28 @@ Here's an example of a :class:`dict` row factory: ... print(row) {'a': 1, 'b': 2} -Here's an example of a optimised :class:`~collections.namedtuple` factory: +Here's an example of a factory that return a :class:`~collections.namedtuple`. .. testcode:: from collections import namedtuple - from functools import lru_cache - def _fields(cursor): + def _get_fields(cursor): return (col[0] for col in cursor.description) - @lru_cache def _make_cls(fields): return namedtuple("Row", fields) - def NamedTupleFactory(cursor, row): - cls = _make_cls(_fields(cursor)) + def namedtuple_factory(cursor, row): + cls = _make_cls(_get_fields(cursor)) return cls._make(row) -Example use: +:func:`!namedtuple_factory` can be used as follows: .. doctest:: >>> con = sqlite3.connect(":memory:") - >>> con.row_factory = NamedTupleFactory + >>> con.row_factory = namedtuple_factory >>> cur = con.execute("SELECT 1 AS a, 2 AS b") >>> row = cur.fetchone() >>> row From ab98877348b64acebdad0bafe06f2196b3b5c9ca Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 16 Nov 2022 12:45:44 +0100 Subject: [PATCH 07/16] Document Cursor.row_factory; tweak some sentences --- Doc/library/sqlite3.rst | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 7f906483b43c89..4987e96fa65965 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1577,6 +1577,15 @@ Cursor objects including :abbr:`CTE (Common Table Expression)` queries. It is only updated by the :meth:`execute` and :meth:`executemany` methods. + .. attribute:: row_factory + + This attribute is copied from :attr:`Connection.row_factory` + upon :class:`!Cursor` creation. + Assigning to this attribute does not affect the original + connection :attr:`!row_factory`. + + See :ref:`sqlite3-howto-row-factory` for more details. + .. The sqlite3.Row example used to be a how-to. It has now been incorporated into the Row reference. We keep the anchor here in order not to break @@ -2342,12 +2351,18 @@ use the :class:`sqlite3.Row` class or a custom :attr:`~Connection.row_factory`. :class:`!Row` provides indexed and case-insensitive named access to columns, with low memory overhead and minimal performance impact. -For example: +In order to use :class:`Row` as a row factory, +simply assign it to the :attr:`Connection.row_factory` attribute: .. doctest:: >>> con = sqlite3.connect(":memory:") >>> con.row_factory = sqlite3.Row + +Query results are now processed using :class:`Row`: + +.. doctest:: + >>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius") >>> row = res.fetchone() >>> row.keys() @@ -2359,7 +2374,27 @@ For example: >>> row["RADIUS"] # Column names are case-insensitive. 6378 -If you need more flexibility, you can implement your own row factory. +It is also possible to assign row factories to cursors using +:attr:`Cursor.row_factory`: + +.. doctest:: + + >>> cur = con.cursor() + >>> cur.row_factory == con.row_factory + True + >>> cur.row_factory = None # Override cursor row factory. + + # The cursor and the connection row factories now differ. + >>> cur.row_factory == con.row_factory + False + >>> cur.execute("SELECT 'Hello'").fetchone() + ('Hello',) + + # The connection still uses sqlite3.Row, as set in the previous example. + >>> con.execute("SELECT 'Hello'").fetchone() + + +If more flexibility is needed, implement a custom row factory. Here's an example of one returning a :class:`dict`: .. doctest:: From 1aca9dfe1af8d28fa85997c70a5f88e2c5301397 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 17 Nov 2022 18:25:46 +0100 Subject: [PATCH 08/16] Address reviews --- Doc/library/sqlite3.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 4987e96fa65965..b06ed505b48c3f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2351,7 +2351,7 @@ use the :class:`sqlite3.Row` class or a custom :attr:`~Connection.row_factory`. :class:`!Row` provides indexed and case-insensitive named access to columns, with low memory overhead and minimal performance impact. -In order to use :class:`Row` as a row factory, +In order to use :class:`!Row` as a row factory, simply assign it to the :attr:`Connection.row_factory` attribute: .. doctest:: @@ -2359,7 +2359,7 @@ simply assign it to the :attr:`Connection.row_factory` attribute: >>> con = sqlite3.connect(":memory:") >>> con.row_factory = sqlite3.Row -Query results are now processed using :class:`Row`: +Query results are now returned as :class:`!Row` instances: .. doctest:: @@ -2440,11 +2440,15 @@ Here's an example of a factory that return a :class:`~collections.namedtuple`. >>> row.b # Attribute access. 2 +As an optimisation, decorate :func:`!_make_cls` with +:func:`!functools.lru_cache` to avoid creating multiple named tuples +with identical column spec. +See :func:`functools.lru_cache` for more details. + With some adjustments, the above recipe can be adapted to use a :class:`~dataclasses.dataclass`, or any other custom class, instead of a :class:`~collections.namedtuple`. - .. _sqlite3-explanation: Explanation From 1719d0dbe55d92fb1fcb10a629003466876d9881 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 17 Nov 2022 23:12:34 +0100 Subject: [PATCH 09/16] Address parts of latest review --- Doc/library/sqlite3.rst | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index b06ed505b48c3f..acb03d8d2d1906 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1604,7 +1604,8 @@ Row objects It supports iteration, equality testing, :func:`len`, and :term:`mapping` access by column name and index. - Two row objects compare equal if they have equal columns and equal members. + Two :class:`!Row` objects compare equal + if they have equal columns and equal members. See :ref:`sqlite3-howto-row-factory` for more details. @@ -2345,7 +2346,7 @@ can be found in the `SQLite URI documentation`_. How to create and use row factories ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -By default, :mod:`!sqlite3` represent fetched rows as :class:`tuples `. +By default, :mod:`!sqlite3` represents each fetched row as a :class:`tuple`. If a :class:`!tuple` does not suit your needs, use the :class:`sqlite3.Row` class or a custom :attr:`~Connection.row_factory`. @@ -2409,20 +2410,15 @@ Here's an example of one returning a :class:`dict`: ... print(row) {'a': 1, 'b': 2} -Here's an example of a factory that return a :class:`~collections.namedtuple`. +The following row factory returns a :class:`~collections.namedtuple`. .. testcode:: from collections import namedtuple - def _get_fields(cursor): - return (col[0] for col in cursor.description) - - def _make_cls(fields): - return namedtuple("Row", fields) - def namedtuple_factory(cursor, row): - cls = _make_cls(_get_fields(cursor)) + fields = [col[0] for col in cursor.description] + cls = namedtuple("Row", fields) return cls._make(row) :func:`!namedtuple_factory` can be used as follows: @@ -2440,15 +2436,16 @@ Here's an example of a factory that return a :class:`~collections.namedtuple`. >>> row.b # Attribute access. 2 -As an optimisation, decorate :func:`!_make_cls` with -:func:`!functools.lru_cache` to avoid creating multiple named tuples -with identical column spec. -See :func:`functools.lru_cache` for more details. - With some adjustments, the above recipe can be adapted to use a :class:`~dataclasses.dataclass`, or any other custom class, instead of a :class:`~collections.namedtuple`. +As an exercise left for the reader, the above example can be optimised +by extracting to a new function the code that create the ``fields`` variable. +Then decorate that new function with :func:`functools.lru_cache` to avoid +creating multiple classes ``cls`` from identical column specs. + + .. _sqlite3-explanation: Explanation From e1dd69ab6694dbf2dbfd688e32b4024fa3c349d2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 22 Nov 2022 11:03:12 +0100 Subject: [PATCH 10/16] Adjust row equality sentence and remove the premature optimisation exercise --- Doc/library/sqlite3.rst | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index acb03d8d2d1906..bfbc7b58d5a44d 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1605,7 +1605,7 @@ Row objects and :term:`mapping` access by column name and index. Two :class:`!Row` objects compare equal - if they have equal columns and equal members. + if they have identical columns and values. See :ref:`sqlite3-howto-row-factory` for more details. @@ -2440,11 +2440,6 @@ With some adjustments, the above recipe can be adapted to use a :class:`~dataclasses.dataclass`, or any other custom class, instead of a :class:`~collections.namedtuple`. -As an exercise left for the reader, the above example can be optimised -by extracting to a new function the code that create the ``fields`` variable. -Then decorate that new function with :func:`functools.lru_cache` to avoid -creating multiple classes ``cls`` from identical column specs. - .. _sqlite3-explanation: From d12f5598ca964905fd6fdd11a44528900d5d6778 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 22 Nov 2022 13:01:34 +0100 Subject: [PATCH 11/16] Reword reference and remove example from ab98877 --- Doc/library/sqlite3.rst | 58 ++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index bfbc7b58d5a44d..0472b59a73c356 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1317,14 +1317,11 @@ Connection objects .. attribute:: row_factory - A callable that accepts two arguments, - a :class:`Cursor` object and the raw row results as a :class:`tuple`, - and returns a custom object representing an SQLite row. - - If returning a tuple doesn't suffice - and name-based access to columns is needed, - :attr:`row_factory` can be set to the - highly optimized :class:`sqlite3.Row` row factory. + Define the default :attr:`Cursor.row_factory` + for cursors created off of this connection. + Assigning to this attribute does not affect the :attr:`!row_factory` + of open cursors belonging to this connection. + Is ``None`` by default. See :ref:`sqlite3-howto-row-factory` for more details. @@ -1483,7 +1480,7 @@ Cursor objects .. method:: fetchone() - If :attr:`~Connection.row_factory` is ``None``, + If :attr:`~Cursor.row_factory` is ``None``, return the next row query result set as a :class:`tuple`. Else, pass it to the row factory and return its result. Return ``None`` if no more data is available. @@ -1579,10 +1576,17 @@ Cursor objects .. attribute:: row_factory - This attribute is copied from :attr:`Connection.row_factory` + Control how a row fetched from this :class:`!Cursor` is represented. + Set to :class:`sqlite3.Row` or a custom :term:`callable` + that accept two arguments, + a :class:`Cursor` object and the raw row results as a :class:`tuple`, + and return a custom object representing an SQLite row. + If ``None``, a fetched row is represented as a :class:`tuple`. + + Defaults to what :attr:`Connection.row_factory` was set to upon :class:`!Cursor` creation. - Assigning to this attribute does not affect the original - connection :attr:`!row_factory`. + Assigning to this attribute does not affect + :attr:`Connection.row_factory` of the parent connection. See :ref:`sqlite3-howto-row-factory` for more details. @@ -2348,7 +2352,12 @@ How to create and use row factories By default, :mod:`!sqlite3` represents each fetched row as a :class:`tuple`. If a :class:`!tuple` does not suit your needs, -use the :class:`sqlite3.Row` class or a custom :attr:`~Connection.row_factory`. +use the :class:`sqlite3.Row` class or a custom :attr:`~Cursor.row_factory`. + +Even though :attr:`!row_factory` exists as an attribute both on the +:class:`Cursor` and the :class:`Connection`, +it is recommended to set it as :class:`Connection.row_factory`, +so all cursors created off of the connection will use the same row factory. :class:`!Row` provides indexed and case-insensitive named access to columns, with low memory overhead and minimal performance impact. @@ -2375,29 +2384,6 @@ Query results are now returned as :class:`!Row` instances: >>> row["RADIUS"] # Column names are case-insensitive. 6378 -It is also possible to assign row factories to cursors using -:attr:`Cursor.row_factory`: - -.. doctest:: - - >>> cur = con.cursor() - >>> cur.row_factory == con.row_factory - True - >>> cur.row_factory = None # Override cursor row factory. - - # The cursor and the connection row factories now differ. - >>> cur.row_factory == con.row_factory - False - >>> cur.execute("SELECT 'Hello'").fetchone() - ('Hello',) - - # The connection still uses sqlite3.Row, as set in the previous example. - >>> con.execute("SELECT 'Hello'").fetchone() - - -If more flexibility is needed, implement a custom row factory. -Here's an example of one returning a :class:`dict`: - .. doctest:: >>> def dict_factory(cursor, row): From a2774670c9f8e13f3b2491e4dfc770ece5dbb4a1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 23 Nov 2022 10:33:40 +0100 Subject: [PATCH 12/16] Address most of CAM's latest review --- Doc/library/sqlite3.rst | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 0472b59a73c356..801991ac8795ef 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1317,10 +1317,10 @@ Connection objects .. attribute:: row_factory - Define the default :attr:`Cursor.row_factory` - for cursors created off of this connection. + The default :attr:`~Cursor.row_factory` + for :class:`Cursor` objects created from this connection. Assigning to this attribute does not affect the :attr:`!row_factory` - of open cursors belonging to this connection. + of existing cursors belonging to this connection, only new ones. Is ``None`` by default. See :ref:`sqlite3-howto-row-factory` for more details. @@ -1577,14 +1577,14 @@ Cursor objects .. attribute:: row_factory Control how a row fetched from this :class:`!Cursor` is represented. - Set to :class:`sqlite3.Row` or a custom :term:`callable` - that accept two arguments, - a :class:`Cursor` object and the raw row results as a :class:`tuple`, - and return a custom object representing an SQLite row. + Set to :class:`sqlite3.Row`; + or a custom :term:`callable` that accepts two arguments, + a :class:`Cursor` object and a :class:`tuple` of the row results, + and returns a custom object representing an SQLite row. If ``None``, a fetched row is represented as a :class:`tuple`. Defaults to what :attr:`Connection.row_factory` was set to - upon :class:`!Cursor` creation. + when the :class:`!Cursor` was created. Assigning to this attribute does not affect :attr:`Connection.row_factory` of the parent connection. @@ -1609,7 +1609,7 @@ Row objects and :term:`mapping` access by column name and index. Two :class:`!Row` objects compare equal - if they have identical columns and values. + if they have identical column names and values. See :ref:`sqlite3-howto-row-factory` for more details. @@ -2352,16 +2352,17 @@ How to create and use row factories By default, :mod:`!sqlite3` represents each fetched row as a :class:`tuple`. If a :class:`!tuple` does not suit your needs, -use the :class:`sqlite3.Row` class or a custom :attr:`~Cursor.row_factory`. +you can use the :class:`sqlite3.Row` class +or a custom :attr:`~Cursor.row_factory`. -Even though :attr:`!row_factory` exists as an attribute both on the +While :attr:`!row_factory` exists as an attribute both on the :class:`Cursor` and the :class:`Connection`, -it is recommended to set it as :class:`Connection.row_factory`, -so all cursors created off of the connection will use the same row factory. +it is recommended to set :class:`Connection.row_factory`, +so all cursors created from the connection will use the same row factory. :class:`!Row` provides indexed and case-insensitive named access to columns, -with low memory overhead and minimal performance impact. -In order to use :class:`!Row` as a row factory, +with minimal memory overhead and performance impact over a :class:`!tuple`. +To use :class:`!Row` as a row factory, simply assign it to the :attr:`Connection.row_factory` attribute: .. doctest:: @@ -2377,7 +2378,7 @@ Query results are now returned as :class:`!Row` instances: >>> row = res.fetchone() >>> row.keys() ['name', 'radius'] - >>> row[0] # Access by index. + >>> row[0] # Access by index. 'Earth' >>> row["name"] # Access by name. 'Earth' @@ -2387,7 +2388,7 @@ Query results are now returned as :class:`!Row` instances: .. doctest:: >>> def dict_factory(cursor, row): - ... col_names = [col[0] for col in cursor.description] + ... col_names = [column[0] for column in cursor.description] ... return {key: value for key, value in zip(col_names, row)} >>> con = sqlite3.connect(":memory:") @@ -2396,14 +2397,14 @@ Query results are now returned as :class:`!Row` instances: ... print(row) {'a': 1, 'b': 2} -The following row factory returns a :class:`~collections.namedtuple`. +The following row factory returns a :term:`named tuple`: .. testcode:: from collections import namedtuple def namedtuple_factory(cursor, row): - fields = [col[0] for col in cursor.description] + fields = [column[0] for column in cursor.description] cls = namedtuple("Row", fields) return cls._make(row) From cd12c6bcb657fb2db4c9250f53da9a2dcad7b35e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 23 Nov 2022 21:26:35 +0100 Subject: [PATCH 13/16] Address review --- Doc/library/sqlite3.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 801991ac8795ef..58b2b567656dc0 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1321,7 +1321,8 @@ Connection objects for :class:`Cursor` objects created from this connection. Assigning to this attribute does not affect the :attr:`!row_factory` of existing cursors belonging to this connection, only new ones. - Is ``None`` by default. + Is ``None`` by default, + meaning each row is returned as a :class:`tuple`. See :ref:`sqlite3-howto-row-factory` for more details. @@ -1577,11 +1578,11 @@ Cursor objects .. attribute:: row_factory Control how a row fetched from this :class:`!Cursor` is represented. - Set to :class:`sqlite3.Row`; - or a custom :term:`callable` that accepts two arguments, - a :class:`Cursor` object and a :class:`tuple` of the row results, + If ``None``, a row is represented as a :class:`tuple`. + Can be set to the included :class:`sqlite3.Row`; + or a :term:`callable` that accepts two arguments, + a :class:`Cursor` object and the :class:`!tuple` of row values, and returns a custom object representing an SQLite row. - If ``None``, a fetched row is represented as a :class:`tuple`. Defaults to what :attr:`Connection.row_factory` was set to when the :class:`!Cursor` was created. @@ -2350,7 +2351,7 @@ can be found in the `SQLite URI documentation`_. How to create and use row factories ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -By default, :mod:`!sqlite3` represents each fetched row as a :class:`tuple`. +By default, :mod:`!sqlite3` represents each row as a :class:`tuple`. If a :class:`!tuple` does not suit your needs, you can use the :class:`sqlite3.Row` class or a custom :attr:`~Cursor.row_factory`. From 86a2c43ad791f0b6e8b0487b58cc5eb0e6a216f6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 24 Nov 2022 11:07:28 +0100 Subject: [PATCH 14/16] Address reviews --- Doc/library/sqlite3.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 58b2b567656dc0..75b1e6350b6546 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1317,7 +1317,7 @@ Connection objects .. attribute:: row_factory - The default :attr:`~Cursor.row_factory` + The initial :attr:`~Cursor.row_factory` for :class:`Cursor` objects created from this connection. Assigning to this attribute does not affect the :attr:`!row_factory` of existing cursors belonging to this connection, only new ones. @@ -2364,14 +2364,14 @@ so all cursors created from the connection will use the same row factory. :class:`!Row` provides indexed and case-insensitive named access to columns, with minimal memory overhead and performance impact over a :class:`!tuple`. To use :class:`!Row` as a row factory, -simply assign it to the :attr:`Connection.row_factory` attribute: +assign it to the :attr:`!row_factory` attribute: .. doctest:: >>> con = sqlite3.connect(":memory:") >>> con.row_factory = sqlite3.Row -Query results are now returned as :class:`!Row` instances: +Queries now return :class:`!Row` objects: .. doctest:: @@ -2386,6 +2386,9 @@ Query results are now returned as :class:`!Row` instances: >>> row["RADIUS"] # Column names are case-insensitive. 6378 +To create and use a custom :attr:`~Cursor.row_factory`, +in this case returning a :class:`dict` mapping column names to values: + .. doctest:: >>> def dict_factory(cursor, row): From f9fa2ee1fbf9879f9e4c1f94cc071f7c665af2e8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 25 Nov 2022 09:40:35 +0100 Subject: [PATCH 15/16] Split dict row factory example in two --- Doc/library/sqlite3.rst | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 75b1e6350b6546..8a81c3e04f6a7b 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2386,14 +2386,18 @@ Queries now return :class:`!Row` objects: >>> row["RADIUS"] # Column names are case-insensitive. 6378 -To create and use a custom :attr:`~Cursor.row_factory`, -in this case returning a :class:`dict` mapping column names to values: +You can create a custom :attr:`~Cursor.row_factory` +that returns each row as a :class:`dict`, mapping column names to values: -.. doctest:: +.. testcode:: + + def dict_factory(cursor, row): + fields = [column[0] for column in cursor.description] + return {key: value for key, value in zip(fields, row)} + +Using it, queries now return a :class:`!dict` instead of a :class:`!tuple`: - >>> def dict_factory(cursor, row): - ... col_names = [column[0] for column in cursor.description] - ... return {key: value for key, value in zip(col_names, row)} +.. doctest:: >>> con = sqlite3.connect(":memory:") >>> con.row_factory = dict_factory From 6f50503500691b763913aa4634a5e2f45bef77bc Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 25 Nov 2022 10:16:48 +0100 Subject: [PATCH 16/16] Missed one --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 8a81c3e04f6a7b..0dac2312b2feb1 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -2387,7 +2387,7 @@ Queries now return :class:`!Row` objects: 6378 You can create a custom :attr:`~Cursor.row_factory` -that returns each row as a :class:`dict`, mapping column names to values: +that returns each row as a :class:`dict`, with column names mapped to values: .. testcode::