From e6623e7083ce08a247e5df169bcc749f99327823 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 12 Aug 2022 01:05:12 +0200 Subject: [PATCH] gh-95273: Improve sqlite3.complete_statement docs (#95840) Co-authored-by: Ezio Melotti Co-authored-by: CAM Gerlach --- Doc/includes/sqlite3/complete_statement.py | 33 ---------------------- Doc/library/sqlite3.rst | 23 +++++++++++---- Lib/sqlite3/__main__.py | 23 +++++++++++++++ 3 files changed, 40 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 a5c94796991..00000000000 --- 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 06ed7af052f..67f8b31f11f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -222,14 +222,25 @@ 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. + No syntactic verification or parsing of any kind is performed, + other than checking that there are no unclosed string literals + and the statement is terminated by a semicolon. - This can be used to build a shell for SQLite, as in the following example: + For example:: - .. literalinclude:: ../includes/sqlite3/complete_statement.py + >>> 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`. + + 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 c62fad84e74..f8a5cca24e5 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: