Skip to content

Commit 20e1c4a

Browse files
committed
feat: Add transaction status support to prompt
1 parent 0d1672b commit 20e1c4a

File tree

4 files changed

+62
-0
lines changed

4 files changed

+62
-0
lines changed

pgcli/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,7 @@ def get_prompt(self, string):
12631263
string = string.replace("\\i", str(self.pgexecute.pid) or "(none)")
12641264
string = string.replace("\\#", "#" if self.pgexecute.superuser else ">")
12651265
string = string.replace("\\n", "\n")
1266+
string = string.replace("\\x", self.pgexecute.transaction_status())
12661267
return string
12671268

12681269
def get_last_query(self):

pgcli/pgclirc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ verbose_errors = False
184184
# \i - Postgres PID
185185
# \# - "@" sign if logged in as superuser, '>' in other case
186186
# \n - Newline
187+
# \x - Transaction status: '*' if in a valid transaction, '!' if in a failed transaction, empty otherwise
187188
# \dsn_alias - name of dsn connection string alias if -D option is used (empty otherwise)
188189
# \x1b[...m - insert ANSI escape sequence
189190
# eg: prompt = '\x1b[35m\u@\x1b[32m\h:\x1b[36m\d>'

pgcli/pgexecute.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,20 @@ def valid_transaction(self):
298298
status = self.conn.info.transaction_status
299299
return status == psycopg.pq.TransactionStatus.ACTIVE or status == psycopg.pq.TransactionStatus.INTRANS
300300

301+
def transaction_status(self):
302+
"""Return transaction status character for prompt.
303+
304+
Following psql convention:
305+
- '*' when in a valid transaction (ACTIVE or INTRANS)
306+
- '!' when in a failed transaction (INERROR)
307+
- '' when idle
308+
"""
309+
if self.failed_transaction():
310+
return "!"
311+
elif self.valid_transaction():
312+
return "*"
313+
return ""
314+
301315
def run(
302316
self,
303317
statement,

tests/test_main.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,52 @@ def test_duration_in_words(duration_in_seconds, words):
577577
assert duration_in_words(duration_in_seconds) == words
578578

579579

580+
@pytest.mark.parametrize(
581+
"transaction_status,expected",
582+
[
583+
("*", "*testuser"),
584+
("!", "!testuser"),
585+
("", "testuser"),
586+
],
587+
)
588+
def test_get_prompt_with_transaction_status(transaction_status, expected):
589+
"""Test that \\x prompt variable shows transaction status."""
590+
cli = PGCli()
591+
cli.pgexecute = mock.MagicMock()
592+
cli.pgexecute.user = "testuser"
593+
cli.pgexecute.dbname = "testdb"
594+
cli.pgexecute.host = "localhost"
595+
cli.pgexecute.short_host = "localhost"
596+
cli.pgexecute.port = 5432
597+
cli.pgexecute.pid = 12345
598+
cli.pgexecute.superuser = False
599+
600+
with mock.patch.object(
601+
cli.pgexecute, "transaction_status", return_value=transaction_status
602+
):
603+
result = cli.get_prompt("\\x\\u")
604+
assert result == expected
605+
606+
607+
def test_get_prompt_transaction_status_in_full_prompt():
608+
"""Test that \\x works in a full prompt format."""
609+
cli = PGCli()
610+
cli.pgexecute = mock.MagicMock()
611+
cli.pgexecute.user = "user"
612+
cli.pgexecute.dbname = "mydb"
613+
cli.pgexecute.host = "db.example.com"
614+
cli.pgexecute.short_host = "db.example.com"
615+
cli.pgexecute.port = 5432
616+
cli.pgexecute.pid = 12345
617+
cli.pgexecute.superuser = False
618+
619+
with mock.patch.object(
620+
cli.pgexecute, "transaction_status", return_value="*"
621+
):
622+
result = cli.get_prompt("\\x\\u@\\h:\\d> ")
623+
assert result == "*user@db.example.com:mydb> "
624+
625+
580626
@dbtest
581627
def test_notifications(executor):
582628
run(executor, "listen chan1")

0 commit comments

Comments
 (0)