Skip to content

Commit 8749121

Browse files
erlend-aaslandCAM-Gerlachezio-melotti
authored
gh-96168: Add sqlite3 row factory how-to (#99507)
Co-authored-by: C.A.M. Gerlach <[email protected]> Co-authored-by: Ezio Melotti <[email protected]>
1 parent d386115 commit 8749121

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

@@ -1316,31 +1317,14 @@ Connection objects
13161317

13171318
.. attribute:: row_factory
13181319

1319-
A callable that accepts two arguments,
1320-
a :class:`Cursor` object and the raw row results as a :class:`tuple`,
1321-
and returns a custom object representing an SQLite row.
1322-
1323-
Example:
1320+
The initial :attr:`~Cursor.row_factory`
1321+
for :class:`Cursor` objects created from this connection.
1322+
Assigning to this attribute does not affect the :attr:`!row_factory`
1323+
of existing cursors belonging to this connection, only new ones.
1324+
Is ``None`` by default,
1325+
meaning each row is returned as a :class:`tuple`.
13241326

1325-
.. doctest::
1326-
1327-
>>> def dict_factory(cursor, row):
1328-
... col_names = [col[0] for col in cursor.description]
1329-
... return {key: value for key, value in zip(col_names, row)}
1330-
>>> con = sqlite3.connect(":memory:")
1331-
>>> con.row_factory = dict_factory
1332-
>>> for row in con.execute("SELECT 1 AS a, 2 AS b"):
1333-
... print(row)
1334-
{'a': 1, 'b': 2}
1335-
1336-
If returning a tuple doesn't suffice and you want name-based access to
1337-
columns, you should consider setting :attr:`row_factory` to the
1338-
highly optimized :class:`sqlite3.Row` type. :class:`Row` provides both
1339-
index-based and case-insensitive name-based access to columns with almost no
1340-
memory overhead. It will probably be better than your own custom
1341-
dictionary-based approach or even a db_row based solution.
1342-
1343-
.. XXX what's a db_row-based solution?
1327+
See :ref:`sqlite3-howto-row-factory` for more details.
13441328

13451329
.. attribute:: text_factory
13461330

@@ -1497,7 +1481,7 @@ Cursor objects
14971481

14981482
.. method:: fetchone()
14991483

1500-
If :attr:`~Connection.row_factory` is ``None``,
1484+
If :attr:`~Cursor.row_factory` is ``None``,
15011485
return the next row query result set as a :class:`tuple`.
15021486
Else, pass it to the row factory and return its result.
15031487
Return ``None`` if no more data is available.
@@ -1591,6 +1575,22 @@ Cursor objects
15911575
including :abbr:`CTE (Common Table Expression)` queries.
15921576
It is only updated by the :meth:`execute` and :meth:`executemany` methods.
15931577

1578+
.. attribute:: row_factory
1579+
1580+
Control how a row fetched from this :class:`!Cursor` is represented.
1581+
If ``None``, a row is represented as a :class:`tuple`.
1582+
Can be set to the included :class:`sqlite3.Row`;
1583+
or a :term:`callable` that accepts two arguments,
1584+
a :class:`Cursor` object and the :class:`!tuple` of row values,
1585+
and returns a custom object representing an SQLite row.
1586+
1587+
Defaults to what :attr:`Connection.row_factory` was set to
1588+
when the :class:`!Cursor` was created.
1589+
Assigning to this attribute does not affect
1590+
:attr:`Connection.row_factory` of the parent connection.
1591+
1592+
See :ref:`sqlite3-howto-row-factory` for more details.
1593+
15941594

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

1612-
Two row objects compare equal if have equal columns and equal members.
1612+
Two :class:`!Row` objects compare equal
1613+
if they have identical column names and values.
1614+
1615+
See :ref:`sqlite3-howto-row-factory` for more details.
16131616

16141617
.. method:: keys
16151618

@@ -1620,21 +1623,6 @@ Row objects
16201623
.. versionchanged:: 3.5
16211624
Added support of slicing.
16221625

1623-
Example:
1624-
1625-
.. doctest::
1626-
1627-
>>> con = sqlite3.connect(":memory:")
1628-
>>> con.row_factory = sqlite3.Row
1629-
>>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius")
1630-
>>> row = res.fetchone()
1631-
>>> row.keys()
1632-
['name', 'radius']
1633-
>>> row[0], row["name"] # Access by index and name.
1634-
('Earth', 'Earth')
1635-
>>> row["RADIUS"] # Column names are case-insensitive.
1636-
6378
1637-
16381626

16391627
.. _sqlite3-blob-objects:
16401628

@@ -2358,6 +2346,96 @@ can be found in the `SQLite URI documentation`_.
23582346
.. _SQLite URI documentation: https://www.sqlite.org/uri.html
23592347

23602348

2349+
.. _sqlite3-howto-row-factory:
2350+
2351+
How to create and use row factories
2352+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2353+
2354+
By default, :mod:`!sqlite3` represents each row as a :class:`tuple`.
2355+
If a :class:`!tuple` does not suit your needs,
2356+
you can use the :class:`sqlite3.Row` class
2357+
or a custom :attr:`~Cursor.row_factory`.
2358+
2359+
While :attr:`!row_factory` exists as an attribute both on the
2360+
:class:`Cursor` and the :class:`Connection`,
2361+
it is recommended to set :class:`Connection.row_factory`,
2362+
so all cursors created from the connection will use the same row factory.
2363+
2364+
:class:`!Row` provides indexed and case-insensitive named access to columns,
2365+
with minimal memory overhead and performance impact over a :class:`!tuple`.
2366+
To use :class:`!Row` as a row factory,
2367+
assign it to the :attr:`!row_factory` attribute:
2368+
2369+
.. doctest::
2370+
2371+
>>> con = sqlite3.connect(":memory:")
2372+
>>> con.row_factory = sqlite3.Row
2373+
2374+
Queries now return :class:`!Row` objects:
2375+
2376+
.. doctest::
2377+
2378+
>>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius")
2379+
>>> row = res.fetchone()
2380+
>>> row.keys()
2381+
['name', 'radius']
2382+
>>> row[0] # Access by index.
2383+
'Earth'
2384+
>>> row["name"] # Access by name.
2385+
'Earth'
2386+
>>> row["RADIUS"] # Column names are case-insensitive.
2387+
6378
2388+
2389+
You can create a custom :attr:`~Cursor.row_factory`
2390+
that returns each row as a :class:`dict`, with column names mapped to values:
2391+
2392+
.. testcode::
2393+
2394+
def dict_factory(cursor, row):
2395+
fields = [column[0] for column in cursor.description]
2396+
return {key: value for key, value in zip(fields, row)}
2397+
2398+
Using it, queries now return a :class:`!dict` instead of a :class:`!tuple`:
2399+
2400+
.. doctest::
2401+
2402+
>>> con = sqlite3.connect(":memory:")
2403+
>>> con.row_factory = dict_factory
2404+
>>> for row in con.execute("SELECT 1 AS a, 2 AS b"):
2405+
... print(row)
2406+
{'a': 1, 'b': 2}
2407+
2408+
The following row factory returns a :term:`named tuple`:
2409+
2410+
.. testcode::
2411+
2412+
from collections import namedtuple
2413+
2414+
def namedtuple_factory(cursor, row):
2415+
fields = [column[0] for column in cursor.description]
2416+
cls = namedtuple("Row", fields)
2417+
return cls._make(row)
2418+
2419+
:func:`!namedtuple_factory` can be used as follows:
2420+
2421+
.. doctest::
2422+
2423+
>>> con = sqlite3.connect(":memory:")
2424+
>>> con.row_factory = namedtuple_factory
2425+
>>> cur = con.execute("SELECT 1 AS a, 2 AS b")
2426+
>>> row = cur.fetchone()
2427+
>>> row
2428+
Row(a=1, b=2)
2429+
>>> row[0] # Indexed access.
2430+
1
2431+
>>> row.b # Attribute access.
2432+
2
2433+
2434+
With some adjustments, the above recipe can be adapted to use a
2435+
:class:`~dataclasses.dataclass`, or any other custom class,
2436+
instead of a :class:`~collections.namedtuple`.
2437+
2438+
23612439
.. _sqlite3-explanation:
23622440

23632441
Explanation

0 commit comments

Comments
 (0)