gh-95432: Add doctest for sqlite3 tutorial (#96193)

This commit is contained in:
Erlend E. Aasland 2022-08-23 08:48:13 +02:00 committed by GitHub
parent 1499d73b3e
commit 04c73e5efb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 47 additions and 16 deletions

View File

@ -8,6 +8,15 @@
**Source code:** :source:`Lib/sqlite3/`
.. Make sure we always doctest the tutorial with an empty database.
.. testsetup::
import sqlite3
src = sqlite3.connect(":memory:", isolation_level=None)
dst = sqlite3.connect("tutorial.db", isolation_level=None)
src.backup(dst)
del src, dst
.. _sqlite3-intro:
@ -65,7 +74,9 @@ First, we need to create a new database and open
a database connection to allow :mod:`!sqlite3` to work with it.
Call :func:`sqlite3.connect` to to create a connection to
the database :file:`tutorial.db` in the current working directory,
implicitly creating it if it does not exist::
implicitly creating it if it does not exist:
.. testcode::
import sqlite3
con = sqlite3.connect("tutorial.db")
@ -75,7 +86,9 @@ represents the connection to the on-disk database.
In order to execute SQL statements and fetch results from SQL queries,
we will need to use a database cursor.
Call :meth:`con.cursor() <Connection.cursor>` to create the :class:`Cursor`::
Call :meth:`con.cursor() <Connection.cursor>` to create the :class:`Cursor`:
.. testcode::
cur = con.cursor()
@ -86,7 +99,9 @@ For simplicity, we can just use column names in the table declaration --
thanks to the `flexible typing`_ feature of SQLite,
specifying the data types is optional.
Execute the ``CREATE TABLE`` statement
by calling :meth:`cur.execute(...) <Cursor.execute>`::
by calling :meth:`cur.execute(...) <Cursor.execute>`:
.. testcode::
cur.execute("CREATE TABLE movie(title, year, score)")
@ -99,7 +114,9 @@ which should now contain an entry for the ``movie`` table definition
(see `The Schema Table`_ for details).
Execute that query by calling :meth:`cur.execute(...) <Cursor.execute>`,
assign the result to ``res``,
and call :meth:`res.fetchone() <Cursor.fetchone>` to fetch the resulting row::
and call :meth:`res.fetchone() <Cursor.fetchone>` to fetch the resulting row:
.. doctest::
>>> res = cur.execute("SELECT name FROM sqlite_master")
>>> res.fetchone()
@ -108,7 +125,9 @@ and call :meth:`res.fetchone() <Cursor.fetchone>` to fetch the resulting row::
We can see that the table has been created,
as the query returns a :class:`tuple` containing the table's name.
If we query ``sqlite_master`` for a non-existent table ``spam``,
:meth:`!res.fetchone()` will return ``None``::
:meth:`!res.fetchone()` will return ``None``:
.. doctest::
>>> res = cur.execute("SELECT name FROM sqlite_master WHERE name='spam'")
>>> res.fetchone() is None
@ -116,7 +135,9 @@ If we query ``sqlite_master`` for a non-existent table ``spam``,
Now, add two rows of data supplied as SQL literals
by executing an ``INSERT`` statement,
once again by calling :meth:`cur.execute(...) <Cursor.execute>`::
once again by calling :meth:`cur.execute(...) <Cursor.execute>`:
.. testcode::
cur.execute("""
INSERT INTO movie VALUES
@ -128,7 +149,9 @@ The ``INSERT`` statement implicitly opens a transaction,
which needs to be committed before changes are saved in the database
(see :ref:`sqlite3-controlling-transactions` for details).
Call :meth:`con.commit() <Connection.commit>` on the connection object
to commit the transaction::
to commit the transaction:
.. testcode::
con.commit()
@ -136,7 +159,9 @@ We can verify that the data was inserted correctly
by executing a ``SELECT`` query.
Use the now-familiar :meth:`cur.execute(...) <Cursor.execute>` to
assign the result to ``res``,
and call :meth:`res.fetchall() <Cursor.fetchall>` to return all resulting rows::
and call :meth:`res.fetchall() <Cursor.fetchall>` to return all resulting rows:
.. doctest::
>>> res = cur.execute("SELECT score FROM movie")
>>> res.fetchall()
@ -146,7 +171,9 @@ The result is a :class:`list` of two :class:`!tuple`\s, one per row,
each containing that row's ``score`` value.
Now, insert three more rows by calling
:meth:`cur.executemany(...) <Cursor.executemany>`::
:meth:`cur.executemany(...) <Cursor.executemany>`:
.. testcode::
data = [
("Monty Python Live at the Hollywood Bowl", 1982, 7.9),
@ -164,14 +191,16 @@ to avoid `SQL injection attacks`_
We can verify that the new rows were inserted
by executing a ``SELECT`` query,
this time iterating over the results of the query::
this time iterating over the results of the query:
.. doctest::
>>> for row in cur.execute("SELECT year, title FROM movie ORDER BY year"):
... print(row)
(1971, "And Now for Something Completely Different")
(1975, "Monty Python and the Holy Grail")
(1971, 'And Now for Something Completely Different')
(1975, 'Monty Python and the Holy Grail')
(1979, "Monty Python's Life of Brian")
(1982, "Monty Python Live at the Hollywood Bowl")
(1982, 'Monty Python Live at the Hollywood Bowl')
(1983, "Monty Python's The Meaning of Life")
Each row is a two-item :class:`tuple` of ``(year, title)``,
@ -180,15 +209,17 @@ matching the columns selected in the query.
Finally, verify that the database has been written to disk
by calling :meth:`con.close() <Connection.close>`
to close the existing connection, opening a new one,
creating a new cursor, then querying the database::
creating a new cursor, then querying the database:
.. doctest::
>>> con.close()
>>> new_con = sqlite3.connect("tutorial.db")
>>> new_cur = new_con.cursor()
>>> res = new_cur.execute("SELECT year, title FROM movie ORDER BY score DESC"):
>>> res = new_cur.execute("SELECT title, year FROM movie ORDER BY score DESC")
>>> title, year = res.fetchone()
>>> print(f'The highest scoring Monty Python movie is {title!r}, released in {year}')
'The highest scoring Monty Python movie is "Monty Python and the Holy Grail", released in 1975'
The highest scoring Monty Python movie is 'Monty Python and the Holy Grail', released in 1975
You've now created an SQLite database using the :mod:`!sqlite3` module,
inserted data and retrieved values from it in multiple ways.