From 9c9c3ce721046bb3bc4189866ff0d75658cf086e Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Mon, 19 Oct 2026 09:50:19 -0500 Subject: [PATCH] [PLT-4664] Add Project.clear_queue_reservations Clears every reservation in one task queue of a project and returns how many reservations the queue held, or None when that count could not be read. Destructive and irreversible: holders lose their rows and unsaved work on them. Only reservations are cleared; no labels are deleted and no data rows are enqueued. Raises LabelboxError when the clear fails. Requires the clearQueueReservations mutation to be public (intelligence, PLT-4664). Co-Authored-By: Claude Opus 5.5 --- libs/labelbox/src/labelbox/schema/project.py | 43 ++++++++++ ...t_unit_project_clear_queue_reservations.py | 85 +++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 libs/labelbox/tests/unit/test_unit_project_clear_queue_reservations.py diff --git a/libs/labelbox/src/labelbox/schema/project.py b/libs/labelbox/src/labelbox/schema/project.py index 04b365c7a..9007b1420 100644 --- a/libs/labelbox/src/labelbox/schema/project.py +++ b/libs/labelbox/src/labelbox/schema/project.py @@ -1543,6 +1543,49 @@ def extend_reservations(self, queue_type) -> int: res = self.client.execute(query_str, {id_param: self.uid}) return res["extendReservations"] + def clear_queue_reservations(self, task_queue_id: str) -> Optional[int]: + """Clears every reservation in one task queue of this project. + + Warning: + This is destructive and cannot be undone. Every labeler or + reviewer holding a data row in the queue loses it, and unsaved + work on those data rows can be lost. Only reservations are + cleared: no labels are deleted and no data rows are enqueued or + moved. The data rows go back to the queue for anyone to pick up. + + Requires the project permission to clear all reservations (the Admin + and Team Manager roles, or a custom role that grants it). Not + supported on GitHub-integrated projects with the reservation + lifecycle enabled. + + Args: + task_queue_id: ID of a task queue of this project, from + ``project.task_queues()``. + Returns: + The number of reservations the queue held when they were + cleared, or ``None`` if that count could not be read (the + reservations were still cleared). + Raises: + ResourceNotFoundError: If the task queue is not in this project. + LabelboxError: If the reservations could not be cleared. + """ + query_str = """mutation ClearQueueReservationsPyApi($input: ClearQueueReservationsInput!) { + clearQueueReservations(input: $input) { + success + clearedReservationCount + } + }""" + + result = self.client.execute( + query_str, + {"input": {"projectId": self.uid, "queueId": task_queue_id}}, + )["clearQueueReservations"] + if not result["success"]: + raise LabelboxError( + f"Reservations of task queue {task_queue_id} were not cleared" + ) + return result["clearedReservationCount"] + def bulk_assign_data_rows( self, user_id: str, diff --git a/libs/labelbox/tests/unit/test_unit_project_clear_queue_reservations.py b/libs/labelbox/tests/unit/test_unit_project_clear_queue_reservations.py new file mode 100644 index 000000000..c2967d995 --- /dev/null +++ b/libs/labelbox/tests/unit/test_unit_project_clear_queue_reservations.py @@ -0,0 +1,85 @@ +from unittest.mock import MagicMock + +import pytest +from lbox.exceptions import LabelboxError + +from labelbox.schema.project import Project + + +@pytest.fixture +def mock_client(): + return MagicMock() + + +@pytest.fixture +def project(mock_client): + return Project( + mock_client, + { + "id": "test_project_id", + "name": "test", + "createdAt": "2021-06-01T00:00:00.000Z", + "updatedAt": "2021-06-01T00:00:00.000Z", + "autoAuditNumberOfLabels": 1, + "autoAuditPercentage": 100, + "dataRowCount": 1, + "description": "test", + "editorTaskType": "MODEL_CHAT_EVALUATION", + "lastActivityTime": "2021-06-01T00:00:00.000Z", + "allowedMediaType": "IMAGE", + "setupComplete": "2021-06-01T00:00:00.000Z", + "modelSetupComplete": None, + "uploadType": "Auto", + "isBenchmarkEnabled": False, + "isConsensusEnabled": False, + }, + ) + + +def test_clear_queue_reservations_sends_mutation_and_returns_count( + project, mock_client +): + mock_client.execute.return_value = { + "clearQueueReservations": { + "success": True, + "clearedReservationCount": 12, + } + } + + result = project.clear_queue_reservations("queue_1") + + assert result == 12 + mock_client.execute.assert_called_once() + query_str, params = mock_client.execute.call_args[0] + assert "ClearQueueReservationsPyApi" in query_str + assert "clearedReservationCount" in query_str + assert params == { + "input": {"projectId": "test_project_id", "queueId": "queue_1"} + } + + +def test_clear_queue_reservations_returns_none_when_count_unknown( + project, mock_client +): + mock_client.execute.return_value = { + "clearQueueReservations": { + "success": True, + "clearedReservationCount": None, + } + } + + assert project.clear_queue_reservations("queue_1") is None + + +def test_clear_queue_reservations_raises_on_server_failure( + project, mock_client +): + mock_client.execute.return_value = { + "clearQueueReservations": { + "success": False, + "clearedReservationCount": None, + } + } + + with pytest.raises(LabelboxError, match="queue_1"): + project.clear_queue_reservations("queue_1")