From 791edb39a3f9cf1e5aa0e655ca0477dcf2e45bd1 Mon Sep 17 00:00:00 2001 From: Michal Noszczak Date: Wed, 3 Apr 2024 10:00:59 +0000 Subject: [PATCH 1/6] Make export_v2 methods use streamable backend --- labelbox/schema/catalog.py | 17 +++++++---- labelbox/schema/data_row.py | 29 ++++++++++-------- labelbox/schema/dataset.py | 19 +++++++----- labelbox/schema/export_task.py | 54 +++++++++++++++++++++++++++++++++- labelbox/schema/model_run.py | 17 +++++++---- labelbox/schema/project.py | 17 +++++++---- labelbox/schema/slice.py | 19 +++++++----- 7 files changed, 127 insertions(+), 45 deletions(-) diff --git a/labelbox/schema/catalog.py b/labelbox/schema/catalog.py index 6cbc6adf8..d34cbf22a 100644 --- a/labelbox/schema/catalog.py +++ b/labelbox/schema/catalog.py @@ -23,7 +23,7 @@ def export_v2( task_name: Optional[str] = None, filters: Union[CatalogExportFilters, Dict[str, List[str]], None] = None, params: Optional[CatalogExportParams] = None, - ) -> Task: + ) -> Union[Task, ExportTask]: """ Creates a catalog export task with the given params, filters and returns the task. @@ -42,7 +42,10 @@ def export_v2( >>> task.wait_till_done() >>> task.result """ - return self._export(task_name, filters, params, False) + task, is_streamable = self._export(task_name, filters, params) + if (is_streamable): + return ExportTask(task, True) + return task @experimental def export( @@ -83,7 +86,7 @@ def export( >>> stream_type=lb.StreamType.RESULT >>> ).start(stream_handler=json_stream_handler) """ - task = self._export(task_name, filters, params, True) + task, _ = self._export(task_name, filters, params, streamable=True) return ExportTask(task) def _export(self, @@ -91,7 +94,7 @@ def _export(self, filters: Union[CatalogExportFilters, Dict[str, List[str]], None] = None, params: Optional[CatalogExportParams] = None, - streamable: bool = False) -> Task: + streamable: bool = False) -> tuple[Task, bool]: _params = params or CatalogExportParams({ "attachments": False, @@ -120,7 +123,7 @@ def _export(self, create_task_query_str = ( f"mutation {mutation_name}PyApi" f"($input: ExportDataRowsInCatalogInput!)" - f"{{{mutation_name}(input: $input){{taskId}}}}") + f"{{{mutation_name}(input: $input){{taskId isStreamable}}}}") media_type_override = _params.get('media_type_override', None) query_params: Dict[str, Any] = { @@ -132,6 +135,7 @@ def _export(self, "query": None, } }, + "isStreamableReady": True, "params": { "mediaTypeOverride": media_type_override.value @@ -171,4 +175,5 @@ def _export(self, error_log_key="errors") res = res[mutation_name] task_id = res["taskId"] - return Task.get_task(self.client, task_id) + is_streamable = res["isStreamable"] + return Task.get_task(self.client, task_id), is_streamable diff --git a/labelbox/schema/data_row.py b/labelbox/schema/data_row.py index 295d42230..4acb5a968 100644 --- a/labelbox/schema/data_row.py +++ b/labelbox/schema/data_row.py @@ -210,12 +210,12 @@ def export( >>> task.wait_till_done() >>> task.result """ - task = DataRow._export(client, - data_rows, - global_keys, - task_name, - params, - streamable=True) + task, _ = DataRow._export(client, + data_rows, + global_keys, + task_name, + params, + streamable=True) return ExportTask(task) @staticmethod @@ -225,7 +225,7 @@ def export_v2( global_keys: Optional[List[str]] = None, task_name: Optional[str] = None, params: Optional[CatalogExportParams] = None, - ) -> Task: + ) -> Union[Task, ExportTask]: """ Creates a data rows export task with the given list, params and returns the task. Args: @@ -249,8 +249,11 @@ def export_v2( >>> task.wait_till_done() >>> task.result """ - return DataRow._export(client, data_rows, global_keys, task_name, - params) + task, is_streamable = DataRow._export(client, data_rows, global_keys, + task_name, params) + if is_streamable: + return ExportTask(task, True) + return task @staticmethod def _export( @@ -260,7 +263,7 @@ def _export( task_name: Optional[str] = None, params: Optional[CatalogExportParams] = None, streamable: bool = False, - ) -> Task: + ) -> tuple[Task, bool]: _params = params or CatalogExportParams({ "attachments": False, "metadata_fields": False, @@ -282,7 +285,7 @@ def _export( create_task_query_str = ( f"mutation {mutation_name}PyApi" f"($input: ExportDataRowsInCatalogInput!)" - f"{{{mutation_name}(input: $input){{taskId}}}}") + f"{{{mutation_name}(input: $input){{taskId isStreamable}}}}") data_row_ids = [] if data_rows is not None: @@ -315,6 +318,7 @@ def _export( "query": search_query } }, + "isStreamableReady": True, "params": { "mediaTypeOverride": media_type_override.value @@ -352,4 +356,5 @@ def _export( print(res) res = res[mutation_name] task_id = res["taskId"] - return Task.get_task(client, task_id) + is_streamable = res["isStreamable"] + return Task.get_task(client, task_id), is_streamable diff --git a/labelbox/schema/dataset.py b/labelbox/schema/dataset.py index e43577112..09959a8b5 100644 --- a/labelbox/schema/dataset.py +++ b/labelbox/schema/dataset.py @@ -650,7 +650,7 @@ def export( >>> task.wait_till_done() >>> task.result """ - task = self._export(task_name, filters, params, streamable=True) + task, _ = self._export(task_name, filters, params, streamable=True) return ExportTask(task) def export_v2( @@ -658,7 +658,7 @@ def export_v2( task_name: Optional[str] = None, filters: Optional[DatasetExportFilters] = None, params: Optional[CatalogExportParams] = None, - ) -> Task: + ) -> Union[Task, ExportTask]: """ Creates a dataset export task with the given params and returns the task. @@ -676,7 +676,10 @@ def export_v2( >>> task.wait_till_done() >>> task.result """ - return self._export(task_name, filters, params) + task, is_streamable = self._export(task_name, filters, params) + if (is_streamable): + return ExportTask(task, True) + return task def _export( self, @@ -684,7 +687,7 @@ def _export( filters: Optional[DatasetExportFilters] = None, params: Optional[CatalogExportParams] = None, streamable: bool = False, - ) -> Task: + ) -> tuple[Task, bool]: _params = params or CatalogExportParams({ "attachments": False, "metadata_fields": False, @@ -712,7 +715,7 @@ def _export( create_task_query_str = ( f"mutation {mutation_name}PyApi" f"($input: ExportDataRowsInCatalogInput!)" - f"{{{mutation_name}(input: $input){{taskId}}}}") + f"{{{mutation_name}(input: $input){{taskId isStreamable}}}}") media_type_override = _params.get('media_type_override', None) if task_name is None: @@ -726,6 +729,7 @@ def _export( "query": None, } }, + "isStreamableReady": True, "params": { "mediaTypeOverride": media_type_override.value @@ -771,7 +775,8 @@ def _export( error_log_key="errors") res = res[mutation_name] task_id = res["taskId"] - return Task.get_task(self.client, task_id) + is_streamable = res["isStreamable"] + return Task.get_task(self.client, task_id), is_streamable def upsert_data_rows(self, items, file_upload_thread_count=20) -> "Task": """ @@ -868,4 +873,4 @@ def _convert_items_to_upsert_format(self, _items): k: v for k, v in item.items() if v is not None } # remove None values _upsert_items.append(DataRowUpsertItem(payload=item, id=key)) - return _upsert_items + return _upsert_items \ No newline at end of file diff --git a/labelbox/schema/export_task.py b/labelbox/schema/export_task.py index 3ec94bd2a..9f22c2c75 100644 --- a/labelbox/schema/export_task.py +++ b/labelbox/schema/export_task.py @@ -467,7 +467,8 @@ class ExportTask: class ExportTaskException(Exception): """Raised when the task is not ready yet.""" - def __init__(self, task: Task) -> None: + def __init__(self, task: Task, is_export_v2: bool = False) -> None: + self._is_export_v2 = is_export_v2 self._task = task def __repr__(self): @@ -530,9 +531,60 @@ def metadata(self): """Returns the metadata of the task.""" return self._task.metadata + @property + def errors(self): + """Returns the errors of the task.""" + if not self._is_export_v2: + raise ExportTask.ExportTaskException( + "This property is only available for export_v2 tasks due to compatibility reasons, please use streamable errors instead" + ) + if self.status == "FAILED": + raise ExportTask.ExportTaskException("Task failed") + if self.status != "COMPLETE": + raise ExportTask.ExportTaskException("Task is not ready yet") + + if not self.has_errors(): + return [] + + data = [] + + metadata_header = ExportTask._get_metadata_header( + self._task.client, self._task.uid, StreamType.ERRORS) + if metadata_header is None: + raise ValueError( + f"Task {self._task.uid} does not have a {StreamType.ERRORS.value} stream" + ) + Stream( + _TaskContext(self._task.client, self._task.uid, StreamType.ERRORS, + metadata_header), + _MultiGCSFileReader(), + JsonConverter(), + ).start(stream_handler=lambda output: data.append(output.json_str)) + return data + @property def result(self): """Returns the result of the task.""" + if self._is_export_v2: + if self.status == "FAILED": + raise ExportTask.ExportTaskException("Task failed") + if self.status != "COMPLETE": + raise ExportTask.ExportTaskException("Task is not ready yet") + data = [] + + metadata_header = ExportTask._get_metadata_header( + self._task.client, self._task.uid, StreamType.RESULT) + if metadata_header is None: + raise ValueError( + f"Task {self._task.uid} does not have a {StreamType.RESULT.value} stream" + ) + Stream( + _TaskContext(self._task.client, self._task.uid, + StreamType.RESULT, metadata_header), + _MultiGCSFileReader(), + JsonConverter(), + ).start(stream_handler=lambda output: data.append(output.json_str)) + return data return self._task.result_url @property diff --git a/labelbox/schema/model_run.py b/labelbox/schema/model_run.py index 64c444ff5..1597cfb98 100644 --- a/labelbox/schema/model_run.py +++ b/labelbox/schema/model_run.py @@ -521,33 +521,36 @@ def export(self, >>> export_task = export("my_export_task", params={"media_attributes": True}) """ - task = self._export(task_name, params, streamable=True) + task, _ = self._export(task_name, params, streamable=True) return ExportTask(task) def export_v2( self, task_name: Optional[str] = None, params: Optional[ModelRunExportParams] = None, - ) -> Task: + ) -> Union[Task, ExportTask]: """ Creates a model run export task with the given params and returns the task. >>> export_task = export_v2("my_export_task", params={"media_attributes": True}) """ - return self._export(task_name, params) + task, is_streamable = self._export(task_name, params) + if (is_streamable): + return ExportTask(task, True) + return task def _export( self, task_name: Optional[str] = None, params: Optional[ModelRunExportParams] = None, streamable: bool = False, - ) -> Task: + ) -> tuple[Task, bool]: mutation_name = "exportDataRowsInModelRun" create_task_query_str = ( f"mutation {mutation_name}PyApi" f"($input: ExportDataRowsInModelRunInput!)" - f"{{{mutation_name}(input: $input){{taskId}}}}") + f"{{{mutation_name}(input: $input){{taskId isStreamable}}}}") _params = params or ModelRunExportParams() @@ -557,6 +560,7 @@ def _export( "filters": { "modelRunId": self.uid }, + "isStreamableReady": True, "params": { "mediaTypeOverride": _params.get('media_type_override', None), @@ -579,7 +583,8 @@ def _export( error_log_key="errors") res = res[mutation_name] task_id = res["taskId"] - return Task.get_task(self.client, task_id) + is_streamable = res["isStreamable"] + return Task.get_task(self.client, task_id), is_streamable def send_to_annotate_from_model( self, destination_project_id: str, task_queue_id: Optional[str], diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index 0230dd7c4..0e516f08a 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -476,7 +476,7 @@ def export( >>> task.wait_till_done() >>> task.result """ - task = self._export(task_name, filters, params, streamable=True) + task, _ = self._export(task_name, filters, params, streamable=True) return ExportTask(task) def export_v2( @@ -484,7 +484,7 @@ def export_v2( task_name: Optional[str] = None, filters: Optional[ProjectExportFilters] = None, params: Optional[ProjectExportParams] = None, - ) -> Task: + ) -> Union[Task, ExportTask]: """ Creates a project export task with the given params and returns the task. @@ -504,7 +504,10 @@ def export_v2( >>> task.wait_till_done() >>> task.result """ - return self._export(task_name, filters, params) + task, is_streamable = self._export(task_name, filters, params) + if (is_streamable): + return ExportTask(task, True) + return task def _export( self, @@ -512,7 +515,7 @@ def _export( filters: Optional[ProjectExportFilters] = None, params: Optional[ProjectExportParams] = None, streamable: bool = False, - ) -> Task: + ) -> tuple[Task, bool]: _params = params or ProjectExportParams({ "attachments": False, "metadata_fields": False, @@ -537,12 +540,13 @@ def _export( create_task_query_str = ( f"mutation {mutation_name}PyApi" f"($input: ExportDataRowsInProjectInput!)" - f"{{{mutation_name}(input: $input){{taskId}}}}") + f"{{{mutation_name}(input: $input){{taskId isStreamable}}}}") media_type_override = _params.get('media_type_override', None) query_params: Dict[str, Any] = { "input": { "taskName": task_name, + "isStreamableReady": True, "filters": { "projectId": self.uid, "searchQuery": { @@ -581,7 +585,8 @@ def _export( error_log_key="errors") res = res[mutation_name] task_id = res["taskId"] - return Task.get_task(self.client, task_id) + is_streamable = res["isStreamable"] + return Task.get_task(self.client, task_id), is_streamable def export_issues(self, status=None) -> str: """ Calls the server-side Issues exporting that diff --git a/labelbox/schema/slice.py b/labelbox/schema/slice.py index 8683c3c32..c03167776 100644 --- a/labelbox/schema/slice.py +++ b/labelbox/schema/slice.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional +from typing import Optional, Union import warnings from labelbox.orm.db_object import DbObject, experimental from labelbox.orm.model import Field @@ -137,14 +137,14 @@ def export(self, >>> task.wait_till_done() >>> task.result """ - task = self._export(task_name, params, streamable=True) + task, _ = self._export(task_name, params, streamable=True) return ExportTask(task) def export_v2( self, task_name: Optional[str] = None, params: Optional[CatalogExportParams] = None, - ) -> Task: + ) -> Union[Task, ExportTask]: """ Creates a slice export task with the given params and returns the task. >>> slice = client.get_catalog_slice("SLICE_ID") @@ -154,14 +154,17 @@ def export_v2( >>> task.wait_till_done() >>> task.result """ - return self._export(task_name, params) + task, is_streamable = self._export(task_name, params) + if (is_streamable): + return ExportTask(task, True) + return task def _export( self, task_name: Optional[str] = None, params: Optional[CatalogExportParams] = None, streamable: bool = False, - ) -> Task: + ) -> tuple[Task, bool]: _params = params or CatalogExportParams({ "attachments": False, "metadata_fields": False, @@ -182,7 +185,7 @@ def _export( create_task_query_str = ( f"mutation {mutation_name}PyApi" f"($input: ExportDataRowsInSliceInput!)" - f"{{{mutation_name}(input: $input){{taskId}}}}") + f"{{{mutation_name}(input: $input){{taskId isStreamable}}}}") media_type_override = _params.get('media_type_override', None) query_params = { @@ -191,6 +194,7 @@ def _export( "filters": { "sliceId": self.uid }, + "isStreamableReady": True, "params": { "mediaTypeOverride": media_type_override.value @@ -227,7 +231,8 @@ def _export( error_log_key="errors") res = res[mutation_name] task_id = res["taskId"] - return Task.get_task(self.client, task_id) + is_streamable = res["isStreamable"] + return Task.get_task(self.client, task_id), is_streamable class ModelSlice(Slice): From 60969953547b87286e2f25e13feccb0db76521d0 Mon Sep 17 00:00:00 2001 From: Michal Noszczak Date: Tue, 2 Apr 2024 13:38:46 +0000 Subject: [PATCH 2/6] Add errors_url, result_Url --- labelbox/schema/export_task.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/labelbox/schema/export_task.py b/labelbox/schema/export_task.py index 9f22c2c75..39488c3c8 100644 --- a/labelbox/schema/export_task.py +++ b/labelbox/schema/export_task.py @@ -531,6 +531,28 @@ def metadata(self): """Returns the metadata of the task.""" return self._task.metadata + @property + def result_url(self): + """Returns the result URL of the task.""" + if not self._is_export_v2: + raise ExportTask.ExportTaskException( + "This property is only available for export_v2 tasks due to compatibility reasons, please use streamable errors instead" + ) + base_url = self._task.client.rest_endpoint + return base_url + '/export-results/' + self._task.uid + '/' + self._task.client.get_organization( + ).uid + + @property + def errors_url(self): + """Returns the errors URL of the task.""" + if not self._is_export_v2: + raise ExportTask.ExportTaskException( + "This property is only available for export_v2 tasks due to compatibility reasons, please use streamable errors instead" + ) + base_url = self._task.client.rest_endpoint + return base_url + '/export-errors/' + self._task.uid + '/' + self._task.client.get_organization( + ).uid + @property def errors(self): """Returns the errors of the task.""" From 02aa1ed46e3d40f394c5564e1d0086325d83f7a6 Mon Sep 17 00:00:00 2001 From: Michal Noszczak Date: Wed, 3 Apr 2024 10:06:02 +0000 Subject: [PATCH 3/6] Mypyp --- labelbox/schema/catalog.py | 4 ++-- labelbox/schema/data_row.py | 4 ++-- labelbox/schema/dataset.py | 4 ++-- labelbox/schema/model_run.py | 4 ++-- labelbox/schema/project.py | 2 +- labelbox/schema/slice.py | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/labelbox/schema/catalog.py b/labelbox/schema/catalog.py index d34cbf22a..711c2dfc7 100644 --- a/labelbox/schema/catalog.py +++ b/labelbox/schema/catalog.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Tuple, Union from labelbox.orm.db_object import experimental from labelbox.schema.export_filters import CatalogExportFilters, build_filters @@ -94,7 +94,7 @@ def _export(self, filters: Union[CatalogExportFilters, Dict[str, List[str]], None] = None, params: Optional[CatalogExportParams] = None, - streamable: bool = False) -> tuple[Task, bool]: + streamable: bool = False) -> Tuple[Task, bool]: _params = params or CatalogExportParams({ "attachments": False, diff --git a/labelbox/schema/data_row.py b/labelbox/schema/data_row.py index 4acb5a968..50eab4241 100644 --- a/labelbox/schema/data_row.py +++ b/labelbox/schema/data_row.py @@ -1,6 +1,6 @@ import logging from enum import Enum -from typing import TYPE_CHECKING, List, Optional, Union, Any +from typing import TYPE_CHECKING, List, Optional, Tuple, Union, Any import json from labelbox.orm import query @@ -263,7 +263,7 @@ def _export( task_name: Optional[str] = None, params: Optional[CatalogExportParams] = None, streamable: bool = False, - ) -> tuple[Task, bool]: + ) -> Tuple[Task, bool]: _params = params or CatalogExportParams({ "attachments": False, "metadata_fields": False, diff --git a/labelbox/schema/dataset.py b/labelbox/schema/dataset.py index 09959a8b5..8f77718bb 100644 --- a/labelbox/schema/dataset.py +++ b/labelbox/schema/dataset.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Dict, Generator, List, Optional, Any, Final +from typing import Dict, Generator, List, Optional, Any, Final, Tuple, Union import os import json import logging @@ -687,7 +687,7 @@ def _export( filters: Optional[DatasetExportFilters] = None, params: Optional[CatalogExportParams] = None, streamable: bool = False, - ) -> tuple[Task, bool]: + ) -> Tuple[Task, bool]: _params = params or CatalogExportParams({ "attachments": False, "metadata_fields": False, diff --git a/labelbox/schema/model_run.py b/labelbox/schema/model_run.py index 1597cfb98..af3517dde 100644 --- a/labelbox/schema/model_run.py +++ b/labelbox/schema/model_run.py @@ -5,7 +5,7 @@ import warnings from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, Dict, Iterable, Union, List, Optional, Any +from typing import TYPE_CHECKING, Dict, Iterable, Union, Tuple, List, Optional, Any import requests @@ -545,7 +545,7 @@ def _export( task_name: Optional[str] = None, params: Optional[ModelRunExportParams] = None, streamable: bool = False, - ) -> tuple[Task, bool]: + ) -> Tuple[Task, bool]: mutation_name = "exportDataRowsInModelRun" create_task_query_str = ( f"mutation {mutation_name}PyApi" diff --git a/labelbox/schema/project.py b/labelbox/schema/project.py index 0e516f08a..e4aaab744 100644 --- a/labelbox/schema/project.py +++ b/labelbox/schema/project.py @@ -515,7 +515,7 @@ def _export( filters: Optional[ProjectExportFilters] = None, params: Optional[ProjectExportParams] = None, streamable: bool = False, - ) -> tuple[Task, bool]: + ) -> Tuple[Task, bool]: _params = params or ProjectExportParams({ "attachments": False, "metadata_fields": False, diff --git a/labelbox/schema/slice.py b/labelbox/schema/slice.py index c03167776..5215b1ffd 100644 --- a/labelbox/schema/slice.py +++ b/labelbox/schema/slice.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional, Union +from typing import Optional, Tuple, Union import warnings from labelbox.orm.db_object import DbObject, experimental from labelbox.orm.model import Field @@ -164,7 +164,7 @@ def _export( task_name: Optional[str] = None, params: Optional[CatalogExportParams] = None, streamable: bool = False, - ) -> tuple[Task, bool]: + ) -> Tuple[Task, bool]: _params = params or CatalogExportParams({ "attachments": False, "metadata_fields": False, From 525424c299f5b84680f1aaffaa9aa7c5dd3c7d07 Mon Sep 17 00:00:00 2001 From: Michal Noszczak Date: Wed, 3 Apr 2024 11:31:14 +0000 Subject: [PATCH 4/6] Update errors response --- labelbox/schema/export_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labelbox/schema/export_task.py b/labelbox/schema/export_task.py index 39488c3c8..8c12ae349 100644 --- a/labelbox/schema/export_task.py +++ b/labelbox/schema/export_task.py @@ -566,7 +566,7 @@ def errors(self): raise ExportTask.ExportTaskException("Task is not ready yet") if not self.has_errors(): - return [] + return None data = [] From fa638f583fe8300cccd098901afbfa988183fa6a Mon Sep 17 00:00:00 2001 From: Michal Noszczak Date: Wed, 3 Apr 2024 13:29:46 +0000 Subject: [PATCH 5/6] JSON --- labelbox/schema/export_task.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/labelbox/schema/export_task.py b/labelbox/schema/export_task.py index 8c12ae349..9ba23ca16 100644 --- a/labelbox/schema/export_task.py +++ b/labelbox/schema/export_task.py @@ -605,7 +605,8 @@ def result(self): StreamType.RESULT, metadata_header), _MultiGCSFileReader(), JsonConverter(), - ).start(stream_handler=lambda output: data.append(output.json_str)) + ).start(stream_handler=lambda output: data.append( + json.loads(output.json_str))) return data return self._task.result_url From 4ed5dddbfa5fd0b7b913ba544882d21c315adc1d Mon Sep 17 00:00:00 2001 From: Michal Noszczak Date: Wed, 3 Apr 2024 14:32:00 +0000 Subject: [PATCH 6/6] Fix return type --- labelbox/schema/export_task.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/labelbox/schema/export_task.py b/labelbox/schema/export_task.py index 9ba23ca16..fa84456aa 100644 --- a/labelbox/schema/export_task.py +++ b/labelbox/schema/export_task.py @@ -573,9 +573,7 @@ def errors(self): metadata_header = ExportTask._get_metadata_header( self._task.client, self._task.uid, StreamType.ERRORS) if metadata_header is None: - raise ValueError( - f"Task {self._task.uid} does not have a {StreamType.ERRORS.value} stream" - ) + return None Stream( _TaskContext(self._task.client, self._task.uid, StreamType.ERRORS, metadata_header), @@ -597,9 +595,7 @@ def result(self): metadata_header = ExportTask._get_metadata_header( self._task.client, self._task.uid, StreamType.RESULT) if metadata_header is None: - raise ValueError( - f"Task {self._task.uid} does not have a {StreamType.RESULT.value} stream" - ) + return [] Stream( _TaskContext(self._task.client, self._task.uid, StreamType.RESULT, metadata_header),