Skip to content

Commit 3973c3a

Browse files
authored
Merge pull request #2018 from kili-technology/feature/lab-4165-poc-aau-i-use-an-audio-interface
feat(LAB-4165): add audio asset import support
2 parents 7347a37 + 2a0f846 commit 3973c3a

4 files changed

Lines changed: 183 additions & 0 deletions

File tree

src/kili/domain_api/assets.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ class AssetsNamespace(DomainNamespace): # pylint: disable=too-many-public-metho
257257
- create_pdf(): Create PDF assets
258258
- create_text(): Create plain text assets
259259
- create_rich_text(): Create rich-text formatted text assets
260+
- create_audio(): Create audio assets
260261
- delete(): Delete assets from projects
261262
- add_metadata(): Add metadata to assets
262263
- set_metadata(): Set metadata on assets
@@ -1311,6 +1312,102 @@ def create_rich_text(
13111312
**kwargs,
13121313
)
13131314

1315+
@overload
1316+
def create_audio(
1317+
self,
1318+
*,
1319+
project_id: str,
1320+
content: Union[str, dict],
1321+
external_id: Optional[str] = None,
1322+
json_metadata: Optional[dict] = None,
1323+
wait_until_availability: bool = True,
1324+
**kwargs,
1325+
) -> dict[Literal["id", "asset_ids"], Union[str, List[str]]]:
1326+
...
1327+
1328+
@overload
1329+
def create_audio(
1330+
self,
1331+
*,
1332+
project_id: str,
1333+
content_array: Union[List[str], List[dict]],
1334+
external_id_array: Optional[List[str]] = None,
1335+
json_metadata_array: Optional[List[dict]] = None,
1336+
disable_tqdm: Optional[bool] = None,
1337+
wait_until_availability: bool = True,
1338+
**kwargs,
1339+
) -> dict[Literal["id", "asset_ids"], Union[str, List[str]]]:
1340+
...
1341+
1342+
@typechecked
1343+
def create_audio(
1344+
self,
1345+
*,
1346+
project_id: str,
1347+
content: Optional[Union[str, dict]] = None,
1348+
content_array: Optional[Union[List[str], List[dict]]] = None,
1349+
external_id: Optional[str] = None,
1350+
external_id_array: Optional[List[str]] = None,
1351+
json_metadata: Optional[dict] = None,
1352+
json_metadata_array: Optional[List[dict]] = None,
1353+
disable_tqdm: Optional[bool] = None,
1354+
wait_until_availability: bool = True,
1355+
**kwargs,
1356+
) -> dict[Literal["id", "asset_ids"], Union[str, List[str]]]:
1357+
"""Create audio assets in a project.
1358+
1359+
Args:
1360+
project_id: Identifier of the project
1361+
content: URL or local file path to an audio file
1362+
content_array: List of URLs or local file paths to audio files
1363+
external_id: External id to identify the asset
1364+
external_id_array: List of external ids given to identify the assets
1365+
json_metadata: The metadata given to the asset
1366+
json_metadata_array: The metadata given to each asset
1367+
disable_tqdm: If True, the progress bar will be disabled
1368+
wait_until_availability: If True, waits until assets are fully processed
1369+
**kwargs: Additional arguments (e.g., is_honeypot)
1370+
1371+
Returns:
1372+
A dictionary with project id and list of created asset ids
1373+
1374+
Examples:
1375+
>>> # Create single audio asset
1376+
>>> result = kili.assets.create_audio(
1377+
... project_id="my_project",
1378+
... content="https://example.com/audio.mp3"
1379+
... )
1380+
1381+
>>> # Create multiple audio assets
1382+
>>> result = kili.assets.create_audio(
1383+
... project_id="my_project",
1384+
... content_array=["https://example.com/audio1.mp3", "https://example.com/audio2.wav"]
1385+
... )
1386+
1387+
>>> # Create audio with metadata
1388+
>>> result = kili.assets.create_audio(
1389+
... project_id="my_project",
1390+
... content="https://example.com/audio.mp3",
1391+
... json_metadata={"speaker": "John Doe"}
1392+
... )
1393+
"""
1394+
if content is not None:
1395+
content_array = cast(Union[list[str], list[dict]], [content])
1396+
if external_id is not None:
1397+
external_id_array = [external_id]
1398+
if json_metadata is not None:
1399+
json_metadata_array = [json_metadata]
1400+
1401+
return self._client.append_many_to_dataset(
1402+
project_id=project_id,
1403+
content_array=content_array,
1404+
external_id_array=external_id_array,
1405+
json_metadata_array=json_metadata_array,
1406+
disable_tqdm=disable_tqdm,
1407+
wait_until_availability=wait_until_availability,
1408+
**kwargs,
1409+
)
1410+
13141411
@overload
13151412
def delete(
13161413
self,

src/kili/services/asset_import/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
ImportValidationError,
88
)
99

10+
from .audio import AudioDataImporter
1011
from .base import (
1112
BaseAbstractAssetImporter,
1213
LoggerParams,
@@ -24,6 +25,7 @@
2425
from kili.client import Kili
2526

2627
importer_by_type: dict[str, type[BaseAbstractAssetImporter]] = {
28+
"AUDIO": AudioDataImporter,
2729
"PDF": PdfDataImporter,
2830
"IMAGE": ImageDataImporter,
2931
"GEOSPATIAL": ImageDataImporter,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Functions to import assets into an AUDIO project."""
2+
3+
import os
4+
from enum import Enum
5+
6+
from kili.core.helpers import is_url
7+
from kili.domain.project import InputType
8+
9+
from .base import (
10+
BaseAbstractAssetImporter,
11+
BatchParams,
12+
ContentBatchImporter,
13+
)
14+
from .exceptions import ImportValidationError
15+
from .types import AssetLike
16+
17+
18+
class AudioDataType(Enum):
19+
"""Audio data type."""
20+
21+
LOCAL_FILE = "LOCAL_FILE"
22+
HOSTED_FILE = "HOSTED_FILE"
23+
24+
25+
class AudioDataImporter(BaseAbstractAssetImporter):
26+
"""Class for importing data into an AUDIO project."""
27+
28+
@staticmethod
29+
def get_data_type(assets: list[AssetLike]) -> AudioDataType:
30+
"""Determine the type of data to upload from the service payload."""
31+
content_array = [asset.get("content", "") for asset in assets]
32+
has_local_file = any(os.path.exists(content) for content in content_array) # type: ignore
33+
has_hosted_file = any(is_url(content) for content in content_array)
34+
if has_local_file and has_hosted_file:
35+
raise ImportValidationError(
36+
"""
37+
Cannot upload hosted data and local files at the same time.
38+
Please separate the assets into 2 calls
39+
"""
40+
)
41+
if has_local_file:
42+
return AudioDataType.LOCAL_FILE
43+
return AudioDataType.HOSTED_FILE
44+
45+
def import_assets(self, assets: list[AssetLike], input_type: InputType):
46+
"""Import AUDIO assets into Kili."""
47+
self._check_upload_is_allowed(assets)
48+
data_type = self.get_data_type(assets)
49+
assets = self.filter_duplicate_external_ids(assets)
50+
if data_type == AudioDataType.LOCAL_FILE:
51+
assets = self.filter_local_assets(assets, self.raise_error)
52+
batch_params = BatchParams(is_hosted=False, is_asynchronous=False)
53+
batch_importer = ContentBatchImporter(
54+
self.kili, self.project_params, batch_params, self.pbar
55+
)
56+
elif data_type == AudioDataType.HOSTED_FILE:
57+
batch_params = BatchParams(is_hosted=True, is_asynchronous=False)
58+
batch_importer = ContentBatchImporter(
59+
self.kili, self.project_params, batch_params, self.pbar
60+
)
61+
else:
62+
raise ImportValidationError
63+
return self.import_assets_by_batch(assets, batch_importer)

tests/unit/domain_api/test_assets.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,27 @@ def test_create_image_assets(self, assets_namespace, mock_client):
273273
wait_until_availability=True,
274274
)
275275

276+
def test_create_audio_assets(self, assets_namespace, mock_client):
277+
"""Test create_audio method delegates to client."""
278+
expected_result = {"id": "project_123", "asset_ids": ["asset1", "asset2"]}
279+
mock_client.append_many_to_dataset.return_value = expected_result
280+
281+
result = assets_namespace.create_audio(
282+
project_id="project_123",
283+
content_array=["https://example.com/audio.mp3"],
284+
external_id_array=["ext1"],
285+
)
286+
287+
assert result == expected_result
288+
mock_client.append_many_to_dataset.assert_called_once_with(
289+
project_id="project_123",
290+
content_array=["https://example.com/audio.mp3"],
291+
external_id_array=["ext1"],
292+
json_metadata_array=None,
293+
disable_tqdm=None,
294+
wait_until_availability=True,
295+
)
296+
276297
def test_delete_assets(self, assets_namespace, mock_client):
277298
"""Test delete method delegates to client."""
278299
expected_result = {"id": "project_123"}

0 commit comments

Comments
 (0)