1616)
1717from .project_api import get_project_and_folder_metadata
1818from .projects import (
19- get_project_default_image_quality_in_editor , _get_available_image_counts
19+ get_project_default_image_quality_in_editor , _get_available_image_counts ,
20+ get_project_metadata
2021)
22+ from .teams import get_team_metadata
2123from ..mixp .decorators import Trackable
22- from .utils import _get_upload_auth_token , _get_boto_session_by_credentials , upload_image_array_to_s3 , get_image_array_to_upload , __create_image , __copy_images , __move_images , get_project_folder_string
24+ from .utils import _unassign_images , _assign_images , _get_upload_auth_token , _get_boto_session_by_credentials , upload_image_array_to_s3 , \
25+ get_image_array_to_upload , __create_image , __copy_images , __move_images , get_project_folder_string
2326
2427logger = logging .getLogger ("superannotate-python-sdk" )
2528_api = API .get_instance ()
2629
2730
2831@Trackable
2932def upload_image_to_project (
30- project ,
31- img ,
32- image_name = None ,
33- annotation_status = "NotStarted" ,
34- from_s3_bucket = None ,
35- image_quality_in_editor = None
33+ project ,
34+ img ,
35+ image_name = None ,
36+ annotation_status = "NotStarted" ,
37+ from_s3_bucket = None ,
38+ image_quality_in_editor = None
3639):
3740 """Uploads image (io.BytesIO() or filepath to image) to project.
3841 Sets status of the uploaded image to set_status if it is not None.
@@ -134,8 +137,8 @@ def upload_image_to_project(
134137
135138
136139def _copy_images (
137- source_project , destination_project , image_names , include_annotations ,
138- copy_annotation_status , copy_pin
140+ source_project , destination_project , image_names , include_annotations ,
141+ copy_annotation_status , copy_pin
139142):
140143 NUM_TO_SEND = 500
141144 source_project , source_project_folder = source_project
@@ -164,7 +167,7 @@ def _copy_images(
164167 res ['completed' ] = []
165168 for start_index in range (0 , len (image_names ), NUM_TO_SEND ):
166169 json_req ["image_names" ] = image_names [start_index :start_index +
167- NUM_TO_SEND ]
170+ NUM_TO_SEND ]
168171 response = _api .send_request (
169172 req_type = 'POST' ,
170173 path = '/image/copy' ,
@@ -190,12 +193,12 @@ def _copy_images(
190193
191194@Trackable
192195def copy_images (
193- source_project ,
194- image_names ,
195- destination_project ,
196- include_annotations = True ,
197- copy_annotation_status = True ,
198- copy_pin = True
196+ source_project ,
197+ image_names ,
198+ destination_project ,
199+ include_annotations = True ,
200+ copy_annotation_status = True ,
201+ copy_pin = True
199202):
200203 """Copy images in bulk between folders in a project
201204
@@ -305,12 +308,12 @@ def delete_images(project, image_names):
305308
306309@Trackable
307310def move_images (
308- source_project ,
309- image_names ,
310- destination_project ,
311- include_annotations = True ,
312- copy_annotation_status = True ,
313- copy_pin = True ,
311+ source_project ,
312+ image_names ,
313+ destination_project ,
314+ include_annotations = True ,
315+ copy_annotation_status = True ,
316+ copy_pin = True ,
314317):
315318 """Move images in bulk between folders in a project
316319
@@ -370,12 +373,12 @@ def move_images(
370373
371374@Trackable
372375def copy_image (
373- source_project ,
374- image_name ,
375- destination_project ,
376- include_annotations = False ,
377- copy_annotation_status = False ,
378- copy_pin = False
376+ source_project ,
377+ image_name ,
378+ destination_project ,
379+ include_annotations = False ,
380+ copy_annotation_status = False ,
381+ copy_pin = False
379382):
380383 """Copy image to a project. The image's project is the same as destination
381384 project then the name will be changed to <image_name>_(<num>).<image_ext>,
@@ -415,10 +418,10 @@ def copy_image(
415418 else :
416419 for m in p .finditer (new_name ):
417420 if m .start () + len (m .group ()
418- ) + len (extension ) - 1 == len (new_name ):
421+ ) + len (extension ) - 1 == len (new_name ):
419422 num = int (m .group ()[2 :- 2 ])
420423 new_name = new_name [:m .start () +
421- 2 ] + str (num + 1 ) + ")" + extension
424+ 2 ] + str (num + 1 ) + ")" + extension
422425 break
423426 else :
424427 new_name = Path (new_name ).stem + "_(1)" + extension
@@ -438,9 +441,9 @@ def copy_image(
438441
439442
440443def _copy_annotations_and_metadata (
441- source_project , source_project_folder , image_name , destination_project ,
442- destination_project_folder , new_name , include_annotations ,
443- copy_annotation_status , copy_pin
444+ source_project , source_project_folder , image_name , destination_project ,
445+ destination_project_folder , new_name , include_annotations ,
446+ copy_annotation_status , copy_pin
444447):
445448 if include_annotations :
446449 annotations = get_image_annotations (
@@ -477,12 +480,12 @@ def _copy_annotations_and_metadata(
477480
478481@Trackable
479482def move_image (
480- source_project ,
481- image_name ,
482- destination_project ,
483- include_annotations = True ,
484- copy_annotation_status = True ,
485- copy_pin = True
483+ source_project ,
484+ image_name ,
485+ destination_project ,
486+ include_annotations = True ,
487+ copy_annotation_status = True ,
488+ copy_pin = True
486489):
487490 """Move image from source_project to destination_project. source_project
488491 and destination_project cannot be the same.
@@ -563,30 +566,126 @@ def assign_images(project, image_names, user):
563566 :param user: user email
564567 :type user: str
565568 """
566- logger .info ("Assign %s images to user %s" , len (image_names ), user )
567569 if len (image_names ) == 0 :
568570 return
569- project , project_folder = get_project_and_folder_metadata (project )
570- images = search_images ((project , project_folder ), return_metadata = True )
571- image_dict = {}
572- for image in images :
573- image_dict [image ["name" ]] = image ["id" ]
574571
575- image_ids = []
576- for image_name in image_names :
577- image_ids .append (image_dict [image_name ])
578- team_id , project_id = project ["team_id" ], project ["id" ]
579- params = {"team_id" : team_id , "project_id" : project_id }
580- if project_folder is not None :
581- params ['folder_id' ] = project_folder ['id' ]
582- json_req = {"user_id" : user , "image_ids" : image_ids }
572+ project , folder = get_project_and_folder_metadata (project )
573+ project_meta = get_project_metadata (project , include_contributors = True )
574+ verified_users = project_meta ["contributors" ]
575+ verified_users = [i ['user_id' ] for i in verified_users ]
576+ if user not in verified_users :
577+ logger .warn (
578+ f'Skipping { user } . { user } is not a verified contributor for the { project ["name" ]} '
579+ )
580+ return
581+
582+ folder_name = 'root'
583+ if folder :
584+ folder_name = folder ['name' ]
585+
586+ logs = _assign_images (folder_name = folder_name , image_names = image_names , user = user , project_id = project ['id' ],
587+ team_id = project ['team_id' ])
588+ for log in logs :
589+ logger .warn (log )
590+ logger .info ("Assign images to user %s" , user )
591+
592+ @Trackable
593+ def assign_folder (project , folder_name , users ):
594+ """Assigns folder to users. With SDK, the user can be
595+ assigned to a role in the project with the share_project function.
596+
597+ :param project: project name or metadata of the project
598+ :type project: str or dict
599+ :param folder_name: folder name to assign
600+ :type folder_name: str
601+ :param users: list of user emails
602+ :type user: list of str
603+ """
604+
605+ project_meta = get_project_metadata (project , include_contributors = True )
606+ verified_users = project_meta ["contributors" ]
607+ verified_users = [i ['user_id' ] for i in verified_users ]
608+ project_name = project_meta ['name' ]
609+ verified_users = set (users ).intersection (set (verified_users ))
610+ unverified_contributor = set (users ) - verified_users
611+
612+ for user in unverified_contributor :
613+ logger .warn (
614+ f'Skipping { user } from assignees. { user } is not a verified contributor for the { project_name } '
615+ )
616+
617+ if not verified_users :
618+ return
619+
620+ params = {
621+ "project_id" : project_meta ['id' ],
622+ "team_id" : project_meta ["team_id" ]
623+ }
624+ json_req = {
625+ "assign_user_ids" : list (verified_users ),
626+ "folder_name" : folder_name
627+ }
583628 response = _api .send_request (
584629 req_type = 'POST' ,
585- path = '/images/assign ' ,
630+ path = '/folder/editAssignment ' ,
586631 params = params ,
587632 json_req = json_req
588633 )
634+
589635 if not response .ok :
590636 raise SABaseException (
591- response .status_code , "Couldn't assign images " + response .text
637+ response .status_code , "Couldn't assign folder " + response .text
592638 )
639+ logger .info (f'Assigned { folder_name } to users: { list (verified_users )} ' )
640+
641+ @Trackable
642+ def unassign_folder (project , folder_name ):
643+ """Removes assignment of given folder for all assignees.
644+ With SDK, the user can be assigned to a role in the project
645+ with the share_project function.
646+
647+ :param project: project name or folder path (e.g., "project1/folder1")
648+ :type project: str
649+ :param folder_name: folder name to remove assignees
650+ :type folder_name: str
651+ """
652+
653+ project_meta = get_project_metadata (project )
654+ params = {
655+ "project_id" : project_meta ['id' ],
656+ "team_id" : project_meta ["team_id" ]
657+ }
658+ json_req = {"folder_name" : folder_name , "remove_user_ids" : ["all" ]}
659+ response = _api .send_request (
660+ req_type = 'POST' ,
661+ path = '/folder/editAssignment' ,
662+ params = params ,
663+ json_req = json_req
664+ )
665+
666+ if not response .ok :
667+ raise SABaseException (
668+ response .status_code , "Couldn't unassign folder " + response .text
669+ )
670+
671+ @Trackable
672+ def unassign_images (project , image_names ):
673+ """Removes assignment of given images for all assignees.With SDK,
674+ the user can be assigned to a role in the project with the share_project
675+ function.
676+
677+ :param project: project name or folder path (e.g., "project1/folder1")
678+ :type project: str
679+ :param image_names: list of image unassign
680+ :type image_names: list of str
681+ """
682+ if not image_names :
683+ return
684+ project , folder = get_project_and_folder_metadata (project )
685+
686+ folder_name = 'root'
687+ if folder :
688+ folder_name = folder ['name' ]
689+ logs = _unassign_images (folder_name = folder_name ,image_names = image_names ,project_id = project ['id' ],team_id = project ['team_id' ])
690+ for log in logs :
691+ logger .warn (log )
0 commit comments