https://github.com/python/cpython/commit/30b1d8f11d5905888f696db14867eb733e7b824c
commit: 30b1d8f11d5905888f696db14867eb733e7b824c
branch: main
author: Stan Ulbrych <89152624+stanfromirel...@users.noreply.github.com>
committer: erlend-aasland <erlend.aasl...@protonmail.com>
date: 2025-05-10T07:59:01Z
summary:

gh-133447: Add basic color to `sqlite3` CLI (#133461)

files:
A Misc/NEWS.d/next/Library/2025-05-05-18-50-00.gh-issue-133447.ajshdb.rst
M Lib/sqlite3/__main__.py
M Lib/test/test_sqlite3/test_cli.py

diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py
index 4ccf292ddf211c..c2fa23c46cf990 100644
--- a/Lib/sqlite3/__main__.py
+++ b/Lib/sqlite3/__main__.py
@@ -10,9 +10,10 @@
 from argparse import ArgumentParser
 from code import InteractiveConsole
 from textwrap import dedent
+from _colorize import get_theme, theme_no_color
 
 
-def execute(c, sql, suppress_errors=True):
+def execute(c, sql, suppress_errors=True, theme=theme_no_color):
     """Helper that wraps execution of SQL code.
 
     This is used both by the REPL and by direct execution from the CLI.
@@ -25,11 +26,15 @@ def execute(c, sql, suppress_errors=True):
         for row in c.execute(sql):
             print(row)
     except sqlite3.Error as e:
+        t = theme.traceback
         tp = type(e).__name__
         try:
-            print(f"{tp} ({e.sqlite_errorname}): {e}", file=sys.stderr)
+            tp += f" ({e.sqlite_errorname})"
         except AttributeError:
-            print(f"{tp}: {e}", file=sys.stderr)
+            pass
+        print(
+            f"{t.type}{tp}{t.reset}: {t.message}{e}{t.reset}", file=sys.stderr
+        )
         if not suppress_errors:
             sys.exit(1)
 
@@ -37,10 +42,11 @@ def execute(c, sql, suppress_errors=True):
 class SqliteInteractiveConsole(InteractiveConsole):
     """A simple SQLite REPL."""
 
-    def __init__(self, connection):
+    def __init__(self, connection, use_color=False):
         super().__init__()
         self._con = connection
         self._cur = connection.cursor()
+        self._use_color = use_color
 
     def runsource(self, source, filename="<input>", symbol="single"):
         """Override runsource, the core of the InteractiveConsole REPL.
@@ -48,6 +54,8 @@ def runsource(self, source, filename="<input>", 
symbol="single"):
         Return True if more input is needed; buffering is done automatically.
         Return False if input is a complete statement ready for execution.
         """
+        theme = get_theme(force_no_color=not self._use_color)
+
         if not source or source.isspace():
             return False
         if source[0] == ".":
@@ -61,12 +69,13 @@ def runsource(self, source, filename="<input>", 
symbol="single"):
                 case "":
                     pass
                 case _ as unknown:
-                    self.write("Error: unknown command or invalid arguments:"
-                               f'  "{unknown}".\n')
+                    t = theme.traceback
+                    self.write(f'{t.type}Error{t.reset}:{t.message} unknown'
+                               f'command or invalid arguments:  
"{unknown}".\n{t.reset}')
         else:
             if not sqlite3.complete_statement(source):
                 return True
-            execute(self._cur, source)
+            execute(self._cur, source, theme=theme)
         return False
 
 
@@ -113,17 +122,21 @@ def main(*args):
         Each command will be run using execute() on the cursor.
         Type ".help" for more information; type ".quit" or {eofkey} to quit.
     """).strip()
-    sys.ps1 = "sqlite> "
-    sys.ps2 = "    ... "
+
+    theme = get_theme()
+    s = theme.syntax
+
+    sys.ps1 = f"{s.prompt}sqlite> {s.reset}"
+    sys.ps2 = f"{s.prompt}    ... {s.reset}"
 
     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)
+            execute(con, args.sql, suppress_errors=False, theme=theme)
         else:
             # No SQL provided; start the REPL.
-            console = SqliteInteractiveConsole(con)
+            console = SqliteInteractiveConsole(con, use_color=True)
             try:
                 import readline  # noqa: F401
             except ImportError:
diff --git a/Lib/test/test_sqlite3/test_cli.py 
b/Lib/test/test_sqlite3/test_cli.py
index a03d7cbe16ba84..37e0f74f688659 100644
--- a/Lib/test/test_sqlite3/test_cli.py
+++ b/Lib/test/test_sqlite3/test_cli.py
@@ -8,10 +8,11 @@
     captured_stdout,
     captured_stderr,
     captured_stdin,
-    force_not_colorized,
+    force_not_colorized_test_class,
 )
 
 
+@force_not_colorized_test_class
 class CommandLineInterface(unittest.TestCase):
 
     def _do_test(self, *args, expect_success=True):
@@ -37,7 +38,6 @@ def expect_failure(self, *args):
         self.assertEqual(out, "")
         return err
 
-    @force_not_colorized
     def test_cli_help(self):
         out = self.expect_success("-h")
         self.assertIn("usage: ", out)
@@ -69,6 +69,7 @@ def test_cli_on_disk_db(self):
         self.assertIn("(0,)", out)
 
 
+@force_not_colorized_test_class
 class InteractiveSession(unittest.TestCase):
     MEMORY_DB_MSG = "Connected to a transient in-memory database"
     PS1 = "sqlite> "
@@ -190,6 +191,14 @@ def test_interact_on_disk_file(self):
         out, _ = self.run_cli(TESTFN, commands=("SELECT count(t) FROM t;",))
         self.assertIn("(0,)\n", out)
 
+    def test_color(self):
+        with unittest.mock.patch("_colorize.can_colorize", return_value=True):
+            out, err = self.run_cli(commands="TEXT\n")
+            self.assertIn("\x1b[1;35msqlite> \x1b[0m", out)
+            self.assertIn("\x1b[1;35m    ... \x1b[0m\x1b", out)
+            out, err = self.run_cli(commands=("sel;",))
+            self.assertIn('\x1b[1;35mOperationalError (SQLITE_ERROR)\x1b[0m: '
+                          '\x1b[35mnear "sel": syntax error\x1b[0m', err)
 
 if __name__ == "__main__":
     unittest.main()
diff --git 
a/Misc/NEWS.d/next/Library/2025-05-05-18-50-00.gh-issue-133447.ajshdb.rst 
b/Misc/NEWS.d/next/Library/2025-05-05-18-50-00.gh-issue-133447.ajshdb.rst
new file mode 100644
index 00000000000000..f453690cab2cb3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-05-18-50-00.gh-issue-133447.ajshdb.rst
@@ -0,0 +1 @@
+Add basic color to :mod:`sqlite3` CLI interface.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to