From 7dde03558436951353e834e816a5ba443c186a2c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 24 Aug 2022 09:07:18 +0200 Subject: [PATCH 1/6] gh-95432: Add doctests for the sqlite3 docs As a consequence of the added test, this commit also includes fixes for broken examples. --- Doc/library/sqlite3.rst | 170 ++++++++++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 58 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 798c2d795b1a61f..69263a8c7d1e094 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -343,7 +343,9 @@ Module functions other than checking that there are no unclosed string literals and the statement is terminated by a semicolon. - For example:: + For example: + + .. doctest:: >>> sqlite3.complete_statement("SELECT foo FROM bar;") True @@ -367,22 +369,23 @@ Module functions to disable the feature again. Register an :func:`unraisable hook handler ` for an - improved debug experience:: + improved debug experience: + + .. doctest:: - >>> import sqlite3 >>> sqlite3.enable_callback_tracebacks(True) - >>> cx = sqlite3.connect(":memory:") - >>> cx.set_trace_callback(lambda stmt: 5/0) - >>> cx.execute("select 1") - Exception ignored in: at 0x10b4e3ee0> - Traceback (most recent call last): - File "", line 1, in - ZeroDivisionError: division by zero + >>> con = sqlite3.connect(":memory:") + >>> def evil_trace(stmt): + ... 5/0 + >>> con.set_trace_callback(evil_trace) + >>> def debug(unraisable): + ... print(f"{unraisable.exc_value!r} in callback {unraisable.object.__name__}") + ... print(f"Error message: {unraisable.err_msg}") >>> import sys - >>> sys.unraisablehook = lambda unraisable: print(unraisable) - >>> cx.execute("select 1") - UnraisableHookArgs(exc_type=, exc_value=ZeroDivisionError('division by zero'), exc_traceback=, err_msg=None, object= at 0x10b4e3ee0>) - + >>> sys.unraisablehook = debug + >>> cur = con.execute("select 1") + ZeroDivisionError('division by zero') in callback evil_trace + Error message: None .. function:: register_adapter(type, adapter, /) @@ -939,12 +942,12 @@ Connection objects Useful when saving an in-memory database for later restoration. Similar to the ``.dump`` command in the :program:`sqlite3` shell. - Example:: + Example: - # Convert file existing_db.db to SQL dump file dump.sql - import sqlite3 + .. testcode:: - 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: for line in con.iterdump(): f.write('%s\n' % line) @@ -987,25 +990,29 @@ Connection objects The number of seconds to sleep between successive attempts 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): print(f'Copied {total-remaining} of {total} pages...') - con = sqlite3.connect('existing_db.db') + con = sqlite3.connect('example.db') bck = sqlite3.connect('backup.db') with bck: con.backup(bck, pages=1, progress=progress) bck.close() con.close() - Example 2, copy an existing database into a transient copy:: + .. testoutput:: - import sqlite3 + Copied 0 of 0 pages... + + Example 2, copy an existing database into a transient copy: - source = sqlite3.connect('existing_db.db') + .. testcode:: + + source = sqlite3.connect('example.db') dest = sqlite3.connect(':memory:') source.backup(dest) @@ -1023,12 +1030,20 @@ Connection objects :raises ProgrammingError: 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 10000000000): + + .. testsetup:: sqlite3.limits import sqlite3 con = sqlite3.connect(":memory:") - lim = con.getlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH) - print(f"SQLITE_LIMIT_SQL_LENGTH={lim}") + con.setlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH, 1_000_000_000) + con.setlimit(sqlite3.SQLITE_LIMIT_ATTACHED, 10) + + .. doctest:: sqlite3.limits + + >>> con.getlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH) + 1000000000 .. versionadded:: 3.11 @@ -1052,11 +1067,15 @@ Connection objects :raises ProgrammingError: 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 - con = sqlite3.connect(":memory:") - con.setlimit(sqlite3.SQLITE_LIMIT_ATTACHED, 1) + .. doctest:: sqlite3.limits + + >>> con.setlimit(sqlite3.SQLITE_LIMIT_ATTACHED, 1) + 10 + >>> con.getlimit(sqlite3.SQLITE_LIMIT_ATTACHED) + 1 .. versionadded:: 3.11 @@ -1132,10 +1151,20 @@ Cursor objects Cursor objects are :term:`iterators `, 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: + + .. 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)") - for row in cur.execute("select * from data"): - print(row) + .. doctest:: sqlite3.cursor + + >>> for row in cur.execute("SELECT t FROM data"): + ... print(row) + (1,) .. _database cursor: https://en.wikipedia.org/wiki/Cursor_(databases) @@ -1172,14 +1201,18 @@ Cursor objects :term:`iterator` yielding parameters instead of a sequence. Uses the same implicit transaction handling as :meth:`~Cursor.execute`. - Example:: + Example: - data = [ - ("row1",), - ("row2",), - ] - # cur is an sqlite3.Cursor object - cur.executemany("insert into t values(?)", data) + .. testcode:: + + con = sqlite3.connect(":memory:") # doctest: +SKIP + cur = con.execute("CREATE TABLE t(t)") # doctest: +SKIP + data = [ + ("row1",), + ("row2",), + ] + # cur is an sqlite3.Cursor object + cur.executemany("insert into t values(?)", data) .. method:: executescript(sql_script, /) @@ -1191,7 +1224,9 @@ Cursor objects *sql_script* must be a :class:`string `. - Example:: + Example: + + .. testcode:: # cur is an sqlite3.Cursor object cur.executescript(""" @@ -1288,7 +1323,9 @@ Cursor objects Read-only attribute that provides the SQLite database :class:`Connection` belonging to the cursor. A :class:`Cursor` object created by calling :meth:`con.cursor() ` will have a - :attr:`connection` attribute that refers to *con*:: + :attr:`connection` attribute that refers to *con*: + + .. doctest:: >>> con = sqlite3.connect(":memory:") >>> cur = con.cursor() @@ -1323,7 +1360,9 @@ Row objects .. versionchanged:: 3.5 Added support of slicing. - Example:: + Example: + + .. doctest:: >>> con = sqlite3.connect(":memory:") >>> con.row_factory = sqlite3.Row @@ -1665,7 +1704,7 @@ and constructs a :class:`!Point` object from it. Converter functions are **always** passed a :class:`bytes` object, no matter the underlying SQLite data type. -:: +.. testcode:: def convert_point(s): x, y = map(float, s.split(b";")) @@ -1728,7 +1767,7 @@ Adapter and converter recipes This section shows recipes for common adapters and converters. -.. code-block:: +.. testcode:: import datetime import sqlite3 @@ -1741,7 +1780,7 @@ This section shows recipes for common adapters and converters. """Adapt datetime.datetime to timezone-naive ISO 8601 date.""" return val.isoformat() - def adapt_datetime_epoch(val) + def adapt_datetime_epoch(val): """Adapt datetime.datetime to Unix timestamp.""" return int(val.timestamp()) @@ -1815,23 +1854,38 @@ Working with SQLite URIs Some useful URI tricks include: -* Open a database in read-only mode:: +* Open a database in read-only mode: + +.. doctest:: - con = sqlite3.connect("file:template.db?mode=ro", uri=True) + >>> 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; - will raise :exc:`~sqlite3.OperationalError` if unable to create a new file:: + will raise :exc:`~sqlite3.OperationalError` if unable to create a new file: + +.. doctest:: - con = sqlite3.connect("file:nosuchdb.db?mode=rw", uri=True) + >>> 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:: -* Create a shared named in-memory database:: + 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, can be found in the `SQLite URI documentation`_. From 9904a5a64381938965eda4d7fdfc2b9633ef0601 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 24 Aug 2022 10:05:30 +0200 Subject: [PATCH 2/6] Fix cursor iter example --- Doc/library/sqlite3.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 69263a8c7d1e094..a2d0acee45fb8d5 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1160,10 +1160,13 @@ Cursor objects cur = con.execute("CREATE TABLE data(t)") cur.execute("INSERT INTO data VALUES(1)") - .. doctest:: sqlite3.cursor + .. testcode:: sqlite3.cursor + + for row in cur.execute("SELECT t FROM data"): + print(row) + + .. testoutput:: sqlite3.cursor - >>> for row in cur.execute("SELECT t FROM data"): - ... print(row) (1,) .. _database cursor: https://en.wikipedia.org/wiki/Cursor_(databases) From 1d8c24020f224844a3ae856025dadbe631551273 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 24 Aug 2022 10:08:01 +0200 Subject: [PATCH 3/6] Fix mention of default max sql length limit --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index a2d0acee45fb8d5..7b42ad0bbff369a 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1031,7 +1031,7 @@ Connection objects If *category* is not recognised by the underlying SQLite library. Example, query the maximum length of an SQL statement - for :class:`Connection` ``con`` (the default is 10000000000): + for :class:`Connection` ``con`` (the default is 1000000000): .. testsetup:: sqlite3.limits From 9a830f3c6ca205c44f44fcabb0d45724fb4d86f0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 24 Aug 2022 18:37:09 +0200 Subject: [PATCH 4/6] Hide testoutput and fixup executemany --- Doc/library/sqlite3.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 7b42ad0bbff369a..8e71a5a363102f1 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1005,6 +1005,7 @@ Connection objects con.close() .. testoutput:: + :hide: Copied 0 of 0 pages... @@ -1166,6 +1167,7 @@ Cursor objects print(row) .. testoutput:: sqlite3.cursor + :hide: (1,) @@ -1206,10 +1208,13 @@ Cursor objects Example: - .. testcode:: + .. testsetup:: sqlite3.executemany + + con = sqlite3.connect(":memory:") + cur = con.execute("CREATE TABLE t(t)") + + .. testcode:: sqlite3.executemany - con = sqlite3.connect(":memory:") # doctest: +SKIP - cur = con.execute("CREATE TABLE t(t)") # doctest: +SKIP data = [ ("row1",), ("row2",), From 32e378e33fb8bf0133688837413b204bdacc5333 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 24 Aug 2022 22:11:53 +0200 Subject: [PATCH 5/6] Fix trace and cursor tests - Add separate "namespace" for trace tests bco. callback - Move more Cursor examples under the sqlite3.cursor namespace --- Doc/library/sqlite3.rst | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 8e71a5a363102f1..6f7ffebdade74c1 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -371,7 +371,11 @@ Module functions Register an :func:`unraisable hook handler ` for an improved debug experience: - .. doctest:: + .. testsetup:: sqlite3.trace + + import sqlite3 + + .. doctest:: sqlite3.trace >>> sqlite3.enable_callback_tracebacks(True) >>> con = sqlite3.connect(":memory:") @@ -1208,19 +1212,14 @@ Cursor objects Example: - .. testsetup:: sqlite3.executemany + .. testcode:: sqlite3.cursor - con = sqlite3.connect(":memory:") - cur = con.execute("CREATE TABLE t(t)") - - .. testcode:: sqlite3.executemany - - data = [ + rows = [ ("row1",), ("row2",), ] # cur is an sqlite3.Cursor object - cur.executemany("insert into t values(?)", data) + cur.executemany("insert into data values(?)", rows) .. method:: executescript(sql_script, /) @@ -1234,7 +1233,7 @@ Cursor objects Example: - .. testcode:: + .. testcode:: sqlite3.cursor # cur is an sqlite3.Cursor object cur.executescript(""" From 42ece9f63172333a27f2a31f3046d8666a4dee00 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 28 Aug 2022 21:30:54 +0200 Subject: [PATCH 6/6] Normalise naming in backup examples --- Doc/library/sqlite3.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 6f7ffebdade74c1..cc6d141192eb0c2 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1001,12 +1001,12 @@ Connection objects def progress(status, remaining, total): print(f'Copied {total-remaining} of {total} pages...') - con = sqlite3.connect('example.db') - bck = sqlite3.connect('backup.db') - with bck: - con.backup(bck, pages=1, progress=progress) - bck.close() - con.close() + src = sqlite3.connect('example.db') + dst = sqlite3.connect('backup.db') + with dst: + src.backup(dst, pages=1, progress=progress) + dst.close() + src.close() .. testoutput:: :hide: @@ -1017,9 +1017,9 @@ Connection objects .. testcode:: - source = sqlite3.connect('example.db') - dest = sqlite3.connect(':memory:') - source.backup(dest) + src = sqlite3.connect('example.db') + dst = sqlite3.connect(':memory:') + src.backup(dst) .. versionadded:: 3.7