Skip to content

Commit aabe831

Browse files
miss-islingtonerlend-aaslandCAM-Gerlachezio-melotti
authored
gh-96168: Add sqlite3 row factory how-to (GH-99507)
(cherry picked from commit 8749121) Co-authored-by: Erlend E. Aasland <[email protected]> Co-authored-by: C.A.M. Gerlach <[email protected]> Co-authored-by: Ezio Melotti <[email protected]>
1 parent c426815 commit aabe831

File tree

1 file changed

+119
-41
lines changed

1 file changed

+119
-41
lines changed

Doc/library/sqlite3.rst

Lines changed: 119 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ inserted data and retrieved values from it in multiple ways.
239239
* :ref:`sqlite3-adapters`
240240
* :ref:`sqlite3-converters`
241241
* :ref:`sqlite3-connection-context-manager`
242+
* :ref:`sqlite3-howto-row-factory`
242243

243244
* :ref:`sqlite3-explanation` for in-depth background on transaction control.
244245

@@ -946,31 +947,14 @@ Connection objects
946947

947948
.. attribute:: row_factory
948949

949-
A callable that accepts two arguments,
950-
a :class:`Cursor` object and the raw row results as a :class:`tuple`,
951-
and returns a custom object representing an SQLite row.
952-
953-
Example:
950+
The initial :attr:`~Cursor.row_factory`
951+
for :class:`Cursor` objects created from this connection.
952+
Assigning to this attribute does not affect the :attr:`!row_factory`
953+
of existing cursors belonging to this connection, only new ones.
954+
Is ``None`` by default,
955+
meaning each row is returned as a :class:`tuple`.
954956

955-
.. doctest::
956-
957-
>>> def dict_factory(cursor, row):
958-
... col_names = [col[0] for col in cursor.description]
959-
... return {key: value for key, value in zip(col_names, row)}
960-
>>> con = sqlite3.connect(":memory:")
961-
>>> con.row_factory = dict_factory
962-
>>> for row in con.execute("SELECT 1 AS a, 2 AS b"):
963-
... print(row)
964-
{'a': 1, 'b': 2}
965-
966-
If returning a tuple doesn't suffice and you want name-based access to
967-
columns, you should consider setting :attr:`row_factory` to the
968-
highly optimized :class:`sqlite3.Row` type. :class:`Row` provides both
969-
index-based and case-insensitive name-based access to columns with almost no
970-
memory overhead. It will probably be better than your own custom
971-
dictionary-based approach or even a db_row based solution.
972-
973-
.. XXX what's a db_row-based solution?
957+
See :ref:`sqlite3-howto-row-factory` for more details.
974958

975959
.. attribute:: text_factory
976960

@@ -1122,7 +1106,7 @@ Cursor objects
11221106

11231107
.. method:: fetchone()
11241108

1125-
If :attr:`~Connection.row_factory` is ``None``,
1109+
If :attr:`~Cursor.row_factory` is ``None``,
11261110
return the next row query result set as a :class:`tuple`.
11271111
Else, pass it to the row factory and return its result.
11281112
Return ``None`` if no more data is available.
@@ -1216,6 +1200,22 @@ Cursor objects
12161200
including :abbr:`CTE (Common Table Expression)` queries.
12171201
It is only updated by the :meth:`execute` and :meth:`executemany` methods.
12181202

1203+
.. attribute:: row_factory
1204+
1205+
Control how a row fetched from this :class:`!Cursor` is represented.
1206+
If ``None``, a row is represented as a :class:`tuple`.
1207+
Can be set to the included :class:`sqlite3.Row`;
1208+
or a :term:`callable` that accepts two arguments,
1209+
a :class:`Cursor` object and the :class:`!tuple` of row values,
1210+
and returns a custom object representing an SQLite row.
1211+
1212+
Defaults to what :attr:`Connection.row_factory` was set to
1213+
when the :class:`!Cursor` was created.
1214+
Assigning to this attribute does not affect
1215+
:attr:`Connection.row_factory` of the parent connection.
1216+
1217+
See :ref:`sqlite3-howto-row-factory` for more details.
1218+
12191219

12201220
.. The sqlite3.Row example used to be a how-to. It has now been incorporated
12211221
into the Row reference. We keep the anchor here in order not to break
@@ -1234,7 +1234,10 @@ Row objects
12341234
It supports iteration, equality testing, :func:`len`,
12351235
and :term:`mapping` access by column name and index.
12361236

1237-
Two row objects compare equal if have equal columns and equal members.
1237+
Two :class:`!Row` objects compare equal
1238+
if they have identical column names and values.
1239+
1240+
See :ref:`sqlite3-howto-row-factory` for more details.
12381241

12391242
.. method:: keys
12401243

@@ -1245,21 +1248,6 @@ Row objects
12451248
.. versionchanged:: 3.5
12461249
Added support of slicing.
12471250

1248-
Example:
1249-
1250-
.. doctest::
1251-
1252-
>>> con = sqlite3.connect(":memory:")
1253-
>>> con.row_factory = sqlite3.Row
1254-
>>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius")
1255-
>>> row = res.fetchone()
1256-
>>> row.keys()
1257-
['name', 'radius']
1258-
>>> row[0], row["name"] # Access by index and name.
1259-
('Earth', 'Earth')
1260-
>>> row["RADIUS"] # Column names are case-insensitive.
1261-
6378
1262-
12631251

12641252
PrepareProtocol objects
12651253
^^^^^^^^^^^^^^^^^^^^^^^
@@ -1855,6 +1843,96 @@ can be found in the `SQLite URI documentation`_.
18551843
.. _SQLite URI documentation: https://www.sqlite.org/uri.html
18561844

18571845

1846+
.. _sqlite3-howto-row-factory:
1847+
1848+
How to create and use row factories
1849+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1850+
1851+
By default, :mod:`!sqlite3` represents each row as a :class:`tuple`.
1852+
If a :class:`!tuple` does not suit your needs,
1853+
you can use the :class:`sqlite3.Row` class
1854+
or a custom :attr:`~Cursor.row_factory`.
1855+
1856+
While :attr:`!row_factory` exists as an attribute both on the
1857+
:class:`Cursor` and the :class:`Connection`,
1858+
it is recommended to set :class:`Connection.row_factory`,
1859+
so all cursors created from the connection will use the same row factory.
1860+
1861+
:class:`!Row` provides indexed and case-insensitive named access to columns,
1862+
with minimal memory overhead and performance impact over a :class:`!tuple`.
1863+
To use :class:`!Row` as a row factory,
1864+
assign it to the :attr:`!row_factory` attribute:
1865+
1866+
.. doctest::
1867+
1868+
>>> con = sqlite3.connect(":memory:")
1869+
>>> con.row_factory = sqlite3.Row
1870+
1871+
Queries now return :class:`!Row` objects:
1872+
1873+
.. doctest::
1874+
1875+
>>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius")
1876+
>>> row = res.fetchone()
1877+
>>> row.keys()
1878+
['name', 'radius']
1879+
>>> row[0] # Access by index.
1880+
'Earth'
1881+
>>> row["name"] # Access by name.
1882+
'Earth'
1883+
>>> row["RADIUS"] # Column names are case-insensitive.
1884+
6378
1885+
1886+
You can create a custom :attr:`~Cursor.row_factory`
1887+
that returns each row as a :class:`dict`, with column names mapped to values:
1888+
1889+
.. testcode::
1890+
1891+
def dict_factory(cursor, row):
1892+
fields = [column[0] for column in cursor.description]
1893+
return {key: value for key, value in zip(fields, row)}
1894+
1895+
Using it, queries now return a :class:`!dict` instead of a :class:`!tuple`:
1896+
1897+
.. doctest::
1898+
1899+
>>> con = sqlite3.connect(":memory:")
1900+
>>> con.row_factory = dict_factory
1901+
>>> for row in con.execute("SELECT 1 AS a, 2 AS b"):
1902+
... print(row)
1903+
{'a': 1, 'b': 2}
1904+
1905+
The following row factory returns a :term:`named tuple`:
1906+
1907+
.. testcode::
1908+
1909+
from collections import namedtuple
1910+
1911+
def namedtuple_factory(cursor, row):
1912+
fields = [column[0] for column in cursor.description]
1913+
cls = namedtuple("Row", fields)
1914+
return cls._make(row)
1915+
1916+
:func:`!namedtuple_factory` can be used as follows:
1917+
1918+
.. doctest::
1919+
1920+
>>> con = sqlite3.connect(":memory:")
1921+
>>> con.row_factory = namedtuple_factory
1922+
>>> cur = con.execute("SELECT 1 AS a, 2 AS b")
1923+
>>> row = cur.fetchone()
1924+
>>> row
1925+
Row(a=1, b=2)
1926+
>>> row[0] # Indexed access.
1927+
1
1928+
>>> row.b # Attribute access.
1929+
2
1930+
1931+
With some adjustments, the above recipe can be adapted to use a
1932+
:class:`~dataclasses.dataclass`, or any other custom class,
1933+
instead of a :class:`~collections.namedtuple`.
1934+
1935+
18581936
.. _sqlite3-explanation:
18591937

18601938
Explanation

0 commit comments

Comments
 (0)