Skip to content

Commit e985bab

Browse files
gh-96168: Add sqlite3 row factory how-to
1 parent ee821dc commit e985bab

File tree

1 file changed

+82
-33
lines changed

1 file changed

+82
-33
lines changed

Doc/library/sqlite3.rst

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,27 +1320,11 @@ Connection objects
13201320
a :class:`Cursor` object and the raw row results as a :class:`tuple`,
13211321
and returns a custom object representing an SQLite row.
13221322

1323-
Example:
1324-
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-
13361323
If returning a tuple doesn't suffice and you want name-based access to
13371324
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.
1325+
highly optimized :class:`sqlite3.Row`
13421326

1343-
.. XXX what's a db_row-based solution?
1327+
See :ref:`sqlite3-row-factory-how-to` for more details.
13441328

13451329
.. attribute:: text_factory
13461330

@@ -1611,6 +1595,8 @@ Row objects
16111595

16121596
Two row objects compare equal if have equal columns and equal members.
16131597

1598+
See :ref:`sqlite3-row-factory-how-to` for more details.
1599+
16141600
.. method:: keys
16151601

16161602
Return a :class:`list` of column names as :class:`strings <str>`.
@@ -1620,21 +1606,6 @@ Row objects
16201606
.. versionchanged:: 3.5
16211607
Added support of slicing.
16221608

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-
16381609

16391610
.. _sqlite3-blob-objects:
16401611

@@ -2358,6 +2329,84 @@ can be found in the `SQLite URI documentation`_.
23582329
.. _SQLite URI documentation: https://www.sqlite.org/uri.html
23592330

23602331

2332+
.. _sqlite3-row-factory-how-to:
2333+
2334+
How to work with row factories
2335+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2336+
2337+
By default, :mod:`!sqlite3` represent fetched rows as :class:`tuples <tuple>`.
2338+
If a :class:`!tuple` does not suit your needs,
2339+
use the built-in :class:`Row` type or a custom :attr:`~Connection.row_factory`.
2340+
The former provides both indexed and case-insensitive named access to columns,
2341+
with low memory overhead and minimal performance impact.
2342+
Example use:
2343+
2344+
.. doctest::
2345+
2346+
>>> con = sqlite3.connect(":memory:")
2347+
>>> con.row_factory = sqlite3.Row
2348+
>>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius")
2349+
>>> row = res.fetchone()
2350+
>>> row.keys()
2351+
['name', 'radius']
2352+
>>> row[0], row["name"] # Access by index and name.
2353+
('Earth', 'Earth')
2354+
>>> row["RADIUS"] # Column names are case-insensitive.
2355+
6378
2356+
2357+
If you need more flexibility, you can design your own row factory.
2358+
Here's an example of a :class:`dict` row factory:
2359+
2360+
.. doctest::
2361+
2362+
>>> def dict_factory(cursor, row):
2363+
... col_names = [col[0] for col in cursor.description]
2364+
... return {key: value for key, value in zip(col_names, row)}
2365+
2366+
>>> con = sqlite3.connect(":memory:")
2367+
>>> con.row_factory = dict_factory
2368+
>>> for row in con.execute("SELECT 1 AS a, 2 AS b"):
2369+
... print(row)
2370+
{'a': 1, 'b': 2}
2371+
2372+
Here's an example of a optimised :class:`~collections.namedtuple` factory:
2373+
2374+
.. testcode::
2375+
2376+
from collections import namedtuple
2377+
from functools import lru_cache
2378+
2379+
def _fields(cursor):
2380+
return [col[0] for col in cursor.description]
2381+
2382+
@lru_cache
2383+
def _make_cls(fields):
2384+
return namedtuple("Row", fields)
2385+
2386+
def NamedTupleFactory(cursor, row):
2387+
cls = _make_cls(_fields(cursor))
2388+
return cls._make(row)
2389+
2390+
Example use:
2391+
2392+
.. doctest::
2393+
2394+
>>> con = sqlite3.connect(":memory:")
2395+
>>> con.row_factory = NamedTupleRow
2396+
>>> cur = con.execute("SELECT 1 AS a, 2 AS b")
2397+
>>> row = cur.fetchone()
2398+
>>> row
2399+
Row(a='1', b='2')
2400+
>>> row[0] # Indexed access.
2401+
1
2402+
>>> row.b # Attribute access.
2403+
2
2404+
2405+
With some adjustments, the above recipe can be adapted to use a
2406+
:class:`~dataclasses.dataclass`, or any other custom class,
2407+
instead of a :class:`~collections.namedtuple`.
2408+
2409+
23612410
.. _sqlite3-explanation:
23622411

23632412
Explanation

0 commit comments

Comments
 (0)