(project_id:Union[int,str], entity_ids, annotation_map:Dict[str,Any],
        update_existing=False)
return annotation_objects, new_annotations, existing_annotations
Annotate the entities with the given <entity_ids> with the given
    annotations. These annotations are expected to come as dictionary of
    annotation name versus an object with at least the field 'user_id'
    annotator ID. If the 'creation_time' and/or 'edition_time' fields are
    available, they will be used for the respective columns. A listof all
    annotation class instances that have been used is returned. Annotation
    names can contain the counting pattern {nX} with X being a number. This
    will add an incrementing number starting from X for each entity.
Annotate the entities with the given <entity_ids> with the given
    annotations. These annotations are expected to come as dictionary of
    annotation name versus an object with at least the field 'user_id'
    annotator ID. If the 'creation_time' and/or 'edition_time' fields are
    available, they will be used for the respective columns. A listof all
    annotation class instances that have been used is returned. Annotation
    names can contain the counting pattern {nX} with X being a number. This
    will add an incrementing number starting from X for each entity.
Annotate the entities with the given <entity_ids > with the given annotations . These annotations are expected to come as dictionary of annotation name versus an object with at least the field user_id annotator ID . If the creation_time and / or edition_time fields are available they will be used for the respective columns . A listof all annotation class instances that have been used is returned . Annotation names can contain the counting pattern { nX } with X being a number . This will add an incrementing number starting from X for each entity .
def _annotate_entities(project_id:Union[int,str], entity_ids, annotation_map:Dict[str,Any],
        update_existing=False) -> Tuple[Dict,Set,Set]:
    """ Annotate the entities with the given <entity_ids> with the given
    annotations. These annotations are expected to come as dictionary of
    annotation name versus an object with at least the field 'user_id'
    annotator ID. If the 'creation_time' and/or 'edition_time' fields are
    available, they will be used for the respective columns. A listof all
    annotation class instances that have been used is returned. Annotation
    names can contain the counting pattern {nX} with X being a number. This
    will add an incrementing number starting from X for each entity.
    """
    new_annotations = set()
    existing_annotations = set()
    r = Relation.objects.get(project_id = project_id,
            relation_name = 'annotated_with')

    annotation_class = Class.objects.get(project_id = project_id,
                                         class_name = 'annotation')
    annotation_objects = {}
    # Create a regular expression to find allowed patterns. The first group is
    # the whole {nX} part, while the second group is X only.
    counting_pattern = re.compile(r"(\{n(\d+)\})")
    for annotation, meta in annotation_map.items():
        # Look for patterns, replace all {n} with {n1} to normalize
        annotation = annotation.replace("{n}", "{n1}")
        # Find all {nX} in the annotation name
        expanded_annotations = {}

        if counting_pattern.search(annotation):
            # Create annotation names based on the counting patterns found, for
            # each entitiy.
            for i, eid in enumerate(entity_ids):
                a = annotation
                while True:
                    # Find next match and cancel if there isn't any
                    m = counting_pattern.search(a)
                    if not m:
                        break
                    # Replace match
                    count = int(m.groups()[1]) + i
                    a = m.string[:m.start()] + str(count) + m.string[m.end():]
                # Remember this annotation for the current entity
                expanded_annotations[a] = [eid]
        else:
            # No matches, so use same annotation for all entities
            expanded_annotations = {annotation: entity_ids}

        # Make sure the annotation's class instance exists.
        for a, a_entity_ids in expanded_annotations.items():
            ci, created = ClassInstance.objects.get_or_create(
                    project_id=project_id, name=a,
                    class_column=annotation_class,
                    defaults={'user_id': meta['user_id']})

            if created:
                new_annotations.add(ci.id)

            newly_annotated = set()
            # Annotate each of the entities. Don't allow duplicates.
            for entity_id in a_entity_ids:
                new_cici_defaults = {
                    'class_instance_a_id': entity_id,
                    'user_id': meta['user_id']
                }
                for field in ('creation_time', 'edition_time'):
                    value = meta.get(field)
                    if value:
                        new_cici_defaults[field] = value

                cici, created = ClassInstanceClassInstance.objects.get_or_create(
                        project_id=project_id, relation=r,
                        class_instance_a__id=entity_id, class_instance_b=ci,
                        defaults=new_cici_defaults)
                if created:
                    newly_annotated.add(entity_id)
                else:
                    existing_annotations.add(ci.id)
                    if update_existing:
                        # Update creation time and edition_time, if requested
                        cici.update(**new_cici_defaults)

            # Remember which entities got newly annotated
            annotation_objects[ci] = newly_annotated

    return annotation_objects, new_annotations, existing_annotations
def _annotate_entities ( project_id : Union [ int , str ] , entity_ids , annotation_map : Dict [ str , Any ] , update_existing = False ) -> Tuple [ Dict , Set , Set ] : new_annotations = set ( ) existing_annotations = set ( ) r = Relation . objects . get ( project_id = project_id , relation_name = 'annotated_with' ) annotation_class = Class . objects . get ( project_id = project_id , class_name = 'annotation' ) annotation_objects = { } # Create a regular expression to find allowed patterns. The first group is # the whole {nX} part, while the second group is X only. counting_pattern = re . compile ( r"(\{n(\d+)\})" ) for annotation , meta in annotation_map . items ( ) : # Look for patterns, replace all {n} with {n1} to normalize annotation = annotation . replace ( "{n}" , "{n1}" ) # Find all {nX} in the annotation name expanded_annotations = { } if counting_pattern . search ( annotation ) : # Create annotation names based on the counting patterns found, for # each entitiy. for i , eid in enumerate ( entity_ids ) : a = annotation while True : # Find next match and cancel if there isn't any m = counting_pattern . search ( a ) if not m : break # Replace match count = int ( m . groups ( ) [ 1 ] ) + i a = m . string [ : m . start ( ) ] + str ( count ) + m . string [ m . end ( ) : ] # Remember this annotation for the current entity expanded_annotations [ a ] = [ eid ] else : # No matches, so use same annotation for all entities expanded_annotations = { annotation : entity_ids } # Make sure the annotation's class instance exists. for a , a_entity_ids in expanded_annotations . items ( ) : ci , created = ClassInstance . objects . get_or_create ( project_id = project_id , name = a , class_column = annotation_class , defaults = { 'user_id' : meta [ 'user_id' ] } ) if created : new_annotations . add ( ci . id ) newly_annotated = set ( ) # Annotate each of the entities. Don't allow duplicates. for entity_id in a_entity_ids : new_cici_defaults = { 'class_instance_a_id' : entity_id , 'user_id' : meta [ 'user_id' ] } for field in ( 'creation_time' , 'edition_time' ) : value = meta . get ( field ) if value : new_cici_defaults [ field ] = value cici , created = ClassInstanceClassInstance . objects . get_or_create ( project_id = project_id , relation = r , class_instance_a__id = entity_id , class_instance_b = ci , defaults = new_cici_defaults ) if created : newly_annotated . add ( entity_id ) else : existing_annotations . add ( ci . id ) if update_existing : # Update creation time and edition_time, if requested cici . update ( * * new_cici_defaults ) # Remember which entities got newly annotated annotation_objects [ ci ] = newly_annotated return annotation_objects , new_annotations , existing_annotations