Skip to content

Commit f59ab57

Browse files
committed
adding -y / --yes parameter to avoid questions in pgcli w/tests
1 parent 1723391 commit f59ab57

File tree

3 files changed

+72
-2
lines changed

3 files changed

+72
-2
lines changed

changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ Features:
99
* Support dsn specific init-command in the config file
1010
* Add suggestion when setting the search_path
1111
* Allow per dsn_alias ssh tunnel selection
12+
* Add support for forcing destructive commands without confirmation.
13+
* Command line option `-y` or `--yes`.
14+
* Skips the destructive command confirmation prompt when enabled.
15+
* Useful for automated scripts and CI/CD pipelines.
1216

1317
Internal:
1418
---------

pgcli/main.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,14 @@ def __init__(
185185
warn=None,
186186
ssh_tunnel_url: Optional[str] = None,
187187
log_file: Optional[str] = None,
188+
force_destructive: bool = False,
188189
):
189190
self.force_passwd_prompt = force_passwd_prompt
190191
self.never_passwd_prompt = never_passwd_prompt
191192
self.pgexecute = pgexecute
192193
self.dsn_alias = None
193194
self.watch_command = None
195+
self.force_destructive = force_destructive
194196

195197
# Load config.
196198
c = self.config = get_config(pgclirc_file)
@@ -484,7 +486,10 @@ def execute_from_file(self, pattern, **_):
484486
):
485487
message = "Destructive statements must be run within a transaction. Command execution stopped."
486488
return [(None, None, None, message)]
487-
destroy = confirm_destructive_query(query, self.destructive_warning, self.dsn_alias)
489+
if self.force_destructive:
490+
destroy = True
491+
else:
492+
destroy = confirm_destructive_query(query, self.destructive_warning, self.dsn_alias)
488493
if destroy is False:
489494
message = "Wise choice. Command execution stopped."
490495
return [(None, None, None, message)]
@@ -792,7 +797,10 @@ def execute_command(self, text, handle_closed_connection=True):
792797
):
793798
click.secho("Destructive statements must be run within a transaction.")
794799
raise KeyboardInterrupt
795-
destroy = confirm_destructive_query(text, self.destructive_warning, self.dsn_alias)
800+
if self.force_destructive:
801+
destroy = True
802+
else:
803+
destroy = confirm_destructive_query(text, self.destructive_warning, self.dsn_alias)
796804
if destroy is False:
797805
click.secho("Wise choice!")
798806
raise KeyboardInterrupt
@@ -1426,6 +1434,14 @@ def echo_via_pager(self, text, color=None):
14261434
type=str,
14271435
help="SQL statement to execute after connecting.",
14281436
)
1437+
@click.option(
1438+
"-y",
1439+
"--yes",
1440+
"force_destructive",
1441+
is_flag=True,
1442+
default=False,
1443+
help="Force destructive commands without confirmation prompt.",
1444+
)
14291445
@click.argument("dbname", default=lambda: None, envvar="PGDATABASE", nargs=1)
14301446
@click.argument("username", default=lambda: None, envvar="PGUSER", nargs=1)
14311447
def cli(
@@ -1454,6 +1470,7 @@ def cli(
14541470
ssh_tunnel: str,
14551471
init_command: str,
14561472
log_file: str,
1473+
force_destructive: bool,
14571474
):
14581475
if version:
14591476
print("Version:", __version__)
@@ -1512,6 +1529,7 @@ def cli(
15121529
warn=warn,
15131530
ssh_tunnel_url=ssh_tunnel,
15141531
log_file=log_file,
1532+
force_destructive=force_destructive,
15151533
)
15161534

15171535
# Choose which ever one has a valid value.

tests/test_main.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,3 +595,51 @@ def test_notifications(executor):
595595
with mock.patch("pgcli.main.click.secho") as mock_secho:
596596
run(executor, "notify chan1, 'testing2'")
597597
mock_secho.assert_not_called()
598+
599+
600+
def test_force_destructive_flag():
601+
"""Test that PGCli can be initialized with force_destructive flag."""
602+
cli = PGCli(force_destructive=True)
603+
assert cli.force_destructive is True
604+
605+
cli = PGCli(force_destructive=False)
606+
assert cli.force_destructive is False
607+
608+
cli = PGCli()
609+
assert cli.force_destructive is False
610+
611+
612+
@dbtest
613+
def test_force_destructive_skips_confirmation(executor):
614+
"""Test that force_destructive=True skips confirmation for destructive commands."""
615+
cli = PGCli(pgexecute=executor, force_destructive=True)
616+
cli.destructive_warning = ["drop", "alter"]
617+
618+
# Mock confirm_destructive_query to ensure it's not called
619+
with mock.patch("pgcli.main.confirm_destructive_query") as mock_confirm:
620+
# Execute a destructive command
621+
result = cli.execute_command("ALTER TABLE test_table ADD COLUMN test_col TEXT;")
622+
623+
# Verify that confirm_destructive_query was NOT called
624+
mock_confirm.assert_not_called()
625+
626+
# Verify that the command was attempted (even if it fails due to missing table)
627+
assert result is not None
628+
629+
630+
@dbtest
631+
def test_without_force_destructive_calls_confirmation(executor):
632+
"""Test that without force_destructive, confirmation is called for destructive commands."""
633+
cli = PGCli(pgexecute=executor, force_destructive=False)
634+
cli.destructive_warning = ["drop", "alter"]
635+
636+
# Mock confirm_destructive_query to return True (user confirms)
637+
with mock.patch("pgcli.main.confirm_destructive_query", return_value=True) as mock_confirm:
638+
# Execute a destructive command
639+
result = cli.execute_command("ALTER TABLE test_table ADD COLUMN test_col TEXT;")
640+
641+
# Verify that confirm_destructive_query WAS called
642+
mock_confirm.assert_called_once()
643+
644+
# Verify that the command was attempted
645+
assert result is not None

0 commit comments

Comments
 (0)