Skip to content

Commit bbda11a

Browse files
committed
Add a project method for creating annotation jobs
1 parent b9b4796 commit bbda11a

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

roboflow/core/project.py

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

tests/test_project.py

Lines changed: 84 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,86 @@ 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+
"name": job_name,
180+
"batch": batch_id,
181+
"num_images": num_images,
182+
"labelerEmail": labeler_email,
183+
"reviewerEmail": reviewer_email
184+
})
185+
]
186+
)
187+
188+
result = self.project.create_annotation_job(
189+
name=job_name,
190+
batch_id=batch_id,
191+
num_images=num_images,
192+
labeler_email=labeler_email,
193+
reviewer_email=reviewer_email
194+
)
195+
196+
self.assertEqual(result, expected_response)
197+
self.assertTrue(result["success"])
198+
self.assertEqual(result["job"]["id"], "job-123")
199+
self.assertEqual(result["job"]["name"], job_name)
200+
201+
def test_create_annotation_job_error(self):
202+
job_name = "Test Job"
203+
batch_id = "invalid-batch"
204+
num_images = 10
205+
labeler_email = "[email protected]"
206+
reviewer_email = "[email protected]"
207+
208+
error_response = {
209+
"error": "Batch not found"
210+
}
211+
212+
expected_url = f"{API_URL}/{WORKSPACE_NAME}/{PROJECT_NAME}/jobs?api_key={ROBOFLOW_API_KEY}"
213+
214+
responses.add(
215+
responses.POST,
216+
expected_url,
217+
json=error_response,
218+
status=404
219+
)
220+
221+
with self.assertRaises(RuntimeError) as context:
222+
self.project.create_annotation_job(
223+
name=job_name,
224+
batch_id=batch_id,
225+
num_images=num_images,
226+
labeler_email=labeler_email,
227+
reviewer_email=reviewer_email
228+
)
229+
230+
self.assertEqual(str(context.exception), "Batch not found")

0 commit comments

Comments
 (0)