From 0270ec58cfd8a291247c033bd7ee6096e5872e9a Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 20 Oct 2026 16:03:57 -0300 Subject: [PATCH] Let auto-expand work in -c/-f mode, where there is no prompt _evaluate_command() read the terminal width from self.prompt_app, which -c and -f never build, so --auto-vertical-output, \x auto or auto_expand in the config failed every statement with "'NoneType' object has no attribute 'output'". A user with auto_expand = True could not use -c or -f at all. Without a prompt the terminal is asked directly, and only when stdout is a terminal: psql's \x auto leaves a pipe or a file with the plain table whatever its width. --- changelog.rst | 9 +++++++++ pgcli/main.py | 15 ++++++++++++++- tests/test_main.py | 26 ++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/changelog.rst b/changelog.rst index 81fbefe78..7fd7b16d6 100644 --- a/changelog.rst +++ b/changelog.rst @@ -6,6 +6,15 @@ Features: * Add a ``\service`` prompt token for connections made using ``service=...`` or ``PGSERVICE``. For example, set ``prompt = '\service> '``. +Bug fixes: +---------- +* ``--auto-vertical-output``, ``\x auto`` and ``auto_expand`` in the config work + in ``-c``/``-f`` mode. The terminal width was read from the prompt, which + those modes never build, so every statement failed with ``'NoneType' object + has no attribute 'output'``. Without a prompt the terminal is asked directly, + and only when the output is one: a pipe or a file gets the plain table, as + with psql's ``\x auto``. + 4.7.1 (2026-09-20) ================== diff --git a/pgcli/main.py b/pgcli/main.py index 994f116e5..847426965 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -1314,6 +1314,19 @@ def _limit_output(self, cur): return new_cur, new_status + def _output_width(self): + """Columns available to auto-expand into, or None when it must not apply. + + In -c/-f mode no prompt is ever built, so the terminal is asked + directly. psql's \\x auto only expands when the output is a terminal: + a pipe or a file gets the plain table whatever its width. + """ + if self.prompt_app: + return self.prompt_app.output.get_size().columns + if sys.stdout.isatty(): + return shutil.get_terminal_size().columns + return None + def _evaluate_command(self, text): """Used to run a command entered by the user during CLI operation (Puts the E in REPL) @@ -1356,7 +1369,7 @@ def _evaluate_command(self, text): cur, status = self._limit_output(cur) if self.pgspecial.auto_expand or self.auto_expand: - max_width = self.prompt_app.output.get_size().columns + max_width = self._output_width() else: max_width = None diff --git a/tests/test_main.py b/tests/test_main.py index decae97b1..00597589e 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1087,3 +1087,29 @@ def test_connect_timeout_config_value_must_be_a_number(tmpdir): f.write("[main]\nconnect_timeout = soon\n") with pytest.raises(ValueError): PGCli(pgclirc_file=rc) + + +@dbtest +def test_auto_expand_works_without_a_prompt(executor): + # -c/-f mode never builds the prompt, and --auto-vertical-output (or + # \x auto, or auto_expand in the config) then failed every statement + # with "'NoneType' object has no attribute 'output'". Output that is not + # a terminal gets the plain table, as psql's \x auto does. + cli = PGCli(pgexecute=executor, auto_vertical_output=True) + assert cli.prompt_app is None + with mock.patch("pgcli.main.sys.stdout.isatty", return_value=False): + output, query = cli._evaluate_command("select 1 as a, 2 as b") + assert query.successful + assert not any("RECORD 1" in line for line in output) + + +@dbtest +def test_auto_expand_without_a_prompt_asks_the_terminal(executor): + cli = PGCli(pgexecute=executor, auto_vertical_output=True) + with ( + mock.patch("pgcli.main.sys.stdout.isatty", return_value=True), + mock.patch("pgcli.main.shutil.get_terminal_size", return_value=os.terminal_size((5, 24))), + ): + output, query = cli._evaluate_command("select 1 as a, 2 as b") + assert query.successful + assert any("RECORD 1" in line for line in output)