From 18911a16de48e8a7d0a175b2f47755b28d4954b8 Mon Sep 17 00:00:00 2001 From: Paul Noirel Date: Wed, 27 Apr 2022 21:10:27 +0100 Subject: [PATCH 1/6] Add MediaType support to Project --- labelbox/client.py | 14 +++++++++ labelbox/orm/db_object.py | 2 +- labelbox/schema/project.py | 51 +++++++++++++++++++++++++++++-- tests/integration/test_project.py | 42 ++++++++++++++++++++++--- 4 files changed, 101 insertions(+), 8 deletions(-) diff --git a/labelbox/client.py b/labelbox/client.py index 49d3d5d1b..a653ba999 100644 --- a/labelbox/client.py +++ b/labelbox/client.py @@ -611,6 +611,20 @@ def create_project(self, **kwargs) -> Project: InvalidAttributeError: If the Project type does not contain any of the attribute names given in kwargs. """ + media_type = kwargs.get("media_type") + if media_type: + if isinstance(media_type, Project.MediaType + ) and media_type is not Project.MediaType.Unknown: + kwargs["media_type"] = media_type.value + else: + media_types = [ + item for item in Project.MediaType.__members__ + if item != "Unknown" + ] + raise TypeError( + f"{media_type} is not a supported type. Please use any of {media_types} from the {type(media_type).__name__} enumeration." + ) + return self._create(Entity.Project, kwargs) def get_roles(self) -> List[Role]: diff --git a/labelbox/orm/db_object.py b/labelbox/orm/db_object.py index 41bfd7f5a..cc10088c8 100644 --- a/labelbox/orm/db_object.py +++ b/labelbox/orm/db_object.py @@ -69,7 +69,7 @@ def _set_field_values(self, field_values): "Failed to convert value '%s' to datetime for " "field %s", value, field) elif isinstance(field.field_type, Field.EnumType): - value = field.field_type.enum_cls[value] + value = field.field_type.enum_cls(value) setattr(self, field.name, value) def __repr__(self): diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index bc7059375..4b56fd0e9 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -1,4 +1,4 @@ -import enum +from enum import Enum import json import logging import time @@ -63,6 +63,37 @@ class Project(DbObject, Updateable, Deletable): benchmarks (Relationship): `ToMany` relationship to Benchmark ontology (Relationship): `ToOne` relationship to Ontology """ + + class MediaType(Enum): + """add DOCUMENT, GEOSPATIAL_TILE, SIMPLE_TILE to match the UI choices""" + Audio = "AUDIO" + Conversational = "CONVERSATIONAL" + Dicom = "DICOM" + Document = "PDF" + Geospatial_Tile = "TMS_GEO" + Image = "IMAGE" + Json = "JSON" + Pdf = "PDF" + Simple_Tile = "TMS_SIMPLE" + Text = "TEXT" + Tms_Geo = "TMS_GEO" + Tms_Simple = "TMS_SIMPLE" + Video = "VIDEO" + Unknown = "UNKNOWN" + + @classmethod + def _missing_(cls, name): + """Handle missing null data types for projects + created without setting allowedMediaType""" + # return Project.MediaType.UNKNOWN + + if name is None: + return cls.Unknown + + for member in cls.__members__: + if member.name == name.upper(): + return member + name = Field.String("name") description = Field.String("description") updated_at = Field.DateTime("updated_at") @@ -71,6 +102,8 @@ class Project(DbObject, Updateable, Deletable): last_activity_time = Field.DateTime("last_activity_time") auto_audit_number_of_labels = Field.Int("auto_audit_number_of_labels") auto_audit_percentage = Field.Float("auto_audit_percentage") + # Bind data_type and allowedMediaTYpe using the GraphQL type MediaType + media_type = Field.Enum(MediaType, "media_type", "allowedMediaType") # Relationships datasets = Relationship.ToMany("Dataset", True) @@ -85,7 +118,7 @@ class Project(DbObject, Updateable, Deletable): benchmarks = Relationship.ToMany("Benchmark", False) ontology = Relationship.ToOne("Ontology", True) - class QueueMode(enum.Enum): + class QueueMode(Enum): Batch = "Batch" Dataset = "Dataset" @@ -94,6 +127,20 @@ def update(self, **kwargs): if mode: self._update_queue_mode(mode) + media_type = kwargs.get("media_type") + if media_type: + if isinstance(media_type, Project.MediaType + ) and media_type != Project.MediaType.Unknown: + kwargs["media_type"] = media_type.value + else: + media_types = [ + item for item in Project.MediaType.__members__ + if item != "Unknown" + ] + raise TypeError( + f"{media_type} is not a supported type. Please use any of {media_types} from the {type(media_type).__name__} enumeration." + ) + return super().update(**kwargs) def members(self) -> PaginatedCollection: diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index 9112a5298..5095bb80a 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -1,4 +1,3 @@ -import json import time import os @@ -44,11 +43,19 @@ def test_project(client, rand_gen): assert set(final) == set(before) -@pytest.mark.skip( - reason="this will fail if run multiple times, limit is defaulted to 3 per org" - "add this back in when either all test orgs have unlimited, or we delete all tags befoer running" -) def test_update_project_resource_tags(client, rand_gen): + + def delete_tag(tag_id: str): + """Deletes a tag given the tag uid. Currently internal use only so this is not public""" + res = client.execute( + """mutation deleteResourceTagPyApi($tag_id: String!) { + deleteResourceTag(input: {id: $tag_id}) { + id + } + } + """, {"tag_id": tag_id}) + return res + before = list(client.get_projects()) for o in before: assert isinstance(o, Project) @@ -92,6 +99,9 @@ def test_update_project_resource_tags(client, rand_gen): assert len(project_resource_tag) == 1 assert project_resource_tag[0].uid == tagA.uid + delete_tag(tagA.uid) + delete_tag(tagB.uid) + def test_project_filtering(client, rand_gen): name_1 = rand_gen(str) @@ -191,3 +201,25 @@ def test_queue_mode(configured_project: Project): ) == configured_project.QueueMode.Dataset configured_project.update(queue_mode=configured_project.QueueMode.Batch) assert configured_project.queue_mode() == configured_project.QueueMode.Batch + + +def test_media_type(client, configured_project: Project, rand_gen): + # Existing project + assert configured_project.media_type is None or isinstance( + configured_project.media_type, Project.MediaType) + + # No media_type + project = client.create_project(name=rand_gen(str)) + assert project.media_type == Project.MediaType.Unknown + project.update(media_type=Project.MediaType.Image) + assert project.media_type == Project.MediaType.Image + project.delete() + + for media_type in Project.MediaType: + if media_type == Project.MediaType.Unknown: + continue + + project = client.create_project(name=rand_gen(str), + media_type=media_type) + assert project.media_type == media_type + project.delete() From 7e81564725d7e03b5bfc6b484fd891d693b23a72 Mon Sep 17 00:00:00 2001 From: Paul Noirel Date: Fri, 29 Apr 2022 19:10:53 +0100 Subject: [PATCH 2/6] Place MediaType in a dedicated file --- examples/basics/projects.ipynb | 91 +++++++++++++++++-------------- labelbox/client.py | 15 ++--- labelbox/schema/media_type.py | 46 ++++++++++++++++ labelbox/schema/project.py | 44 ++------------- tests/integration/test_project.py | 21 +++---- 5 files changed, 115 insertions(+), 102 deletions(-) create mode 100644 labelbox/schema/media_type.py diff --git a/examples/basics/projects.ipynb b/examples/basics/projects.ipynb index 7e918dbab..421fab49b 100644 --- a/examples/basics/projects.ipynb +++ b/examples/basics/projects.ipynb @@ -2,15 +2,16 @@ "cells": [ { "cell_type": "markdown", + "metadata": {}, "source": [ "\n", " \n", "" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "\n", "\n", "" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Projects" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "* A project can be thought of as a specific labeling task on a set of labels\n", "* That set of labels is defined by the datasets attached to the project\n", @@ -41,123 +42,129 @@ "\n", "** Note that there is a lot of advanced usage that is not covered in this notebook. See project_setup for those functions.\n", "* Also note that deprecated functions are not explained here." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 1, + "metadata": {}, + "outputs": [], "source": [ "!pip install labelbox" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 2, + "metadata": {}, + "outputs": [], "source": [ "from labelbox import Client\n", + "from labelbox.schema.media_type import MediaType\n", "import os" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# API Key and Client\n", "Provide a valid api key below in order to properly connect to the Labelbox Client." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 5, + "metadata": {}, + "outputs": [], "source": [ "# Add your api key\n", "API_KEY = None\n", "client = Client(api_key=API_KEY)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Create\n" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 6, + "metadata": {}, + "outputs": [], "source": [ - "# Creates an empty project\n", + "# Creates an empty project without a media type\n", "project = client.create_project(name=\"my-test-project\",\n", - " description=\"a description\")" - ], - "outputs": [], - "metadata": {} + " description=\"a description\")\n", + "\n", + "# Creates an empty project a media type\n", + "project = client.create_project(name=\"my-test-project\",\n", + " description=\"a description\",\n", + " media_type=MediaType.Image)" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Read" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# Note the project is not setup (so a lot of these fiels are empty). Follow the project setup workflow\n", "print(\"Project is not setup yet:\", project.setup_complete is None)\n", "print(\"Project name:\", project.name)\n", "print(\"Project description:\", project.description)\n", + "print(\"Media Type:\", project.media_type)\n", "print(\"Dataset:\", list(project.datasets()))\n", "print(\"Ontology:\", project.ontology().normalized)\n", "print(\"Benchmarks:\", project.benchmarks())" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Update\n", "\n" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# Attach dataset\n", "ds = client.create_dataset(name=\"test-ds\")\n", "project.datasets.connect(ds)\n", "print([ds.name for ds in project.datasets()])\n", "ds.delete()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Delete" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 9, + "metadata": {}, + "outputs": [], "source": [ "project.delete()" - ], - "outputs": [], - "metadata": {} + ] } ], "metadata": { @@ -181,4 +188,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/labelbox/client.py b/labelbox/client.py index a653ba999..33de6555b 100644 --- a/labelbox/client.py +++ b/labelbox/client.py @@ -32,6 +32,8 @@ from labelbox.schema.project import Project from labelbox.schema.role import Role +from labelbox.schema.media_type import MediaType + logger = logging.getLogger(__name__) _LABELBOX_API_KEY = "LABELBOX_API_KEY" @@ -613,17 +615,12 @@ def create_project(self, **kwargs) -> Project: """ media_type = kwargs.get("media_type") if media_type: - if isinstance(media_type, Project.MediaType - ) and media_type is not Project.MediaType.Unknown: + if MediaType.is_accepted(media_type): kwargs["media_type"] = media_type.value else: - media_types = [ - item for item in Project.MediaType.__members__ - if item != "Unknown" - ] - raise TypeError( - f"{media_type} is not a supported type. Please use any of {media_types} from the {type(media_type).__name__} enumeration." - ) + raise TypeError(f"{media_type} is not a valid media type. Use" + f" any of {MediaType.get_accepted_members()}" + " from MediaType. Example: MediaType.Image.") return self._create(Entity.Project, kwargs) diff --git a/labelbox/schema/media_type.py b/labelbox/schema/media_type.py new file mode 100644 index 000000000..76b8f6bd5 --- /dev/null +++ b/labelbox/schema/media_type.py @@ -0,0 +1,46 @@ +from enum import Enum + + +class MediaType(Enum): + """add DOCUMENT, GEOSPATIAL_TILE, SIMPLE_TILE to match the UI choices""" + Audio = "AUDIO" + Conversational = "CONVERSATIONAL" + Dicom = "DICOM" + Document = "PDF" + Geospatial_Tile = "TMS_GEO" + Image = "IMAGE" + Json = "JSON" + Pdf = "PDF" + Simple_Tile = "TMS_SIMPLE" + Text = "TEXT" + Tms_Geo = "TMS_GEO" + Tms_Simple = "TMS_SIMPLE" + Video = "VIDEO" + Unknown = "UNKNOWN" + Unsupported = "UNSUPPORTED" + + @classmethod + def _missing_(cls, name): + """Handle missing null data types for projects + created without setting allowedMediaType + Handle upper case names for compatibility with + the GraphQL""" + + if name is None: + return cls.Unknown + + for member in cls.__members__: + if member.name == name.upper(): + return member + + @classmethod + def is_accepted(cls, value): + return isinstance(value, + cls) and value not in [cls.Unknown, cls.Unsupported] + + @classmethod + def get_accepted_members(cls): + return [ + item for item in cls.__members__ + if item not in ["Unknown", "Unsupported"] + ] diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index 4b56fd0e9..affcd81f9 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -19,6 +19,7 @@ from labelbox.orm.model import Entity, Field, Relationship from labelbox.pagination import PaginatedCollection from labelbox.schema.resource_tag import ResourceTag +from labelbox.schema.media_type import MediaType if TYPE_CHECKING: from labelbox import BulkImportRequest @@ -64,36 +65,6 @@ class Project(DbObject, Updateable, Deletable): ontology (Relationship): `ToOne` relationship to Ontology """ - class MediaType(Enum): - """add DOCUMENT, GEOSPATIAL_TILE, SIMPLE_TILE to match the UI choices""" - Audio = "AUDIO" - Conversational = "CONVERSATIONAL" - Dicom = "DICOM" - Document = "PDF" - Geospatial_Tile = "TMS_GEO" - Image = "IMAGE" - Json = "JSON" - Pdf = "PDF" - Simple_Tile = "TMS_SIMPLE" - Text = "TEXT" - Tms_Geo = "TMS_GEO" - Tms_Simple = "TMS_SIMPLE" - Video = "VIDEO" - Unknown = "UNKNOWN" - - @classmethod - def _missing_(cls, name): - """Handle missing null data types for projects - created without setting allowedMediaType""" - # return Project.MediaType.UNKNOWN - - if name is None: - return cls.Unknown - - for member in cls.__members__: - if member.name == name.upper(): - return member - name = Field.String("name") description = Field.String("description") updated_at = Field.DateTime("updated_at") @@ -129,17 +100,12 @@ def update(self, **kwargs): media_type = kwargs.get("media_type") if media_type: - if isinstance(media_type, Project.MediaType - ) and media_type != Project.MediaType.Unknown: + if MediaType.is_accepted(media_type): kwargs["media_type"] = media_type.value else: - media_types = [ - item for item in Project.MediaType.__members__ - if item != "Unknown" - ] - raise TypeError( - f"{media_type} is not a supported type. Please use any of {media_types} from the {type(media_type).__name__} enumeration." - ) + raise TypeError(f"{media_type} is not a valid media type. Use" + f" any of {MediaType.get_accepted_members()}" + " from MediaType. Example: MediaType.Image.") return super().update(**kwargs) diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index 5095bb80a..7fcee0675 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -6,6 +6,7 @@ from labelbox import Project, LabelingFrontend from labelbox.exceptions import InvalidQueryError +from labelbox.schema.media_type import MediaType def test_project(client, rand_gen): @@ -204,22 +205,18 @@ def test_queue_mode(configured_project: Project): def test_media_type(client, configured_project: Project, rand_gen): - # Existing project - assert configured_project.media_type is None or isinstance( - configured_project.media_type, Project.MediaType) + # Existing project with no media_type + assert isinstance(configured_project.media_type, MediaType) - # No media_type + # Update test project = client.create_project(name=rand_gen(str)) - assert project.media_type == Project.MediaType.Unknown - project.update(media_type=Project.MediaType.Image) - assert project.media_type == Project.MediaType.Image + project.update(media_type=MediaType.Image) + assert project.media_type == MediaType.Image project.delete() - for media_type in Project.MediaType: - if media_type == Project.MediaType.Unknown: - continue + for media_type in MediaType.get_accepted_members(): project = client.create_project(name=rand_gen(str), - media_type=media_type) - assert project.media_type == media_type + media_type=MediaType[media_type]) + assert project.media_type == MediaType[media_type] project.delete() From 74adbf3b812ca34481d36fe52c75f27288a842e2 Mon Sep 17 00:00:00 2001 From: Paul Noirel Date: Tue, 3 May 2022 15:38:02 +0100 Subject: [PATCH 3/6] Update MediaType methods' names, add media_type import --- labelbox/__init__.py | 1 + labelbox/client.py | 4 ++-- labelbox/schema/__init__.py | 1 + labelbox/schema/media_type.py | 4 ++-- labelbox/schema/project.py | 4 ++-- tests/integration/test_project.py | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/labelbox/__init__.py b/labelbox/__init__.py index a3636d46d..5312f58f7 100644 --- a/labelbox/__init__.py +++ b/labelbox/__init__.py @@ -26,3 +26,4 @@ from labelbox.schema.iam_integration import IAMIntegration from labelbox.schema.resource_tag import ResourceTag from labelbox.schema.project_resource_tag import ProjectResourceTag +from labelbox.schema.media_type import MediaType diff --git a/labelbox/client.py b/labelbox/client.py index 33de6555b..133fb4a40 100644 --- a/labelbox/client.py +++ b/labelbox/client.py @@ -615,11 +615,11 @@ def create_project(self, **kwargs) -> Project: """ media_type = kwargs.get("media_type") if media_type: - if MediaType.is_accepted(media_type): + if MediaType.is_supported(media_type): kwargs["media_type"] = media_type.value else: raise TypeError(f"{media_type} is not a valid media type. Use" - f" any of {MediaType.get_accepted_members()}" + f" any of {MediaType.get_supported_members()}" " from MediaType. Example: MediaType.Image.") return self._create(Entity.Project, kwargs) diff --git a/labelbox/schema/__init__.py b/labelbox/schema/__init__.py index 56fddda62..09d872621 100644 --- a/labelbox/schema/__init__.py +++ b/labelbox/schema/__init__.py @@ -20,3 +20,4 @@ import labelbox.schema.data_row_metadata import labelbox.schema.batch import labelbox.schema.iam_integration +import labelbox.schema.media_type diff --git a/labelbox/schema/media_type.py b/labelbox/schema/media_type.py index 76b8f6bd5..c4e139a67 100644 --- a/labelbox/schema/media_type.py +++ b/labelbox/schema/media_type.py @@ -34,12 +34,12 @@ def _missing_(cls, name): return member @classmethod - def is_accepted(cls, value): + def is_supported(cls, value): return isinstance(value, cls) and value not in [cls.Unknown, cls.Unsupported] @classmethod - def get_accepted_members(cls): + def get_supported_members(cls): return [ item for item in cls.__members__ if item not in ["Unknown", "Unsupported"] diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index affcd81f9..72a761e85 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -100,11 +100,11 @@ def update(self, **kwargs): media_type = kwargs.get("media_type") if media_type: - if MediaType.is_accepted(media_type): + if MediaType.is_supported(media_type): kwargs["media_type"] = media_type.value else: raise TypeError(f"{media_type} is not a valid media type. Use" - f" any of {MediaType.get_accepted_members()}" + f" any of {MediaType.get_supported_members()}" " from MediaType. Example: MediaType.Image.") return super().update(**kwargs) diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index 7fcee0675..2ee6955ba 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -214,7 +214,7 @@ def test_media_type(client, configured_project: Project, rand_gen): assert project.media_type == MediaType.Image project.delete() - for media_type in MediaType.get_accepted_members(): + for media_type in MediaType.get_supported_members(): project = client.create_project(name=rand_gen(str), media_type=MediaType[media_type]) From 899a7bf9f7643598fecc82f917b569b73ecd5226 Mon Sep 17 00:00:00 2001 From: ezekielemerson <91079021+ezekielemerson@users.noreply.github.com> Date: Tue, 3 May 2022 11:36:04 -0400 Subject: [PATCH 4/6] removed reviews from project attributes --- labelbox/schema/project.py | 1 - 1 file changed, 1 deletion(-) diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index 31b2af319..013b8d1ea 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -55,7 +55,6 @@ class Project(DbObject, Updateable, Deletable): datasets (Relationship): `ToMany` relationship to Dataset created_by (Relationship): `ToOne` relationship to User organization (Relationship): `ToOne` relationship to Organization - reviews (Relationship): `ToMany` relationship to Review labeling_frontend (Relationship): `ToOne` relationship to LabelingFrontend labeling_frontend_options (Relationship): `ToMany` relationship to LabelingFrontendOptions labeling_parameter_overrides (Relationship): `ToMany` relationship to LabelingParameterOverride From 73fe8cf6673d95b22f1cbe93fe55a65b2cc48312 Mon Sep 17 00:00:00 2001 From: Paul Noirel Date: Wed, 4 May 2022 20:30:05 +0100 Subject: [PATCH 5/6] Update changelog for AL-2183 - MediaType support --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa275ce5..8b1baac6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +# Version 3.21.0 (in progress) +## Added + * Projects can be created with a `media_type` + * Added `media_type` attribute to `Project` + * New `MediaType` enumeration + # Version 3.20.1 (2022-05-02) ## Updated * Ontology Classification `scope` field is only set for top level classifications From 92836a6e7e58c676fe2cbd57e8e46e922ac4326e Mon Sep 17 00:00:00 2001 From: Matt Sokoloff Date: Wed, 11 May 2022 17:18:09 -0400 Subject: [PATCH 6/6] add back mime types for bulk uploads. Bump version --- CHANGELOG.md | 5 ++++- labelbox/__init__.py | 4 ++-- labelbox/schema/dataset.py | 4 +++- tests/integration/test_dataset.py | 16 ++++++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1baac6a..befc05758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ # Changelog -# Version 3.21.0 (in progress) +# Version 3.21.0 ## Added * Projects can be created with a `media_type` * Added `media_type` attribute to `Project` * New `MediaType` enumeration +## Fix + * Added back the mimetype to datarow bulk uploads for orgs that require delegated access + # Version 3.20.1 (2022-05-02) ## Updated * Ontology Classification `scope` field is only set for top level classifications diff --git a/labelbox/__init__.py b/labelbox/__init__.py index 5857460bc..7d4e16315 100644 --- a/labelbox/__init__.py +++ b/labelbox/__init__.py @@ -1,11 +1,11 @@ name = "labelbox" -__version__ = "3.20.1" +__version__ = "3.21.0" import sys import warnings if sys.version_info < (3, 7): - warnings.warn("""Python 3.6 will no longer be actively supported + warnings.warn("""Python 3.6 will no longer be actively supported starting 06/01/2022. Please upgrade to Python 3.7 or higher.""") from labelbox.client import Client diff --git a/labelbox/schema/dataset.py b/labelbox/schema/dataset.py index 0a5d45251..6fe8ec86e 100644 --- a/labelbox/schema/dataset.py +++ b/labelbox/schema/dataset.py @@ -314,7 +314,9 @@ def convert_item(item): items = [future.result() for future in as_completed(futures)] # Prepare and upload the desciptor file data = json.dumps(items) - return self.client.upload_data(data) + return self.client.upload_data(data, + content_type="application/json", + filename="json_import.json") def data_rows_for_external_id(self, external_id, diff --git a/tests/integration/test_dataset.py b/tests/integration/test_dataset.py index e464a30a8..5fd3a6271 100644 --- a/tests/integration/test_dataset.py +++ b/tests/integration/test_dataset.py @@ -109,3 +109,19 @@ def test_data_row_export(dataset, image_url): result = list(dataset.export_data_rows()) assert len(result) == n_data_rows assert set(result) == ids + + +def test_create_descriptor_file(dataset): + import unittest.mock as mock + with mock.patch.object(dataset.client, + 'upload_data', + wraps=dataset.client.upload_data) as upload_data_spy: + dataset._create_descriptor_file(items=[{'row_data': 'some text...'}]) + upload_data_spy.assert_called() + call_args, call_kwargs = upload_data_spy.call_args_list[0][ + 0], upload_data_spy.call_args_list[0][1] + assert call_args == ('[{"data": "some text..."}]',) + assert call_kwargs == { + 'content_type': 'application/json', + 'filename': 'json_import.json' + }