Skip to content

Commit 123e00a

Browse files
authored
Re-run last query with bare \watch (#1283)
* Re-run last query with bare `\watch` * add test * clean up post test refactor * lint * rerun tests
1 parent c654957 commit 123e00a

File tree

3 files changed

+89
-14
lines changed

3 files changed

+89
-14
lines changed

changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Features:
55
---------
66

77
* Add `max_field_width` setting to config, to enable more control over field truncation ([related issue](https://github.com/dbcli/pgcli/issues/1250)).
8+
* Re-run last query via bare `\watch`. (Thanks: `Saif Hakim`_)
89

910
Bug fixes:
1011
----------

pgcli/main.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -772,31 +772,48 @@ def run_cli(self):
772772
click.secho(str(e), err=True, fg="red")
773773
continue
774774

775-
# Initialize default metaquery in case execution fails
776-
self.watch_command, timing = special.get_watch_command(text)
777-
if self.watch_command:
778-
while self.watch_command:
779-
try:
780-
query = self.execute_command(self.watch_command)
781-
click.echo(f"Waiting for {timing} seconds before repeating")
782-
sleep(timing)
783-
except KeyboardInterrupt:
784-
self.watch_command = None
785-
else:
786-
query = self.execute_command(text)
775+
self.handle_watch_command(text)
787776

788777
self.now = dt.datetime.today()
789778

790779
# Allow PGCompleter to learn user's preferred keywords, etc.
791780
with self._completer_lock:
792781
self.completer.extend_query_history(text)
793782

794-
self.query_history.append(query)
795-
796783
except (PgCliQuitError, EOFError):
797784
if not self.less_chatty:
798785
print("Goodbye!")
799786

787+
def handle_watch_command(self, text):
788+
# Initialize default metaquery in case execution fails
789+
self.watch_command, timing = special.get_watch_command(text)
790+
791+
# If we run \watch without a command, apply it to the last query run.
792+
if self.watch_command is not None and not self.watch_command.strip():
793+
try:
794+
self.watch_command = self.query_history[-1].query
795+
except IndexError:
796+
click.secho(
797+
"\\watch cannot be used with an empty query", err=True, fg="red"
798+
)
799+
self.watch_command = None
800+
801+
# If there's a command to \watch, run it in a loop.
802+
if self.watch_command:
803+
while self.watch_command:
804+
try:
805+
query = self.execute_command(self.watch_command)
806+
click.echo(f"Waiting for {timing} seconds before repeating")
807+
sleep(timing)
808+
except KeyboardInterrupt:
809+
self.watch_command = None
810+
811+
# Otherwise, execute it as a regular command.
812+
else:
813+
query = self.execute_command(text)
814+
815+
self.query_history.append(query)
816+
800817
def _build_cli(self, history):
801818
key_bindings = pgcli_bindings(self)
802819

tests/test_main.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,63 @@ def test_i_works(tmpdir, executor):
297297
run(executor, statement, pgspecial=cli.pgspecial)
298298

299299

300+
@dbtest
301+
def test_watch_works(executor):
302+
cli = PGCli(pgexecute=executor)
303+
304+
def run_with_watch(
305+
query, target_call_count=1, expected_output="", expected_timing=None
306+
):
307+
"""
308+
:param query: Input to the CLI
309+
:param target_call_count: Number of times the user lets the command run before Ctrl-C
310+
:param expected_output: Substring expected to be found for each executed query
311+
:param expected_timing: value `time.sleep` expected to be called with on every invocation
312+
"""
313+
with mock.patch.object(cli, "echo_via_pager") as mock_echo, mock.patch(
314+
"pgcli.main.sleep"
315+
) as mock_sleep:
316+
mock_sleep.side_effect = [None] * (target_call_count - 1) + [
317+
KeyboardInterrupt
318+
]
319+
cli.handle_watch_command(query)
320+
# Validate that sleep was called with the right timing
321+
for i in range(target_call_count - 1):
322+
assert mock_sleep.call_args_list[i][0][0] == expected_timing
323+
# Validate that the output of the query was expected
324+
assert mock_echo.call_count == target_call_count
325+
for i in range(target_call_count):
326+
assert expected_output in mock_echo.call_args_list[i][0][0]
327+
328+
# With no history, it errors.
329+
with mock.patch("pgcli.main.click.secho") as mock_secho:
330+
cli.handle_watch_command(r"\watch 2")
331+
mock_secho.assert_called()
332+
assert (
333+
r"\watch cannot be used with an empty query"
334+
in mock_secho.call_args_list[0][0][0]
335+
)
336+
337+
# Usage 1: Run a query and then re-run it with \watch across two prompts.
338+
run_with_watch("SELECT 111", expected_output="111")
339+
run_with_watch(
340+
"\\watch 10", target_call_count=2, expected_output="111", expected_timing=10
341+
)
342+
343+
# Usage 2: Run a query and \watch via the same prompt.
344+
run_with_watch(
345+
"SELECT 222; \\watch 4",
346+
target_call_count=3,
347+
expected_output="222",
348+
expected_timing=4,
349+
)
350+
351+
# Usage 3: Re-run the last watched command with a new timing
352+
run_with_watch(
353+
"\\watch 5", target_call_count=4, expected_output="222", expected_timing=5
354+
)
355+
356+
300357
def test_missing_rc_dir(tmpdir):
301358
rcfile = str(tmpdir.join("subdir").join("rcfile"))
302359

0 commit comments

Comments
 (0)