gh-95432: Add doctests for the sqlite3 docs (GH-96225)

As a consequence of the added test, this commit also includes
fixes for broken examples.

- Add separate namespace for trace tests bco. module level callback
- Move more backup and cursor examples under separate namespaces
(cherry picked from commit bf9259776d)

Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
This commit is contained in:
Miss Islington (bot) 2022-08-29 02:01:37 -07:00 committed by GitHub
parent ca7e78dc3a
commit 2ba877258a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -343,7 +343,9 @@ Module functions
other than checking that there are no unclosed string literals other than checking that there are no unclosed string literals
and the statement is terminated by a semicolon. and the statement is terminated by a semicolon.
For example:: For example:
.. doctest::
>>> sqlite3.complete_statement("SELECT foo FROM bar;") >>> sqlite3.complete_statement("SELECT foo FROM bar;")
True True
@ -364,22 +366,27 @@ Module functions
to disable the feature again. to disable the feature again.
Register an :func:`unraisable hook handler <sys.unraisablehook>` for an Register an :func:`unraisable hook handler <sys.unraisablehook>` for an
improved debug experience:: improved debug experience:
.. testsetup:: sqlite3.trace
import sqlite3
.. doctest:: sqlite3.trace
>>> import sqlite3
>>> sqlite3.enable_callback_tracebacks(True) >>> sqlite3.enable_callback_tracebacks(True)
>>> cx = sqlite3.connect(":memory:") >>> con = sqlite3.connect(":memory:")
>>> cx.set_trace_callback(lambda stmt: 5/0) >>> def evil_trace(stmt):
>>> cx.execute("select 1") ... 5/0
Exception ignored in: <function <lambda> at 0x10b4e3ee0> >>> con.set_trace_callback(evil_trace)
Traceback (most recent call last): >>> def debug(unraisable):
File "<stdin>", line 1, in <lambda> ... print(f"{unraisable.exc_value!r} in callback {unraisable.object.__name__}")
ZeroDivisionError: division by zero ... print(f"Error message: {unraisable.err_msg}")
>>> import sys >>> import sys
>>> sys.unraisablehook = lambda unraisable: print(unraisable) >>> sys.unraisablehook = debug
>>> cx.execute("select 1") >>> cur = con.execute("select 1")
UnraisableHookArgs(exc_type=<class 'ZeroDivisionError'>, exc_value=ZeroDivisionError('division by zero'), exc_traceback=<traceback object at 0x10b559900>, err_msg=None, object=<function <lambda> at 0x10b4e3ee0>) ZeroDivisionError('division by zero') in callback evil_trace
<sqlite3.Cursor object at 0x10b1fe840> Error message: None
.. function:: register_adapter(type, adapter, /) .. function:: register_adapter(type, adapter, /)
@ -926,12 +933,12 @@ Connection objects
Useful when saving an in-memory database for later restoration. Useful when saving an in-memory database for later restoration.
Similar to the ``.dump`` command in the :program:`sqlite3` shell. Similar to the ``.dump`` command in the :program:`sqlite3` shell.
Example:: Example:
# Convert file existing_db.db to SQL dump file dump.sql .. testcode::
import sqlite3
con = sqlite3.connect('existing_db.db') # Convert file example.db to SQL dump file dump.sql
con = sqlite3.connect('example.db')
with open('dump.sql', 'w') as f: with open('dump.sql', 'w') as f:
for line in con.iterdump(): for line in con.iterdump():
f.write('%s\n' % line) f.write('%s\n' % line)
@ -974,27 +981,32 @@ Connection objects
The number of seconds to sleep between successive attempts The number of seconds to sleep between successive attempts
to back up remaining pages. to back up remaining pages.
Example 1, copy an existing database into another:: Example 1, copy an existing database into another:
import sqlite3 .. testcode::
def progress(status, remaining, total): def progress(status, remaining, total):
print(f'Copied {total-remaining} of {total} pages...') print(f'Copied {total-remaining} of {total} pages...')
con = sqlite3.connect('existing_db.db') src = sqlite3.connect('example.db')
bck = sqlite3.connect('backup.db') dst = sqlite3.connect('backup.db')
with bck: with dst:
con.backup(bck, pages=1, progress=progress) src.backup(dst, pages=1, progress=progress)
bck.close() dst.close()
con.close() src.close()
Example 2, copy an existing database into a transient copy:: .. testoutput::
:hide:
import sqlite3 Copied 0 of 0 pages...
source = sqlite3.connect('existing_db.db') Example 2, copy an existing database into a transient copy:
dest = sqlite3.connect(':memory:')
source.backup(dest) .. testcode::
src = sqlite3.connect('example.db')
dst = sqlite3.connect(':memory:')
src.backup(dst)
.. versionadded:: 3.7 .. versionadded:: 3.7
@ -1010,12 +1022,20 @@ Connection objects
:raises ProgrammingError: :raises ProgrammingError:
If *category* is not recognised by the underlying SQLite library. If *category* is not recognised by the underlying SQLite library.
Example, query the maximum length of an SQL statement:: Example, query the maximum length of an SQL statement
for :class:`Connection` ``con`` (the default is 1000000000):
.. testsetup:: sqlite3.limits
import sqlite3 import sqlite3
con = sqlite3.connect(":memory:") con = sqlite3.connect(":memory:")
lim = con.getlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH) con.setlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH, 1_000_000_000)
print(f"SQLITE_LIMIT_SQL_LENGTH={lim}") con.setlimit(sqlite3.SQLITE_LIMIT_ATTACHED, 10)
.. doctest:: sqlite3.limits
>>> con.getlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH)
1000000000
.. versionadded:: 3.11 .. versionadded:: 3.11
@ -1039,11 +1059,15 @@ Connection objects
:raises ProgrammingError: :raises ProgrammingError:
If *category* is not recognised by the underlying SQLite library. If *category* is not recognised by the underlying SQLite library.
Example, limit the number of attached databases to 1:: Example, limit the number of attached databases to 1
for :class:`Connection` ``con`` (the default limit is 10):
import sqlite3 .. doctest:: sqlite3.limits
con = sqlite3.connect(":memory:")
con.setlimit(sqlite3.SQLITE_LIMIT_ATTACHED, 1) >>> con.setlimit(sqlite3.SQLITE_LIMIT_ATTACHED, 1)
10
>>> con.getlimit(sqlite3.SQLITE_LIMIT_ATTACHED)
1
.. versionadded:: 3.11 .. versionadded:: 3.11
@ -1119,11 +1143,25 @@ Cursor objects
Cursor objects are :term:`iterators <iterator>`, Cursor objects are :term:`iterators <iterator>`,
meaning that if you :meth:`~Cursor.execute` a ``SELECT`` query, meaning that if you :meth:`~Cursor.execute` a ``SELECT`` query,
you can simply iterate over the cursor to fetch the resulting rows:: you can simply iterate over the cursor to fetch the resulting rows:
for row in cur.execute("select * from data"): .. testsetup:: sqlite3.cursor
import sqlite3
con = sqlite3.connect(":memory:", isolation_level=None)
cur = con.execute("CREATE TABLE data(t)")
cur.execute("INSERT INTO data VALUES(1)")
.. testcode:: sqlite3.cursor
for row in cur.execute("SELECT t FROM data"):
print(row) print(row)
.. testoutput:: sqlite3.cursor
:hide:
(1,)
.. _database cursor: https://en.wikipedia.org/wiki/Cursor_(databases) .. _database cursor: https://en.wikipedia.org/wiki/Cursor_(databases)
.. class:: Cursor .. class:: Cursor
@ -1159,14 +1197,16 @@ Cursor objects
:term:`iterator` yielding parameters instead of a sequence. :term:`iterator` yielding parameters instead of a sequence.
Uses the same implicit transaction handling as :meth:`~Cursor.execute`. Uses the same implicit transaction handling as :meth:`~Cursor.execute`.
Example:: Example:
data = [ .. testcode:: sqlite3.cursor
rows = [
("row1",), ("row1",),
("row2",), ("row2",),
] ]
# cur is an sqlite3.Cursor object # cur is an sqlite3.Cursor object
cur.executemany("insert into t values(?)", data) cur.executemany("insert into data values(?)", rows)
.. method:: executescript(sql_script, /) .. method:: executescript(sql_script, /)
@ -1178,7 +1218,9 @@ Cursor objects
*sql_script* must be a :class:`string <str>`. *sql_script* must be a :class:`string <str>`.
Example:: Example:
.. testcode:: sqlite3.cursor
# cur is an sqlite3.Cursor object # cur is an sqlite3.Cursor object
cur.executescript(""" cur.executescript("""
@ -1275,7 +1317,9 @@ Cursor objects
Read-only attribute that provides the SQLite database :class:`Connection` Read-only attribute that provides the SQLite database :class:`Connection`
belonging to the cursor. A :class:`Cursor` object created by belonging to the cursor. A :class:`Cursor` object created by
calling :meth:`con.cursor() <Connection.cursor>` will have a calling :meth:`con.cursor() <Connection.cursor>` will have a
:attr:`connection` attribute that refers to *con*:: :attr:`connection` attribute that refers to *con*:
.. doctest::
>>> con = sqlite3.connect(":memory:") >>> con = sqlite3.connect(":memory:")
>>> cur = con.cursor() >>> cur = con.cursor()
@ -1310,7 +1354,9 @@ Row objects
.. versionchanged:: 3.5 .. versionchanged:: 3.5
Added support of slicing. Added support of slicing.
Example:: Example:
.. doctest::
>>> con = sqlite3.connect(":memory:") >>> con = sqlite3.connect(":memory:")
>>> con.row_factory = sqlite3.Row >>> con.row_factory = sqlite3.Row
@ -1662,7 +1708,7 @@ and constructs a :class:`!Point` object from it.
Converter functions are **always** passed a :class:`bytes` object, Converter functions are **always** passed a :class:`bytes` object,
no matter the underlying SQLite data type. no matter the underlying SQLite data type.
:: .. testcode::
def convert_point(s): def convert_point(s):
x, y = map(float, s.split(b";")) x, y = map(float, s.split(b";"))
@ -1690,7 +1736,7 @@ Adapter and converter recipes
This section shows recipes for common adapters and converters. This section shows recipes for common adapters and converters.
.. code-block:: .. testcode::
import datetime import datetime
import sqlite3 import sqlite3
@ -1703,7 +1749,7 @@ This section shows recipes for common adapters and converters.
"""Adapt datetime.datetime to timezone-naive ISO 8601 date.""" """Adapt datetime.datetime to timezone-naive ISO 8601 date."""
return val.isoformat() return val.isoformat()
def adapt_datetime_epoch(val) def adapt_datetime_epoch(val):
"""Adapt datetime.datetime to Unix timestamp.""" """Adapt datetime.datetime to Unix timestamp."""
return int(val.timestamp()) return int(val.timestamp())
@ -1777,23 +1823,38 @@ How to work with SQLite URIs
Some useful URI tricks include: Some useful URI tricks include:
* Open a database in read-only mode:: * Open a database in read-only mode:
con = sqlite3.connect("file:template.db?mode=ro", uri=True) .. doctest::
>>> con = sqlite3.connect("file:tutorial.db?mode=ro", uri=True)
>>> con.execute("CREATE TABLE readonly(data)")
Traceback (most recent call last):
OperationalError: attempt to write a readonly database
* Do not implicitly create a new database file if it does not already exist; * Do not implicitly create a new database file if it does not already exist;
will raise :exc:`~sqlite3.OperationalError` if unable to create a new file:: will raise :exc:`~sqlite3.OperationalError` if unable to create a new file:
con = sqlite3.connect("file:nosuchdb.db?mode=rw", uri=True) .. doctest::
* Create a shared named in-memory database:: >>> con = sqlite3.connect("file:nosuchdb.db?mode=rw", uri=True)
Traceback (most recent call last):
OperationalError: unable to open database file
* Create a shared named in-memory database:
.. testcode::
db = "file:mem1?mode=memory&cache=shared"
con1 = sqlite3.connect(db, uri=True)
con2 = sqlite3.connect(db, uri=True)
with con1:
con1.execute("CREATE TABLE shared(data)")
con1.execute("INSERT INTO shared VALUES(28)")
res = con2.execute("SELECT data FROM shared")
assert res.fetchone() == (28,)
con1 = sqlite3.connect("file:mem1?mode=memory&cache=shared", uri=True)
con2 = sqlite3.connect("file:mem1?mode=memory&cache=shared", uri=True)
con1.execute("create table t(t)")
con1.execute("insert into t values(28)")
con1.commit()
rows = con2.execute("select * from t").fetchall()
More information about this feature, including a list of parameters, More information about this feature, including a list of parameters,
can be found in the `SQLite URI documentation`_. can be found in the `SQLite URI documentation`_.