mirror of https://github.com/python/cpython
gh-96168: Add sqlite3 row factory how-to (#99507)
Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM> Co-authored-by: Ezio Melotti <ezio.melotti@gmail.com>
This commit is contained in:
parent
d386115039
commit
8749121b07
|
@ -239,6 +239,7 @@ inserted data and retrieved values from it in multiple ways.
|
||||||
* :ref:`sqlite3-adapters`
|
* :ref:`sqlite3-adapters`
|
||||||
* :ref:`sqlite3-converters`
|
* :ref:`sqlite3-converters`
|
||||||
* :ref:`sqlite3-connection-context-manager`
|
* :ref:`sqlite3-connection-context-manager`
|
||||||
|
* :ref:`sqlite3-howto-row-factory`
|
||||||
|
|
||||||
* :ref:`sqlite3-explanation` for in-depth background on transaction control.
|
* :ref:`sqlite3-explanation` for in-depth background on transaction control.
|
||||||
|
|
||||||
|
@ -1316,31 +1317,14 @@ Connection objects
|
||||||
|
|
||||||
.. attribute:: row_factory
|
.. attribute:: row_factory
|
||||||
|
|
||||||
A callable that accepts two arguments,
|
The initial :attr:`~Cursor.row_factory`
|
||||||
a :class:`Cursor` object and the raw row results as a :class:`tuple`,
|
for :class:`Cursor` objects created from this connection.
|
||||||
and returns a custom object representing an SQLite row.
|
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,
|
||||||
|
meaning each row is returned as a :class:`tuple`.
|
||||||
|
|
||||||
Example:
|
See :ref:`sqlite3-howto-row-factory` for more details.
|
||||||
|
|
||||||
.. 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.
|
|
||||||
|
|
||||||
.. XXX what's a db_row-based solution?
|
|
||||||
|
|
||||||
.. attribute:: text_factory
|
.. attribute:: text_factory
|
||||||
|
|
||||||
|
@ -1497,7 +1481,7 @@ Cursor objects
|
||||||
|
|
||||||
.. method:: fetchone()
|
.. 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`.
|
return the next row query result set as a :class:`tuple`.
|
||||||
Else, pass it to the row factory and return its result.
|
Else, pass it to the row factory and return its result.
|
||||||
Return ``None`` if no more data is available.
|
Return ``None`` if no more data is available.
|
||||||
|
@ -1591,6 +1575,22 @@ Cursor objects
|
||||||
including :abbr:`CTE (Common Table Expression)` queries.
|
including :abbr:`CTE (Common Table Expression)` queries.
|
||||||
It is only updated by the :meth:`execute` and :meth:`executemany` methods.
|
It is only updated by the :meth:`execute` and :meth:`executemany` methods.
|
||||||
|
|
||||||
|
.. attribute:: row_factory
|
||||||
|
|
||||||
|
Control how a row fetched from this :class:`!Cursor` is represented.
|
||||||
|
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.
|
||||||
|
|
||||||
|
Defaults to what :attr:`Connection.row_factory` was set to
|
||||||
|
when the :class:`!Cursor` was created.
|
||||||
|
Assigning to this attribute does not affect
|
||||||
|
:attr:`Connection.row_factory` of the parent connection.
|
||||||
|
|
||||||
|
See :ref:`sqlite3-howto-row-factory` for more details.
|
||||||
|
|
||||||
|
|
||||||
.. The sqlite3.Row example used to be a how-to. It has now been incorporated
|
.. 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
|
into the Row reference. We keep the anchor here in order not to break
|
||||||
|
@ -1609,7 +1609,10 @@ Row objects
|
||||||
It supports iteration, equality testing, :func:`len`,
|
It supports iteration, equality testing, :func:`len`,
|
||||||
and :term:`mapping` access by column name and index.
|
and :term:`mapping` access by column name and index.
|
||||||
|
|
||||||
Two row objects compare equal if have equal columns and equal members.
|
Two :class:`!Row` objects compare equal
|
||||||
|
if they have identical column names and values.
|
||||||
|
|
||||||
|
See :ref:`sqlite3-howto-row-factory` for more details.
|
||||||
|
|
||||||
.. method:: keys
|
.. method:: keys
|
||||||
|
|
||||||
|
@ -1620,21 +1623,6 @@ Row objects
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
Added support of slicing.
|
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:
|
.. _sqlite3-blob-objects:
|
||||||
|
|
||||||
|
@ -2358,6 +2346,96 @@ can be found in the `SQLite URI documentation`_.
|
||||||
.. _SQLite URI documentation: https://www.sqlite.org/uri.html
|
.. _SQLite URI documentation: https://www.sqlite.org/uri.html
|
||||||
|
|
||||||
|
|
||||||
|
.. _sqlite3-howto-row-factory:
|
||||||
|
|
||||||
|
How to create and use row factories
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
While :attr:`!row_factory` exists as an attribute both on the
|
||||||
|
:class:`Cursor` and the :class:`Connection`,
|
||||||
|
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 minimal memory overhead and performance impact over a :class:`!tuple`.
|
||||||
|
To use :class:`!Row` as a row factory,
|
||||||
|
assign it to the :attr:`!row_factory` attribute:
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> con = sqlite3.connect(":memory:")
|
||||||
|
>>> con.row_factory = sqlite3.Row
|
||||||
|
|
||||||
|
Queries now return :class:`!Row` objects:
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> res = con.execute("SELECT 'Earth' AS name, 6378 AS radius")
|
||||||
|
>>> row = res.fetchone()
|
||||||
|
>>> row.keys()
|
||||||
|
['name', 'radius']
|
||||||
|
>>> row[0] # Access by index.
|
||||||
|
'Earth'
|
||||||
|
>>> row["name"] # Access by name.
|
||||||
|
'Earth'
|
||||||
|
>>> row["RADIUS"] # Column names are case-insensitive.
|
||||||
|
6378
|
||||||
|
|
||||||
|
You can create a custom :attr:`~Cursor.row_factory`
|
||||||
|
that returns each row as a :class:`dict`, with column names mapped to values:
|
||||||
|
|
||||||
|
.. 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`:
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> 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}
|
||||||
|
|
||||||
|
The following row factory returns a :term:`named tuple`:
|
||||||
|
|
||||||
|
.. testcode::
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
def namedtuple_factory(cursor, row):
|
||||||
|
fields = [column[0] for column in cursor.description]
|
||||||
|
cls = namedtuple("Row", fields)
|
||||||
|
return cls._make(row)
|
||||||
|
|
||||||
|
:func:`!namedtuple_factory` can be used as follows:
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> con = sqlite3.connect(":memory:")
|
||||||
|
>>> con.row_factory = namedtuple_factory
|
||||||
|
>>> 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:
|
.. _sqlite3-explanation:
|
||||||
|
|
||||||
Explanation
|
Explanation
|
||||||
|
|
Loading…
Reference in New Issue