Skip to content

Commit 6ed8e99

Browse files
committed
feat: Add upload_many method for multiple file uploads
1 parent 7b3f4a4 commit 6ed8e99

File tree

1 file changed

+127
-1
lines changed

1 file changed

+127
-1
lines changed

google/genai/files.py

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import mimetypes
2121
import os
2222
import pathlib
23-
from typing import Any, Optional, Union
23+
from typing import Any, Optional, Union, List
2424
from urllib.parse import urlencode
2525
from . import _api_module
2626
from . import _common
@@ -697,6 +697,69 @@ def upload(
697697
kwargs=config_model.model_dump() if config else {},
698698
)
699699

700+
def upload_many(
701+
self,
702+
*,
703+
files: List[Union[str, pathlib.Path, os.PathLike, io.IOBase]],
704+
configs: Optional[List[Optional[types.UploadFileConfigOrDict]]] = None,
705+
) -> List[types.File]:
706+
"""Uploads multiple files using a supported file service.
707+
708+
Args:
709+
files: A list of paths to files or `IOBase` objects to be uploaded. Each file
710+
must follow the same requirements as the single file upload method. If it's an
711+
IOBase object, it must be opened in blocking (the default) mode and
712+
binary mode. In other words, do not use non-blocking mode or text mode.
713+
The given stream must be seekable, that is, it must be able to call
714+
`seek()` on 'path'.
715+
configs: Optional list of configurations for each file. If provided, must have
716+
the same length as the files list. Each config can contain parameters to set
717+
`display_name`, `mime_type`, and `name`.
718+
719+
Returns:
720+
A list of uploaded File objects.
721+
722+
Raises:
723+
ValueError: If the number of configs doesn't match the number of files.
724+
FileNotFoundError: If any of the file paths are invalid.
725+
ValueError: If the mime type cannot be determined for any file.
726+
ValueError: If any file is not opened in binary mode.
727+
ValueError: If this method is called with a Vertex AI client.
728+
729+
Example:
730+
```python
731+
files = client.files.upload_many(
732+
files=["file1.txt", "file2.txt"],
733+
configs=[
734+
{"display_name": "First File"},
735+
{"display_name": "Second File"}
736+
]
737+
)
738+
```
739+
"""
740+
if self._api_client.vertexai:
741+
raise ValueError(
742+
'This method is only supported in the Gemini Developer client.'
743+
)
744+
745+
if not files:
746+
return []
747+
748+
if configs is not None and len(configs) != len(files):
749+
raise ValueError(
750+
'The number of configs must match the number of files'
751+
)
752+
753+
if configs is None:
754+
configs = [None] * len(files)
755+
756+
uploaded_files = []
757+
for file, config in zip(files, configs):
758+
uploaded_file = self.upload(file=file, config=config)
759+
uploaded_files.append(uploaded_file)
760+
761+
return uploaded_files
762+
700763
def list(
701764
self, *, config: Optional[types.ListFilesConfigOrDict] = None
702765
) -> Pager[types.File]:
@@ -1160,6 +1223,69 @@ async def upload(
11601223
kwargs=config_model.model_dump() if config else {},
11611224
)
11621225

1226+
async def upload_many(
1227+
self,
1228+
*,
1229+
files: List[Union[str, pathlib.Path, os.PathLike, io.IOBase]],
1230+
configs: Optional[List[Optional[types.UploadFileConfigOrDict]]] = None,
1231+
) -> List[types.File]:
1232+
"""Uploads multiple files asynchronously using a supported file service.
1233+
1234+
Args:
1235+
files: A list of paths to files or `IOBase` objects to be uploaded. Each file
1236+
must follow the same requirements as the single file upload method. If it's an
1237+
IOBase object, it must be opened in blocking (the default) mode and
1238+
binary mode. In other words, do not use non-blocking mode or text mode.
1239+
The given stream must be seekable, that is, it must be able to call
1240+
`seek()` on 'path'.
1241+
configs: Optional list of configurations for each file. If provided, must have
1242+
the same length as the files list. Each config can contain parameters to set
1243+
`display_name`, `mime_type`, and `name`.
1244+
1245+
Returns:
1246+
A list of uploaded File objects.
1247+
1248+
Raises:
1249+
ValueError: If the number of configs doesn't match the number of files.
1250+
FileNotFoundError: If any of the file paths are invalid.
1251+
ValueError: If the mime type cannot be determined for any file.
1252+
ValueError: If any file is not opened in binary mode.
1253+
ValueError: If this method is called with a Vertex AI client.
1254+
1255+
Example:
1256+
```python
1257+
files = await client.aio.files.upload_many(
1258+
files=["file1.txt", "file2.txt"],
1259+
configs=[
1260+
{"display_name": "First File"},
1261+
{"display_name": "Second File"}
1262+
]
1263+
)
1264+
```
1265+
"""
1266+
if self._api_client.vertexai:
1267+
raise ValueError(
1268+
'This method is only supported in the Gemini Developer client.'
1269+
)
1270+
1271+
if not files:
1272+
return []
1273+
1274+
if configs is not None and len(configs) != len(files):
1275+
raise ValueError(
1276+
'The number of configs must match the number of files'
1277+
)
1278+
1279+
if configs is None:
1280+
configs = [None] * len(files)
1281+
1282+
uploaded_files = []
1283+
for file, config in zip(files, configs):
1284+
uploaded_file = await self.upload(file=file, config=config)
1285+
uploaded_files.append(uploaded_file)
1286+
1287+
return uploaded_files
1288+
11631289
async def list(
11641290
self, *, config: Optional[types.ListFilesConfigOrDict] = None
11651291
) -> AsyncPager[types.File]:

0 commit comments

Comments
 (0)