Skip to content

Commit 2d27b1a

Browse files
authored
Merge pull request #363 from roboflow/sb/add-create-annotation-job
Add a project method for creating annotation jobs
2 parents b9b4796 + f73f029 commit 2d27b1a

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

roboflow/core/project.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,3 +812,57 @@ def image(self, image_id: str) -> Dict:
812812
image_details = data["image"]
813813

814814
return image_details
815+
816+
def create_annotation_job(
817+
self, name: str, batch_id: str, num_images: int, labeler_email: str, reviewer_email: str
818+
) -> Dict:
819+
"""
820+
Create a new annotation job in the project.
821+
822+
Args:
823+
name (str): The name of the annotation job
824+
batch_id (str): The ID of the batch that contains the images to annotate
825+
num_images (int): The number of images to include in the job
826+
labeler_email (str): The email of the user who will label the images
827+
reviewer_email (str): The email of the user who will review the annotations
828+
829+
Returns:
830+
Dict: A dictionary containing the created job details
831+
832+
Example:
833+
>>> import roboflow
834+
835+
>>> rf = roboflow.Roboflow(api_key="YOUR_API_KEY")
836+
837+
>>> project = rf.workspace().project("PROJECT_ID")
838+
839+
>>> job = project.create_annotation_job(
840+
... name="Job created by API",
841+
... batch_id="batch123",
842+
... num_images=10,
843+
... labeler_email="[email protected]",
844+
... reviewer_email="[email protected]"
845+
... )
846+
"""
847+
url = f"{API_URL}/{self.__workspace}/{self.__project_name}/jobs?api_key={self.__api_key}"
848+
849+
payload = {
850+
"name": name,
851+
"batch": batch_id,
852+
"num_images": num_images,
853+
"labelerEmail": labeler_email,
854+
"reviewerEmail": reviewer_email,
855+
}
856+
857+
response = requests.post(url, headers={"Content-Type": "application/json"}, json=payload)
858+
859+
if response.status_code != 200:
860+
try:
861+
error_data = response.json()
862+
if "error" in error_data:
863+
raise RuntimeError(error_data["error"])
864+
raise RuntimeError(response.text)
865+
except ValueError:
866+
raise RuntimeError(f"Failed to create annotation job: {response.text}")
867+
868+
return response.json()

tests/test_project.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import requests
22
import responses
3+
from responses.matchers import json_params_matcher
34

45
from roboflow import API_URL
56
from roboflow.adapters.rfapi import AnnotationSaveError, ImageUploadError
@@ -144,3 +145,81 @@ def test_image_invalid_json_response(self):
144145
self.project.image(image_id)
145146

146147
self.assertIn("Expecting value", str(context.exception))
148+
149+
def test_create_annotation_job_success(self):
150+
job_name = "Test Job"
151+
batch_id = "test-batch-123"
152+
num_images = 10
153+
labeler_email = "[email protected]"
154+
reviewer_email = "[email protected]"
155+
156+
expected_response = {
157+
"success": True,
158+
"job": {
159+
"id": "job-123",
160+
"name": job_name,
161+
"batch": batch_id,
162+
"num_images": num_images,
163+
"labeler": labeler_email,
164+
"reviewer": reviewer_email,
165+
"status": "created",
166+
"created": 1616161616,
167+
},
168+
}
169+
170+
expected_url = f"{API_URL}/{WORKSPACE_NAME}/{PROJECT_NAME}/jobs?api_key={ROBOFLOW_API_KEY}"
171+
172+
responses.add(
173+
responses.POST,
174+
expected_url,
175+
json=expected_response,
176+
status=200,
177+
match=[
178+
json_params_matcher(
179+
{
180+
"name": job_name,
181+
"batch": batch_id,
182+
"num_images": num_images,
183+
"labelerEmail": labeler_email,
184+
"reviewerEmail": reviewer_email,
185+
}
186+
)
187+
],
188+
)
189+
190+
result = self.project.create_annotation_job(
191+
name=job_name,
192+
batch_id=batch_id,
193+
num_images=num_images,
194+
labeler_email=labeler_email,
195+
reviewer_email=reviewer_email,
196+
)
197+
198+
self.assertEqual(result, expected_response)
199+
self.assertTrue(result["success"])
200+
self.assertEqual(result["job"]["id"], "job-123")
201+
self.assertEqual(result["job"]["name"], job_name)
202+
203+
def test_create_annotation_job_error(self):
204+
job_name = "Test Job"
205+
batch_id = "invalid-batch"
206+
num_images = 10
207+
labeler_email = "[email protected]"
208+
reviewer_email = "[email protected]"
209+
210+
error_response = {"error": "Batch not found"}
211+
212+
expected_url = f"{API_URL}/{WORKSPACE_NAME}/{PROJECT_NAME}/jobs?api_key={ROBOFLOW_API_KEY}"
213+
214+
responses.add(responses.POST, expected_url, json=error_response, status=404)
215+
216+
with self.assertRaises(RuntimeError) as context:
217+
self.project.create_annotation_job(
218+
name=job_name,
219+
batch_id=batch_id,
220+
num_images=num_images,
221+
labeler_email=labeler_email,
222+
reviewer_email=reviewer_email,
223+
)
224+
225+
self.assertEqual(str(context.exception), "Batch not found")

0 commit comments

Comments
 (0)