| 
									
										
										
										
											2022-08-12 01:05:12 +02:00
										 |  |  | """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. | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  | import sqlite3 | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from argparse import ArgumentParser | 
					
						
							|  |  |  | from code import InteractiveConsole | 
					
						
							|  |  |  | from textwrap import dedent | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  | from _colorize import get_theme, theme_no_color | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-06 22:52:41 +08:00
										 |  |  | from ._completer import completer | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  | def execute(c, sql, suppress_errors=True, theme=theme_no_color): | 
					
						
							| 
									
										
										
										
											2022-08-12 01:05:12 +02:00
										 |  |  |     """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. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |     try: | 
					
						
							|  |  |  |         for row in c.execute(sql): | 
					
						
							|  |  |  |             print(row) | 
					
						
							|  |  |  |     except sqlite3.Error as e: | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |         t = theme.traceback | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |         tp = type(e).__name__ | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |             tp += f" ({e.sqlite_errorname})" | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |         except AttributeError: | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |             pass | 
					
						
							|  |  |  |         print( | 
					
						
							|  |  |  |             f"{t.type}{tp}{t.reset}: {t.message}{e}{t.reset}", file=sys.stderr | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |         if not suppress_errors: | 
					
						
							|  |  |  |             sys.exit(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SqliteInteractiveConsole(InteractiveConsole): | 
					
						
							| 
									
										
										
										
											2022-08-12 01:05:12 +02:00
										 |  |  |     """A simple SQLite REPL.""" | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |     def __init__(self, connection, use_color=False): | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |         super().__init__() | 
					
						
							|  |  |  |         self._con = connection | 
					
						
							|  |  |  |         self._cur = connection.cursor() | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |         self._use_color = use_color | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def runsource(self, source, filename="<input>", symbol="single"): | 
					
						
							| 
									
										
										
										
											2022-08-12 01:05:12 +02:00
										 |  |  |         """Override runsource, the core of the InteractiveConsole REPL.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Return True if more input is needed; buffering is done automatically. | 
					
						
							| 
									
										
										
										
											2025-03-07 00:10:37 -06:00
										 |  |  |         Return False if input is a complete statement ready for execution. | 
					
						
							| 
									
										
										
										
											2022-08-12 01:05:12 +02:00
										 |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |         theme = get_theme(force_no_color=not self._use_color) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-09 19:41:10 +08:00
										 |  |  |         if not source or source.isspace(): | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  |         if source[0] == ".": | 
					
						
							|  |  |  |             match source[1:].strip(): | 
					
						
							|  |  |  |                 case "version": | 
					
						
							|  |  |  |                     print(f"{sqlite3.sqlite_version}") | 
					
						
							|  |  |  |                 case "help": | 
					
						
							|  |  |  |                     print("Enter SQL code and press enter.") | 
					
						
							|  |  |  |                 case "quit": | 
					
						
							|  |  |  |                     sys.exit(0) | 
					
						
							|  |  |  |                 case "": | 
					
						
							|  |  |  |                     pass | 
					
						
							|  |  |  |                 case _ as unknown: | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |                     t = theme.traceback | 
					
						
							|  |  |  |                     self.write(f'{t.type}Error{t.reset}:{t.message} unknown' | 
					
						
							|  |  |  |                                f'command or invalid arguments:  "{unknown}".\n{t.reset}') | 
					
						
							| 
									
										
										
										
											2025-05-09 19:41:10 +08:00
										 |  |  |         else: | 
					
						
							|  |  |  |             if not sqlite3.complete_statement(source): | 
					
						
							|  |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |             execute(self._cur, source, theme=theme) | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-28 14:17:34 +02:00
										 |  |  | def main(*args): | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |     parser = ArgumentParser( | 
					
						
							|  |  |  |         description="Python sqlite3 CLI", | 
					
						
							| 
									
										
										
										
											2025-05-05 20:46:46 +03:00
										 |  |  |         color=True, | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |     ) | 
					
						
							|  |  |  |     parser.add_argument( | 
					
						
							|  |  |  |         "filename", type=str, default=":memory:", nargs="?", | 
					
						
							|  |  |  |         help=( | 
					
						
							|  |  |  |             "SQLite database to open (defaults to ':memory:'). " | 
					
						
							|  |  |  |             "A new database is created if the file does not previously exist." | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     parser.add_argument( | 
					
						
							|  |  |  |         "sql", type=str, nargs="?", | 
					
						
							|  |  |  |         help=( | 
					
						
							|  |  |  |             "An SQL query to execute. " | 
					
						
							|  |  |  |             "Any returned rows are printed to stdout." | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     parser.add_argument( | 
					
						
							|  |  |  |         "-v", "--version", action="version", | 
					
						
							|  |  |  |         version=f"SQLite version {sqlite3.sqlite_version}", | 
					
						
							|  |  |  |         help="Print underlying SQLite library version", | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2023-08-28 14:17:34 +02:00
										 |  |  |     args = parser.parse_args(*args) | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if args.filename == ":memory:": | 
					
						
							|  |  |  |         db_name = "a transient in-memory database" | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         db_name = repr(args.filename) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-12 01:05:12 +02:00
										 |  |  |     # Prepare REPL banner and prompts. | 
					
						
							| 
									
										
										
										
											2023-04-27 23:22:26 +02:00
										 |  |  |     if sys.platform == "win32" and "idlelib.run" not in sys.modules: | 
					
						
							|  |  |  |         eofkey = "CTRL-Z" | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         eofkey = "CTRL-D" | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |     banner = dedent(f"""
 | 
					
						
							|  |  |  |         sqlite3 shell, running on SQLite version {sqlite3.sqlite_version} | 
					
						
							|  |  |  |         Connected to {db_name} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Each command will be run using execute() on the cursor. | 
					
						
							| 
									
										
										
										
											2023-04-27 21:23:10 +02:00
										 |  |  |         Type ".help" for more information; type ".quit" or {eofkey} to quit. | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |     """).strip()
 | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     theme = get_theme() | 
					
						
							|  |  |  |     s = theme.syntax | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sys.ps1 = f"{s.prompt}sqlite> {s.reset}" | 
					
						
							|  |  |  |     sys.ps2 = f"{s.prompt}    ... {s.reset}" | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     con = sqlite3.connect(args.filename, isolation_level=None) | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         if args.sql: | 
					
						
							| 
									
										
										
										
											2022-08-12 01:05:12 +02:00
										 |  |  |             # SQL statement provided on the command-line; execute it directly. | 
					
						
							| 
									
										
										
										
											2025-05-10 08:59:01 +01:00
										 |  |  |             execute(con, args.sql, suppress_errors=False, theme=theme) | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2022-08-12 01:05:12 +02:00
										 |  |  |             # No SQL provided; start the REPL. | 
					
						
							| 
									
										
										
										
											2025-06-06 22:52:41 +08:00
										 |  |  |             with completer(): | 
					
						
							|  |  |  |                 console = SqliteInteractiveConsole(con, use_color=True) | 
					
						
							|  |  |  |                 console.interact(banner, exitmsg="") | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  |     finally: | 
					
						
							|  |  |  |         con.close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-28 14:17:34 +02:00
										 |  |  |     sys.exit(0) | 
					
						
							| 
									
										
										
										
											2022-08-01 12:25:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-28 14:17:34 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							| 
									
										
										
										
											2023-08-29 11:39:42 +02:00
										 |  |  |     main(sys.argv[1:]) |