From 2d3b7f10363a091e17767427e7b50c05d61af573 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 10 Aug 2022 00:37:26 +0200 Subject: [PATCH 1/6] gh-95273: Improve sqlite3.complete_statement docs --- Doc/includes/sqlite3/complete_statement.py | 33 ---------------------- Doc/library/sqlite3.rst | 14 +++++---- 2 files changed, 8 insertions(+), 39 deletions(-) delete mode 100644 Doc/includes/sqlite3/complete_statement.py diff --git a/Doc/includes/sqlite3/complete_statement.py b/Doc/includes/sqlite3/complete_statement.py deleted file mode 100644 index a5c947969910d4..00000000000000 --- a/Doc/includes/sqlite3/complete_statement.py +++ /dev/null @@ -1,33 +0,0 @@ -# A minimal SQLite shell for experiments - -import sqlite3 - -con = sqlite3.connect(":memory:") -con.isolation_level = None -cur = con.cursor() - -buffer = "" - -print("Enter your SQL commands to execute in sqlite3.") -print("Enter a blank line to exit.") - -while True: - line = input() - if line == "": - break - buffer += line - if sqlite3.complete_statement(buffer): - try: - buffer = buffer.strip() - cur.execute(buffer) - - if buffer.lstrip().upper().startswith("SELECT"): - print(cur.fetchall()) - except sqlite3.Error as e: - err_msg = str(e) - err_code = e.sqlite_errorcode - err_name = e.sqlite_errorname - print(f"{err_name} ({err_code}): {err_msg}") - buffer = "" - -con.close() diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 06ed7af052f00e..9ea9b3f4b5f015 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -222,14 +222,16 @@ Module functions .. function:: complete_statement(statement) - Returns ``True`` if the string *statement* contains one or more complete SQL - statements terminated by semicolons. It does not verify that the SQL is - syntactically correct, only that there are no unclosed string literals and the - statement is terminated by a semicolon. + Return ``True`` if the string *statement* appears to contain one or more + complete SQL statements terminated by semicolons. + No syntactic verification or parsing of any kind is performed. - This can be used to build a shell for SQLite, as in the following example: + This function may be useful during command-line input + to determine if the entered text seems to form a complete SQL statement, + or if additional input is needed before :meth:`executing ` + the statement. - .. literalinclude:: ../includes/sqlite3/complete_statement.py + See :source:`Lib/sqlite3/__main__.py` for example use. .. function:: enable_callback_tracebacks(flag, /) From 31b60fb0e6e53e2b6258b2905e0145d2dc25d194 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 10 Aug 2022 09:40:54 +0200 Subject: [PATCH 2/6] Address review --- Doc/library/sqlite3.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 9ea9b3f4b5f015..55e8eb5125dc3f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -222,14 +222,15 @@ Module functions .. function:: complete_statement(statement) - Return ``True`` if the string *statement* appears to contain one or more - complete SQL statements terminated by semicolons. - No syntactic verification or parsing of any kind is performed. + Return ``True`` if the string *statement* appears to contain + one or more complete SQL statements. + No syntactic verification or parsing of any kind is performed, + other than checking that there are no unclosed string literals + and the statment is terminated by a semicolon. This function may be useful during command-line input to determine if the entered text seems to form a complete SQL statement, - or if additional input is needed before :meth:`executing ` - the statement. + or if additional input is needed before calling :meth:`~Cursor.execute`. See :source:`Lib/sqlite3/__main__.py` for example use. From 4c85590e0d151aea27c001a9d545a12e0d035537 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 10 Aug 2022 11:28:07 +0200 Subject: [PATCH 3/6] Typo Co-authored-by: Ezio Melotti --- 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 55e8eb5125dc3f..347c93ba0a2c0f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -226,7 +226,7 @@ Module functions one or more complete SQL statements. No syntactic verification or parsing of any kind is performed, other than checking that there are no unclosed string literals - and the statment is terminated by a semicolon. + and the statement is terminated by a semicolon. This function may be useful during command-line input to determine if the entered text seems to form a complete SQL statement, From e6122892c6496ad30e96e17d42eb9e717aa74375 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 11 Aug 2022 09:58:36 +0200 Subject: [PATCH 4/6] Address review: add trivial example Co-authored-by: CAM Gerlach --- Doc/library/sqlite3.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 347c93ba0a2c0f..e24c8a87de001f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -232,7 +232,14 @@ Module functions to determine if the entered text seems to form a complete SQL statement, or if additional input is needed before calling :meth:`~Cursor.execute`. - See :source:`Lib/sqlite3/__main__.py` for example use. + For example:: + + >>> sqlite3.complete_statement('SELECT foo FROM bar;') + True + >>> sqlite3.complete_statement('SELECT foo') + False + + See :source:`Lib/sqlite3/__main__.py` for a more complete real-world use. .. function:: enable_callback_tracebacks(flag, /) From 887879f4d9026ee7f25b19809408b22db75531ad Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 11 Aug 2022 10:35:28 +0200 Subject: [PATCH 5/6] Add comments to CLI so it serves better as an example --- Doc/library/sqlite3.rst | 2 +- Lib/sqlite3/__main__.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index e24c8a87de001f..3ed3bf8c68cd1c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -239,7 +239,7 @@ Module functions >>> sqlite3.complete_statement('SELECT foo') False - See :source:`Lib/sqlite3/__main__.py` for a more complete real-world use. + See ``runsource()`` in :source:`Lib/sqlite3/__main__.py` for real-world use. .. function:: enable_callback_tracebacks(flag, /) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index c62fad84e74bb8..36ddf440278a5a 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -1,3 +1,9 @@ +"""A simple SQLite CLI for the sqlite3 module. + +Apart from using 'argparse' for the command-line interface, +this module implements the REPL as a thin wrapper around +the InteractiveConsole class from the 'code' stdlib module. +""" import sqlite3 import sys @@ -7,6 +13,14 @@ def execute(c, sql, suppress_errors=True): + """Helper that wraps execution of SQL code. + + This is used both by the REPL and by direct execution from the CLI. + + 'c' may be a cursor or a connection. + 'sql' is the SQL string to execute + """ + try: for row in c.execute(sql): print(row) @@ -21,6 +35,7 @@ def execute(c, sql, suppress_errors=True): class SqliteInteractiveConsole(InteractiveConsole): + """A simple SQLite REPL.""" def __init__(self, connection): super().__init__() @@ -28,6 +43,11 @@ def __init__(self, connection): self._cur = connection.cursor() def runsource(self, source, filename="", symbol="single"): + """Override runsource, the core of the InteractiveConsole REPL. + + Return True if more input is needed; buffering is done automatically. + Return False is input is a complete statement ready for execution. + """ match source: case ".version": print(f"{sqlite3.sqlite_version}") @@ -73,6 +93,7 @@ def main(): else: db_name = repr(args.filename) + # Prepare REPL banner and prompts. banner = dedent(f""" sqlite3 shell, running on SQLite version {sqlite3.sqlite_version} Connected to {db_name} @@ -86,8 +107,10 @@ def main(): con = sqlite3.connect(args.filename, isolation_level=None) try: if args.sql: + # SQL statement provided on the command-line; execute it directly. execute(con, args.sql, suppress_errors=False) else: + # No SQL provided; start the REPL. console = SqliteInteractiveConsole(con) console.interact(banner, exitmsg="") finally: From 44ddf4a7d3119ba4d5c3d4d5e2a8d88fecc79c09 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 11 Aug 2022 23:07:09 +0200 Subject: [PATCH 6/6] Address review and relocate example --- Doc/library/sqlite3.rst | 17 +++++++++-------- Lib/sqlite3/__main__.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 3ed3bf8c68cd1c..67f8b31f11f4bb 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -228,18 +228,19 @@ Module functions other than checking that there are no unclosed string literals and the statement is terminated by a semicolon. + For example:: + + >>> sqlite3.complete_statement("SELECT foo FROM bar;") + True + >>> sqlite3.complete_statement("SELECT foo") + False + This function may be useful during command-line input to determine if the entered text seems to form a complete SQL statement, or if additional input is needed before calling :meth:`~Cursor.execute`. - For example:: - - >>> sqlite3.complete_statement('SELECT foo FROM bar;') - True - >>> sqlite3.complete_statement('SELECT foo') - False - - See ``runsource()`` in :source:`Lib/sqlite3/__main__.py` for real-world use. + See :func:`!runsource` in :source:`Lib/sqlite3/__main__.py` + for real-world use. .. function:: enable_callback_tracebacks(flag, /) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 36ddf440278a5a..f8a5cca24e56af 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -18,7 +18,7 @@ def execute(c, sql, suppress_errors=True): This is used both by the REPL and by direct execution from the CLI. 'c' may be a cursor or a connection. - 'sql' is the SQL string to execute + 'sql' is the SQL string to execute. """ try: