Skip to content

Commit b50341e

Browse files
Stream file upload (#556)
* Add IOBase support to FileServiceClient.create_file * add support to upload file-like object * Give clear errors for 'unknown' mime-types Change-Id: Iea071c396c4cfe2b2c8eacae74dd8fb0acbc128f * Remove duplicate check for mime_type * Add a test uploading from a file-like IO object Change-Id: I572f02ed98b9ca45299b76e7a01695fdcf917e1e * fix type check Change-Id: I220c05eee73ae76ced25254d67332f70a4069f7e --------- Co-authored-by: Mark Daoust <[email protected]>
1 parent be00c19 commit b50341e

File tree

4 files changed

+49
-14
lines changed

4 files changed

+49
-14
lines changed

google/generativeai/client.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, cast
99
from collections.abc import Sequence
1010
import httplib2
11+
from io import IOBase
1112

1213
import google.ai.generativelanguage as glm
1314
import google.generativeai.protos as protos
@@ -88,7 +89,7 @@ def _setup_discovery_api(self, metadata: dict | Sequence[tuple[str, str]] = ()):
8889

8990
def create_file(
9091
self,
91-
path: str | pathlib.Path | os.PathLike,
92+
path: str | pathlib.Path | os.PathLike | IOBase,
9293
*,
9394
mime_type: str | None = None,
9495
name: str | None = None,
@@ -105,9 +106,15 @@ def create_file(
105106
if display_name is not None:
106107
file["displayName"] = display_name
107108

108-
media = googleapiclient.http.MediaFileUpload(
109-
filename=path, mimetype=mime_type, resumable=resumable
110-
)
109+
if isinstance(path, IOBase):
110+
media = googleapiclient.http.MediaIoBaseUpload(
111+
fd=path, mimetype=mime_type, resumable=resumable
112+
)
113+
else:
114+
media = googleapiclient.http.MediaFileUpload(
115+
filename=path, mimetype=mime_type, resumable=resumable
116+
)
117+
111118
request = self._discovery_api.media().upload(body={"file": file}, media_body=media)
112119
for key, value in metadata:
113120
request.headers[key] = value

google/generativeai/files.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import logging
2222
from google.generativeai import protos
2323
from itertools import islice
24+
from io import IOBase
2425

2526
from google.generativeai.types import file_types
2627

@@ -32,7 +33,7 @@
3233

3334

3435
def upload_file(
35-
path: str | pathlib.Path | os.PathLike,
36+
path: str | pathlib.Path | os.PathLike | IOBase,
3637
*,
3738
mime_type: str | None = None,
3839
name: str | None = None,
@@ -42,7 +43,7 @@ def upload_file(
4243
"""Calls the API to upload a file using a supported file service.
4344
4445
Args:
45-
path: The path to the file to be uploaded.
46+
path: The path to the file or a file-like object (e.g., BytesIO) to be uploaded.
4647
mime_type: The MIME type of the file. If not provided, it will be
4748
inferred from the file extension.
4849
name: The name of the file in the destination (e.g., 'files/sample-image').
@@ -57,17 +58,30 @@ def upload_file(
5758
"""
5859
client = get_default_file_client()
5960

60-
path = pathlib.Path(os.fspath(path))
61+
if isinstance(path, IOBase):
62+
if mime_type is None:
63+
raise ValueError(
64+
"Unknown mime type: When passing a file like object to `path` (instead of a\n"
65+
" path-like object) you must set the `mime_type` argument"
66+
)
67+
else:
68+
path = pathlib.Path(os.fspath(path))
6169

62-
if mime_type is None:
63-
mime_type, _ = mimetypes.guess_type(path)
70+
if display_name is None:
71+
display_name = path.name
72+
73+
if mime_type is None:
74+
mime_type, _ = mimetypes.guess_type(path)
75+
76+
if mime_type is None:
77+
raise ValueError(
78+
"Unknown mime type: Could not determine the mimetype for your file\n"
79+
" please set the `mime_type` argument"
80+
)
6481

6582
if name is not None and "/" not in name:
6683
name = f"files/{name}"
6784

68-
if display_name is None:
69-
display_name = path.name
70-
7185
response = client.create_file(
7286
path=path, mime_type=mime_type, name=name, display_name=display_name, resumable=resumable
7387
)

samples/files.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ def test_files_create_pdf(self):
8383
print(response.text)
8484
# [END files_create_pdf]
8585

86+
def test_files_create_from_IO(self):
87+
# [START files_create_io]
88+
# You can pass a file-like object, instead of a path.
89+
# Useful for streaming.
90+
model = genai.GenerativeModel("gemini-1.5-flash")
91+
fpath = media / "test.pdf"
92+
with open(fpath, "rb") as f:
93+
sample_pdf = genai.upload_file(f, mime_type="application/pdf")
94+
response = model.generate_content(["Give me a summary of this pdf file.", sample_pdf])
95+
print(response.text)
96+
# [END files_create_io]
97+
8698
def test_files_list(self):
8799
# [START files_list]
88100
print("My files:")

tests/test_files.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import collections
2020
import datetime
21+
import io
2122
import os
2223
from typing import Iterable, Sequence
2324
import pathlib
@@ -38,7 +39,7 @@ def __init__(self, test):
3839

3940
def create_file(
4041
self,
41-
path: str | pathlib.Path | os.PathLike,
42+
path: str | io.IOBase | os.PathLike,
4243
*,
4344
mime_type: str | None = None,
4445
name: str | None = None,
@@ -102,12 +103,13 @@ def test_video_metadata(self):
102103
protos.File(
103104
uri="https://test",
104105
state="ACTIVE",
106+
mime_type="video/quicktime",
105107
video_metadata=dict(video_duration=datetime.timedelta(seconds=30)),
106108
error=dict(code=7, message="ok?"),
107109
)
108110
)
109111

110-
f = genai.upload_file(path="dummy")
112+
f = genai.upload_file(path="dummy.mov")
111113
self.assertEqual(google.rpc.status_pb2.Status(code=7, message="ok?"), f.error)
112114
self.assertEqual(
113115
protos.VideoMetadata(dict(video_duration=datetime.timedelta(seconds=30))),

0 commit comments

Comments
 (0)