From def2e81abe0a5df99eaf0d4f168b9eb144874ede Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 28 May 2020 10:36:33 +0200 Subject: [PATCH 01/33] Add proof-of-concept REPL --- Lib/sqlite3/__main__.py | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Lib/sqlite3/__main__.py diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py new file mode 100644 index 00000000000000..e5d9acc1c49c93 --- /dev/null +++ b/Lib/sqlite3/__main__.py @@ -0,0 +1,79 @@ +import sqlite3 + +from code import InteractiveConsole + + +class SqliteInteractiveConsole(InteractiveConsole): + + def __init__(self, database): + super().__init__() + self._con = sqlite3.connect(database, isolation_level=None) + self._cur = self._con.cursor() + + def runsql(self, sql): + if sqlite3.complete_statement(sql): + try: + self._cur.execute(sql) + rows = self._cur.fetchall() + for row in rows: + print(row) + except sqlite3.Error as e: + print(f"{e.sqlite_errorname}: {e}") + return False + return True + + def runpy(self, source, filename="", symbol="single"): + code = self.compile(source, filename, symbol) + self.runcode(code) + return False + + def printhelp(self, ignored): + print("Enter SQL code and press enter.") + + def runsource(self, source, filename="", symbol="single"): + keywords = { + "version": lambda x: print(f"{sqlite3.sqlite_version}"), + "help": self.printhelp, + "copyright": self.runpy, + "credits": self.runpy, + "license": self.runpy, + "license()": self.runpy, + "quit()": self.runpy, + "quit": self.runpy, + } + return keywords.get(source, self.runsql)(source) + + +if __name__ == "__main__": + import sys + from argparse import ArgumentParser + from textwrap import dedent + + parser = ArgumentParser( + description="Python sqlite3 REPL", + prog="python -m sqlite3", + ) + parser.add_argument( + "-f", "--filename", + type=str, dest="database", action="store", default=":memory:", + help="Database to open (default in-memory database)", + ) + args = parser.parse_args() + + if args.database == ":memory:": + db_name = "a transient in-memory database" + else: + db_name = f"'{args.database}'" + + 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. + Type "help" for more information; type "quit" or CTRL-D to quit. + """).strip() + sys.ps1 = "sqlite> " + sys.ps2 = " ... " + + console = SqliteInteractiveConsole(args.database) + console.interact(banner, exitmsg="") From 1f01905e133c92ffa0354b548321193ba70a598d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 20 Jul 2022 00:24:09 +0200 Subject: [PATCH 02/33] Add NEWS --- .../next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst diff --git a/Misc/NEWS.d/next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst b/Misc/NEWS.d/next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst new file mode 100644 index 00000000000000..b685a737fcdf70 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst @@ -0,0 +1 @@ +Add interactive shell for :mod:`sqlite3`. Patch by Erlend Aasland. From 86a456691574400d89a1b3bb57fb9f412a214a25 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 20 Jul 2022 17:25:33 +0200 Subject: [PATCH 03/33] Remove redundant __name__ condition --- Lib/sqlite3/__main__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index e5d9acc1c49c93..3a9289d3d382ff 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -1,6 +1,9 @@ import sqlite3 +import sys +from argparse import ArgumentParser from code import InteractiveConsole +from textwrap import dedent class SqliteInteractiveConsole(InteractiveConsole): @@ -44,11 +47,7 @@ def runsource(self, source, filename="", symbol="single"): return keywords.get(source, self.runsql)(source) -if __name__ == "__main__": - import sys - from argparse import ArgumentParser - from textwrap import dedent - +def main(): parser = ArgumentParser( description="Python sqlite3 REPL", prog="python -m sqlite3", @@ -77,3 +76,6 @@ def runsource(self, source, filename="", symbol="single"): console = SqliteInteractiveConsole(args.database) console.interact(banner, exitmsg="") + + +main() From 896834c6dd2bb849d178eb41fc5188fe06c8f8b9 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 20 Jul 2022 17:35:42 +0200 Subject: [PATCH 04/33] Add -v argument for dumping SQLite version --- Lib/sqlite3/__main__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 3a9289d3d382ff..ee000b8b4f076a 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -47,6 +47,10 @@ def runsource(self, source, filename="", symbol="single"): return keywords.get(source, self.runsql)(source) +def dump_version(): + print(f"SQLite version {sqlite3.sqlite_version}") + + def main(): parser = ArgumentParser( description="Python sqlite3 REPL", @@ -57,7 +61,14 @@ def main(): type=str, dest="database", action="store", default=":memory:", help="Database to open (default in-memory database)", ) + parser.add_argument( + "-v", "--version", + dest="show_version", action="store_true", default=False, + help="Print SQLite version", + ) args = parser.parse_args() + if args.show_version: + return dump_version() if args.database == ":memory:": db_name = "a transient in-memory database" From 4ba731ac1669389be788d49304f4b52158bbe80b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 20 Jul 2022 20:51:32 +0200 Subject: [PATCH 05/33] Address review: close connection explicitly --- Lib/sqlite3/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index ee000b8b4f076a..18fcaa895c526d 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -13,6 +13,9 @@ def __init__(self, database): self._con = sqlite3.connect(database, isolation_level=None) self._cur = self._con.cursor() + def __del__(self): + self._con.close() + def runsql(self, sql): if sqlite3.complete_statement(sql): try: From 8179a68e8e031f65e9db4f28575a05541a468ad1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 29 Jul 2022 12:09:57 +0200 Subject: [PATCH 06/33] Add docs --- Doc/library/sqlite3.rst | 17 +++++++++++++++++ Lib/sqlite3/__main__.py | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 6a430f00aea4d1..1bc7ef5825cf0c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1718,6 +1718,23 @@ the context manager is a no-op. .. literalinclude:: ../includes/sqlite3/ctx_manager.py +Command-line interface +---------------------- + +The ``sqlite3`` module can be invoked as a script +in order to provide a simple SQLite shell. + +.. program:: python -m sqlite3 + +.. cmdoption:: -f, --filename + Database to open (defaults to ``':memory:'``). + +.. cmdoption:: -v, --version + Print underlying SQLite library version. + +.. versionadded:: 3.12 + + .. rubric:: Footnotes .. [#f1] The sqlite3 module is not built with loadable extension support by diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 18fcaa895c526d..3b9498ffe3c516 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -62,12 +62,12 @@ def main(): parser.add_argument( "-f", "--filename", type=str, dest="database", action="store", default=":memory:", - help="Database to open (default in-memory database)", + help="Database to open (defaults to ':memory:')", ) parser.add_argument( "-v", "--version", dest="show_version", action="store_true", default=False, - help="Print SQLite version", + help="Print underlying SQLite library version", ) args = parser.parse_args() if args.show_version: From 91a77b7c906361d71ea53e7e869aff3ea1661174 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 29 Jul 2022 12:11:17 +0200 Subject: [PATCH 07/33] Remove copyright, credits, and license cli commands --- Lib/sqlite3/__main__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 3b9498ffe3c516..9343ea28ba6bcf 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -40,10 +40,6 @@ def runsource(self, source, filename="", symbol="single"): keywords = { "version": lambda x: print(f"{sqlite3.sqlite_version}"), "help": self.printhelp, - "copyright": self.runpy, - "credits": self.runpy, - "license": self.runpy, - "license()": self.runpy, "quit()": self.runpy, "quit": self.runpy, } From 2f27a049e82394e83e8dbafd807826e2131f4f5d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 29 Jul 2022 12:12:40 +0200 Subject: [PATCH 08/33] Document how to quit --- Doc/library/sqlite3.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 1bc7ef5825cf0c..20735f1c0f27e7 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1723,6 +1723,7 @@ Command-line interface The ``sqlite3`` module can be invoked as a script in order to provide a simple SQLite shell. +Type ``quit`` or CTRL-D to exit the shell. .. program:: python -m sqlite3 From ae55e8e86532b62b0f58f772e34575a665055227 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 29 Jul 2022 12:44:50 +0200 Subject: [PATCH 09/33] Fix sphinx option refs by moving the footnote up to where it belongs, and making it a note --- Doc/library/sqlite3.rst | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 20735f1c0f27e7..0f99d711254732 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -753,7 +753,14 @@ Connection Objects aggregates or whole new virtual table implementations. One well-known extension is the fulltext-search extension distributed with SQLite. - Loadable extensions are disabled by default. See [#f1]_. + .. note:: + + The ``sqlite3`` module is not built with loadable extension support by + default, because some platforms (notably macOS) have SQLite + libraries which are compiled without this feature. + To get loadable extension support, + you must pass the :option:`--enable-loadable-sqlite-extensions` option + to :program:`configure`. .. audit-event:: sqlite3.enable_load_extension connection,enabled sqlite3.Connection.enable_load_extension @@ -1727,19 +1734,10 @@ Type ``quit`` or CTRL-D to exit the shell. .. program:: python -m sqlite3 -.. cmdoption:: -f, --filename +.. option:: -f, --filename Database to open (defaults to ``':memory:'``). -.. cmdoption:: -v, --version +.. option:: -v, --version Print underlying SQLite library version. .. versionadded:: 3.12 - - -.. rubric:: Footnotes - -.. [#f1] The sqlite3 module is not built with loadable extension support by - default, because some platforms (notably macOS) have SQLite - libraries which are compiled without this feature. To get loadable - extension support, you must pass the - :option:`--enable-loadable-sqlite-extensions` option to configure. From 024cd90e94a3ea750f566b21ef80afe7e6efc3e7 Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Fri, 29 Jul 2022 23:22:26 +0200 Subject: [PATCH 10/33] Address review: iterate over cursor Co-authored-by: Serhiy Storchaka --- Lib/sqlite3/__main__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 9343ea28ba6bcf..b1022a92dea092 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -19,9 +19,7 @@ def __del__(self): def runsql(self, sql): if sqlite3.complete_statement(sql): try: - self._cur.execute(sql) - rows = self._cur.fetchall() - for row in rows: + for row in self._cur.execute(sql): print(row) except sqlite3.Error as e: print(f"{e.sqlite_errorname}: {e}") From dee441a7b16e01eee1f5227fcd8b6c35cad92a45 Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Fri, 29 Jul 2022 23:23:01 +0200 Subject: [PATCH 11/33] Address review: repr iso. f-string Co-authored-by: Serhiy Storchaka --- Lib/sqlite3/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index b1022a92dea092..43a1fe082efeaf 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -70,7 +70,7 @@ def main(): if args.database == ":memory:": db_name = "a transient in-memory database" else: - db_name = f"'{args.database}'" + db_name = repr(args.database) banner = dedent(f""" sqlite3 shell, running on SQLite version {sqlite3.sqlite_version} From ec04ea3fa33107be88e61548755172c9ac6905da Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 29 Jul 2022 23:26:07 +0200 Subject: [PATCH 12/33] Address review: pass connection iso. path --- Lib/sqlite3/__main__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 43a1fe082efeaf..1d847a3916ec34 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -8,13 +8,10 @@ class SqliteInteractiveConsole(InteractiveConsole): - def __init__(self, database): + def __init__(self, connection): super().__init__() - self._con = sqlite3.connect(database, isolation_level=None) - self._cur = self._con.cursor() - - def __del__(self): - self._con.close() + self._con = connection + self._cur = connection.cursor() def runsql(self, sql): if sqlite3.complete_statement(sql): @@ -82,8 +79,10 @@ def main(): sys.ps1 = "sqlite> " sys.ps2 = " ... " - console = SqliteInteractiveConsole(args.database) + con = sqlite3.connect(args.database, isolation_level=None) + console = SqliteInteractiveConsole(con) console.interact(banner, exitmsg="") + con.close() main() From 9b792309bdee9178ad7aa6aee489e0025d1829a6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 29 Jul 2022 23:28:02 +0200 Subject: [PATCH 13/33] Address review: use argparse version trick --- Lib/sqlite3/__main__.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 1d847a3916ec34..bf7f65f9a00028 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -41,10 +41,6 @@ def runsource(self, source, filename="", symbol="single"): return keywords.get(source, self.runsql)(source) -def dump_version(): - print(f"SQLite version {sqlite3.sqlite_version}") - - def main(): parser = ArgumentParser( description="Python sqlite3 REPL", @@ -56,13 +52,11 @@ def main(): help="Database to open (defaults to ':memory:')", ) parser.add_argument( - "-v", "--version", - dest="show_version", action="store_true", default=False, + "-v", "--version", action="version", + version=f"SQLite version {sqlite3.sqlite_version}", help="Print underlying SQLite library version", ) args = parser.parse_args() - if args.show_version: - return dump_version() if args.database == ":memory:": db_name = "a transient in-memory database" From 74fb53d30542fb32732bc64ef4fddae65966db5d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 29 Jul 2022 23:37:49 +0200 Subject: [PATCH 14/33] Address review: use . prefix for commands, and sys.exit for quitting --- Doc/library/sqlite3.rst | 2 +- Lib/sqlite3/__main__.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 454c034fa68074..8f0531fd6252d1 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1728,7 +1728,7 @@ Command-line interface The ``sqlite3`` module can be invoked as a script in order to provide a simple SQLite shell. -Type ``quit`` or CTRL-D to exit the shell. +Type ``.quit`` or CTRL-D to exit the shell. .. program:: python -m sqlite3 diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index bf7f65f9a00028..3964ba98ebfad1 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -33,10 +33,9 @@ def printhelp(self, ignored): def runsource(self, source, filename="", symbol="single"): keywords = { - "version": lambda x: print(f"{sqlite3.sqlite_version}"), - "help": self.printhelp, - "quit()": self.runpy, - "quit": self.runpy, + ".version": lambda x: print(f"{sqlite3.sqlite_version}"), + ".help": self.printhelp, + ".quit": lambda x: sys.exit(0), } return keywords.get(source, self.runsql)(source) @@ -68,7 +67,7 @@ def main(): Connected to {db_name} Each command will be run using execute() on the cursor. - Type "help" for more information; type "quit" or CTRL-D to quit. + Type ".help" for more information; type ".quit" or CTRL-D to quit. """).strip() sys.ps1 = "sqlite> " sys.ps2 = " ... " From 1d62ccbd65c10363b7c192b8fcf3bf8b2fba7f5e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 29 Jul 2022 23:38:58 +0200 Subject: [PATCH 15/33] Address review: reduce indent level --- Lib/sqlite3/__main__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 3964ba98ebfad1..d7455b51384425 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -14,14 +14,14 @@ def __init__(self, connection): self._cur = connection.cursor() def runsql(self, sql): - if sqlite3.complete_statement(sql): - try: - for row in self._cur.execute(sql): - print(row) - except sqlite3.Error as e: - print(f"{e.sqlite_errorname}: {e}") - return False - return True + if not sqlite3.complete_statement(sql): + return True + try: + for row in self._cur.execute(sql): + print(row) + except sqlite3.Error as e: + print(f"{e.sqlite_errorname}: {e}") + return False def runpy(self, source, filename="", symbol="single"): code = self.compile(source, filename, symbol) From 340b896ce440c1970eec9fbdbbb6eaa2ea2465b2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 30 Jul 2022 21:56:43 +0200 Subject: [PATCH 16/33] Add filename and sql args a la sqlite3 cli --- Lib/sqlite3/__main__.py | 50 +++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index d7455b51384425..8e23c6667c4b65 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -6,9 +6,22 @@ from textwrap import dedent +def execute(c, sql): + try: + for row in c.execute(sql): + print(row) + except sqlite3.Error as e: + tp = type(e).__name__ + try: + print(f"{tp} ({e.sqlite_errorname}): {e}") + except: + print(f"{tp}: {e}") + return None + + class SqliteInteractiveConsole(InteractiveConsole): - def __init__(self, connection): + def __init__(self, connection, sql): super().__init__() self._con = connection self._cur = connection.cursor() @@ -16,11 +29,7 @@ def __init__(self, connection): def runsql(self, sql): if not sqlite3.complete_statement(sql): return True - try: - for row in self._cur.execute(sql): - print(row) - except sqlite3.Error as e: - print(f"{e.sqlite_errorname}: {e}") + execute(self._cur, sql) return False def runpy(self, source, filename="", symbol="single"): @@ -30,6 +39,7 @@ def runpy(self, source, filename="", symbol="single"): def printhelp(self, ignored): print("Enter SQL code and press enter.") + return None def runsource(self, source, filename="", symbol="single"): keywords = { @@ -46,9 +56,18 @@ def main(): prog="python -m sqlite3", ) parser.add_argument( - "-f", "--filename", - type=str, dest="database", action="store", default=":memory:", - help="Database to open (defaults to ':memory:')", + "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", @@ -57,10 +76,10 @@ def main(): ) args = parser.parse_args() - if args.database == ":memory:": + if args.filename == ":memory:": db_name = "a transient in-memory database" else: - db_name = repr(args.database) + db_name = repr(args.filename) banner = dedent(f""" sqlite3 shell, running on SQLite version {sqlite3.sqlite_version} @@ -72,9 +91,12 @@ def main(): sys.ps1 = "sqlite> " sys.ps2 = " ... " - con = sqlite3.connect(args.database, isolation_level=None) - console = SqliteInteractiveConsole(con) - console.interact(banner, exitmsg="") + con = sqlite3.connect(args.filename, isolation_level=None) + if args.sql: + execute(con, args.sql) + else: + console = SqliteInteractiveConsole(con) + console.interact(banner, exitmsg="") con.close() From d906361d6309886934cf234f8cbed8041293237f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 00:52:08 +0200 Subject: [PATCH 17/33] Fix constructor --- Lib/sqlite3/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 8e23c6667c4b65..9d73d1c37db09b 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -21,7 +21,7 @@ def execute(c, sql): class SqliteInteractiveConsole(InteractiveConsole): - def __init__(self, connection, sql): + def __init__(self, connection): super().__init__() self._con = connection self._cur = connection.cursor() From e728e179de26d4e0b88f1c322b1624f7323e30db Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 08:56:25 +0200 Subject: [PATCH 18/33] Address Serhiy's second round of review --- Doc/library/sqlite3.rst | 6 +++--- Lib/sqlite3/__main__.py | 21 +++++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index bc145710225bf3..6367b347ed7c57 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1752,10 +1752,10 @@ The ``sqlite3`` module can be invoked as a script in order to provide a simple SQLite shell. Type ``.quit`` or CTRL-D to exit the shell. -.. program:: python -m sqlite3 +.. program:: python -m sqlite3 [-h] [-v] [filename] [sql] -.. option:: -f, --filename - Database to open (defaults to ``':memory:'``). +.. option:: -h, --help + Print CLI help. .. option:: -v, --version Print underlying SQLite library version. diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 9d73d1c37db09b..36d61f2556b2e0 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -16,7 +16,6 @@ def execute(c, sql): print(f"{tp} ({e.sqlite_errorname}): {e}") except: print(f"{tp}: {e}") - return None class SqliteInteractiveConsole(InteractiveConsole): @@ -37,14 +36,10 @@ def runpy(self, source, filename="", symbol="single"): self.runcode(code) return False - def printhelp(self, ignored): - print("Enter SQL code and press enter.") - return None - def runsource(self, source, filename="", symbol="single"): keywords = { ".version": lambda x: print(f"{sqlite3.sqlite_version}"), - ".help": self.printhelp, + ".help": lambda x: print("Enter SQL code and press enter."), ".quit": lambda x: sys.exit(0), } return keywords.get(source, self.runsql)(source) @@ -92,12 +87,14 @@ def main(): sys.ps2 = " ... " con = sqlite3.connect(args.filename, isolation_level=None) - if args.sql: - execute(con, args.sql) - else: - console = SqliteInteractiveConsole(con) - console.interact(banner, exitmsg="") - con.close() + try: + if args.sql: + execute(con, args.sql) + else: + console = SqliteInteractiveConsole(con) + console.interact(banner, exitmsg="") + finally: + con.close() main() From e2e24b8550ecb4ec8bcd15dd1e46726f2ecf87a8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 09:01:01 +0200 Subject: [PATCH 19/33] Remove useless function and lambda params --- Lib/sqlite3/__main__.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 36d61f2556b2e0..6f2a3fa9cc8bf1 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -31,18 +31,16 @@ def runsql(self, sql): execute(self._cur, sql) return False - def runpy(self, source, filename="", symbol="single"): - code = self.compile(source, filename, symbol) - self.runcode(code) - return False - def runsource(self, source, filename="", symbol="single"): keywords = { - ".version": lambda x: print(f"{sqlite3.sqlite_version}"), - ".help": lambda x: print("Enter SQL code and press enter."), - ".quit": lambda x: sys.exit(0), + ".version": lambda: print(f"{sqlite3.sqlite_version}"), + ".help": lambda: print("Enter SQL code and press enter."), + ".quit": lambda: sys.exit(0), } - return keywords.get(source, self.runsql)(source) + if source in keywords: + keywords[source]() + else: + self.runsql(source) def main(): From 294acb9a69be69686a11fa2060a5c6ad64545a18 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 10:30:54 +0200 Subject: [PATCH 20/33] Move CLI docs to Reference --- Doc/library/sqlite3.rst | 36 ++++++++++++++++++------------------ Lib/sqlite3/__main__.py | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 6367b347ed7c57..01686da04e43dd 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1439,6 +1439,24 @@ and you can let the ``sqlite3`` module convert SQLite types to Python types via :ref:`converters `. +Command-line interface +^^^^^^^^^^^^^^^^^^^^^^ + +The ``sqlite3`` module can be invoked as a script +in order to provide a simple SQLite shell. +Type ``.quit`` or CTRL-D to exit the shell. + +.. program:: python -m sqlite3 [-h] [-v] [filename] [sql] + +.. option:: -h, --help + Print CLI help. + +.. option:: -v, --version + Print underlying SQLite library version. + +.. versionadded:: 3.12 + + .. _sqlite3-howtos: How-to guides @@ -1743,21 +1761,3 @@ regardless of the value of :attr:`~Connection.isolation_level`. .. _SQLite transaction behaviour: https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions - - -Command-line interface ----------------------- - -The ``sqlite3`` module can be invoked as a script -in order to provide a simple SQLite shell. -Type ``.quit`` or CTRL-D to exit the shell. - -.. program:: python -m sqlite3 [-h] [-v] [filename] [sql] - -.. option:: -h, --help - Print CLI help. - -.. option:: -v, --version - Print underlying SQLite library version. - -.. versionadded:: 3.12 diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 6f2a3fa9cc8bf1..096c634f01d6d5 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -45,7 +45,7 @@ def runsource(self, source, filename="", symbol="single"): def main(): parser = ArgumentParser( - description="Python sqlite3 REPL", + description="Python sqlite3 CLI", prog="python -m sqlite3", ) parser.add_argument( From 3cfff7fc3442b02eb18024b74b7801c078907263 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 10:48:04 +0200 Subject: [PATCH 21/33] Improve NEWS and add What's New --- Doc/library/sqlite3.rst | 2 ++ Doc/whatsnew/3.12.rst | 7 +++++++ .../Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 01686da04e43dd..4ecb408bbe9ea2 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1439,6 +1439,8 @@ and you can let the ``sqlite3`` module convert SQLite types to Python types via :ref:`converters `. +.. _sqlite3-cli: + Command-line interface ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 0c53bc0c1111d7..67396f8e02280b 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -112,6 +112,13 @@ os (Contributed by Kumar Aditya in :gh:`93312`.) +sqlite3 +------- + +* Add a :ref:`command-line interface `. + (Contributed by Erlend E. Aasland in :gh:`77617`.) + + Optimizations ============= diff --git a/Misc/NEWS.d/next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst b/Misc/NEWS.d/next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst index b685a737fcdf70..1cbaa7dfe15ec2 100644 --- a/Misc/NEWS.d/next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst +++ b/Misc/NEWS.d/next/Library/2022-07-20-00-23-58.gh-issue-77617.XGaqSQ.rst @@ -1 +1,2 @@ -Add interactive shell for :mod:`sqlite3`. Patch by Erlend Aasland. +Add :mod:`sqlite3` :ref:`command-line interface `. +Patch by Erlend Aasland. From 82c753cf77e6588cddb7404dbb5e9b47ca094df9 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 12:05:34 +0200 Subject: [PATCH 22/33] Partially address Kumar's review: use narrow exception --- Lib/sqlite3/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 096c634f01d6d5..fa74b5fc11a2f4 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -14,7 +14,7 @@ def execute(c, sql): tp = type(e).__name__ try: print(f"{tp} ({e.sqlite_errorname}): {e}") - except: + except AttributeError: print(f"{tp}: {e}") From 36454ab619d7527bd92ba5480412a8c5258f474a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 12:08:08 +0200 Subject: [PATCH 23/33] Partially address more of Kumar's review: add guard --- Lib/sqlite3/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index fa74b5fc11a2f4..5c01cd542c3abb 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -95,4 +95,6 @@ def main(): con.close() -main() +# This file can be invoked when the module is imported, hence the guard: +if __name__ == "__main__": + main() From 625e2e351e8811e8061244049f85c4a4162ed441 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 12:30:58 +0200 Subject: [PATCH 24/33] Use pattern matching --- Lib/sqlite3/__main__.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 5c01cd542c3abb..3f588774508aad 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -32,15 +32,16 @@ def runsql(self, sql): return False def runsource(self, source, filename="", symbol="single"): - keywords = { - ".version": lambda: print(f"{sqlite3.sqlite_version}"), - ".help": lambda: print("Enter SQL code and press enter."), - ".quit": lambda: sys.exit(0), - } - if source in keywords: - keywords[source]() - else: - self.runsql(source) + match source: + case ".version": + print(f"{sqlite3.sqlite_version}") + case ".help": + print("Enter SQL code and press enter.") + case ".quit": + sys.exit(0) + case _: + return self.runsql(source) + return False def main(): From f624873a3eaa0eeb8c2f70faf40b963de9c8656d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 22:08:09 +0200 Subject: [PATCH 25/33] Revert "Partially address more of Kumar's review: add guard" This reverts commit 36454ab619d7527bd92ba5480412a8c5258f474a. --- Lib/sqlite3/__main__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 3f588774508aad..84659be52f0b2d 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -96,6 +96,4 @@ def main(): con.close() -# This file can be invoked when the module is imported, hence the guard: -if __name__ == "__main__": - main() +main() From df75f5f6c994e05dab07f598684a91d39b746899 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 23:43:25 +0200 Subject: [PATCH 26/33] Print errors to stderr iso. stdout --- Lib/sqlite3/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 84659be52f0b2d..18be20c22d40ea 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -13,9 +13,9 @@ def execute(c, sql): except sqlite3.Error as e: tp = type(e).__name__ try: - print(f"{tp} ({e.sqlite_errorname}): {e}") + print(f"{tp} ({e.sqlite_errorname}): {e}", file=sys.stderr) except AttributeError: - print(f"{tp}: {e}") + print(f"{tp}: {e}", file=sys.stderr) class SqliteInteractiveConsole(InteractiveConsole): From 44c2571132a5e04f0ef48e5444a57833e1965b5f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 23:48:13 +0200 Subject: [PATCH 27/33] Non-zero exit for SQL errors passed from command line --- Lib/sqlite3/__main__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index 18be20c22d40ea..b416de209d7de7 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -6,7 +6,7 @@ from textwrap import dedent -def execute(c, sql): +def execute(c, sql, suppress_errors=True): try: for row in c.execute(sql): print(row) @@ -16,6 +16,8 @@ def execute(c, sql): print(f"{tp} ({e.sqlite_errorname}): {e}", file=sys.stderr) except AttributeError: print(f"{tp}: {e}", file=sys.stderr) + if not suppress_errors: + sys.exit(1) class SqliteInteractiveConsole(InteractiveConsole): @@ -88,7 +90,7 @@ def main(): con = sqlite3.connect(args.filename, isolation_level=None) try: if args.sql: - execute(con, args.sql) + execute(con, args.sql, suppress_errors=False) else: console = SqliteInteractiveConsole(con) console.interact(banner, exitmsg="") From d640458bf2b48069be51a82c62488b485183eefc Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 23:49:44 +0200 Subject: [PATCH 28/33] Add tests --- Lib/test/test_sqlite3/test_cli.py | 155 ++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 Lib/test/test_sqlite3/test_cli.py diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py new file mode 100644 index 00000000000000..44d7c00377cdba --- /dev/null +++ b/Lib/test/test_sqlite3/test_cli.py @@ -0,0 +1,155 @@ +"""sqlite3 CLI tests.""" + +import sqlite3 as sqlite +import subprocess +import sys +import unittest + +from test.support import SHORT_TIMEOUT, requires_subprocess +from test.support.os_helper import TESTFN, unlink + + +@requires_subprocess() +class CommandLineInterface(unittest.TestCase): + + def _do_test(self, *args, expect_success=True): + with subprocess.Popen( + [sys.executable, "-m", "sqlite3", *args], + encoding="utf-8", + bufsize=0, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) as proc: + proc.wait() + if expect_success == bool(proc.returncode): + self.fail("".join(proc.stderr)) + stdout = proc.stdout.read() + stderr = proc.stderr.read() + if expect_success: + self.assertEqual(stderr, "") + else: + self.assertEqual(stdout, "") + return stdout, stderr + + def expect_success(self, *args): + out, _ = self._do_test(*args) + return out + + def expect_failure(self, *args): + _, err = self._do_test(*args, expect_success=False) + return err + + def test_cli_help(self): + out = self.expect_success("-h") + self.assertIn("usage: python -m sqlite3", out) + + def test_cli_version(self): + out = self.expect_success("-v") + self.assertIn(sqlite.sqlite_version, out) + + def test_cli_execute_sql(self): + out = self.expect_success(":memory:", "select 1") + self.assertIn("(1,)", out) + + def test_cli_execute_too_much_sql(self): + stderr = self.expect_failure(":memory:", "select 1; select 2") + err = "ProgrammingError: You can only execute one statement at a time" + self.assertIn(err, stderr) + + def test_cli_execute_incomplete_sql(self): + stderr = self.expect_failure(":memory:", "sel") + self.assertIn("OperationalError (SQLITE_ERROR)", stderr) + + def test_cli_on_disk_db(self): + out = self.expect_success(TESTFN, "create table t(t)") + self.addCleanup(unlink, TESTFN) + self.assertEqual(out, "") + out = self.expect_success(TESTFN, "select count(t) from t") + self.assertIn("(0,)", out) + + +@requires_subprocess() +class InteractiveSession(unittest.TestCase): + TIMEOUT = SHORT_TIMEOUT / 10. + MEMORY_DB_MSG = "Connected to a transient in-memory database" + PS1 = "sqlite> " + PS2 = "... " + + def start_cli(self, *args): + return subprocess.Popen( + [sys.executable, "-m", "sqlite3", *args], + encoding="utf-8", + bufsize=0, + stdin=subprocess.PIPE, + # Note: the banner is printed to stderr, the prompt to stdout. + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + def expect_success(self, proc, err): + proc.wait() + if proc.returncode: + self.fail("".join(err)) + + def test_interact(self): + with self.start_cli() as proc: + out, err = proc.communicate(timeout=self.TIMEOUT) + self.assertIn(self.MEMORY_DB_MSG, err) + self.assertIn(self.PS1, out) + self.expect_success(proc, err) + + def test_interact_quit(self): + with self.start_cli() as proc: + out, err = proc.communicate(input=".quit", timeout=self.TIMEOUT) + self.assertIn(self.MEMORY_DB_MSG, err) + self.assertIn(self.PS1, out) + self.expect_success(proc, err) + + def test_interact_version(self): + with self.start_cli() as proc: + out, err = proc.communicate(input=".version", timeout=self.TIMEOUT) + self.assertIn(self.MEMORY_DB_MSG, err) + self.assertIn(sqlite.sqlite_version, out) + self.expect_success(proc, err) + + def test_interact_valid_sql(self): + with self.start_cli() as proc: + out, err = proc.communicate(input="select 1;", + timeout=self.TIMEOUT) + self.assertIn(self.MEMORY_DB_MSG, err) + self.assertIn("(1,)", out) + self.expect_success(proc, err) + + def test_interact_valid_multiline_sql(self): + with self.start_cli() as proc: + out, err = proc.communicate(input="select 1\n;", + timeout=self.TIMEOUT) + self.assertIn(self.MEMORY_DB_MSG, err) + self.assertIn(self.PS2, out) + self.assertIn("(1,)", out) + self.expect_success(proc, err) + + def test_interact_invalid_sql(self): + with self.start_cli() as proc: + out, err = proc.communicate(input="sel;", timeout=self.TIMEOUT) + self.assertIn(self.MEMORY_DB_MSG, err) + self.assertIn("OperationalError (SQLITE_ERROR)", err) + self.expect_success(proc, err) + + def test_interact_on_disk_file(self): + with self.start_cli(TESTFN) as proc: + out, err = proc.communicate(input="create table t(t);", + timeout=self.TIMEOUT) + self.assertIn(TESTFN, err) + self.assertIn(self.PS1, out) + self.expect_success(proc, err) + self.addCleanup(unlink, TESTFN) + with self.start_cli(TESTFN, "select count(t) from t") as proc: + out = proc.stdout.read() + err = proc.stderr.read() + self.assertIn("(0,)", out) + self.expect_success(proc, err) + + +if __name__ == "__main__": + unittest.main() From 0997004261babc0a7bc0178d9a96446c3866c28d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 31 Jul 2022 23:58:49 +0200 Subject: [PATCH 29/33] Simplify interactive tests --- Lib/test/test_sqlite3/test_cli.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py index 44d7c00377cdba..6cdade54b07d6b 100644 --- a/Lib/test/test_sqlite3/test_cli.py +++ b/Lib/test/test_sqlite3/test_cli.py @@ -86,31 +86,31 @@ def start_cli(self, *args): stderr=subprocess.PIPE, ) - def expect_success(self, proc, err): + def expect_success(self, proc): proc.wait() if proc.returncode: - self.fail("".join(err)) + self.fail("".join(proc.stderr)) def test_interact(self): with self.start_cli() as proc: out, err = proc.communicate(timeout=self.TIMEOUT) self.assertIn(self.MEMORY_DB_MSG, err) self.assertIn(self.PS1, out) - self.expect_success(proc, err) + self.expect_success(proc) def test_interact_quit(self): with self.start_cli() as proc: out, err = proc.communicate(input=".quit", timeout=self.TIMEOUT) self.assertIn(self.MEMORY_DB_MSG, err) self.assertIn(self.PS1, out) - self.expect_success(proc, err) + self.expect_success(proc) def test_interact_version(self): with self.start_cli() as proc: out, err = proc.communicate(input=".version", timeout=self.TIMEOUT) self.assertIn(self.MEMORY_DB_MSG, err) self.assertIn(sqlite.sqlite_version, out) - self.expect_success(proc, err) + self.expect_success(proc) def test_interact_valid_sql(self): with self.start_cli() as proc: @@ -118,7 +118,7 @@ def test_interact_valid_sql(self): timeout=self.TIMEOUT) self.assertIn(self.MEMORY_DB_MSG, err) self.assertIn("(1,)", out) - self.expect_success(proc, err) + self.expect_success(proc) def test_interact_valid_multiline_sql(self): with self.start_cli() as proc: @@ -127,14 +127,14 @@ def test_interact_valid_multiline_sql(self): self.assertIn(self.MEMORY_DB_MSG, err) self.assertIn(self.PS2, out) self.assertIn("(1,)", out) - self.expect_success(proc, err) + self.expect_success(proc) def test_interact_invalid_sql(self): with self.start_cli() as proc: out, err = proc.communicate(input="sel;", timeout=self.TIMEOUT) self.assertIn(self.MEMORY_DB_MSG, err) self.assertIn("OperationalError (SQLITE_ERROR)", err) - self.expect_success(proc, err) + self.expect_success(proc) def test_interact_on_disk_file(self): with self.start_cli(TESTFN) as proc: @@ -142,13 +142,13 @@ def test_interact_on_disk_file(self): timeout=self.TIMEOUT) self.assertIn(TESTFN, err) self.assertIn(self.PS1, out) - self.expect_success(proc, err) + self.expect_success(proc) self.addCleanup(unlink, TESTFN) with self.start_cli(TESTFN, "select count(t) from t") as proc: out = proc.stdout.read() err = proc.stderr.read() self.assertIn("(0,)", out) - self.expect_success(proc, err) + self.expect_success(proc) if __name__ == "__main__": From 7ba91e725ec51259ccf3095076692f1171e0b0e1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 1 Aug 2022 00:28:43 +0200 Subject: [PATCH 30/33] Use -Xutf8 --- Lib/test/test_sqlite3/test_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py index 6cdade54b07d6b..b96d0163b92d00 100644 --- a/Lib/test/test_sqlite3/test_cli.py +++ b/Lib/test/test_sqlite3/test_cli.py @@ -14,7 +14,7 @@ class CommandLineInterface(unittest.TestCase): def _do_test(self, *args, expect_success=True): with subprocess.Popen( - [sys.executable, "-m", "sqlite3", *args], + [sys.executable, "-m", "-Xutf8", "sqlite3", *args], encoding="utf-8", bufsize=0, stdout=subprocess.PIPE, @@ -77,7 +77,7 @@ class InteractiveSession(unittest.TestCase): def start_cli(self, *args): return subprocess.Popen( - [sys.executable, "-m", "sqlite3", *args], + [sys.executable, "-Xutf8", "-m", "sqlite3", *args], encoding="utf-8", bufsize=0, stdin=subprocess.PIPE, From b74e010cc538a43669fff73aba6f9d0c53117728 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 1 Aug 2022 00:47:34 +0200 Subject: [PATCH 31/33] Fix argument order --- Lib/test/test_sqlite3/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py index b96d0163b92d00..faad4e5bc33f94 100644 --- a/Lib/test/test_sqlite3/test_cli.py +++ b/Lib/test/test_sqlite3/test_cli.py @@ -14,7 +14,7 @@ class CommandLineInterface(unittest.TestCase): def _do_test(self, *args, expect_success=True): with subprocess.Popen( - [sys.executable, "-m", "-Xutf8", "sqlite3", *args], + [sys.executable, "-Xutf8", "-m", "sqlite3", *args], encoding="utf-8", bufsize=0, stdout=subprocess.PIPE, From 903b86712a6ffacac67bc1b9001b5d25d005edb8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 1 Aug 2022 09:08:56 +0200 Subject: [PATCH 32/33] Address review: move cleanups up --- Lib/test/test_sqlite3/test_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py index faad4e5bc33f94..d374f8ee4fc8d3 100644 --- a/Lib/test/test_sqlite3/test_cli.py +++ b/Lib/test/test_sqlite3/test_cli.py @@ -61,8 +61,8 @@ def test_cli_execute_incomplete_sql(self): self.assertIn("OperationalError (SQLITE_ERROR)", stderr) def test_cli_on_disk_db(self): - out = self.expect_success(TESTFN, "create table t(t)") self.addCleanup(unlink, TESTFN) + out = self.expect_success(TESTFN, "create table t(t)") self.assertEqual(out, "") out = self.expect_success(TESTFN, "select count(t) from t") self.assertIn("(0,)", out) @@ -137,13 +137,13 @@ def test_interact_invalid_sql(self): self.expect_success(proc) def test_interact_on_disk_file(self): + self.addCleanup(unlink, TESTFN) with self.start_cli(TESTFN) as proc: out, err = proc.communicate(input="create table t(t);", timeout=self.TIMEOUT) self.assertIn(TESTFN, err) self.assertIn(self.PS1, out) self.expect_success(proc) - self.addCleanup(unlink, TESTFN) with self.start_cli(TESTFN, "select count(t) from t") as proc: out = proc.stdout.read() err = proc.stderr.read() From 796ba16b6ab164794fc650ffa008aa4f093a23dd Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 1 Aug 2022 09:27:57 +0200 Subject: [PATCH 33/33] Last adjustment: inline runsql() --- Lib/sqlite3/__main__.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index b416de209d7de7..c62fad84e74bb8 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -27,12 +27,6 @@ def __init__(self, connection): self._con = connection self._cur = connection.cursor() - def runsql(self, sql): - if not sqlite3.complete_statement(sql): - return True - execute(self._cur, sql) - return False - def runsource(self, source, filename="", symbol="single"): match source: case ".version": @@ -42,7 +36,9 @@ def runsource(self, source, filename="", symbol="single"): case ".quit": sys.exit(0) case _: - return self.runsql(source) + if not sqlite3.complete_statement(source): + return True + execute(self._cur, source) return False pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy