Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
tests/tmp_test.py

# Translations
*.mo
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,23 @@ History

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


4.4.31 - Feb 27, 2025
_____________________

**Added**

- Guide for Converting CSV and JSONL Formats.
- New SDK Functionality Table.

**Updated**

- ``SAClient.attach_items_from_integrated_storage`` now supports Databricks integration, enabling efficient
data fetching and mapping from Databricks into SuperAnnotate.

4.4.30 - Feb 13, 2025
_____________________

**Added**

- ``SAClient.list_users`` method lists contributors with optional custom field filtering.
Expand Down
2 changes: 2 additions & 0 deletions docs/source/api_reference/api_team.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Team
.. automethod:: superannotate.SAClient.get_user_metadata
.. automethod:: superannotate.SAClient.set_user_custom_field
.. automethod:: superannotate.SAClient.list_users
.. automethod:: superannotate.SAClient.pause_user_activity
.. automethod:: superannotate.SAClient.resume_user_activity
6 changes: 3 additions & 3 deletions docs/source/userguide/SDK_Functions_sheet.csv
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Annotations,upload_annotations(),Yes,Yes,Yes,Yes,Not Relevant
,set_annotation_statuses(),Yes,Not Relevant,Not Relevant,Not Relevant,Not Relevant
,delete_annotations(),Yes,Not Relevant,Not Relevant,Not Relevant,Not Relevant
,upload_annotations_from_folder_to_project(),No,No,Yes,No,AWS
"Annotation
"Annotation
Classes",create_annotation_class(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,Not Relevant
,create_annotation_classes_from_classes_json(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,AWS
,search_annotation_classes(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,Not Relevant
Expand All @@ -57,7 +57,7 @@ Classes",create_annotation_class(),Not Relevant,Not Relevant,Not Relevant,Not Re
Exports,prepare_export(),Yes,Yes,Yes,No,Not Relevant
,download_export(),Yes,Yes,Yes,Yes,AWS
,get_exports(),Yes,Not Relevant,Not Relevant,Not Relevant,Not Relevant
"Custom
"Custom
Metadata
",create_custom_fields(),Yes,Not Relevant,Not Relevant,Not Relevant,Not Relevant
,get_custom_fields(),Yes,Not Relevant,Not Relevant,Not Relevant,Not Relevant
Expand All @@ -82,6 +82,6 @@ Team,get_team_metadata(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,Not
Annotations",import_annotation(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,Not Relevant
,export_annotation(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,Not Relevant
,convert_project_type(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,Not Relevant
"Working w/
"Working w/
Annotations",validate_annotations(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,Not Relevant
,aggregate_annotations_as_df(),Not Relevant,Not Relevant,Not Relevant,Not Relevant,Not Relevant
2 changes: 1 addition & 1 deletion src/superannotate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys


__version__ = "4.4.31dev1"
__version__ = "4.4.31dev2"

os.environ.update({"sa_version": __version__})
sys.path.append(os.path.split(os.path.realpath(__file__))[0])
Expand Down
92 changes: 80 additions & 12 deletions src/superannotate/lib/app/interface/sdk_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,16 @@ def list_users(self, *, include: List[Literal["custom_fields"]] = None, **filter
def pause_user_activity(
self, pk: Union[int, str], projects: Union[List[int], List[str], Literal["*"]]
):
"""
Block the team contributor from requesting items from the projects.

:param pk: The email address or user ID of the team contributor.
:type pk: str or int

:param projects: A list of project names or IDs from which the user should be blocked.
The special value "*" means block access to all projects
:type projects: Union[List[int], List[str], Literal["*"]]
"""
user = self.controller.work_management.get_user_metadata(pk=pk)
if user.role is not WMUserTypeEnum.Contributor:
raise AppException("User must have a contributor role to pause activity.")
Expand All @@ -474,6 +484,16 @@ def pause_user_activity(
def resume_user_activity(
self, pk: Union[int, str], projects: Union[List[int], List[str], Literal["*"]]
):
"""
Resume the team contributor from requesting items from the projects.

:param pk: The email address or user ID of the team contributor.
:type pk: str or int

:param projects: A list of project names or IDs from which the user should be resumed.
The special value "*" means resume access to all projects
:type projects: Union[List[int], List[str], Literal["*"]]
"""
user = self.controller.work_management.get_user_metadata(pk=pk)
if user.role is not WMUserTypeEnum.Contributor:
raise AppException("User must have a contributor role to resume activity.")
Expand Down Expand Up @@ -501,7 +521,7 @@ def get_component_config(self, project: Union[NotEmptyStr, int], component_id: s
"""

def retrieve_context(
component_data: List[dict], component_pk: str
component_data: List[dict], component_pk: str
) -> Tuple[bool, typing.Any]:
try:
for component in component_data:
Expand All @@ -512,9 +532,9 @@ def retrieve_context(
if found:
return found, val
if (
"id" in component and
component["id"] == component_pk
and component["type"] == "webComponent"
"id" in component
and component["id"] == component_pk
and component["type"] == "webComponent"
):
return True, json.loads(component.get("context"))

Expand Down Expand Up @@ -2919,32 +2939,80 @@ def attach_items_from_integrated_storage(
project: NotEmptyStr,
integration: Union[NotEmptyStr, IntegrationEntity],
folder_path: Optional[NotEmptyStr] = None,
*,
query: Optional[NotEmptyStr] = None,
item_name_column: Optional[NotEmptyStr] = None,
custom_item_name: Optional[NotEmptyStr] = None,
component_mapping: Optional[Dict[str, str]] = None,
):
"""Link images from integrated external storage to SuperAnnotate.
"""Link images from integrated external storage to SuperAnnotate from AWS, GCP, Azure, Databricks.

:param project: project name or folder path where items should be attached (e.g., “project1/folder1”).
:type project: str

:param integration: existing integration name or metadata dict to pull items from.
Mandatory keys in integration metadata’s dict is “name”.
:param integration: The existing integration name or metadata dict to pull items from.
Mandatory keys in integration metadata’s dict is “name”.
:type integration: str or dict

:param folder_path: Points to an exact folder/directory within given storage.
If None, items are fetched from the root directory.
If None, items are fetched from the root directory.
:type folder_path: str

:param query: (Only for Databricks). The SQL query to retrieve specific columns from Databricks.
If provided, the function will execute the query and use the results for mapping and uploading.
:type query: Optional[str]

:param item_name_column: (Only for Databricks). The column name from the SQL query whose values
will be used as item names. If this is provided, custom_item_name cannot be used.
The column must exist in the query result.
:type item_name_column: Optional[str]

:param custom_item_name: (Only for Databricks). A manually defined prefix for item names.
A random 10-character suffix will be appended to ensure uniqueness.
If this is provided, item_name_column cannot be used.
:type custom_item_name: Optional[str]

:param component_mapping: (Only for Databricks). A dictionary mapping Databricks
columns to SuperAnnotate component IDs.
:type component_mapping: Optional[dict]


Request Example:
::

client.attach_items_from_integrated_storage(
project="project_name",
integration="databricks_integration",
query="SELECT * FROM integration_data LIMIT 10",
item_name_column="prompt",
component_mapping={
"category": "_item_category",
"prompt_id": "id",
"prompt": "prompt"
}
)

"""
project, folder = self.controller.get_project_folder_by_path(project)
_integration = None
if isinstance(integration, str):
integration = IntegrationEntity(name=integration)
for i in self.controller.integrations.list().data:
if integration.name == i.name:
if integration.name.lower() == i.name.lower():
_integration = i
break
else:
raise AppException("Integration not found.")

response = self.controller.integrations.attach_items(
project, folder, _integration, folder_path
project=project,
folder=folder,
integration=_integration,
folder_path=folder_path,
query=query,
item_name_column=item_name_column,
custom_item_name=custom_item_name,
component_mapping=component_mapping,
)
if response.errors:
raise AppException(response.errors)
Expand Down Expand Up @@ -3593,7 +3661,7 @@ def copy_items(
"skip", "replace", "replace_annotations_only"
] = "skip",
):
"""Copy images in bulk between folders in a project
"""Copy items in bulk between folders in a project

:param source: project name (root) or folder path to pick items from (e.g., “project1/folder1”).
:type source: str
Expand Down Expand Up @@ -3657,7 +3725,7 @@ def move_items(
"skip", "replace", "replace_annotations_only"
] = "skip",
):
"""Move images in bulk between folders in a project
"""Move items in bulk between folders in a project

:param source: project name (root) or folder path to pick items from (e.g., “project1/folder1”).
:type source: str
Expand Down
2 changes: 2 additions & 0 deletions src/superannotate/lib/core/serviceproviders.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ def get_upload_chunks(
self,
project: entities.ProjectEntity,
item_ids: List[int],
chunk_size: int = 1000,
) -> Dict[str, List]:
raise NotImplementedError

Expand Down Expand Up @@ -592,6 +593,7 @@ def attach_items(
folder: entities.FolderEntity,
integration: entities.IntegrationEntity,
folder_name: str = None,
options: Dict[str, str] = None,
) -> ServiceResponse:
raise NotImplementedError

Expand Down
Loading