From b959e115cffbe203bae9dbd4581f88e7b15f5c3d Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sat, 24 Jan 2026 20:02:17 +0530 Subject: [PATCH 1/7] feat: Add support for transaction status indicator in prompt --- pgcli/main.py | 1 + pgcli/pgclirc | 1 + pgcli/pgexecute.py | 13 +++++++++++++ tests/test_main.py | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/pgcli/main.py b/pgcli/main.py index de09cc1af..913228b33 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -1307,6 +1307,7 @@ def get_prompt(self, string): string = string.replace("\\i", str(self.pgexecute.pid) or "(none)") string = string.replace("\\#", "#" if self.pgexecute.superuser else ">") string = string.replace("\\n", "\n") + string = string.replace("\\T", self.pgexecute.transaction_indicator) return string def get_last_query(self): diff --git a/pgcli/pgclirc b/pgcli/pgclirc index cac738937..35ff41c5a 100644 --- a/pgcli/pgclirc +++ b/pgcli/pgclirc @@ -176,6 +176,7 @@ verbose_errors = False # \i - Postgres PID # \# - "@" sign if logged in as superuser, '>' in other case # \n - Newline +# \T - Transaction status: '*' if in a valid transaction, '!' if in a failed transaction, '?' if disconnected, empty otherwise # \dsn_alias - name of dsn connection string alias if -D option is used (empty otherwise) # \x1b[...m - insert ANSI escape sequence # eg: prompt = '\x1b[35m\u@\x1b[32m\h:\x1b[36m\d>' diff --git a/pgcli/pgexecute.py b/pgcli/pgexecute.py index 9918da330..2864c8645 100644 --- a/pgcli/pgexecute.py +++ b/pgcli/pgexecute.py @@ -298,6 +298,19 @@ def valid_transaction(self): status = self.conn.info.transaction_status return status == psycopg.pq.TransactionStatus.ACTIVE or status == psycopg.pq.TransactionStatus.INTRANS + def is_connection_closed(self): + return self.conn.info.transaction_status == psycopg.pq.TransactionStatus.UNKNOWN + + @property + def transaction_indicator(self): + if self.is_connection_closed(): + return "?" + if self.failed_transaction(): + return "!" + if self.valid_transaction(): + return "*" + return "" + def run( self, statement, diff --git a/tests/test_main.py b/tests/test_main.py index 5cf1d09f8..27e029346 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -577,6 +577,47 @@ def test_duration_in_words(duration_in_seconds, words): assert duration_in_words(duration_in_seconds) == words +@pytest.mark.parametrize( + "transaction_indicator,expected", + [ + ("*", "*testuser"), # valid transaction + ("!", "!testuser"), # failed transaction + ("?", "?testuser"), # connection closed + ("", "testuser"), # idle + ], +) +def test_get_prompt_with_transaction_status(transaction_indicator, expected): + cli = PGCli() + cli.pgexecute = mock.MagicMock() + cli.pgexecute.user = "testuser" + cli.pgexecute.dbname = "testdb" + cli.pgexecute.host = "localhost" + cli.pgexecute.short_host = "localhost" + cli.pgexecute.port = 5432 + cli.pgexecute.pid = 12345 + cli.pgexecute.superuser = False + type(cli.pgexecute).transaction_indicator = mock.PropertyMock(return_value=transaction_indicator) + + result = cli.get_prompt("\\T\\u") + assert result == expected + + +def test_get_prompt_transaction_status_in_full_prompt(): + cli = PGCli() + cli.pgexecute = mock.MagicMock() + cli.pgexecute.user = "user" + cli.pgexecute.dbname = "mydb" + cli.pgexecute.host = "db.example.com" + cli.pgexecute.short_host = "db.example.com" + cli.pgexecute.port = 5432 + cli.pgexecute.pid = 12345 + cli.pgexecute.superuser = False + type(cli.pgexecute).transaction_indicator = mock.PropertyMock(return_value="*") + + result = cli.get_prompt("\\T\\u@\\h:\\d> ") + assert result == "*user@db.example.com:mydb> " + + @dbtest def test_notifications(executor): run(executor, "listen chan1") From c98bfe76e13f94d0968fec3c16aca7c146b93ea8 Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sat, 24 Jan 2026 20:04:00 +0530 Subject: [PATCH 2/7] style: Organize comment --- tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index 27e029346..f4b9b9e37 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -583,7 +583,7 @@ def test_duration_in_words(duration_in_seconds, words): ("*", "*testuser"), # valid transaction ("!", "!testuser"), # failed transaction ("?", "?testuser"), # connection closed - ("", "testuser"), # idle + ("", "testuser"), # idle ], ) def test_get_prompt_with_transaction_status(transaction_indicator, expected): From 67f916f5c834564b6f7e9beadd357f82b0c67051 Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sat, 24 Jan 2026 20:29:35 +0530 Subject: [PATCH 3/7] docs: Add to authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 771de13f1..d10bc40e7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,6 +17,7 @@ Core Devs: Contributors: ------------- * Brett + * Devadathan M B * Étienne BERSAC (bersace) * Daniel Schwarz * inkn From f9e4323af8e13b6bbdc20eb1c1c0f2a7e913d358 Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sat, 24 Jan 2026 20:41:31 +0530 Subject: [PATCH 4/7] docs: update authors --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index d10bc40e7..2eb2ea70b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,7 +17,6 @@ Core Devs: Contributors: ------------- * Brett - * Devadathan M B * Étienne BERSAC (bersace) * Daniel Schwarz * inkn @@ -145,6 +144,7 @@ Contributors: * Jay Knight (jay-knight) * fbdb * Charbel Jacquin (charbeljc) + * Devadathan M B (devadathanmb) Creator: -------- From 06bb6645f0e59fc451001047a2657e4bdf139870 Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sat, 24 Jan 2026 21:16:47 +0530 Subject: [PATCH 5/7] test: simplify transaction_indicator mock setup in tests Use direct attribute assignment on MagicMock instead of type() to avoid polluting the global MagicMock class state. --- tests/test_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index f4b9b9e37..314779ca2 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -596,7 +596,7 @@ def test_get_prompt_with_transaction_status(transaction_indicator, expected): cli.pgexecute.port = 5432 cli.pgexecute.pid = 12345 cli.pgexecute.superuser = False - type(cli.pgexecute).transaction_indicator = mock.PropertyMock(return_value=transaction_indicator) + cli.pgexecute.transaction_indicator = transaction_indicator result = cli.get_prompt("\\T\\u") assert result == expected @@ -612,7 +612,7 @@ def test_get_prompt_transaction_status_in_full_prompt(): cli.pgexecute.port = 5432 cli.pgexecute.pid = 12345 cli.pgexecute.superuser = False - type(cli.pgexecute).transaction_indicator = mock.PropertyMock(return_value="*") + cli.pgexecute.transaction_indicator = "*" result = cli.get_prompt("\\T\\u@\\h:\\d> ") assert result == "*user@db.example.com:mydb> " From d72cc5bc77bd23cc815b967bad4b7bcbd8b01bed Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sat, 24 Jan 2026 22:54:37 +0530 Subject: [PATCH 6/7] docs: Add to changelog --- changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog.rst b/changelog.rst index 45ee4adfa..fdfcde538 100644 --- a/changelog.rst +++ b/changelog.rst @@ -1,3 +1,10 @@ +Upcoming (TBD) +============== + +Features: +--------- +* Add support for `\\T` prompt escape sequence to display transaction status (similar to psql's `%x`). + 4.4.0 (2025-12-24) ================== From 2be33de303357f5b99004a77b27b524b142e6880 Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sat, 31 Jan 2026 11:17:56 +0530 Subject: [PATCH 7/7] fix: styling issue --- tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index 314779ca2..52415a008 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -583,7 +583,7 @@ def test_duration_in_words(duration_in_seconds, words): ("*", "*testuser"), # valid transaction ("!", "!testuser"), # failed transaction ("?", "?testuser"), # connection closed - ("", "testuser"), # idle + ("", "testuser"), # idle ], ) def test_get_prompt_with_transaction_status(transaction_indicator, expected):