Skip to content

Commit e16a41b

Browse files
committed
#5 adds pa schedule list command. by: Piotr
1 parent c49990b commit e16a41b

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed

cli/schedule.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import typer
44
from tabulate import tabulate
55

6-
from pythonanywhere.scripts_commons import get_logger, get_task_from_id
6+
from pythonanywhere.scripts_commons import get_logger, get_task_from_id, tabulate_formats
77
from pythonanywhere.snakesay import snakesay
88
from pythonanywhere.task import Task, TaskList
99

@@ -194,9 +194,41 @@ def get(
194194
logger.info(tabulate(table, tablefmt="simple"))
195195

196196

197+
def tablefmt_callback(value: str):
198+
if value not in tabulate_formats:
199+
raise typer.BadParameter(f"Table format has to be one of: {', '.join(tabulate_formats)}")
200+
return value
201+
202+
197203
@app.command("list")
198-
def list_():
199-
raise NotImplementedError
204+
def list_(
205+
tablefmt: str = typer.Option(
206+
"simple", "-f", "--format", help="Table format", callback=tablefmt_callback
207+
)
208+
):
209+
"""Get list of user's scheduled tasks as a table with columns:
210+
id, interval, at (hour:minute/minute past), status (enabled/disabled), command.
211+
212+
Note:
213+
This script provides an overview of all tasks. Once a task id is
214+
known and some specific data is required it's more convenient to get
215+
it using `pa schedule get` command instead of parsing the table.
216+
"""
217+
218+
logger = get_logger(set_info=True)
219+
220+
headers = "id", "interval", "at", "status", "command"
221+
attrs = "task_id", "interval", "printable_time", "enabled", "command"
222+
223+
def stringify_values(task, attr):
224+
value = getattr(task, attr)
225+
if attr == "enabled":
226+
value = "enabled" if value else "disabled"
227+
return value
228+
229+
table = [[stringify_values(task, attr) for attr in attrs] for task in TaskList().tasks]
230+
msg = tabulate(table, headers, tablefmt=tablefmt) if table else snakesay("No scheduled tasks")
231+
logger.info(msg)
200232

201233

202234
@app.command()

tests/test_cli_schedule.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,35 @@
55
from typer.testing import CliRunner
66

77
from cli.schedule import app, delete_app
8+
from pythonanywhere.scripts_commons import tabulate_formats
89

910
runner = CliRunner()
1011

1112

1213
@pytest.fixture
1314
def task_list(mocker):
15+
username = getpass.getuser()
16+
specs1 = {
17+
"can_enable": False,
18+
"command": "echo foo",
19+
"enabled": True,
20+
"expiry": None,
21+
"extend_url": f"/user/{username}/schedule/task/42/extend",
22+
"hour": 16,
23+
"task_id": 42,
24+
"interval": "daily",
25+
"logfile": "/user/{username}/files/var/log/tasklog-126708-daily-at-1600-echo_foo.log",
26+
"minute": 0,
27+
"printable_time": "16:00",
28+
"url": f"/api/v0/user/{username}/schedule/42",
29+
"user": username,
30+
}
31+
specs2 = {**specs1}
32+
specs2.update({"task_id": 43, "enabled": False})
1433
mock_task_list = mocker.patch("cli.schedule.TaskList")
15-
mock_task_list.return_value.tasks = [Mock(task_id=1), Mock(task_id=2)]
34+
mock_task_list.return_value.tasks = [Mock(**specs1), Mock(**specs2)]
1635
return mock_task_list
1736

18-
1937
@pytest.fixture
2038
def mock_confirm(mocker):
2139
return mocker.patch("cli.schedule.typer.confirm")
@@ -213,3 +231,45 @@ def test_logs_only_value_of_requested_task_spec(self, mocker, task_from_id):
213231
assert task_from_id.call_args == call(42)
214232
assert mock_logger.call_args == call(set_info=True)
215233
assert mock_logger.return_value.info.call_args == call("10:23")
234+
@pytest.mark.clischedulelist
235+
class TestList:
236+
def test_logs_table_with_correct_headers_and_values(self, mocker, task_list):
237+
mock_logger = mocker.patch("cli.schedule.get_logger")
238+
mock_tabulate = mocker.patch("cli.schedule.tabulate")
239+
240+
result = runner.invoke(app, ["list", "--format", "orgtbl"])
241+
242+
headers = "id", "interval", "at", "status", "command"
243+
attrs = "task_id", "interval", "printable_time", "enabled", "command"
244+
table = [[getattr(task, attr) for attr in attrs] for task in task_list.return_value.tasks]
245+
table = [
246+
["enabled" if spec == True else "disabled" if spec == False else spec for spec in row]
247+
for row in table
248+
]
249+
assert mock_logger.call_args == call(set_info=True)
250+
assert task_list.call_count == 1
251+
assert mock_tabulate.call_args == call(table, headers, tablefmt="orgtbl")
252+
assert mock_logger.return_value.info.call_args == call(mock_tabulate.return_value)
253+
254+
def test_snakesays_when_no_scheduled_tasks(self, mocker):
255+
mock_logger = mocker.patch("cli.schedule.get_logger").return_value
256+
mock_tabulate = mocker.patch("cli.schedule.tabulate")
257+
mock_snakesay = mocker.patch("cli.schedule.snakesay")
258+
mock_tasks = mocker.patch("cli.schedule.TaskList")
259+
mock_tasks.return_value.tasks = []
260+
261+
runner.invoke(app, ["list"])
262+
263+
assert mock_tabulate.call_count == 0
264+
assert mock_snakesay.call_args == call("No scheduled tasks")
265+
assert mock_logger.info.call_args == call(mock_snakesay.return_value)
266+
267+
def test_warns_when_wrong_format_provided(self, mocker, task_list):
268+
mock_tabulate = mocker.patch("cli.schedule.tabulate")
269+
wrong_format = "excel"
270+
271+
result = runner.invoke(app, ["list", "--format", "excel"])
272+
273+
assert mock_tabulate.call_count == 0
274+
assert wrong_format not in tabulate_formats
275+
assert "Table format has to be one of" in result.stdout

0 commit comments

Comments
 (0)