From 56ab82c77453c7a05abab4277d99a3d1681b34b3 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 15 Sep 2026 15:52:44 -0300 Subject: [PATCH] Add --no-timings and --no-status -t/--tuples-only already turns off the timing line and the status footer, but it also drops the headers, the title and the table formatting. Scripts that post-process pgcli output often want the table intact and only one of the two footers gone, and there was no way to ask for that. The two flags are independent of each other and of -t, which keeps working as the psql-compatible shorthand for both. 8 tests, 2 of which fail if the flags are wired to -t instead of standing on their own. --- changelog.rst | 5 +++ pgcli/main.py | 34 +++++++++++++-- tests/test_no_timings_no_status.py | 67 ++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 tests/test_no_timings_no_status.py diff --git a/changelog.rst b/changelog.rst index ee5d1fcf5..a008bcf0f 100644 --- a/changelog.rst +++ b/changelog.rst @@ -8,6 +8,11 @@ Features: * You can specify multiple times. * Runs one statement at a time, like `-f`, and can be combined with `-f`: both run, the same way psql does. +* Add ``--no-timings`` and ``--no-status`` to suppress the timing line and the + status footer independently. ``-t``/``--tuples-only`` already turns off both, + along with the headers and the title, but there was no way to keep the table + formatting and drop only one of the two, which scripts that post-process the + output often want. * Add support for forcing destructive commands without confirmation. * Command line option `-y` or `--yes`. * Skips the destructive command confirmation prompt when enabled. diff --git a/pgcli/main.py b/pgcli/main.py index 3ff820ce2..23f3a21dc 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -120,7 +120,7 @@ OutputSettings = namedtuple( "OutputSettings", "table_format dcmlfmt floatfmt column_date_formats missingval expanded max_width case_function style_output " - "max_field_width tuples_only", + "max_field_width tuples_only show_status", ) OutputSettings.__new__.__defaults__ = ( None, @@ -134,6 +134,7 @@ None, DEFAULT_MAX_FIELD_WIDTH, False, + True, ) @@ -224,6 +225,8 @@ def __init__( single_connection=False, less_chatty=None, tuples_only=None, + no_timings=False, + no_status=False, prompt=None, prompt_dsn=None, auto_vertical_output=False, @@ -290,8 +293,14 @@ def __init__( # alone here and switched to an unadorned one at output time, so \T # still reports (and can change) the configured format. self.tuples_only = bool(tuples_only) - if self.tuples_only: + # --no-timings and --no-status turn off one thing each, for scripts that + # want the rows plus one of the two. -t is the psql-compatible shorthand + # that turns off both, along with the headers and the title. + self.show_status = not no_status + if no_timings or self.tuples_only: self.pgspecial.timing_enabled = False + if self.tuples_only: + self.show_status = False self.syntax_style = c["main"]["syntax_style"] self.cli_style = c["colors"] self.wider_completion_menu = c["main"].as_bool("wider_completion_menu") @@ -1370,6 +1379,7 @@ def _evaluate_command(self, text): style_output=self.style_output, max_field_width=self.max_field_width, tuples_only=self.tuples_only, + show_status=self.show_status, ) # Hide query text for named queries in quiet mode @@ -1650,6 +1660,20 @@ def echo_via_pager(self, text, color=None): default=False, help="Print rows only: no column headers, no status footer and no timing, like psql.", ) +@click.option( + "--no-timings", + "no_timings", + is_flag=True, + default=False, + help="Do not print the timing line after each query.", +) +@click.option( + "--no-status", + "no_status", + is_flag=True, + default=False, + help="Do not print the status footer (SELECT 3, UPDATE 1, ...) after each query.", +) @click.option("--prompt", help='Prompt format (Default: "\\u@\\h:\\d> ").') @click.option( "--prompt-dsn", @@ -1737,6 +1761,8 @@ def cli( application_name, less_chatty, tuples_only, + no_timings: bool, + no_status: bool, prompt, prompt_dsn, list_databases, @@ -1809,6 +1835,8 @@ def cli( single_connection=single_connection, less_chatty=less_chatty, tuples_only=tuples_only, + no_timings=no_timings, + no_status=no_status, prompt=prompt, prompt_dsn=prompt_dsn, auto_vertical_output=auto_vertical_output, @@ -2229,7 +2257,7 @@ def format_status(cur, status): output = itertools.chain(output, formatted) # Likewise the status footer. - if status and not settings.tuples_only: + if status and settings.show_status and not settings.tuples_only: output = itertools.chain(output, [format_status(cur, status)]) return output diff --git a/tests/test_no_timings_no_status.py b/tests/test_no_timings_no_status.py new file mode 100644 index 000000000..dc87aecda --- /dev/null +++ b/tests/test_no_timings_no_status.py @@ -0,0 +1,67 @@ +from unittest.mock import patch + +from click.testing import CliRunner + +from pgcli.main import cli, format_output, OutputSettings, PGCli + + +def test_no_timings_flag_passed_to_pgcli(): + runner = CliRunner() + with patch.object(PGCli, "__init__", autospec=True, return_value=None) as mock_pgcli: + runner.invoke(cli, ["--no-timings", "mydb"]) + assert mock_pgcli.call_args[1]["no_timings"] is True + + +def test_no_status_flag_passed_to_pgcli(): + runner = CliRunner() + with patch.object(PGCli, "__init__", autospec=True, return_value=None) as mock_pgcli: + runner.invoke(cli, ["--no-status", "mydb"]) + assert mock_pgcli.call_args[1]["no_status"] is True + + +def test_both_default_to_false(): + runner = CliRunner() + with patch.object(PGCli, "__init__", autospec=True, return_value=None) as mock_pgcli: + runner.invoke(cli, ["mydb"]) + assert mock_pgcli.call_args[1]["no_timings"] is False + assert mock_pgcli.call_args[1]["no_status"] is False + + +def test_no_timings_turns_off_timing_only(): + """The point of having two flags: each one leaves the other alone.""" + cli_obj = PGCli(no_timings=True) + + assert cli_obj.pgspecial.timing_enabled is False + assert cli_obj.show_status is True + + +def test_no_status_turns_off_status_only(): + cli_obj = PGCli(no_status=True) + + assert cli_obj.show_status is False + assert cli_obj.pgspecial.timing_enabled is True + + +def test_tuples_only_still_turns_off_both(): + """-t stays the psql-compatible shorthand for both.""" + cli_obj = PGCli(tuples_only=True) + + assert cli_obj.show_status is False + assert cli_obj.pgspecial.timing_enabled is False + + +def test_no_status_suppresses_only_the_footer(): + settings = OutputSettings(table_format="psql", show_status=False) + output = "\n".join(format_output("Title", [(1,)], ["a"], "SELECT 1", settings)) + + assert "SELECT 1" not in output + # Rows, headers and title are untouched. + assert "Title" in output + assert "a" in output and "1" in output + + +def test_status_footer_is_printed_by_default(): + settings = OutputSettings(table_format="psql") + output = "\n".join(format_output("Title", [(1,)], ["a"], "SELECT 1", settings)) + + assert "SELECT 1" in output