Skip to content

Commit 2bb0128

Browse files
authored
New create project flow, payload updates and error handling #4 from R1M1N/main
New create project flow, payload updates and error handling
1 parent c71679a commit 2bb0128

File tree

4 files changed

+128
-127
lines changed

4 files changed

+128
-127
lines changed
-16 Bytes
Binary file not shown.
-3.87 KB
Binary file not shown.
-16 Bytes
Binary file not shown.

labellerr/client.py

Lines changed: 128 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
LOCAL_EXPORT_FORMAT=['json', 'coco_json', 'csv', 'png']
2323
LOCAL_EXPORT_STATUS=['review', 'r_assigned','client_review', 'cr_assigned','accepted']
2424

25-
## DATA TYPES: image, video, audio, document, text
25+
# DATA TYPES: image, video, audio, document, text
2626
DATA_TYPES=('image', 'video', 'audio', 'document', 'text')
2727
DATA_TYPE_FILE_EXT = {
2828
'image': ['.jpg','.jpeg', '.png', '.tiff'],
@@ -440,7 +440,7 @@ def get_all_project_per_client_id(self,client_id):
440440
'request_id': unique_id
441441
})
442442

443-
print(response.text)
443+
# print(response.text)
444444
return response.json()
445445
except Exception as e:
446446
logging.error(f"Failed to retrieve projects: {str(e)}")
@@ -888,163 +888,164 @@ def create_local_export(self,project_id,client_id,export_config):
888888

889889
def create_project(self, project_name, data_type, client_id, dataset_id, annotation_template_id, rotation_config, created_by=None):
890890
"""
891-
Creates a project.
892-
893-
:param project_name: The name of the project.
894-
:param data_type: The type of data.
895-
:param client_id: The ID of the client.
896-
:param created_by: The ID of the user who created the project.
897-
:param rotation_config: The rotation configuration for the project.
898-
:return: A dictionary containing the dataset ID, project ID, and project configuration.
891+
Creates a project with the given configuration.
899892
"""
900893
url = f"{constants.BASE_URL}/projects/create?client_id={client_id}"
894+
895+
901896
payload = json.dumps({
902897
"project_name": project_name,
903-
"dataset_id": dataset_id,
898+
"attached_datasets": [dataset_id],
904899
"data_type": data_type,
905900
"annotation_template_id": annotation_template_id,
906901
"rotations": rotation_config,
907902
"created_by": created_by
908903
})
904+
909905
headers = {
910906
'api_key': self.api_key,
911907
'api_secret': self.api_secret,
912908
'Origin': 'https://pro.labellerr.com',
913909
'Content-Type': 'application/json'
914910
}
911+
912+
# print(f"{payload}")
913+
915914
response = requests.post(url, headers=headers, data=payload)
916-
return response.json()
915+
response_data = response.json()
916+
917+
# print(f"{response_data}")
918+
919+
920+
if 'error' in response_data and response_data['error']:
921+
error_details = response_data['error']
922+
error_msg = f"Validation Error: {response_data.get('message', 'Unknown error')}"
923+
for error in error_details:
924+
error_msg += f"\n- Field '{error['field']}': {error['message']}"
925+
raise LabellerrError(error_msg)
926+
927+
return response_data
917928

918-
def initiate_create_project(self, payload):
919929

920-
# Creating an empty dataset by function call
930+
def initiate_create_project(self, payload):
921931
"""
922-
Creates an empty project.
923-
924-
:param payload: A dictionary containing the configuration for the project.
925-
:return: A dictionary containing the dataset ID, project ID, and project configuration.
932+
Orchestrates project creation by handling dataset creation, annotation guidelines,
933+
and final project setup.
926934
"""
927-
928935
try:
929-
result={}
930-
# validate all the parameters
931-
required_params = ['client_id', 'dataset_name', 'dataset_description', 'data_type', 'created_by', 'project_name','annotation_guide','autolabel']
936+
937+
required_params = ['client_id', 'dataset_name', 'dataset_description', 'data_type',
938+
'created_by', 'project_name', 'annotation_guide', 'autolabel']
932939
for param in required_params:
933940
if param not in payload:
934941
raise LabellerrError(f"Required parameter {param} is missing")
935-
if(param == 'client_id'):
936-
# it should be an instance of string
937-
if not isinstance(payload[param], str) or not payload[param].strip():
938-
raise LabellerrError(f"client_id must be a string")
939-
if(param=='annotation_guide'):
940-
annotation_guides=payload['annotation_guide']
941-
942-
# annotation_guides is an array and iterate
943-
for annotation_guide in annotation_guides:
944-
945-
if 'option_type' not in annotation_guide:
946-
raise LabellerrError(f"option_type is required in annotation_guide")
947-
else:
948-
if annotation_guide['option_type'] not in OPTION_TYPE_LIST:
949-
raise LabellerrError(f"option_type must be one of {OPTION_TYPE_LIST}")
950-
951-
952942

953-
if 'folder_to_upload' in payload or 'files_to_upload' in payload:
954-
if 'folder_to_upload' in payload:
955-
# make sure the folder path exist
956-
if not os.path.exists(payload['folder_to_upload']):
957-
raise LabellerrError(f"Folder {payload['folder_to_upload']} does not exist")
958-
if not os.path.isdir(payload['folder_to_upload']):
959-
raise LabellerrError(f"Folder {payload['folder_to_upload']} is not a directory")
960-
elif 'files_to_upload' in payload:
961-
# make sure the files exist in the array payload['files_to_upload']
962-
for file in payload['files_to_upload']:
963-
if not os.path.exists(file):
964-
raise LabellerrError(f"File {file} does not exist")
965-
if not os.path.isfile(file):
966-
raise LabellerrError(f"File {file} is not a file")
967-
968-
if 'rotation_config' in payload:
969-
self.validate_rotation_config(payload['rotation_config'])
970-
print("Rotation configuration validated . . .")
971-
else:
943+
944+
if param == 'client_id' and not isinstance(payload[param], str):
945+
raise LabellerrError("client_id must be a non-empty string")
946+
947+
if param == 'annotation_guide':
948+
for guide in payload['annotation_guide']:
949+
if 'option_type' not in guide:
950+
raise LabellerrError("option_type is required in annotation_guide")
951+
if guide['option_type'] not in OPTION_TYPE_LIST:
952+
raise LabellerrError(f"option_type must be one of {OPTION_TYPE_LIST}")
953+
954+
955+
if 'folder_to_upload' in payload and 'files_to_upload' in payload:
956+
raise LabellerrError("Cannot provide both files_to_upload and folder_to_upload")
957+
958+
if 'folder_to_upload' not in payload and 'files_to_upload' not in payload:
959+
raise LabellerrError("Either files_to_upload or folder_to_upload must be provided")
960+
961+
962+
if 'rotation_config' not in payload:
972963
payload['rotation_config'] = {
973-
'annotation_rotation_count':1,
974-
'review_rotation_count':1,
975-
'client_review_rotation_count':1
964+
'annotation_rotation_count': 1,
965+
'review_rotation_count': 1,
966+
'client_review_rotation_count': 1
976967
}
968+
self.validate_rotation_config(payload['rotation_config'])
969+
977970

978971
if payload['data_type'] not in DATA_TYPES:
979972
raise LabellerrError(f"Invalid data_type. Must be one of {DATA_TYPES}")
980-
981-
if 'files_to_upload' in payload and 'folder_to_upload' in payload:
982-
raise LabellerrError("Both files_to_upload and folder_to_upload cannot be provided at the same time.")
983-
elif 'files_to_upload' not in payload and 'folder_to_upload' not in payload:
984-
raise LabellerrError("Either files_to_upload or folder_to_upload must be provided.")
985-
else:
986-
if 'files_to_upload' in payload:
987-
if payload['files_to_upload'] is None and len(payload['files_to_upload'])==0:
988-
raise LabellerrError("files_to_upload must be a non-empty string.")
989-
elif 'folder_to_upload' in payload:
990-
if not isinstance(payload['folder_to_upload'], str) or not payload['folder_to_upload'].strip():
991-
raise LabellerrError("folder_to_upload must be a non-empty string.")
992-
993-
994-
try:
995-
print("creating dataset . . .")
996-
response = self.create_dataset({
997-
'client_id': payload['client_id'],
998-
'dataset_name': payload['project_name'],
999-
'data_type': payload['data_type'],
1000-
'dataset_description': payload['dataset_description'],
1001-
}, files_to_upload=payload.get('files_to_upload', None), folder_to_upload=payload.get('folder_to_upload', None))
1002-
dataset_id = response['dataset_id']
1003-
gd = lambda: self.get_dataset(payload['client_id'], dataset_id)
1004-
utils.poll(gd, lambda x: x['response']['status_code'] == 300, interval=5, timeout=300)
1005-
1006-
except KeyError as e:
1007-
# Preserve the original traceback while providing context
1008-
raise LabellerrError(f"Missing required field in payload: {str(e)}") from e
1009-
except Exception as e:
1010-
# Log the original exception with traceback but preserve it in the raised exception
1011-
logging.exception("Failed to create dataset during project creation")
1012-
raise LabellerrError(f"Failed to create dataset: {str(e)}") from e
1013-
1014-
try:
1015-
annotation_template_id = self.create_annotation_guideline(
1016-
payload['client_id'],
1017-
payload['annotation_guide'],
1018-
payload['project_name'],
1019-
payload['data_type']
1020-
)
1021-
except Exception as e:
1022-
# Log the original exception with traceback but preserve it in the raised exception
1023-
logging.exception("Failed to create annotation guideline during project creation")
1024-
raise LabellerrError(f"Failed to create annotation guideline: {str(e)}") from e
1025-
1026-
try:
1027-
return self.create_project(
1028-
payload['project_name'],
1029-
payload['data_type'],
1030-
payload['client_id'],
1031-
dataset_id,
1032-
annotation_template_id,
1033-
payload['rotation_config'],
1034-
created_by=payload['created_by']
1035-
)
1036-
except Exception as e:
1037-
# Log the original exception with traceback but preserve it in the raised exception
1038-
logging.exception("Failed to create project after dataset and annotation template creation")
1039-
raise LabellerrError(f"Failed to create project: {str(e)}") from e
1040-
1041-
except LabellerrError:
1042-
# Re-raise LabellerrError directly to preserve its context
973+
974+
print("Rotation configuration validated . . .")
975+
976+
977+
print("Creating dataset . . .")
978+
dataset_response = self.create_dataset({
979+
'client_id': payload['client_id'],
980+
'dataset_name': payload['dataset_name'],
981+
'data_type': payload['data_type'],
982+
'dataset_description': payload['dataset_description'],
983+
},
984+
files_to_upload=payload.get('files_to_upload'),
985+
folder_to_upload=payload.get('folder_to_upload'))
986+
987+
dataset_id = dataset_response['dataset_id']
988+
989+
990+
def dataset_ready():
991+
try:
992+
dataset_status = self.get_dataset(payload['client_id'], dataset_id)
993+
994+
if isinstance(dataset_status, dict):
995+
996+
if 'response' in dataset_status:
997+
return dataset_status['response'].get('status_code', 200) == 300
998+
else:
999+
1000+
return True
1001+
return False
1002+
except Exception as e:
1003+
print(f"Error checking dataset status: {e}")
1004+
return False
1005+
1006+
1007+
utils.poll(
1008+
function=dataset_ready,
1009+
condition=lambda x: x is True,
1010+
interval=5,
1011+
timeout=60
1012+
)
1013+
1014+
print("Dataset created and ready for use")
1015+
1016+
1017+
annotation_template_id = self.create_annotation_guideline(
1018+
payload['client_id'],
1019+
payload['annotation_guide'],
1020+
payload['project_name'],
1021+
payload['data_type']
1022+
)
1023+
print("Annotation guidelines created")
1024+
1025+
1026+
project_response = self.create_project(
1027+
project_name=payload['project_name'],
1028+
data_type=payload['data_type'],
1029+
client_id=payload['client_id'],
1030+
dataset_id=dataset_id,
1031+
annotation_template_id=annotation_template_id,
1032+
rotation_config=payload['rotation_config'],
1033+
created_by=payload['created_by']
1034+
)
1035+
1036+
1037+
return {
1038+
'status': 'success',
1039+
'message': 'Project created successfully',
1040+
'project_id': project_response
1041+
}
1042+
1043+
except LabellerrError as e:
1044+
logging.error(f"Project creation failed: {str(e)}")
10431045
raise
10441046
except Exception as e:
1045-
# For other exceptions, wrap with more context but preserve the original
1046-
logging.exception("Unexpected error in initiate_create_project")
1047-
raise LabellerrError(f"Failed to create project due to unexpected error: {str(e)}") from e
1047+
logging.exception("Unexpected error in project creation")
1048+
raise LabellerrError(f"Project creation failed: {str(e)}") from e
10481049

10491050
def upload_folder_files_to_dataset(self, data_config):
10501051
"""

0 commit comments

Comments
 (0)