Skip to content

Commit 6e2c729

Browse files
authored
Merge pull request #672 from superannotateai/develop
Develop
2 parents 5be819a + 6f580a0 commit 6e2c729

File tree

12 files changed

+91
-14
lines changed

12 files changed

+91
-14
lines changed

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ History
66

77
All release highlights of this project will be documented in this file.
88

9+
4.4.19 - February 08, 2024
10+
__________________________
11+
12+
13+
**Updated**
14+
15+
- ``SAClient.attach_items()`` added the ability to attach items from custom integrated storage.
16+
17+
918
4.4.18 - January 18, 2024
1019
__________________________
1120

src/superannotate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55

6-
__version__ = "4.4.18"
6+
__version__ = "4.4.19"
77

88
sys.path.append(os.path.split(os.path.realpath(__file__))[0])
99

src/superannotate/lib/app/helpers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Union
77

88
import boto3
9+
import numpy as np
910
import pandas as pd
1011
from superannotate.lib.app.exceptions import AppException
1112
from superannotate.lib.core import ATTACHED_VIDEO_ANNOTATION_POSTFIX
@@ -78,6 +79,7 @@ def get_s3_annotation_paths(folder_path, s3_bucket, annotation_paths, recursive)
7879

7980
def get_name_url_duplicated_from_csv(csv_path):
8081
image_data = pd.read_csv(csv_path, dtype=str)
82+
image_data.replace({pd.NA: None}, inplace=True)
8183
if "url" not in image_data.columns:
8284
raise AppException("Column 'url' is required")
8385
image_data = image_data[~image_data["url"].isnull()]
@@ -90,7 +92,8 @@ def get_name_url_duplicated_from_csv(csv_path):
9092
else:
9193
image_data["name"] = [str(uuid.uuid4()) for _ in range(len(image_data.index))]
9294

93-
image_data = pd.DataFrame(image_data, columns=["name", "url"])
95+
image_data = pd.DataFrame(image_data, columns=["name", "url", "integration"])
96+
image_data = image_data.replace(np.nan, None)
9497
img_names_urls = image_data.to_dict(orient="records")
9598
duplicate_images = []
9699
seen = []

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
from lib.core.entities.classes import AnnotationClassEntity
5151
from lib.core.entities.classes import AttributeGroup
5252
from lib.core.entities.integrations import IntegrationEntity
53+
from lib.core.entities.integrations import IntegrationTypeEnum
5354
from lib.core.enums import ImageQuality
5455
from lib.core.enums import ProjectType
5556
from lib.core.enums import ClassTypeEnum
@@ -64,7 +65,6 @@
6465
from lib.infrastructure.utils import extract_project_folder
6566
from lib.infrastructure.validators import wrap_error
6667

67-
6868
logger = logging.getLogger("sa")
6969

7070
NotEmptyStr = TypeVar("NotEmptyStr", bound=constr(strict=True, min_length=1))
@@ -77,7 +77,6 @@
7777
"Video",
7878
"Document",
7979
"Tiled",
80-
"Other",
8180
"PointCloud",
8281
"GenAI",
8382
]
@@ -110,6 +109,7 @@ class PriorityScore(TypedDict):
110109
class Attachment(TypedDict, total=False):
111110
url: Required[str] # noqa
112111
name: NotRequired[str] # noqa
112+
integration: NotRequired[str] # noqa
113113

114114

115115
class SAClient(BaseInterfaceFacade, metaclass=TrackableMeta):
@@ -311,7 +311,7 @@ def create_project(
311311
:param project_description: the new project's description
312312
:type project_description: str
313313
314-
:param project_type: the new project type, Vector, Pixel, Video, Document, Tiled, PointCloud, Other.
314+
:param project_type: the new project type, Vector, Pixel, Video, Document, Tiled, PointCloud, GenAI.
315315
:type project_type: str
316316
317317
:param settings: list of settings objects
@@ -2559,6 +2559,20 @@ def attach_items(
25592559
attachments = [{"name": "item", "url": "https://..."}]
25602560
)
25612561
2562+
Example of attaching items from custom integration:
2563+
::
2564+
client = SAClient()
2565+
client.attach_items(
2566+
project = "Medical Annotations",
2567+
attachments = [
2568+
{
2569+
"name": "item",
2570+
"url": "https://sa-public-files.s3.../text_file_example_1.jpeg"
2571+
"integration": "custom-integration"
2572+
}
2573+
]
2574+
)
2575+
25622576
"""
25632577

25642578
project_name, folder_name = extract_project_folder(project)
@@ -2579,25 +2593,51 @@ def attach_items(
25792593
logger.info("Dropping duplicates.")
25802594
unique_attachments = parse_obj_as(List[AttachmentEntity], unique_attachments)
25812595
uploaded, fails, duplicated = [], [], []
2582-
if unique_attachments:
2596+
_unique_attachments = []
2597+
if any(i.integration for i in unique_attachments):
2598+
integtation_item_map = {
2599+
i.name: i
2600+
for i in self.controller.integrations.list().data
2601+
if i.type == IntegrationTypeEnum.CUSTOM
2602+
}
2603+
invalid_integrations = set()
2604+
for attachment in unique_attachments:
2605+
if attachment.integration:
2606+
if attachment.integration in integtation_item_map:
2607+
attachment.integration_id = integtation_item_map[
2608+
attachment.integration
2609+
].id
2610+
else:
2611+
invalid_integrations.add(attachment.integration)
2612+
continue
2613+
_unique_attachments.append(attachment)
2614+
if invalid_integrations:
2615+
logger.error(
2616+
f"The ['{','.join(invalid_integrations)}'] integrations specified for the items doesn't exist in the "
2617+
"list of integrations on the platform. Any associated items will be skipped."
2618+
)
2619+
else:
2620+
_unique_attachments = unique_attachments
2621+
2622+
if _unique_attachments:
25832623
logger.info(
2584-
f"Attaching {len(unique_attachments)} file(s) to project {project}."
2624+
f"Attaching {len(_unique_attachments)} file(s) to project {project}."
25852625
)
25862626
project, folder = self.controller.get_project_folder(
25872627
project_name, folder_name
25882628
)
25892629
response = self.controller.items.attach(
25902630
project=project,
25912631
folder=folder,
2592-
attachments=unique_attachments,
2632+
attachments=_unique_attachments,
25932633
annotation_status=annotation_status,
25942634
)
25952635
if response.errors:
25962636
raise AppException(response.errors)
25972637
uploaded, duplicated = response.data
25982638
fails = [
25992639
attachment.name
2600-
for attachment in unique_attachments
2640+
for attachment in _unique_attachments
26012641
if attachment.name not in uploaded and attachment.name not in duplicated
26022642
]
26032643
return uploaded, fails, duplicated

src/superannotate/lib/core/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ def setup_logging(level=DEFAULT_LOGGING_LEVEL, file_path=LOG_FILE_LOCATION):
9999
LIMITED_FUNCTIONS = {
100100
ProjectType.VIDEO: DEPRECATED_VIDEO_PROJECTS_MESSAGE,
101101
ProjectType.DOCUMENT: DEPRECATED_DOCUMENT_PROJECTS_MESSAGE,
102-
ProjectType.OTHER: DEPRECATED_PROJECTS_MESSAGE,
103102
ProjectType.POINT_CLOUD: DEPRECATED_PROJECTS_MESSAGE,
104103
ProjectType.TILED: DEPRECATED_PROJECTS_MESSAGE,
105104
}

src/superannotate/lib/core/entities/project.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class TimedBaseModel(BaseModel):
4040
class AttachmentEntity(BaseModel):
4141
name: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4()))
4242
url: str
43+
integration: Optional[str] = None
44+
integration_id: Optional[int] = None
4345

4446
class Config:
4547
extra = Extra.ignore

src/superannotate/lib/core/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ class IntegrationTypeEnum(BaseTitledEnum):
171171
AWS = "aws", 1
172172
GCP = "gcp", 2
173173
AZURE = "azure", 3
174+
CUSTOM = "custom", 4
174175

175176

176177
class TrainingStatus(BaseTitledEnum):

src/superannotate/lib/core/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ class PriorityScoreEntity(BaseModel):
3333
class Attachment(BaseModel):
3434
name: str
3535
path: str
36+
integration_id: Optional[int] = None
3637

3738

3839
class AttachmentMeta(BaseModel):
3940
width: Optional[float] = None
4041
height: Optional[float] = None
42+
integration_id: Optional[int] = None

src/superannotate/lib/core/usecases/items.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,8 @@ def validate_upload_state(self):
410410
raise AppValidationException(constants.ATTACHING_UPLOAD_STATE_ERROR)
411411

412412
@staticmethod
413-
def generate_meta() -> AttachmentMeta:
414-
return AttachmentMeta(width=None, height=None)
413+
def generate_meta(integration_id=None) -> AttachmentMeta:
414+
return AttachmentMeta(width=None, height=None, integration_id=integration_id)
415415

416416
def execute(self) -> Response:
417417
if self.is_valid():
@@ -436,7 +436,9 @@ def execute(self) -> Response:
436436
to_upload.append(
437437
Attachment(name=attachment.name, path=attachment.url)
438438
)
439-
to_upload_meta[attachment.name] = self.generate_meta()
439+
to_upload_meta[attachment.name] = self.generate_meta(
440+
attachment.integration_id
441+
)
440442
if to_upload:
441443
backend_response = self._service_provider.items.attach(
442444
project=self._project,

src/superannotate/lib/infrastructure/services/item.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class ItemService(BaseItemService):
4040
ProjectType.PIXEL: ImageResponse,
4141
ProjectType.DOCUMENT: DocumentResponse,
4242
ProjectType.POINT_CLOUD: PointCloudResponse,
43-
ProjectType.GEN_AI: ImageResponse
43+
ProjectType.GEN_AI: ImageResponse,
4444
}
4545

4646
def get_by_id(self, item_id, project_id, project_type):

0 commit comments

Comments
 (0)