From 4286ec4e51c3a136a96f6bb36c039b04fe400527 Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Sat, 14 Sep 2024 20:43:38 -0500 Subject: [PATCH 1/8] finish remaining integration tests --- .../test_bulk_import_request.py | 48 ----------- .../data/annotation_import/test_data_types.py | 83 ------------------- .../test_generic_data_types.py | 70 ---------------- .../test_mea_prediction_import.py | 70 ++++++++++++++-- .../ndjson/test_generic_data_row_data.py | 79 ++++++++++++++++++ 5 files changed, 144 insertions(+), 206 deletions(-) delete mode 100644 libs/labelbox/tests/data/annotation_import/test_data_types.py create mode 100644 libs/labelbox/tests/data/serialization/ndjson/test_generic_data_row_data.py diff --git a/libs/labelbox/tests/data/annotation_import/test_bulk_import_request.py b/libs/labelbox/tests/data/annotation_import/test_bulk_import_request.py index 9abae1422..b2289503e 100644 --- a/libs/labelbox/tests/data/annotation_import/test_bulk_import_request.py +++ b/libs/labelbox/tests/data/annotation_import/test_bulk_import_request.py @@ -1,38 +1,11 @@ -from unittest.mock import patch import uuid from labelbox import parser, Project -from labelbox.data.annotation_types.data.generic_data_row_data import ( - GenericDataRowData, -) import pytest -import random -from labelbox.data.annotation_types.annotation import ObjectAnnotation -from labelbox.data.annotation_types.classification.classification import ( - Checklist, - ClassificationAnnotation, - ClassificationAnswer, - Radio, -) -from labelbox.data.annotation_types.data.video import VideoData -from labelbox.data.annotation_types.geometry.point import Point -from labelbox.data.annotation_types.geometry.rectangle import ( - Rectangle, - RectangleUnit, -) -from labelbox.data.annotation_types.label import Label -from labelbox.data.annotation_types.data.text import TextData -from labelbox.data.annotation_types.ner import ( - DocumentEntity, - DocumentTextSelection, -) -from labelbox.data.annotation_types.video import VideoObjectAnnotation from labelbox.data.serialization import NDJsonConverter from labelbox.exceptions import MALValidationError, UuidError from labelbox.schema.bulk_import_request import BulkImportRequest from labelbox.schema.enums import BulkImportRequestState -from labelbox.schema.annotation_import import LabelImport, MALPredictionImport -from labelbox.schema.media_type import MediaType """ - Here we only want to check that the uploads are calling the validation @@ -87,27 +60,6 @@ def test_create_from_objects( ) -def test_create_from_label_objects( - module_project, predictions, annotation_import_test_helpers -): - name = str(uuid.uuid4()) - - labels = list(NDJsonConverter.deserialize(predictions)) - bulk_import_request = module_project.upload_annotations( - name=name, annotations=labels - ) - - assert bulk_import_request.project() == module_project - assert bulk_import_request.name == name - assert bulk_import_request.error_file_url is None - assert bulk_import_request.status_file_url is None - assert bulk_import_request.state == BulkImportRequestState.RUNNING - normalized_predictions = list(NDJsonConverter.serialize(labels)) - annotation_import_test_helpers.assert_file_content( - bulk_import_request.input_file_url, normalized_predictions - ) - - def test_create_from_local_file( tmp_path, predictions, module_project, annotation_import_test_helpers ): diff --git a/libs/labelbox/tests/data/annotation_import/test_data_types.py b/libs/labelbox/tests/data/annotation_import/test_data_types.py deleted file mode 100644 index 1e45295ef..000000000 --- a/libs/labelbox/tests/data/annotation_import/test_data_types.py +++ /dev/null @@ -1,83 +0,0 @@ -import pytest - -from labelbox.data.annotation_types.data import ( - AudioData, - ConversationData, - DocumentData, - HTMLData, - ImageData, - TextData, -) -from labelbox.data.serialization import NDJsonConverter -from labelbox.data.annotation_types.data.video import VideoData - -import labelbox.types as lb_types -from labelbox.schema.media_type import MediaType - -# Unit test for label based on data type. -# TODO: Dicom removed it is unstable when you deserialize and serialize on label import. If we intend to keep this library this needs add generic data types tests work with this data type. -# TODO: add MediaType.LLMPromptResponseCreation(data gen) once supported and llm human preference once media type is added - - -@pytest.mark.parametrize( - "media_type, data_type_class", - [ - (MediaType.Audio, AudioData), - (MediaType.Html, HTMLData), - (MediaType.Image, ImageData), - (MediaType.Text, TextData), - (MediaType.Video, VideoData), - (MediaType.Conversational, ConversationData), - (MediaType.Document, DocumentData), - ], -) -def test_data_row_type_by_data_row_id( - media_type, - data_type_class, - annotations_by_media_type, - hardcoded_datarow_id, -): - annotations_ndjson = annotations_by_media_type[media_type] - annotations_ndjson = [annotation[0] for annotation in annotations_ndjson] - - label = list(NDJsonConverter.deserialize(annotations_ndjson))[0] - - data_label = lb_types.Label( - data=data_type_class(uid=hardcoded_datarow_id()), - annotations=label.annotations, - ) - - assert data_label.data.uid == label.data.uid - assert label.annotations == data_label.annotations - - -@pytest.mark.parametrize( - "media_type, data_type_class", - [ - (MediaType.Audio, AudioData), - (MediaType.Html, HTMLData), - (MediaType.Image, ImageData), - (MediaType.Text, TextData), - (MediaType.Video, VideoData), - (MediaType.Conversational, ConversationData), - (MediaType.Document, DocumentData), - ], -) -def test_data_row_type_by_global_key( - media_type, - data_type_class, - annotations_by_media_type, - hardcoded_global_key, -): - annotations_ndjson = annotations_by_media_type[media_type] - annotations_ndjson = [annotation[0] for annotation in annotations_ndjson] - - label = list(NDJsonConverter.deserialize(annotations_ndjson))[0] - - data_label = lb_types.Label( - data=data_type_class(global_key=hardcoded_global_key()), - annotations=label.annotations, - ) - - assert data_label.data.global_key == label.data.global_key - assert label.annotations == data_label.annotations diff --git a/libs/labelbox/tests/data/annotation_import/test_generic_data_types.py b/libs/labelbox/tests/data/annotation_import/test_generic_data_types.py index f8f0c449a..18385c9d9 100644 --- a/libs/labelbox/tests/data/annotation_import/test_generic_data_types.py +++ b/libs/labelbox/tests/data/annotation_import/test_generic_data_types.py @@ -28,76 +28,6 @@ def validate_iso_format(date_string: str): assert parsed_t.second is not None -@pytest.mark.parametrize( - "media_type, data_type_class", - [ - (MediaType.Audio, GenericDataRowData), - (MediaType.Html, GenericDataRowData), - (MediaType.Image, GenericDataRowData), - (MediaType.Text, GenericDataRowData), - (MediaType.Video, GenericDataRowData), - (MediaType.Conversational, GenericDataRowData), - (MediaType.Document, GenericDataRowData), - (MediaType.LLMPromptResponseCreation, GenericDataRowData), - (MediaType.LLMPromptCreation, GenericDataRowData), - (OntologyKind.ResponseCreation, GenericDataRowData), - ], -) -def test_generic_data_row_type_by_data_row_id( - media_type, - data_type_class, - annotations_by_media_type, - hardcoded_datarow_id, -): - annotations_ndjson = annotations_by_media_type[media_type] - annotations_ndjson = [annotation[0] for annotation in annotations_ndjson] - - label = list(NDJsonConverter.deserialize(annotations_ndjson))[0] - - data_label = Label( - data=data_type_class(uid=hardcoded_datarow_id()), - annotations=label.annotations, - ) - - assert data_label.data.uid == label.data.uid - assert label.annotations == data_label.annotations - - -@pytest.mark.parametrize( - "media_type, data_type_class", - [ - (MediaType.Audio, GenericDataRowData), - (MediaType.Html, GenericDataRowData), - (MediaType.Image, GenericDataRowData), - (MediaType.Text, GenericDataRowData), - (MediaType.Video, GenericDataRowData), - (MediaType.Conversational, GenericDataRowData), - (MediaType.Document, GenericDataRowData), - # (MediaType.LLMPromptResponseCreation, GenericDataRowData), - # (MediaType.LLMPromptCreation, GenericDataRowData), - (OntologyKind.ResponseCreation, GenericDataRowData), - ], -) -def test_generic_data_row_type_by_global_key( - media_type, - data_type_class, - annotations_by_media_type, - hardcoded_global_key, -): - annotations_ndjson = annotations_by_media_type[media_type] - annotations_ndjson = [annotation[0] for annotation in annotations_ndjson] - - label = list(NDJsonConverter.deserialize(annotations_ndjson))[0] - - data_label = Label( - data=data_type_class(global_key=hardcoded_global_key()), - annotations=label.annotations, - ) - - assert data_label.data.global_key == label.data.global_key - assert label.annotations == data_label.annotations - - @pytest.mark.parametrize( "configured_project, media_type", [ diff --git a/libs/labelbox/tests/data/annotation_import/test_mea_prediction_import.py b/libs/labelbox/tests/data/annotation_import/test_mea_prediction_import.py index fccca2a3f..5f47975ad 100644 --- a/libs/labelbox/tests/data/annotation_import/test_mea_prediction_import.py +++ b/libs/labelbox/tests/data/annotation_import/test_mea_prediction_import.py @@ -1,5 +1,19 @@ import uuid from labelbox import parser +from labelbox.data.annotation_types.annotation import ObjectAnnotation +from labelbox.data.annotation_types.classification.classification import ( + ClassificationAnnotation, + ClassificationAnswer, + Radio, +) +from labelbox.data.annotation_types.data.generic_data_row_data import ( + GenericDataRowData, +) +from labelbox.data.annotation_types.geometry.line import Line +from labelbox.data.annotation_types.geometry.point import Point +from labelbox.data.annotation_types.geometry.polygon import Polygon +from labelbox.data.annotation_types.geometry.rectangle import Rectangle +from labelbox.data.annotation_types.label import Label import pytest from labelbox import ModelRun @@ -193,14 +207,60 @@ def test_create_from_label_objects( annotation_import_test_helpers, ): name = str(uuid.uuid4()) - use_data_row_ids = [ + use_data_row_id = [ p["dataRow"]["id"] for p in object_predictions_for_annotation_import ] - model_run_with_data_rows.upsert_data_rows(use_data_row_ids) - predictions = list( - NDJsonConverter.deserialize(object_predictions_for_annotation_import) - ) + model_run_with_data_rows.upsert_data_rows(use_data_row_id) + + predictions = [] + for data_row_id in use_data_row_id: + predictions.append( + Label( + data=GenericDataRowData( + uid=data_row_id, + ), + annotations=[ + ObjectAnnotation( + name="polygon", + extra={ + "uuid": "6d10fa30-3ea0-4e6c-bbb1-63f5c29fe3e4", + }, + value=Polygon( + points=[ + Point(x=147.692, y=118.154), + Point(x=142.769, y=104.923), + Point(x=57.846, y=118.769), + Point(x=28.308, y=169.846), + Point(x=147.692, y=118.154), + ], + ), + ), + ObjectAnnotation( + name="bbox", + extra={ + "uuid": "15b7138f-4bbc-42c5-ae79-45d87b0a3b2a", + }, + value=Rectangle( + start=Point(x=58.0, y=48.0), + end=Point(x=70.0, y=113.0), + ), + ), + ObjectAnnotation( + name="polyline", + extra={ + "uuid": "cf4c6df9-c39c-4fbc-9541-470f6622978a", + }, + value=Line( + points=[ + Point(x=147.692, y=118.154), + Point(x=150.692, y=160.154), + ], + ), + ), + ], + ), + ) annotation_import = model_run_with_data_rows.add_predictions( name=name, predictions=predictions diff --git a/libs/labelbox/tests/data/serialization/ndjson/test_generic_data_row_data.py b/libs/labelbox/tests/data/serialization/ndjson/test_generic_data_row_data.py new file mode 100644 index 000000000..0dc4c21c0 --- /dev/null +++ b/libs/labelbox/tests/data/serialization/ndjson/test_generic_data_row_data.py @@ -0,0 +1,79 @@ +from labelbox.data.annotation_types.data.generic_data_row_data import ( + GenericDataRowData, +) +from labelbox.data.serialization.ndjson.converter import NDJsonConverter +from labelbox.types import Label, ClassificationAnnotation, Text + + +def test_generic_data_row_global_key(): + label_1 = Label( + data=GenericDataRowData(global_key="test"), + annotations=[ + ClassificationAnnotation( + name="free_text", + value=Text(answer="sample text"), + extra={"uuid": "141c3592-e5f0-4866-9943-d4a21fd47eb0"}, + ) + ], + ) + label_2 = Label( + data={"global_key": "test"}, + annotations=[ + ClassificationAnnotation( + name="free_text", + value=Text(answer="sample text"), + extra={"uuid": "141c3592-e5f0-4866-9943-d4a21fd47eb0"}, + ) + ], + ) + + expected_result = [ + { + "answer": "sample text", + "dataRow": {"globalKey": "test"}, + "name": "free_text", + "uuid": "141c3592-e5f0-4866-9943-d4a21fd47eb0", + } + ] + assert ( + list(NDJsonConverter.serialize([label_1])) + == list(NDJsonConverter.serialize([label_2])) + == expected_result + ) + + +def test_generic_data_row_id(): + label_1 = Label( + data=GenericDataRowData(uid="test"), + annotations=[ + ClassificationAnnotation( + name="free_text", + value=Text(answer="sample text"), + extra={"uuid": "141c3592-e5f0-4866-9943-d4a21fd47eb0"}, + ) + ], + ) + label_2 = Label( + data={"uid": "test"}, + annotations=[ + ClassificationAnnotation( + name="free_text", + value=Text(answer="sample text"), + extra={"uuid": "141c3592-e5f0-4866-9943-d4a21fd47eb0"}, + ) + ], + ) + + expected_result = [ + { + "answer": "sample text", + "dataRow": {"id": "test"}, + "name": "free_text", + "uuid": "141c3592-e5f0-4866-9943-d4a21fd47eb0", + } + ] + assert ( + list(NDJsonConverter.serialize([label_1])) + == list(NDJsonConverter.serialize([label_2])) + == expected_result + ) From ed9ba18689cd6f224305da17f68af1ca5d40e8fd Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Sat, 14 Sep 2024 20:48:07 -0500 Subject: [PATCH 2/8] Removed deserialize and subregistory logic --- .../data/serialization/ndjson/base.py | 12 ---- .../serialization/ndjson/classification.py | 13 ++-- .../data/serialization/ndjson/converter.py | 14 ---- .../data/serialization/ndjson/label.py | 64 +------------------ .../data/serialization/ndjson/metric.py | 5 +- .../labelbox/data/serialization/ndjson/mmc.py | 4 +- .../data/serialization/ndjson/objects.py | 49 +++++--------- .../data/serialization/ndjson/relationship.py | 4 +- 8 files changed, 28 insertions(+), 137 deletions(-) diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/base.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/base.py index 75ebdc100..d8d8cd36f 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/base.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/base.py @@ -8,18 +8,6 @@ from ....annotated_types import Cuid -subclass_registry = {} - - -class _SubclassRegistryBase(BaseModel): - model_config = ConfigDict(extra="allow") - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - if cls.__name__ != "NDAnnotation": - with threading.Lock(): - subclass_registry[cls.__name__] = cls - class DataRow(_CamelCaseMixin): id: Optional[str] = None diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py index b127c4a90..2c3215265 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py @@ -30,7 +30,6 @@ model_serializer, ) from pydantic.alias_generators import to_camel -from .base import _SubclassRegistryBase class NDAnswer(ConfidenceMixin, CustomMetricsMixin): @@ -224,7 +223,7 @@ def from_common( # ====== End of subclasses -class NDText(NDAnnotation, NDTextSubclass, _SubclassRegistryBase): +class NDText(NDAnnotation, NDTextSubclass): @classmethod def from_common( cls, @@ -249,9 +248,7 @@ def from_common( ) -class NDChecklist( - NDAnnotation, NDChecklistSubclass, VideoSupported, _SubclassRegistryBase -): +class NDChecklist(NDAnnotation, NDChecklistSubclass, VideoSupported): @model_serializer(mode="wrap") def serialize_model(self, handler): res = handler(self) @@ -298,9 +295,7 @@ def from_common( ) -class NDRadio( - NDAnnotation, NDRadioSubclass, VideoSupported, _SubclassRegistryBase -): +class NDRadio(NDAnnotation, NDRadioSubclass, VideoSupported): @classmethod def from_common( cls, @@ -343,7 +338,7 @@ def serialize_model(self, handler): return res -class NDPromptText(NDAnnotation, NDPromptTextSubclass, _SubclassRegistryBase): +class NDPromptText(NDAnnotation, NDPromptTextSubclass): @classmethod def from_common( cls, diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/converter.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/converter.py index 01ab8454a..8176d7862 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/converter.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/converter.py @@ -26,20 +26,6 @@ class NDJsonConverter: - @staticmethod - def deserialize(json_data: Iterable[Dict[str, Any]]) -> LabelGenerator: - """ - Converts ndjson data (prediction import format) into the common labelbox format. - - Args: - json_data: An iterable representing the ndjson data - Returns: - LabelGenerator containing the ndjson data. - """ - data = NDLabel(**{"annotations": copy.copy(json_data)}) - res = data.to_common() - return res - @staticmethod def serialize( labels: LabelCollection, diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/label.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/label.py index 18134a228..7039ae834 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/label.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/label.py @@ -46,7 +46,6 @@ from .relationship import NDRelationship from .base import DataRow from pydantic import BaseModel, ValidationError -from .base import subclass_registry, _SubclassRegistryBase from pydantic_core import PydanticUndefined from contextlib import suppress @@ -67,68 +66,7 @@ class NDLabel(BaseModel): - annotations: List[_SubclassRegistryBase] - - def __init__(self, **kwargs): - # NOTE: Deserialization of subclasses in pydantic is difficult, see here https://blog.devgenius.io/deserialize-child-classes-with-pydantic-that-gonna-work-784230e1cf83 - # Below implements the subclass registry as mentioned in the article. The python dicts we pass in can be missing certain fields - # we essentially have to infer the type against all sub classes that have the _SubclasssRegistryBase inheritance. - # It works by checking if the keys of our annotations we are missing in matches any required subclass. - # More keys are prioritized over less keys (closer match). This is used when importing json to our base models not a lot of customer workflows - # depend on this method but this works for all our existing tests with the bonus of added validation. (no subclass found it throws an error) - - for index, annotation in enumerate(kwargs["annotations"]): - if isinstance(annotation, dict): - item_annotation_keys = annotation.keys() - key_subclass_combos = defaultdict(list) - for subclass in subclass_registry.values(): - # Get all required keys from subclass - annotation_keys = [] - for k, field in subclass.model_fields.items(): - if field.default == PydanticUndefined and k != "uuid": - if ( - hasattr(field, "alias") - and field.alias in item_annotation_keys - ): - annotation_keys.append(field.alias) - elif ( - hasattr(field, "validation_alias") - and field.validation_alias - in item_annotation_keys - ): - annotation_keys.append(field.validation_alias) - else: - annotation_keys.append(k) - - key_subclass_combos[subclass].extend(annotation_keys) - - # Sort by subclass that has the most keys i.e. the one with the most keys that matches is most likely our subclass - key_subclass_combos = dict( - sorted( - key_subclass_combos.items(), - key=lambda x: len(x[1]), - reverse=True, - ) - ) - - for subclass, key_subclass_combo in key_subclass_combos.items(): - # Choose the keys from our dict we supplied that matches the required keys of a subclass - check_required_keys = all( - key in list(item_annotation_keys) - for key in key_subclass_combo - ) - if check_required_keys: - # Keep trying subclasses until we find one that has valid values (does not throw an validation error) - with suppress(ValidationError): - annotation = subclass(**annotation) - break - if isinstance(annotation, dict): - raise ValueError( - f"Could not find subclass for fields: {item_annotation_keys}" - ) - - kwargs["annotations"][index] = annotation - super().__init__(**kwargs) + annotations: AnnotationType class _Relationship(BaseModel): """This object holds information about the relationship""" diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/metric.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/metric.py index 60d538b19..b28e575cf 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/metric.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/metric.py @@ -15,7 +15,6 @@ ConfusionMatrixMetricConfidenceValue, ) from pydantic import ConfigDict, model_serializer -from .base import _SubclassRegistryBase class BaseNDMetric(NDJsonBase): @@ -33,7 +32,7 @@ def serialize_model(self, handler): return res -class NDConfusionMatrixMetric(BaseNDMetric, _SubclassRegistryBase): +class NDConfusionMatrixMetric(BaseNDMetric): metric_value: Union[ ConfusionMatrixMetricValue, ConfusionMatrixMetricConfidenceValue ] @@ -65,7 +64,7 @@ def from_common( ) -class NDScalarMetric(BaseNDMetric, _SubclassRegistryBase): +class NDScalarMetric(BaseNDMetric): metric_value: Union[ScalarMetricValue, ScalarMetricConfidenceValue] metric_name: Optional[str] = None aggregation: Optional[ScalarMetricAggregation] = ( diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/mmc.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/mmc.py index 4be24f683..74d185f45 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/mmc.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/mmc.py @@ -2,7 +2,7 @@ from labelbox.utils import _CamelCaseMixin -from .base import _SubclassRegistryBase, DataRow, NDAnnotation +from .base import DataRow, NDAnnotation from ...annotation_types.mmc import ( MessageSingleSelectionTask, MessageMultiSelectionTask, @@ -20,7 +20,7 @@ class MessageTaskData(_CamelCaseMixin): ] -class NDMessageTask(NDAnnotation, _SubclassRegistryBase): +class NDMessageTask(NDAnnotation): message_evaluation_task: MessageTaskData def to_common(self) -> MessageEvaluationTaskAnnotation: diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/objects.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/objects.py index a1465fa06..91abface6 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/objects.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/objects.py @@ -52,7 +52,7 @@ NDSubclassification, NDSubclassificationType, ) -from .base import DataRow, NDAnnotation, NDJsonBase, _SubclassRegistryBase +from .base import DataRow, NDAnnotation, NDJsonBase from pydantic import BaseModel @@ -81,9 +81,7 @@ class Bbox(BaseModel): width: float -class NDPoint( - NDBaseObject, ConfidenceMixin, CustomMetricsMixin, _SubclassRegistryBase -): +class NDPoint(NDBaseObject, ConfidenceMixin, CustomMetricsMixin): point: _Point def to_common(self) -> Point: @@ -114,7 +112,7 @@ def from_common( ) -class NDFramePoint(VideoSupported, _SubclassRegistryBase): +class NDFramePoint(VideoSupported): point: _Point classifications: List[NDSubclassificationType] = [] @@ -148,9 +146,7 @@ def from_common( ) -class NDLine( - NDBaseObject, ConfidenceMixin, CustomMetricsMixin, _SubclassRegistryBase -): +class NDLine(NDBaseObject, ConfidenceMixin, CustomMetricsMixin): line: List[_Point] def to_common(self) -> Line: @@ -181,7 +177,7 @@ def from_common( ) -class NDFrameLine(VideoSupported, _SubclassRegistryBase): +class NDFrameLine(VideoSupported): line: List[_Point] classifications: List[NDSubclassificationType] = [] @@ -215,7 +211,7 @@ def from_common( ) -class NDDicomLine(NDFrameLine, _SubclassRegistryBase): +class NDDicomLine(NDFrameLine): def to_common( self, name: str, @@ -234,9 +230,7 @@ def to_common( ) -class NDPolygon( - NDBaseObject, ConfidenceMixin, CustomMetricsMixin, _SubclassRegistryBase -): +class NDPolygon(NDBaseObject, ConfidenceMixin, CustomMetricsMixin): polygon: List[_Point] def to_common(self) -> Polygon: @@ -267,9 +261,7 @@ def from_common( ) -class NDRectangle( - NDBaseObject, ConfidenceMixin, CustomMetricsMixin, _SubclassRegistryBase -): +class NDRectangle(NDBaseObject, ConfidenceMixin, CustomMetricsMixin): bbox: Bbox def to_common(self) -> Rectangle: @@ -313,7 +305,7 @@ def from_common( ) -class NDDocumentRectangle(NDRectangle, _SubclassRegistryBase): +class NDDocumentRectangle(NDRectangle): page: int unit: str @@ -360,7 +352,7 @@ def from_common( ) -class NDFrameRectangle(VideoSupported, _SubclassRegistryBase): +class NDFrameRectangle(VideoSupported): bbox: Bbox classifications: List[NDSubclassificationType] = [] @@ -496,7 +488,7 @@ def to_common( ] -class NDSegments(NDBaseObject, _SubclassRegistryBase): +class NDSegments(NDBaseObject): segments: List[NDSegment] def to_common(self, name: str, feature_schema_id: Cuid): @@ -532,7 +524,7 @@ def from_common( ) -class NDDicomSegments(NDBaseObject, DicomSupported, _SubclassRegistryBase): +class NDDicomSegments(NDBaseObject, DicomSupported): segments: List[NDDicomSegment] def to_common(self, name: str, feature_schema_id: Cuid): @@ -580,9 +572,7 @@ class _PNGMask(BaseModel): png: str -class NDMask( - NDBaseObject, ConfidenceMixin, CustomMetricsMixin, _SubclassRegistryBase -): +class NDMask(NDBaseObject, ConfidenceMixin, CustomMetricsMixin): mask: Union[_URIMask, _PNGMask] def to_common(self) -> Mask: @@ -646,7 +636,6 @@ class NDVideoMasks( NDJsonBase, ConfidenceMixin, CustomMetricsNotSupportedMixin, - _SubclassRegistryBase, ): masks: NDVideoMasksFramesInstances @@ -678,7 +667,7 @@ def from_common(cls, annotation, data): ) -class NDDicomMasks(NDVideoMasks, DicomSupported, _SubclassRegistryBase): +class NDDicomMasks(NDVideoMasks, DicomSupported): def to_common(self) -> DICOMMaskAnnotation: return DICOMMaskAnnotation( frames=self.masks.frames, @@ -702,9 +691,7 @@ class Location(BaseModel): end: int -class NDTextEntity( - NDBaseObject, ConfidenceMixin, CustomMetricsMixin, _SubclassRegistryBase -): +class NDTextEntity(NDBaseObject, ConfidenceMixin, CustomMetricsMixin): location: Location def to_common(self) -> TextEntity: @@ -738,9 +725,7 @@ def from_common( ) -class NDDocumentEntity( - NDBaseObject, ConfidenceMixin, CustomMetricsMixin, _SubclassRegistryBase -): +class NDDocumentEntity(NDBaseObject, ConfidenceMixin, CustomMetricsMixin): name: str text_selections: List[DocumentTextSelection] @@ -774,7 +759,7 @@ def from_common( ) -class NDConversationEntity(NDTextEntity, _SubclassRegistryBase): +class NDConversationEntity(NDTextEntity): message_id: str def to_common(self) -> ConversationEntity: diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py index fbea7e477..94c8e9879 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py @@ -5,7 +5,7 @@ from ...annotation_types.relationship import RelationshipAnnotation from ...annotation_types.relationship import Relationship from .objects import NDObjectType -from .base import DataRow, _SubclassRegistryBase +from .base import DataRow SUPPORTED_ANNOTATIONS = NDObjectType @@ -16,7 +16,7 @@ class _Relationship(BaseModel): type: str -class NDRelationship(NDAnnotation, _SubclassRegistryBase): +class NDRelationship(NDAnnotation): relationship: _Relationship @staticmethod From fd5c3917d179696aafba7c296a99508de24163bc Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:15:30 -0500 Subject: [PATCH 3/8] temp added v6 to workflows so test kick off --- .github/workflows/lbox-develop.yml | 32 ++++++++--------- .github/workflows/python-package-develop.yml | 38 ++++++++++---------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/workflows/lbox-develop.yml b/.github/workflows/lbox-develop.yml index ba1e4f34e..497ef4afb 100644 --- a/.github/workflows/lbox-develop.yml +++ b/.github/workflows/lbox-develop.yml @@ -2,9 +2,9 @@ name: LBox Develop on: push: - branches: [develop] + branches: [develop, v6] pull_request: - branches: [develop] + branches: [develop, v6] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -27,7 +27,7 @@ jobs: - uses: dorny/paths-filter@v3 id: filter with: - list-files: 'json' + list-files: "json" filters: | lbox: - 'libs/lbox*/**' @@ -36,16 +36,16 @@ jobs: with: files-changed: ${{ steps.filter.outputs.lbox_files }} build: - needs: ['path-filter'] + needs: ["path-filter"] if: ${{ needs.path-filter.outputs.lbox == 'true' }} runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - include: ${{ fromJSON(needs.path-filter.outputs.test-matrix) }} + matrix: + include: ${{ fromJSON(needs.path-filter.outputs.test-matrix) }} concurrency: group: lbox-staging-${{ matrix.python-version }}-${{ matrix.package }} - cancel-in-progress: false + cancel-in-progress: false steps: - uses: actions/checkout@v4 with: @@ -67,19 +67,19 @@ jobs: env: LABELBOX_TEST_API_KEY: ${{ secrets[matrix.api-key] }} DA_GCP_LABELBOX_API_KEY: ${{ secrets[matrix.da-test-key] }} - LABELBOX_TEST_ENVIRON: 'staging' + LABELBOX_TEST_ENVIRON: "staging" run: rye run integration test-pypi: runs-on: ubuntu-latest - needs: ['build', 'path-filter'] + needs: ["build", "path-filter"] if: ${{ needs.path-filter.outputs.lbox == 'true' }} strategy: fail-fast: false - matrix: + matrix: include: ${{ fromJSON(needs.path-filter.outputs.package-matrix) }} - environment: + environment: name: Test-PyPI-${{ matrix.package }} - url: 'https://test.pypi.org/p/${{ matrix.package }}' + url: "https://test.pypi.org/p/${{ matrix.package }}" permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write @@ -90,7 +90,7 @@ jobs: - uses: ./.github/actions/python-package-shared-setup with: rye-version: ${{ vars.RYE_VERSION }} - python-version: '3.8' + python-version: "3.8" - name: Create build id: create-build working-directory: libs/${{ matrix.package }} @@ -107,11 +107,11 @@ jobs: repository-url: https://test.pypi.org/legacy/ test-container: runs-on: ubuntu-latest - needs: ['build', 'path-filter'] + needs: ["build", "path-filter"] if: ${{ needs.path-filter.outputs.lbox == 'true' }} strategy: fail-fast: false - matrix: + matrix: include: ${{ fromJSON(needs.path-filter.outputs.package-matrix) }} permissions: # IMPORTANT: this permission is mandatory for trusted publishing @@ -163,4 +163,4 @@ jobs: - name: Build and push (Pull Request) Output if: github.event_name == 'pull_request' run: | - echo "ghcr.io/labelbox/${{ matrix.package }}:${{ github.sha }}" >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file + echo "ghcr.io/labelbox/${{ matrix.package }}:${{ github.sha }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/python-package-develop.yml b/.github/workflows/python-package-develop.yml index 05eff5dc4..dcec45103 100644 --- a/.github/workflows/python-package-develop.yml +++ b/.github/workflows/python-package-develop.yml @@ -2,9 +2,9 @@ name: Labelbox Python SDK Staging (Develop) on: push: - branches: [develop] + branches: [develop, v6] pull_request: - branches: [develop] + branches: [develop, v6] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -37,10 +37,10 @@ jobs: uses: actions/checkout@v2 with: ref: ${{ github.event.repository.default_branch }} - + - name: Fetch tags run: git fetch --tags - + - name: Get Latest SDK versions id: get_sdk_versions run: | @@ -50,9 +50,9 @@ jobs: exit 1 fi echo "sdk_versions=$sdk_versions" - echo "sdk_versions=$sdk_versions" >> $GITHUB_OUTPUT + echo "sdk_versions=$sdk_versions" >> $GITHUB_OUTPUT build: - needs: ['path-filter', 'get_sdk_versions'] + needs: ["path-filter", "get_sdk_versions"] if: ${{ needs.path-filter.outputs.labelbox == 'true' }} strategy: fail-fast: false @@ -84,15 +84,15 @@ jobs: da-test-key: ${{ matrix.da-test-key }} sdk-version: ${{ matrix.sdk-version }} fixture-profile: true - test-env: 'staging' + test-env: "staging" secrets: inherit test-pypi: runs-on: ubuntu-latest - needs: ['path-filter'] + needs: ["path-filter"] continue-on-error: true - environment: + environment: name: Test-PyPI - url: 'https://test.pypi.org/p/labelbox-test' + url: "https://test.pypi.org/p/labelbox-test" permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write @@ -103,7 +103,7 @@ jobs: - uses: ./.github/actions/python-package-shared-setup with: rye-version: ${{ vars.RYE_VERSION }} - python-version: '3.8' + python-version: "3.8" - name: Create build id: create-build working-directory: libs/labelbox @@ -119,8 +119,8 @@ jobs: packages-dir: dist/ repository-url: https://test.pypi.org/legacy/ test-container: - runs-on: ubuntu-latest - needs: ['path-filter'] + runs-on: ubuntu-latest + needs: ["path-filter"] continue-on-error: true permissions: # IMPORTANT: this permission is mandatory for trusted publishing @@ -138,14 +138,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - + - name: Log in to the Container registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + - name: Build and push (Develop) if: github.event_name == 'push' uses: docker/build-push-action@v5 @@ -154,7 +154,7 @@ jobs: file: ./libs/labelbox/Dockerfile github-token: ${{ secrets.GITHUB_TOKEN }} push: true - + platforms: | linux/amd64 linux/arm64 @@ -162,7 +162,7 @@ jobs: tags: | ${{ env.CONTAINER_IMAGE }}:develop ${{ env.CONTAINER_IMAGE }}:${{ github.sha }} - + - name: Build and push (Pull Request) if: github.event_name == 'pull_request' uses: docker/build-push-action@v5 @@ -171,10 +171,10 @@ jobs: file: ./libs/labelbox/Dockerfile github-token: ${{ secrets.GITHUB_TOKEN }} push: true - + platforms: | linux/amd64 linux/arm64 tags: | - ${{ env.CONTAINER_IMAGE }}:${{ github.sha }} \ No newline at end of file + ${{ env.CONTAINER_IMAGE }}:${{ github.sha }} From 5f4f73356a991da613be6f4e98ef321f275199b8 Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:19:09 -0500 Subject: [PATCH 4/8] fixed linting tests --- .../schema/labeling_service_dashboard.py | 38 ++++--- .../unit/test_labeling_service_dashboard.py | 102 +++++++++--------- 2 files changed, 76 insertions(+), 64 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/labeling_service_dashboard.py b/libs/labelbox/src/labelbox/schema/labeling_service_dashboard.py index 2052897f6..c5e1fa11e 100644 --- a/libs/labelbox/src/labelbox/schema/labeling_service_dashboard.py +++ b/libs/labelbox/src/labelbox/schema/labeling_service_dashboard.py @@ -84,7 +84,8 @@ def __init__(self, **kwargs): super().__init__(**kwargs) if not self.client.enable_experimental: raise RuntimeError( - "Please enable experimental in client to use LabelingService") + "Please enable experimental in client to use LabelingService" + ) @property def service_type(self): @@ -97,20 +98,28 @@ def service_type(self): if self.editor_task_type is None: return sentence_case(self.media_type.value) - if (self.editor_task_type == EditorTaskType.OfflineModelChatEvaluation - and self.media_type == MediaType.Conversational): + if ( + self.editor_task_type == EditorTaskType.OfflineModelChatEvaluation + and self.media_type == MediaType.Conversational + ): return "Offline chat evaluation" - if (self.editor_task_type == EditorTaskType.ModelChatEvaluation and - self.media_type == MediaType.Conversational): + if ( + self.editor_task_type == EditorTaskType.ModelChatEvaluation + and self.media_type == MediaType.Conversational + ): return "Live chat evaluation" - if (self.editor_task_type == EditorTaskType.ResponseCreation and - self.media_type == MediaType.Text): + if ( + self.editor_task_type == EditorTaskType.ResponseCreation + and self.media_type == MediaType.Text + ): return "Response creation" - if (self.media_type == MediaType.LLMPromptCreation or - self.media_type == MediaType.LLMPromptResponseCreation): + if ( + self.media_type == MediaType.LLMPromptCreation + or self.media_type == MediaType.LLMPromptResponseCreation + ): return "Prompt response creation" return sentence_case(self.media_type.value) @@ -154,7 +163,8 @@ def get_all( pageInfo { endCursor } } } - """) + """ + ) else: template = Template( """query SearchProjectsPyApi($$first: Int, $$from: String) { @@ -164,11 +174,13 @@ def get_all( pageInfo { endCursor } } } - """) + """ + ) query_str = template.substitute( labeling_dashboard_selections=GRAPHQL_QUERY_SELECTIONS, search_query=build_search_filter(search_query) - if search_query else None, + if search_query + else None, ) params: Dict[str, Union[str, int]] = {} @@ -186,7 +198,7 @@ def convert_to_labeling_service_dashboard(client, data): experimental=True, ) - @model_validator(mode='before') + @model_validator(mode="before") def convert_boost_data(cls, data): if "boostStatus" in data: data["status"] = LabelingServiceStatus(data.pop("boostStatus")) diff --git a/libs/labelbox/tests/unit/test_labeling_service_dashboard.py b/libs/labelbox/tests/unit/test_labeling_service_dashboard.py index 8ecdef2f1..061efbadf 100644 --- a/libs/labelbox/tests/unit/test_labeling_service_dashboard.py +++ b/libs/labelbox/tests/unit/test_labeling_service_dashboard.py @@ -5,23 +5,23 @@ def test_no_tasks_remaining_count(): labeling_service_dashboard_data = { - 'id': 'cm0eeo4c301lg07061phfhva0', - 'name': 'TestStatus', - 'boostRequestedAt': '2024-08-28T22:08:07.446Z', - 'boostUpdatedAt': '2024-08-28T22:08:07.446Z', - 'boostRequestedBy': None, - 'boostStatus': 'SET_UP', - 'dataRowsCount': 0, - 'dataRowsDoneCount': 0, - 'dataRowsInReviewCount': 0, - 'dataRowsInReworkCount': 0, - 'tasksTotalCount': 0, - 'tasksCompletedCount': 0, - 'tasksRemainingCount': 0, - 'mediaType': 'image', - 'editorTaskType': None, - 'tags': [], - 'client': MagicMock() + "id": "cm0eeo4c301lg07061phfhva0", + "name": "TestStatus", + "boostRequestedAt": "2024-08-28T22:08:07.446Z", + "boostUpdatedAt": "2024-08-28T22:08:07.446Z", + "boostRequestedBy": None, + "boostStatus": "SET_UP", + "dataRowsCount": 0, + "dataRowsDoneCount": 0, + "dataRowsInReviewCount": 0, + "dataRowsInReworkCount": 0, + "tasksTotalCount": 0, + "tasksCompletedCount": 0, + "tasksRemainingCount": 0, + "mediaType": "image", + "editorTaskType": None, + "tags": [], + "client": MagicMock(), } lsd = LabelingServiceDashboard(**labeling_service_dashboard_data) assert lsd.tasks_remaining_count is None @@ -29,23 +29,23 @@ def test_no_tasks_remaining_count(): def test_tasks_remaining_count_exists(): labeling_service_dashboard_data = { - 'id': 'cm0eeo4c301lg07061phfhva0', - 'name': 'TestStatus', - 'boostRequestedAt': '2024-08-28T22:08:07.446Z', - 'boostUpdatedAt': '2024-08-28T22:08:07.446Z', - 'boostRequestedBy': None, - 'boostStatus': 'SET_UP', - 'dataRowsCount': 0, - 'dataRowsDoneCount': 0, - 'dataRowsInReviewCount': 0, - 'dataRowsInReworkCount': 0, - 'tasksTotalCount': 0, - 'tasksCompletedCount': 0, - 'tasksRemainingCount': 1, - 'mediaType': 'image', - 'editorTaskType': None, - 'tags': [], - 'client': MagicMock() + "id": "cm0eeo4c301lg07061phfhva0", + "name": "TestStatus", + "boostRequestedAt": "2024-08-28T22:08:07.446Z", + "boostUpdatedAt": "2024-08-28T22:08:07.446Z", + "boostRequestedBy": None, + "boostStatus": "SET_UP", + "dataRowsCount": 0, + "dataRowsDoneCount": 0, + "dataRowsInReviewCount": 0, + "dataRowsInReworkCount": 0, + "tasksTotalCount": 0, + "tasksCompletedCount": 0, + "tasksRemainingCount": 1, + "mediaType": "image", + "editorTaskType": None, + "tags": [], + "client": MagicMock(), } lsd = LabelingServiceDashboard(**labeling_service_dashboard_data) assert lsd.tasks_remaining_count == 1 @@ -53,23 +53,23 @@ def test_tasks_remaining_count_exists(): def test_tasks_total_no_tasks_remaining_count(): labeling_service_dashboard_data = { - 'id': 'cm0eeo4c301lg07061phfhva0', - 'name': 'TestStatus', - 'boostRequestedAt': '2024-08-28T22:08:07.446Z', - 'boostUpdatedAt': '2024-08-28T22:08:07.446Z', - 'boostRequestedBy': None, - 'boostStatus': 'SET_UP', - 'dataRowsCount': 0, - 'dataRowsDoneCount': 0, - 'dataRowsInReviewCount': 1, - 'dataRowsInReworkCount': 0, - 'tasksTotalCount': 1, - 'tasksCompletedCount': 0, - 'tasksRemainingCount': 0, - 'mediaType': 'image', - 'editorTaskType': None, - 'tags': [], - 'client': MagicMock() + "id": "cm0eeo4c301lg07061phfhva0", + "name": "TestStatus", + "boostRequestedAt": "2024-08-28T22:08:07.446Z", + "boostUpdatedAt": "2024-08-28T22:08:07.446Z", + "boostRequestedBy": None, + "boostStatus": "SET_UP", + "dataRowsCount": 0, + "dataRowsDoneCount": 0, + "dataRowsInReviewCount": 1, + "dataRowsInReworkCount": 0, + "tasksTotalCount": 1, + "tasksCompletedCount": 0, + "tasksRemainingCount": 0, + "mediaType": "image", + "editorTaskType": None, + "tags": [], + "client": MagicMock(), } lsd = LabelingServiceDashboard(**labeling_service_dashboard_data) assert lsd.tasks_remaining_count == 0 From 54909b4184778d323d348530ce3cbe41418ac4a0 Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:31:28 -0500 Subject: [PATCH 5/8] removed formatting --- .github/workflows/lbox-develop.yml | 28 ++++++++-------- .github/workflows/python-package-develop.yml | 34 ++++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/lbox-develop.yml b/.github/workflows/lbox-develop.yml index 497ef4afb..efb642f66 100644 --- a/.github/workflows/lbox-develop.yml +++ b/.github/workflows/lbox-develop.yml @@ -27,7 +27,7 @@ jobs: - uses: dorny/paths-filter@v3 id: filter with: - list-files: "json" + list-files: 'json' filters: | lbox: - 'libs/lbox*/**' @@ -36,16 +36,16 @@ jobs: with: files-changed: ${{ steps.filter.outputs.lbox_files }} build: - needs: ["path-filter"] + needs: ['path-filter'] if: ${{ needs.path-filter.outputs.lbox == 'true' }} runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - include: ${{ fromJSON(needs.path-filter.outputs.test-matrix) }} + matrix: + include: ${{ fromJSON(needs.path-filter.outputs.test-matrix) }} concurrency: group: lbox-staging-${{ matrix.python-version }}-${{ matrix.package }} - cancel-in-progress: false + cancel-in-progress: false steps: - uses: actions/checkout@v4 with: @@ -67,19 +67,19 @@ jobs: env: LABELBOX_TEST_API_KEY: ${{ secrets[matrix.api-key] }} DA_GCP_LABELBOX_API_KEY: ${{ secrets[matrix.da-test-key] }} - LABELBOX_TEST_ENVIRON: "staging" + LABELBOX_TEST_ENVIRON: 'staging' run: rye run integration test-pypi: runs-on: ubuntu-latest - needs: ["build", "path-filter"] + needs: ['build', 'path-filter'] if: ${{ needs.path-filter.outputs.lbox == 'true' }} strategy: fail-fast: false - matrix: + matrix: include: ${{ fromJSON(needs.path-filter.outputs.package-matrix) }} - environment: + environment: name: Test-PyPI-${{ matrix.package }} - url: "https://test.pypi.org/p/${{ matrix.package }}" + url: 'https://test.pypi.org/p/${{ matrix.package }}' permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write @@ -90,7 +90,7 @@ jobs: - uses: ./.github/actions/python-package-shared-setup with: rye-version: ${{ vars.RYE_VERSION }} - python-version: "3.8" + python-version: '3.8' - name: Create build id: create-build working-directory: libs/${{ matrix.package }} @@ -107,11 +107,11 @@ jobs: repository-url: https://test.pypi.org/legacy/ test-container: runs-on: ubuntu-latest - needs: ["build", "path-filter"] + needs: ['build', 'path-filter'] if: ${{ needs.path-filter.outputs.lbox == 'true' }} strategy: fail-fast: false - matrix: + matrix: include: ${{ fromJSON(needs.path-filter.outputs.package-matrix) }} permissions: # IMPORTANT: this permission is mandatory for trusted publishing @@ -163,4 +163,4 @@ jobs: - name: Build and push (Pull Request) Output if: github.event_name == 'pull_request' run: | - echo "ghcr.io/labelbox/${{ matrix.package }}:${{ github.sha }}" >> "$GITHUB_STEP_SUMMARY" + echo "ghcr.io/labelbox/${{ matrix.package }}:${{ github.sha }}" >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file diff --git a/.github/workflows/python-package-develop.yml b/.github/workflows/python-package-develop.yml index dcec45103..769d04c74 100644 --- a/.github/workflows/python-package-develop.yml +++ b/.github/workflows/python-package-develop.yml @@ -37,10 +37,10 @@ jobs: uses: actions/checkout@v2 with: ref: ${{ github.event.repository.default_branch }} - + - name: Fetch tags run: git fetch --tags - + - name: Get Latest SDK versions id: get_sdk_versions run: | @@ -50,9 +50,9 @@ jobs: exit 1 fi echo "sdk_versions=$sdk_versions" - echo "sdk_versions=$sdk_versions" >> $GITHUB_OUTPUT + echo "sdk_versions=$sdk_versions" >> $GITHUB_OUTPUT build: - needs: ["path-filter", "get_sdk_versions"] + needs: ['path-filter', 'get_sdk_versions'] if: ${{ needs.path-filter.outputs.labelbox == 'true' }} strategy: fail-fast: false @@ -84,15 +84,15 @@ jobs: da-test-key: ${{ matrix.da-test-key }} sdk-version: ${{ matrix.sdk-version }} fixture-profile: true - test-env: "staging" + test-env: 'staging' secrets: inherit test-pypi: runs-on: ubuntu-latest - needs: ["path-filter"] + needs: ['path-filter'] continue-on-error: true - environment: + environment: name: Test-PyPI - url: "https://test.pypi.org/p/labelbox-test" + url: 'https://test.pypi.org/p/labelbox-test' permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write @@ -103,7 +103,7 @@ jobs: - uses: ./.github/actions/python-package-shared-setup with: rye-version: ${{ vars.RYE_VERSION }} - python-version: "3.8" + python-version: '3.8' - name: Create build id: create-build working-directory: libs/labelbox @@ -119,8 +119,8 @@ jobs: packages-dir: dist/ repository-url: https://test.pypi.org/legacy/ test-container: - runs-on: ubuntu-latest - needs: ["path-filter"] + runs-on: ubuntu-latest + needs: ['path-filter'] continue-on-error: true permissions: # IMPORTANT: this permission is mandatory for trusted publishing @@ -138,14 +138,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - + - name: Log in to the Container registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + - name: Build and push (Develop) if: github.event_name == 'push' uses: docker/build-push-action@v5 @@ -154,7 +154,7 @@ jobs: file: ./libs/labelbox/Dockerfile github-token: ${{ secrets.GITHUB_TOKEN }} push: true - + platforms: | linux/amd64 linux/arm64 @@ -162,7 +162,7 @@ jobs: tags: | ${{ env.CONTAINER_IMAGE }}:develop ${{ env.CONTAINER_IMAGE }}:${{ github.sha }} - + - name: Build and push (Pull Request) if: github.event_name == 'pull_request' uses: docker/build-push-action@v5 @@ -171,10 +171,10 @@ jobs: file: ./libs/labelbox/Dockerfile github-token: ${{ secrets.GITHUB_TOKEN }} push: true - + platforms: | linux/amd64 linux/arm64 tags: | - ${{ env.CONTAINER_IMAGE }}:${{ github.sha }} + ${{ env.CONTAINER_IMAGE }}:${{ github.sha }} \ No newline at end of file From 1cc59590f80e3192b3b09c435b229f058d411637 Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:41:45 -0500 Subject: [PATCH 6/8] fix linter --- libs/labelbox/src/labelbox/schema/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/labelbox/src/labelbox/schema/__init__.py b/libs/labelbox/src/labelbox/schema/__init__.py index 03327e0d1..d6b74de68 100644 --- a/libs/labelbox/src/labelbox/schema/__init__.py +++ b/libs/labelbox/src/labelbox/schema/__init__.py @@ -1,5 +1,4 @@ import labelbox.schema.asset_attachment -import labelbox.schema.bulk_import_request import labelbox.schema.annotation_import import labelbox.schema.benchmark import labelbox.schema.data_row From 7ea16904ba2f1ce4dca0584c760def2c9ab3cb95 Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:43:14 -0500 Subject: [PATCH 7/8] fix project --- libs/labelbox/src/labelbox/schema/project.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/project.py b/libs/labelbox/src/labelbox/schema/project.py index 88153e48f..f2de4db5e 100644 --- a/libs/labelbox/src/labelbox/schema/project.py +++ b/libs/labelbox/src/labelbox/schema/project.py @@ -1079,8 +1079,7 @@ def _create_batch_async( task = self._wait_for_task(task_id) if task.status != "COMPLETE": raise LabelboxError( - "Batch was not created successfully: " - + json.dumps(task.errors) + "Batch was not created successfully: " + json.dumps(task.errors) ) return self.client.get_batch(self.uid, batch_id) From 0d2321c793adc159078738ac0356fa4baca65f17 Mon Sep 17 00:00:00 2001 From: Gabefire <33893811+Gabefire@users.noreply.github.com> Date: Tue, 17 Sep 2024 19:55:03 -0500 Subject: [PATCH 8/8] removed second bulk import library --- .../test_ndjson_validation.py | 194 ------------------ 1 file changed, 194 deletions(-) delete mode 100644 libs/labelbox/tests/data/annotation_import/test_ndjson_validation.py diff --git a/libs/labelbox/tests/data/annotation_import/test_ndjson_validation.py b/libs/labelbox/tests/data/annotation_import/test_ndjson_validation.py deleted file mode 100644 index 0ec742333..000000000 --- a/libs/labelbox/tests/data/annotation_import/test_ndjson_validation.py +++ /dev/null @@ -1,194 +0,0 @@ -from labelbox.schema.media_type import MediaType -import pytest - -from pytest_cases import parametrize, fixture_ref - -from labelbox.exceptions import MALValidationError -from labelbox.schema.bulk_import_request import ( - NDChecklist, - NDClassification, - NDMask, - NDPolygon, - NDPolyline, - NDRectangle, - NDText, - NDTextEntity, - NDTool, - _validate_ndjson, -) - -""" -- These NDlabels are apart of bulkImportReqeust and should be removed once bulk import request is removed -""" - - -def test_classification_construction(checklist_inference, text_inference): - checklist = NDClassification.build(checklist_inference[0]) - assert isinstance(checklist, NDChecklist) - text = NDClassification.build(text_inference[0]) - assert isinstance(text, NDText) - - -@parametrize( - "inference, expected_type", - [ - (fixture_ref("polygon_inference"), NDPolygon), - (fixture_ref("rectangle_inference"), NDRectangle), - (fixture_ref("line_inference"), NDPolyline), - (fixture_ref("entity_inference"), NDTextEntity), - (fixture_ref("segmentation_inference"), NDMask), - (fixture_ref("segmentation_inference_rle"), NDMask), - (fixture_ref("segmentation_inference_png"), NDMask), - ], -) -def test_tool_construction(inference, expected_type): - assert isinstance(NDTool.build(inference[0]), expected_type) - - -def no_tool(text_inference, module_project): - pred = text_inference[0].copy() - # Missing key - del pred["answer"] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - -@pytest.mark.parametrize("configured_project", [MediaType.Text], indirect=True) -def test_invalid_text(text_inference, configured_project): - # and if it is not a string - pred = text_inference[0].copy() - # Extra and wrong key - del pred["answer"] - pred["answers"] = [] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], configured_project) - del pred["answers"] - - # Invalid type - pred["answer"] = [] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], configured_project) - - # Invalid type - pred["answer"] = None - with pytest.raises(MALValidationError): - _validate_ndjson([pred], configured_project) - - -def test_invalid_checklist_item(checklist_inference, module_project): - # Only two points - pred = checklist_inference[0].copy() - pred["answers"] = [pred["answers"][0], pred["answers"][0]] - # Duplicate schema ids - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - pred["answers"] = [{"name": "asdfg"}] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - pred["answers"] = [{"schemaId": "1232132132"}] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - pred["answers"] = [{}] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - pred["answers"] = [] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - del pred["answers"] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - -def test_invalid_polygon(polygon_inference, module_project): - # Only two points - pred = polygon_inference[0].copy() - pred["polygon"] = [{"x": 100, "y": 100}, {"x": 200, "y": 200}] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - -@pytest.mark.parametrize("configured_project", [MediaType.Text], indirect=True) -def test_incorrect_entity(entity_inference, configured_project): - entity = entity_inference[0].copy() - # Location cannot be a list - entity["location"] = [0, 10] - with pytest.raises(MALValidationError): - _validate_ndjson([entity], configured_project) - - entity["location"] = {"start": -1, "end": 5} - with pytest.raises(MALValidationError): - _validate_ndjson([entity], configured_project) - - entity["location"] = {"start": 15, "end": 5} - with pytest.raises(MALValidationError): - _validate_ndjson([entity], configured_project) - - -@pytest.mark.skip( - "Test wont work/fails randomly since projects have to have a media type and could be missing features from prediction list" -) -def test_all_validate_json(module_project, predictions): - # Predictions contains one of each type of prediction. - # These should be properly formatted and pass. - _validate_ndjson(predictions[0], module_project) - - -def test_incorrect_line(line_inference, module_project): - line = line_inference[0].copy() - line["line"] = [line["line"][0]] # Just one point - with pytest.raises(MALValidationError): - _validate_ndjson([line], module_project) - - -def test_incorrect_rectangle(rectangle_inference, module_project): - del rectangle_inference[0]["bbox"]["top"] - with pytest.raises(MALValidationError): - _validate_ndjson([rectangle_inference], module_project) - - -def test_duplicate_tools(rectangle_inference, module_project): - pred = rectangle_inference[0].copy() - pred["polygon"] = [{"x": 100, "y": 100}, {"x": 200, "y": 200}] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - -def test_invalid_feature_schema(module_project, rectangle_inference): - pred = rectangle_inference[0].copy() - pred["schemaId"] = "blahblah" - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - -def test_name_only_feature_schema(module_project, rectangle_inference): - pred = rectangle_inference[0].copy() - _validate_ndjson([pred], module_project) - - -def test_schema_id_only_feature_schema(module_project, rectangle_inference): - pred = rectangle_inference[0].copy() - del pred["name"] - ontology = module_project.ontology().normalized["tools"] - for tool in ontology: - if tool["name"] == "bbox": - feature_schema_id = tool["featureSchemaId"] - pred["schemaId"] = feature_schema_id - _validate_ndjson([pred], module_project) - - -def test_missing_feature_schema(module_project, rectangle_inference): - pred = rectangle_inference[0].copy() - del pred["name"] - with pytest.raises(MALValidationError): - _validate_ndjson([pred], module_project) - - -@pytest.mark.parametrize("configured_project", [MediaType.Video], indirect=True) -def test_video_upload(video_checklist_inference, configured_project): - pred = video_checklist_inference[0].copy() - _validate_ndjson([pred], configured_project)