From 0e1dcd69feda0ae80904827c78d2296e233e01c8 Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 14 Jun 2024 16:05:20 +0100 Subject: [PATCH 01/12] fix: RND-98: correct response schema for tasks.get() --- label_studio/tests/sdk/test_tasks.py | 30 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/label_studio/tests/sdk/test_tasks.py b/label_studio/tests/sdk/test_tasks.py index bd22f3c3bda..667b2cd96a2 100644 --- a/label_studio/tests/sdk/test_tasks.py +++ b/label_studio/tests/sdk/test_tasks.py @@ -52,17 +52,18 @@ def test_delete_multi_tasks(django_live_url, business_client): ls.actions.create(project=p.id, id='delete_tasks', selected_items={'all': False, 'included': tasks_ids_to_delete}) assert len([task for task in ls.tasks.list(project=p.id)]) == 5 - ls.actions.create(project=p.id, id='delete_tasks', selected_items={'all': True, 'excluded': [tasks[5].id]}) - # another way of calling delete action - # ls.actions.create(request_options={ - # 'additional_query_parameters': { - # 'project': p.id, - # 'id': 'delete_tasks' - # }, - # 'additional_body_parameters': { - # 'selectedItems': {"all": True, "excluded": [tasks[5].id]}, - # } - # }) + # another way of calling delete action instead of + # ls.actions.create(project=p.id, id='delete_tasks', selected_items={'all': True, 'excluded': [tasks[5].id]}) + import json + ls.actions.create(request_options={ + 'additional_query_parameters': { + 'project': p.id, + 'id': 'delete_tasks' + }, + 'additional_body_parameters': { + 'selectedItems': json.dumps({"all": True, "excluded": [tasks[5].id]}), + } + }) remaining_tasks = [task for task in ls.tasks.list(project=p.id)] assert len(remaining_tasks) == 1 @@ -94,7 +95,12 @@ def test_export_tasks(django_live_url, business_client): } ls.annotations.create(id=task_id, **annotation_data) - # by default, only tasks with annotations are exported + # export a singleton task + single_task = ls.tasks.get(id=task_id) + assert single_task.data['my_text'] == 'Test task 7' + assert single_task.total_annotations == 1 + assert single_task.updated_by == [{'user_id': business_client.user.id}] + exported_tasks = [task for task in ls.tasks.list(project=p.id, fields='all') if task.annotations] assert len(exported_tasks) == 1 assert exported_tasks[0].data['my_text'] == 'Test task 7' From da492a1fc5c16c27693655e3c1ceaa0a607b7389 Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 18 Jun 2024 16:48:25 +0100 Subject: [PATCH 02/12] Modify task serializer with updated_by, file_upload of the correct output type --- label_studio/tasks/openapi_schema.py | 4 ++-- label_studio/tasks/serializers.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/label_studio/tasks/openapi_schema.py b/label_studio/tasks/openapi_schema.py index b6c12e6c3b2..abd807c1e39 100644 --- a/label_studio/tasks/openapi_schema.py +++ b/label_studio/tasks/openapi_schema.py @@ -34,8 +34,8 @@ 'comment_count': 0, 'unresolved_comment_count': 0, 'last_comment_updated_at': '2024-01-15T09:30:00Z', - 'updated_by': 1, - 'file_upload': 1, + 'updated_by': [{'user_id': 1}], + 'file_upload': '42d46c4c-my-pic.jpeg', 'comment_authors': [1], } diff --git a/label_studio/tasks/serializers.py b/label_studio/tasks/serializers.py index 6fc612ce5a5..6685110ea5d 100644 --- a/label_studio/tasks/serializers.py +++ b/label_studio/tasks/serializers.py @@ -157,8 +157,23 @@ class Meta: fields = '__all__' +class TaskUpdatedBySerializerField(serializers.JSONField): + class Meta: + swagger_schema_fields = { + 'type': openapi.TYPE_ARRAY, + 'title': 'List of users', + 'description': 'List of users who updated the task', + 'items': { + 'type': openapi.TYPE_OBJECT, + 'title': 'User data', + }, + } + + class BaseTaskSerializer(FlexFieldsModelSerializer): """Task Serializer with project scheme configs validation""" + updated_by = TaskUpdatedBySerializerField(required=False) + file_upload = serializers.CharField(required=False) def project(self, task=None): """Take the project from context""" From 15019e19378fb7008ffe864730d74bfe018cb0bd Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 18 Jun 2024 22:58:40 +0100 Subject: [PATCH 03/12] Adjust task api schema with DMTaskSerializer --- label_studio/data_manager/serializers.py | 23 +++++++++++++++---- label_studio/tasks/api.py | 2 +- label_studio/tasks/serializers.py | 29 ++++++++++-------------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/label_studio/data_manager/serializers.py b/label_studio/data_manager/serializers.py index 71b9964c27e..86399fed81d 100644 --- a/label_studio/data_manager/serializers.py +++ b/label_studio/data_manager/serializers.py @@ -6,6 +6,7 @@ from data_manager.models import Filter, FilterGroup, View from django.conf import settings from django.db import transaction +from drf_yasg import openapi from projects.models import Project from rest_framework import serializers from tasks.models import Task @@ -202,11 +203,25 @@ def update(self, instance, validated_data): return instance +class UpdatedByDMField(serializers.SerializerMethodField): + # TODO: get_updated_by implementation is weird, but we need to adhere schema to it + class Meta: + swagger_schema_fields = { + 'type': openapi.TYPE_ARRAY, + 'title': 'User IDs', + 'description': 'User IDs who updated this task', + 'items': { + 'type': openapi.TYPE_OBJECT, + 'title': 'User IDs' + }, + } + + class DataManagerTaskSerializer(TaskSerializer): - predictions = serializers.SerializerMethodField(required=False, read_only=True) + predictions = PredictionSerializer(required=False, many=True, default=[], read_only=True) annotations = AnnotationSerializer(required=False, many=True, default=[], read_only=True) - drafts = serializers.SerializerMethodField(required=False, read_only=True) - annotators = serializers.SerializerMethodField(required=False, read_only=True) + drafts = AnnotationDraftSerializer(required=False, many=True, default=[], read_only=True) + annotators = serializers.ListField(child=serializers.IntegerField(), required=False, read_only=True) inner_id = serializers.IntegerField(required=False) cancelled_annotations = serializers.IntegerField(required=False) @@ -222,7 +237,7 @@ class DataManagerTaskSerializer(TaskSerializer): predictions_model_versions = serializers.SerializerMethodField(required=False) avg_lead_time = serializers.FloatField(required=False) draft_exists = serializers.BooleanField(required=False) - updated_by = serializers.SerializerMethodField(required=False, read_only=True) + updated_by = UpdatedByDMField(required=False, read_only=True) CHAR_LIMITS = 500 diff --git a/label_studio/tasks/api.py b/label_studio/tasks/api.py index 274ff2e3369..5ac56568225 100644 --- a/label_studio/tasks/api.py +++ b/label_studio/tasks/api.py @@ -208,7 +208,7 @@ def perform_create(self, serializer): request_body=no_body, responses={ '200': openapi.Response( - description='Task', schema=TaskSerializer, examples={'application/json': task_response_example} + description='Task', schema=DataManagerTaskSerializer, examples={'application/json': task_response_example} ) }, ), diff --git a/label_studio/tasks/serializers.py b/label_studio/tasks/serializers.py index 6685110ea5d..e557f0822bc 100644 --- a/label_studio/tasks/serializers.py +++ b/label_studio/tasks/serializers.py @@ -83,13 +83,23 @@ class Meta: fields = ['id', 'first_name', 'last_name', 'avatar', 'email', 'initials'] +class CompletedByDMSerializerWithGenericSchema(serializers.PrimaryKeyRelatedField): + # TODO: likely we need to remove full user details from GET /api/tasks/{id} as it non-secure and currently controlled by the export toggle + class Meta: + swagger_schema_fields = { + 'type': openapi.TYPE_OBJECT, + 'title': 'User details', + 'description': 'User details who completed this annotation.' + } + + class AnnotationSerializer(FlexFieldsModelSerializer): """ """ result = AnnotationResultField(required=False) created_username = serializers.SerializerMethodField(default='', read_only=True, help_text='Username string') created_ago = serializers.CharField(default='', read_only=True, help_text='Time delta from creation time') - completed_by = serializers.PrimaryKeyRelatedField(required=False, queryset=User.objects.all()) + completed_by = CompletedByDMSerializerWithGenericSchema(required=False, queryset=User.objects.all()) unique_id = serializers.CharField(required=False, write_only=True) def create(self, *args, **kwargs): @@ -157,23 +167,8 @@ class Meta: fields = '__all__' -class TaskUpdatedBySerializerField(serializers.JSONField): - class Meta: - swagger_schema_fields = { - 'type': openapi.TYPE_ARRAY, - 'title': 'List of users', - 'description': 'List of users who updated the task', - 'items': { - 'type': openapi.TYPE_OBJECT, - 'title': 'User data', - }, - } - - class BaseTaskSerializer(FlexFieldsModelSerializer): """Task Serializer with project scheme configs validation""" - updated_by = TaskUpdatedBySerializerField(required=False) - file_upload = serializers.CharField(required=False) def project(self, task=None): """Take the project from context""" @@ -624,7 +619,7 @@ class Meta: class AnnotationDraftSerializer(ModelSerializer): - + result = AnnotationResultField(required=False) user = serializers.CharField(default=serializers.CurrentUserDefault()) created_username = serializers.SerializerMethodField(default='', read_only=True, help_text='User name string') created_ago = serializers.CharField(default='', read_only=True, help_text='Delta time from creation time') From 6f3294ca7950797e5e96b8c986dd3d0695d1fc42 Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 18 Jun 2024 22:59:00 +0100 Subject: [PATCH 04/12] Reformat code --- label_studio/data_manager/serializers.py | 5 +---- label_studio/tasks/api.py | 4 +++- label_studio/tasks/serializers.py | 2 +- label_studio/tests/sdk/test_tasks.py | 16 ++++++++-------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/label_studio/data_manager/serializers.py b/label_studio/data_manager/serializers.py index 86399fed81d..9982512f179 100644 --- a/label_studio/data_manager/serializers.py +++ b/label_studio/data_manager/serializers.py @@ -210,10 +210,7 @@ class Meta: 'type': openapi.TYPE_ARRAY, 'title': 'User IDs', 'description': 'User IDs who updated this task', - 'items': { - 'type': openapi.TYPE_OBJECT, - 'title': 'User IDs' - }, + 'items': {'type': openapi.TYPE_OBJECT, 'title': 'User IDs'}, } diff --git a/label_studio/tasks/api.py b/label_studio/tasks/api.py index 5ac56568225..9d02f72fbfe 100644 --- a/label_studio/tasks/api.py +++ b/label_studio/tasks/api.py @@ -208,7 +208,9 @@ def perform_create(self, serializer): request_body=no_body, responses={ '200': openapi.Response( - description='Task', schema=DataManagerTaskSerializer, examples={'application/json': task_response_example} + description='Task', + schema=DataManagerTaskSerializer, + examples={'application/json': task_response_example}, ) }, ), diff --git a/label_studio/tasks/serializers.py b/label_studio/tasks/serializers.py index e557f0822bc..3749ed74a6a 100644 --- a/label_studio/tasks/serializers.py +++ b/label_studio/tasks/serializers.py @@ -89,7 +89,7 @@ class Meta: swagger_schema_fields = { 'type': openapi.TYPE_OBJECT, 'title': 'User details', - 'description': 'User details who completed this annotation.' + 'description': 'User details who completed this annotation.', } diff --git a/label_studio/tests/sdk/test_tasks.py b/label_studio/tests/sdk/test_tasks.py index 667b2cd96a2..ddeaacbbcff 100644 --- a/label_studio/tests/sdk/test_tasks.py +++ b/label_studio/tests/sdk/test_tasks.py @@ -55,15 +55,15 @@ def test_delete_multi_tasks(django_live_url, business_client): # another way of calling delete action instead of # ls.actions.create(project=p.id, id='delete_tasks', selected_items={'all': True, 'excluded': [tasks[5].id]}) import json - ls.actions.create(request_options={ - 'additional_query_parameters': { - 'project': p.id, - 'id': 'delete_tasks' - }, - 'additional_body_parameters': { - 'selectedItems': json.dumps({"all": True, "excluded": [tasks[5].id]}), + + ls.actions.create( + request_options={ + 'additional_query_parameters': {'project': p.id, 'id': 'delete_tasks'}, + 'additional_body_parameters': { + 'selectedItems': json.dumps({'all': True, 'excluded': [tasks[5].id]}), + }, } - }) + ) remaining_tasks = [task for task in ls.tasks.list(project=p.id)] assert len(remaining_tasks) == 1 From 77b2efc4f026796267f7a199c072111303818cf7 Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 18 Jun 2024 23:35:00 +0100 Subject: [PATCH 05/12] Fix AnnotatorsDMField --- label_studio/data_manager/serializers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/label_studio/data_manager/serializers.py b/label_studio/data_manager/serializers.py index 9982512f179..055a1d0df22 100644 --- a/label_studio/data_manager/serializers.py +++ b/label_studio/data_manager/serializers.py @@ -214,11 +214,22 @@ class Meta: } +class AnnotatorsDMField(serializers.SerializerMethodField): + # TODO: get_updated_by implementation is weird, but we need to adhere schema to it + class Meta: + swagger_schema_fields = { + 'type': openapi.TYPE_ARRAY, + 'title': 'Annotators IDs', + 'description': 'Annotators IDs who annotated this task', + 'items': {'type': openapi.TYPE_INTEGER, 'title': 'User IDs'}, + } + + class DataManagerTaskSerializer(TaskSerializer): predictions = PredictionSerializer(required=False, many=True, default=[], read_only=True) annotations = AnnotationSerializer(required=False, many=True, default=[], read_only=True) drafts = AnnotationDraftSerializer(required=False, many=True, default=[], read_only=True) - annotators = serializers.ListField(child=serializers.IntegerField(), required=False, read_only=True) + annotators = AnnotatorsDMField(required=False, read_only=True) inner_id = serializers.IntegerField(required=False) cancelled_annotations = serializers.IntegerField(required=False) From a8e443daf09afc70d5bbccf09d6442519513a707 Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 18 Jun 2024 23:56:45 +0100 Subject: [PATCH 06/12] Fix serializer fields in DM --- label_studio/data_manager/serializers.py | 31 +++++++++++++++++++++--- label_studio/tasks/serializers.py | 14 ++--------- label_studio/tests/sdk/test_tasks.py | 5 ++-- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/label_studio/data_manager/serializers.py b/label_studio/data_manager/serializers.py index 055a1d0df22..ca1540f8142 100644 --- a/label_studio/data_manager/serializers.py +++ b/label_studio/data_manager/serializers.py @@ -10,7 +10,14 @@ from projects.models import Project from rest_framework import serializers from tasks.models import Task -from tasks.serializers import AnnotationDraftSerializer, AnnotationSerializer, PredictionSerializer, TaskSerializer +from tasks.serializers import ( + AnnotationDraftSerializer, + AnnotationResultField, + AnnotationSerializer, + PredictionSerializer, + TaskSerializer, +) +from users.models import User from label_studio.core.utils.common import round_floats @@ -225,10 +232,28 @@ class Meta: } +class CompletedByDMSerializerWithGenericSchema(serializers.PrimaryKeyRelatedField): + # TODO: likely we need to remove full user details from GET /api/tasks/{id} as it non-secure and currently controlled by the export toggle + class Meta: + swagger_schema_fields = { + 'type': openapi.TYPE_OBJECT, + 'title': 'User details', + 'description': 'User details who completed this annotation.', + } + + +class AnnotationsDMFieldSerializer(AnnotationSerializer): + completed_by = CompletedByDMSerializerWithGenericSchema(required=False, queryset=User.objects.all()) + + +class AnnotationDraftDMFieldSerializer(AnnotationDraftSerializer): + result = AnnotationResultField(required=False) + + class DataManagerTaskSerializer(TaskSerializer): predictions = PredictionSerializer(required=False, many=True, default=[], read_only=True) - annotations = AnnotationSerializer(required=False, many=True, default=[], read_only=True) - drafts = AnnotationDraftSerializer(required=False, many=True, default=[], read_only=True) + annotations = AnnotationsDMFieldSerializer(required=False, many=True, default=[], read_only=True) + drafts = AnnotationDraftDMFieldSerializer(required=False, many=True, default=[], read_only=True) annotators = AnnotatorsDMField(required=False, read_only=True) inner_id = serializers.IntegerField(required=False) diff --git a/label_studio/tasks/serializers.py b/label_studio/tasks/serializers.py index 3749ed74a6a..6fc612ce5a5 100644 --- a/label_studio/tasks/serializers.py +++ b/label_studio/tasks/serializers.py @@ -83,23 +83,13 @@ class Meta: fields = ['id', 'first_name', 'last_name', 'avatar', 'email', 'initials'] -class CompletedByDMSerializerWithGenericSchema(serializers.PrimaryKeyRelatedField): - # TODO: likely we need to remove full user details from GET /api/tasks/{id} as it non-secure and currently controlled by the export toggle - class Meta: - swagger_schema_fields = { - 'type': openapi.TYPE_OBJECT, - 'title': 'User details', - 'description': 'User details who completed this annotation.', - } - - class AnnotationSerializer(FlexFieldsModelSerializer): """ """ result = AnnotationResultField(required=False) created_username = serializers.SerializerMethodField(default='', read_only=True, help_text='Username string') created_ago = serializers.CharField(default='', read_only=True, help_text='Time delta from creation time') - completed_by = CompletedByDMSerializerWithGenericSchema(required=False, queryset=User.objects.all()) + completed_by = serializers.PrimaryKeyRelatedField(required=False, queryset=User.objects.all()) unique_id = serializers.CharField(required=False, write_only=True) def create(self, *args, **kwargs): @@ -619,7 +609,7 @@ class Meta: class AnnotationDraftSerializer(ModelSerializer): - result = AnnotationResultField(required=False) + user = serializers.CharField(default=serializers.CurrentUserDefault()) created_username = serializers.SerializerMethodField(default='', read_only=True, help_text='User name string') created_ago = serializers.CharField(default='', read_only=True, help_text='Delta time from creation time') diff --git a/label_studio/tests/sdk/test_tasks.py b/label_studio/tests/sdk/test_tasks.py index ddeaacbbcff..824b3642992 100644 --- a/label_studio/tests/sdk/test_tasks.py +++ b/label_studio/tests/sdk/test_tasks.py @@ -57,12 +57,13 @@ def test_delete_multi_tasks(django_live_url, business_client): import json ls.actions.create( + project=p.id, + id='delete_tasks', request_options={ - 'additional_query_parameters': {'project': p.id, 'id': 'delete_tasks'}, 'additional_body_parameters': { 'selectedItems': json.dumps({'all': True, 'excluded': [tasks[5].id]}), }, - } + }, ) remaining_tasks = [task for task in ls.tasks.list(project=p.id)] From 379596041647cd9c343abddaa37d7b7a7cc55f79 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 19 Jun 2024 11:06:30 +0100 Subject: [PATCH 07/12] Replace back serializers with SerializerMethodFields --- label_studio/data_manager/serializers.py | 106 ++++++++++++++++++++--- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/label_studio/data_manager/serializers.py b/label_studio/data_manager/serializers.py index ca1540f8142..86c82648a33 100644 --- a/label_studio/data_manager/serializers.py +++ b/label_studio/data_manager/serializers.py @@ -48,10 +48,10 @@ def validate_column(self, column: str) -> str: if not column_copy.startswith(required_prefix): raise serializers.ValidationError(f'Filter "{column}" should start with "{required_prefix}"') - column_copy = column_copy[len(required_prefix) :] + column_copy = column_copy[len(required_prefix):] if column_copy.startswith(optional_prefix): - column_copy = column_copy[len(optional_prefix) :] + column_copy = column_copy[len(optional_prefix):] if column_copy.startswith('data.'): # Allow underscores if the filter is based on the `task.data` JSONField, because these don't leverage foreign keys. @@ -210,7 +210,7 @@ def update(self, instance, validated_data): return instance -class UpdatedByDMField(serializers.SerializerMethodField): +class UpdatedByDMFieldSerializer(serializers.SerializerMethodField): # TODO: get_updated_by implementation is weird, but we need to adhere schema to it class Meta: swagger_schema_fields = { @@ -221,7 +221,7 @@ class Meta: } -class AnnotatorsDMField(serializers.SerializerMethodField): +class AnnotatorsDMFieldSerializer(serializers.SerializerMethodField): # TODO: get_updated_by implementation is weird, but we need to adhere schema to it class Meta: swagger_schema_fields = { @@ -246,15 +246,101 @@ class AnnotationsDMFieldSerializer(AnnotationSerializer): completed_by = CompletedByDMSerializerWithGenericSchema(required=False, queryset=User.objects.all()) -class AnnotationDraftDMFieldSerializer(AnnotationDraftSerializer): - result = AnnotationResultField(required=False) +class AnnotationDraftDMFieldSerializer(serializers.SerializerMethodField): + class Meta: + swagger_schema_fields = { + 'type': openapi.TYPE_ARRAY, + 'title': 'Annotation drafts', + 'description': 'Drafts for this task', + 'items': { + 'type': openapi.TYPE_OBJECT, + 'title': 'Draft object', + 'properties': { + 'result': { + 'type': openapi.TYPE_ARRAY, + 'title': 'Draft result', + 'items': { + 'type': openapi.TYPE_OBJECT, + 'title': 'Draft result item', + }, + }, + 'created_at': { + 'type': openapi.TYPE_STRING, + 'format': 'date-time', + 'title': 'Creation time', + }, + 'updated_at': { + 'type': openapi.TYPE_STRING, + 'format': 'date-time', + 'title': 'Last update time', + }, + }, + }, + } + + +class PredictionsDMFieldSerializer(serializers.SerializerMethodField): + class Meta: + swagger_schema_fields = { + 'type': openapi.TYPE_ARRAY, + 'title': 'Predictions', + 'description': 'Predictions for this task', + 'items': { + 'type': openapi.TYPE_OBJECT, + 'title': 'Prediction object', + 'properties': { + 'result': { + 'type': openapi.TYPE_ARRAY, + 'title': 'Prediction result', + 'items': { + 'type': openapi.TYPE_OBJECT, + 'title': 'Prediction result item', + }, + }, + 'score': { + 'type': openapi.TYPE_NUMBER, + 'title': 'Prediction score', + }, + 'model_version': { + 'type': openapi.TYPE_STRING, + 'title': 'Model version', + }, + 'model': { + 'type': openapi.TYPE_OBJECT, + 'title': 'ML Backend instance', + }, + 'model_run': { + 'type': openapi.TYPE_OBJECT, + 'title': 'Model Run instance', + }, + 'task': { + 'type': openapi.TYPE_INTEGER, + 'title': 'Task ID related to the prediction', + }, + 'project': { + 'type': openapi.TYPE_NUMBER, + 'title': 'Project ID related to the prediction', + }, + 'created_at': { + 'type': openapi.TYPE_STRING, + 'format': 'date-time', + 'title': 'Creation time', + }, + 'updated_at': { + 'type': openapi.TYPE_STRING, + 'format': 'date-time', + 'title': 'Last update time', + }, + }, + } + } class DataManagerTaskSerializer(TaskSerializer): - predictions = PredictionSerializer(required=False, many=True, default=[], read_only=True) + predictions = PredictionsDMFieldSerializer(required=False, read_only=True) annotations = AnnotationsDMFieldSerializer(required=False, many=True, default=[], read_only=True) - drafts = AnnotationDraftDMFieldSerializer(required=False, many=True, default=[], read_only=True) - annotators = AnnotatorsDMField(required=False, read_only=True) + drafts = AnnotationDraftDMFieldSerializer(required=False, read_only=True) + annotators = AnnotatorsDMFieldSerializer(required=False, read_only=True) inner_id = serializers.IntegerField(required=False) cancelled_annotations = serializers.IntegerField(required=False) @@ -270,7 +356,7 @@ class DataManagerTaskSerializer(TaskSerializer): predictions_model_versions = serializers.SerializerMethodField(required=False) avg_lead_time = serializers.FloatField(required=False) draft_exists = serializers.BooleanField(required=False) - updated_by = UpdatedByDMField(required=False, read_only=True) + updated_by = UpdatedByDMFieldSerializer(required=False, read_only=True) CHAR_LIMITS = 500 From a82bbed6417b57c6d74367f0be81619806cf5d7d Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 19 Jun 2024 11:07:03 +0100 Subject: [PATCH 08/12] Reformat --- label_studio/data_manager/serializers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/label_studio/data_manager/serializers.py b/label_studio/data_manager/serializers.py index 86c82648a33..87f5a410351 100644 --- a/label_studio/data_manager/serializers.py +++ b/label_studio/data_manager/serializers.py @@ -12,7 +12,6 @@ from tasks.models import Task from tasks.serializers import ( AnnotationDraftSerializer, - AnnotationResultField, AnnotationSerializer, PredictionSerializer, TaskSerializer, @@ -48,10 +47,10 @@ def validate_column(self, column: str) -> str: if not column_copy.startswith(required_prefix): raise serializers.ValidationError(f'Filter "{column}" should start with "{required_prefix}"') - column_copy = column_copy[len(required_prefix):] + column_copy = column_copy[len(required_prefix) :] if column_copy.startswith(optional_prefix): - column_copy = column_copy[len(optional_prefix):] + column_copy = column_copy[len(optional_prefix) :] if column_copy.startswith('data.'): # Allow underscores if the filter is based on the `task.data` JSONField, because these don't leverage foreign keys. @@ -332,7 +331,7 @@ class Meta: 'title': 'Last update time', }, }, - } + }, } From 9bed4be2026e2fc89fe203e033230e53618e6c82 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 19 Jun 2024 11:17:57 +0100 Subject: [PATCH 09/12] Add datetime examples --- label_studio/tasks/openapi_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/label_studio/tasks/openapi_schema.py b/label_studio/tasks/openapi_schema.py index abd807c1e39..341a0ae3e04 100644 --- a/label_studio/tasks/openapi_schema.py +++ b/label_studio/tasks/openapi_schema.py @@ -23,8 +23,8 @@ 'id': 1, 'data': {'image': 'https://example.com/image.jpg', 'text': 'Hello, AI!'}, 'project': 1, - 'created_at': '2024-01-15T09:30:00Z', - 'updated_at': '2024-01-15T09:30:00Z', + 'created_at': '2024-06-18T23:45:46.048490Z', + 'updated_at': '2024-06-18T23:45:46.048538Z', 'is_labeled': False, 'overlap': 1, 'inner_id': 1, From e31fb2bacee31eeff5ac7255ad7667f80554f831 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 19 Jun 2024 11:21:23 +0100 Subject: [PATCH 10/12] Add dm task response example --- label_studio/tasks/api.py | 3 ++- label_studio/tasks/openapi_schema.py | 34 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/label_studio/tasks/api.py b/label_studio/tasks/api.py index 9d02f72fbfe..f50a4460a51 100644 --- a/label_studio/tasks/api.py +++ b/label_studio/tasks/api.py @@ -28,6 +28,7 @@ from tasks.openapi_schema import ( annotation_request_schema, annotation_response_example, + dm_task_response_example, prediction_request_schema, prediction_response_example, task_request_schema, @@ -210,7 +211,7 @@ def perform_create(self, serializer): '200': openapi.Response( description='Task', schema=DataManagerTaskSerializer, - examples={'application/json': task_response_example}, + examples={'application/json': dm_task_response_example}, ) }, ), diff --git a/label_studio/tasks/openapi_schema.py b/label_studio/tasks/openapi_schema.py index 341a0ae3e04..3393aabd48b 100644 --- a/label_studio/tasks/openapi_schema.py +++ b/label_studio/tasks/openapi_schema.py @@ -39,6 +39,40 @@ 'comment_authors': [1], } +dm_task_response_example = { + 'id': 13, + 'predictions': [], + 'annotations': [], + 'drafts': [], + 'annotators': [], + 'inner_id': 2, + 'cancelled_annotations': 0, + 'total_annotations': 0, + 'total_predictions': 0, + 'completed_at': None, + 'annotations_results': '', + 'predictions_results': '', + 'predictions_score': None, + 'file_upload': '6b25fc23-some_3.mp4', + 'storage_filename': None, + 'annotations_ids': '', + 'predictions_model_versions': '', + 'avg_lead_time': None, + 'draft_exists': False, + 'updated_by': [], + 'data': {'image': '/data/upload/1/6b25fc23-some_3.mp4'}, + 'meta': {}, + 'created_at': '2024-06-18T23:45:46.048490Z', + 'updated_at': '2024-06-18T23:45:46.048538Z', + 'is_labeled': False, + 'overlap': 1, + 'comment_count': 0, + 'unresolved_comment_count': 0, + 'last_comment_updated_at': None, + 'project': 1, + 'comment_authors': [], +} + annotation_response_example = { 'id': 1, 'result': result_example, From d3592e9b3529ca24abddaa03ec67f32984f34b9f Mon Sep 17 00:00:00 2001 From: robot-ci-heartex <87703623+robot-ci-heartex@users.noreply.github.com> Date: Wed, 19 Jun 2024 15:59:40 +0400 Subject: [PATCH 11/12] fix: RND-98: Upgrade with task serializer (merge branch) (#6016) Hi @niklub! This PR was [created](https://github.com/HumanSignal/label-studio/actions/runs/9580652200) in a response to PRs in upstream repos: - https://github.com/HumanSignal/label-studio-sdk/pull/252 --------- Signed-off-by: dependabot[bot] Co-authored-by: bmartel Co-authored-by: robot-ci-heartex Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: niklub --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 45ef12dd151..94ae05883e0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1712,12 +1712,12 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", [[package]] name = "label-studio-sdk" -version = "0.0.121" +version = "1.0.2" description = "" optional = false python-versions = "^3.8" files = [ - {file = "f6fb130d41380704d36045e21839d3f27ddf2883.zip", hash = "sha256:c6e97a20f52065de3b2e55be537526eb45a6525b3b8f42fc3f4ab41a6e72bb63"}, + {file = "d523c85745f399399574e234ed5ad274a1be1fc3.zip", hash = "sha256:241e416e23b31a5ff3b49fbad8a8fb2d6c233844249bcdff57813a34c90ec4d7"}, ] [package.dependencies] @@ -1738,7 +1738,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/f6fb130d41380704d36045e21839d3f27ddf2883.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/d523c85745f399399574e234ed5ad274a1be1fc3.zip" [[package]] name = "launchdarkly-server-sdk" @@ -3920,4 +3920,4 @@ mysql = ["mysqlclient"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "7895ec2fb4e2bc2b0dc8fee047b92e109c13ef6903df0b4c68eb90e616c5c113" +content-hash = "cad952e3ece696920e33e5bc079471a6e3ca434b358c62a20e0ca035ff50071e" diff --git a/pyproject.toml b/pyproject.toml index 33c658b9b6e..6576fcbd5bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ openai = "^1.10.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/f6fb130d41380704d36045e21839d3f27ddf2883.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/d523c85745f399399574e234ed5ad274a1be1fc3.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2" From fcf37eecfe6ec7470beb8fdaab383390f73b2b7c Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 21 Jun 2024 10:33:34 +0100 Subject: [PATCH 12/12] Use pyproject from develop --- poetry.lock | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index b15011e0d7a..32c8754662f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1731,7 +1731,7 @@ description = "" optional = false python-versions = "^3.8" files = [ - {file = "d523c85745f399399574e234ed5ad274a1be1fc3.zip", hash = "sha256:241e416e23b31a5ff3b49fbad8a8fb2d6c233844249bcdff57813a34c90ec4d7"}, + {file = "64eea7333be550a857481fa1158dcac39e7da3b7.zip", hash = "sha256:82d03c8e4c292fcb3028ccd35c8a14d562b1772c6500618a5380fa1cf2f4fce7"}, ] [package.dependencies] @@ -1752,7 +1752,7 @@ xmljson = "0.2.1" [package.source] type = "url" -url = "https://github.com/HumanSignal/label-studio-sdk/archive/d523c85745f399399574e234ed5ad274a1be1fc3.zip" +url = "https://github.com/HumanSignal/label-studio-sdk/archive/64eea7333be550a857481fa1158dcac39e7da3b7.zip" [[package]] name = "launchdarkly-server-sdk" @@ -3992,4 +3992,4 @@ mysql = ["mysqlclient"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "cad952e3ece696920e33e5bc079471a6e3ca434b358c62a20e0ca035ff50071e" +content-hash = "17d7d1c6dbd02f5e8c34f8c8bea7d92c70e9924bdaf6bb2886c095a403aaa8de" diff --git a/pyproject.toml b/pyproject.toml index cbbc071d3bb..65c50239a6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ openai = "^1.10.0" # Humansignal repo dependencies -label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/d523c85745f399399574e234ed5ad274a1be1fc3.zip"} +label-studio-sdk = {url = "https://github.com/HumanSignal/label-studio-sdk/archive/64eea7333be550a857481fa1158dcac39e7da3b7.zip"} [tool.poetry.group.test.dependencies] pytest = "7.2.2"