Skip to content

Commit 0ea80d0

Browse files
authored
fix: get tasks endpoint to a enum (#1578)
Currently get_tasks takes a string to filter on task_status, but it should be a enum.
1 parent 4c5b567 commit 0ea80d0

8 files changed

Lines changed: 57 additions & 84 deletions

File tree

docs/reference/openapi.yaml

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,14 @@ components:
333333
- type
334334
title: TaskResult
335335
type: object
336+
TaskStatusEnum:
337+
enum:
338+
- PENDING
339+
- COMPLETE
340+
- ERROR
341+
- RUNNING
342+
title: TaskStatusEnum
343+
type: string
336344
TasksListResponse:
337345
additionalProperties: false
338346
description: Diagnostic information on the tasks
@@ -441,7 +449,7 @@ info:
441449
name: Apache 2.0
442450
url: https://www.apache.org/licenses/LICENSE-2.0.html
443451
title: BlueAPI Control
444-
version: 1.4.0
452+
version: 1.4.1
445453
openapi: 3.1.0
446454
paths:
447455
/api/v1/devices:
@@ -599,15 +607,17 @@ paths:
599607
get:
600608
description: 'Retrieve tasks based on their status.
601609
602-
The status of a newly created task is ''unstarted''.'
610+
The status of a newly created task is PENDING.'
603611
operationId: get_tasks_api_v1_tasks_get
604612
parameters:
605613
- in: query
606614
name: task_status
607615
required: false
608616
schema:
617+
anyOf:
618+
- $ref: '#/components/schemas/TaskStatusEnum'
619+
- type: 'null'
609620
title: Task Status
610-
type: string
611621
responses:
612622
'200':
613623
content:
@@ -997,15 +1007,17 @@ paths:
9971007
deprecated: true
9981008
description: 'Retrieve tasks based on their status.
9991009
1000-
The status of a newly created task is ''unstarted''.'
1010+
The status of a newly created task is PENDING.'
10011011
operationId: get_tasks_tasks_get
10021012
parameters:
10031013
- in: query
10041014
name: task_status
10051015
required: false
10061016
schema:
1017+
anyOf:
1018+
- $ref: '#/components/schemas/TaskStatusEnum'
1019+
- type: 'null'
10071020
title: Task Status
1008-
type: string
10091021
responses:
10101022
'200':
10111023
content:

src/blueapi/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ class ApplicationConfig(BlueapiBaseModel):
322322
"""
323323

324324
#: API version to publish in OpenAPI schema
325-
REST_API_VERSION: ClassVar[str] = "1.4.0"
325+
REST_API_VERSION: ClassVar[str] = "1.4.1"
326326

327327
LICENSE_INFO: ClassVar[dict[str, str]] = {
328328
"name": "Apache 2.0",

src/blueapi/service/interface.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,6 @@ def remove_callback_when_task_finished(
233233
return task
234234

235235

236-
def get_tasks_by_status(status: TaskStatusEnum) -> list[TrackableTask]:
237-
"""Retrieve a list of tasks based on their status."""
238-
return worker().get_tasks_by_status(status)
239-
240-
241236
def get_active_task() -> TrackableTask | None:
242237
"""Task the worker is currently running"""
243238
return worker().get_active_task()
@@ -264,10 +259,10 @@ def cancel_active_task(failure: bool, reason: str | None) -> str:
264259
return worker().cancel_active_task(failure, reason)
265260

266261

267-
def get_tasks() -> list[TrackableTask]:
268-
"""Return a list of all tasks on the worker,
269-
any one of which can be triggered with begin_task"""
270-
return worker().get_tasks()
262+
def get_tasks(status: TaskStatusEnum | None = None) -> list[TrackableTask]:
263+
"""Retrieve a list of tasks based on their status.
264+
Return a list of all tasks on the worker if status is None"""
265+
return worker().get_tasks(status)
271266

272267

273268
def get_task_by_id(task_id: str) -> TrackableTask | None:

src/blueapi/service/main.py

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
2828
from opentelemetry.trace import get_tracer_provider
2929
from pydantic import ValidationError
30-
from pydantic.json_schema import SkipJsonSchema
3130
from starlette.responses import JSONResponse
3231
from super_state_machine.errors import TransitionError
3332

@@ -317,38 +316,18 @@ def delete_submitted_task(
317316
return TaskResponse(task_id=runner.run(interface.clear_task, task_id))
318317

319318

320-
@start_as_current_span(TRACER, "v")
321-
def validate_task_status(v: str) -> TaskStatusEnum:
322-
v_upper = v.upper()
323-
if v_upper not in TaskStatusEnum.__members__:
324-
raise ValueError("Invalid status query parameter")
325-
return TaskStatusEnum(v_upper)
326-
327-
328319
@secure_router_v1.get("/tasks", status_code=status.HTTP_200_OK, tags=[Tag.TASK])
329320
@secure_router.get("/tasks", status_code=status.HTTP_200_OK, tags=[Tag.TASK])
330321
@start_as_current_span(TRACER)
331322
def get_tasks(
332323
runner: Annotated[WorkerDispatcher, Depends(_runner)],
333-
task_status: str | SkipJsonSchema[None] = None,
324+
task_status: TaskStatusEnum | None = None,
334325
) -> TasksListResponse:
335326
"""
336327
Retrieve tasks based on their status.
337-
The status of a newly created task is 'unstarted'.
328+
The status of a newly created task is PENDING.
338329
"""
339-
if task_status:
340-
add_span_attributes({"status": task_status})
341-
try:
342-
desired_status = validate_task_status(task_status)
343-
except ValueError as e:
344-
raise HTTPException(
345-
status_code=status.HTTP_400_BAD_REQUEST,
346-
detail="Invalid status query parameter",
347-
) from e
348-
349-
tasks = runner.run(interface.get_tasks_by_status, desired_status)
350-
else:
351-
tasks = runner.run(interface.get_tasks)
330+
tasks = runner.run(interface.get_tasks, task_status)
352331
return TasksListResponse(tasks=tasks)
353332

354333

src/blueapi/worker/task_worker.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -194,16 +194,6 @@ def cancel_active_task(
194194
add_span_attributes({"Task stopped": reason or default_reason})
195195
return self._current.task_id
196196

197-
@start_as_current_span(TRACER)
198-
def get_tasks(self) -> list[TrackableTask]:
199-
"""
200-
Return a list of all tasks on the worker,
201-
any one of which can be triggered with begin_task.
202-
Returns:
203-
List[TrackableTask[T]]: List of task objects
204-
"""
205-
return list(self._pending_tasks.values()) + list(self._completed_tasks.values())
206-
207197
@start_as_current_span(TRACER, "task_id")
208198
def get_task_by_id(self, task_id: str) -> TrackableTask | None:
209199
"""
@@ -217,12 +207,13 @@ def get_task_by_id(self, task_id: str) -> TrackableTask | None:
217207
"""
218208
return self._pending_tasks.get(task_id, None) or self._completed_tasks[task_id]
219209

220-
@start_as_current_span(TRACER, "status")
221-
def get_tasks_by_status(self, status: TaskStatusEnum) -> list[TrackableTask]:
210+
@start_as_current_span(TRACER)
211+
def get_tasks(self, status: TaskStatusEnum | None = None) -> list[TrackableTask]:
222212
"""
223213
Retrieve a list of tasks based on their status.
224214
Args:
225-
status TaskStatusEnum: The status to filter tasks by.
215+
status Optional[TaskStatusEnum]: The status to filter tasks by.
216+
If status is None return all tasks.
226217
Returns:
227218
list[TrackableTask]: A list of tasks that match the given status.
228219
"""
@@ -236,6 +227,10 @@ def get_tasks_by_status(self, status: TaskStatusEnum) -> list[TrackableTask]:
236227
return [task for task in self._pending_tasks.values() if task.is_pending]
237228
elif status == TaskStatusEnum.COMPLETE:
238229
return list(self._completed_tasks.values())
230+
elif status is None:
231+
return list(self._pending_tasks.values()) + list(
232+
self._completed_tasks.values()
233+
)
239234
return []
240235

241236
@start_as_current_span(TRACER)

tests/unit_tests/service/test_interface.py

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -262,28 +262,32 @@ def test_subscribers_removed_when_task_not_found(
262262
worker.worker_events.unsubscribe.assert_called_once_with(remove_token)
263263

264264

265-
@patch("blueapi.service.interface.TaskWorker.get_tasks_by_status")
266-
def test_get_tasks_by_status(get_tasks_by_status_mock: MagicMock):
267-
pending_task1 = TrackableTask(task_id="0", task=Task(name="pending_task1"))
268-
pending_task2 = TrackableTask(task_id="1", task=Task(name="pending_task2"))
269-
running_task = TrackableTask(task_id="2", task=Task(name="running_task"))
265+
@patch("blueapi.service.interface.TaskWorker.get_tasks")
266+
def test_get_tasks(get_tasks_mock: MagicMock):
267+
running_task = [TrackableTask(task_id="2", task=Task(name="running_task"))]
268+
pending_task = [
269+
TrackableTask(task_id="0", task=Task(name="pending_task1")),
270+
TrackableTask(task_id="1", task=Task(name="pending_task2")),
271+
]
270272

271-
def mock_tasks_by_status(status: TaskStatusEnum) -> list[TrackableTask]:
273+
def mock_tasks(
274+
status: TaskStatusEnum | None = None,
275+
) -> list[TrackableTask]:
272276
if status == TaskStatusEnum.PENDING:
273-
return [pending_task1, pending_task2]
277+
return pending_task
274278
elif status == TaskStatusEnum.RUNNING:
275-
return [running_task]
276-
else:
279+
return running_task
280+
elif status == TaskStatusEnum.COMPLETE:
277281
return []
282+
else:
283+
return pending_task + running_task
278284

279-
get_tasks_by_status_mock.side_effect = mock_tasks_by_status
285+
get_tasks_mock.side_effect = mock_tasks
280286

281-
assert interface.get_tasks_by_status(TaskStatusEnum.PENDING) == [
282-
pending_task1,
283-
pending_task2,
284-
]
285-
assert interface.get_tasks_by_status(TaskStatusEnum.RUNNING) == [running_task]
286-
assert interface.get_tasks_by_status(TaskStatusEnum.COMPLETE) == []
287+
assert interface.get_tasks(TaskStatusEnum.PENDING) == pending_task
288+
assert interface.get_tasks(TaskStatusEnum.RUNNING) == running_task
289+
assert interface.get_tasks(TaskStatusEnum.COMPLETE) == []
290+
assert interface.get_tasks() == pending_task + running_task
287291

288292

289293
@patch("blueapi.service.interface.BlueskyContext.numtracker")
@@ -334,18 +338,6 @@ def test_cancel_active_task(cancel_active_task_mock: MagicMock):
334338
cancel_active_task_mock.assert_called_once_with(fail, reason)
335339

336340

337-
@patch("blueapi.service.interface.TaskWorker.get_tasks")
338-
def test_get_tasks(get_tasks_mock: MagicMock):
339-
tasks = [
340-
TrackableTask(task_id="0", task=Task(name="0")),
341-
TrackableTask(task_id="1", task=Task(name="1")),
342-
TrackableTask(task_id="2", task=Task(name="2")),
343-
]
344-
get_tasks_mock.return_value = tasks
345-
346-
assert interface.get_tasks() == tasks
347-
348-
349341
@pytest.mark.parametrize("tiled_enabled", [True, False])
350342
@patch("blueapi.service.interface.context")
351343
@patch("blueapi.service.interface.config")

tests/unit_tests/service/test_rest_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,9 @@ def test_get_tasks_by_status(mock_runner: Mock, client: TestClient) -> None:
411411
}
412412

413413

414-
def test_get_tasks_by_status_invalid(client: TestClient) -> None:
414+
def test_get_tasks_by_invalid_status(client: TestClient) -> None:
415415
response = client.get("/tasks", params={"task_status": "AN_INVALID_STATUS"})
416-
assert response.status_code == status.HTTP_400_BAD_REQUEST
416+
assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
417417

418418

419419
def test_delete_submitted_task(mock_runner: Mock, client: TestClient) -> None:

tests/unit_tests/worker/test_task_worker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ def callback(unused: Future[list[Any]], stream=stream, sub=sub):
628628
(TaskStatusEnum.COMPLETE, ["task3"]),
629629
],
630630
)
631-
def test_get_tasks_by_status(worker: TaskWorker, status, expected_task_ids):
631+
def test_get_tasks(worker: TaskWorker, status, expected_task_ids):
632632
worker._pending_tasks = {
633633
"task1": TrackableTask(
634634
task_id="task1",
@@ -658,7 +658,7 @@ def test_get_tasks_by_status(worker: TaskWorker, status, expected_task_ids):
658658
),
659659
}
660660

661-
result = worker.get_tasks_by_status(status)
661+
result = worker.get_tasks(status)
662662
result_ids = [task.task_id for task in result]
663663

664664
assert result_ids == expected_task_ids

0 commit comments

Comments
 (0)