Skip to content
Open
4 changes: 3 additions & 1 deletion google/genai/_interactions/_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@


def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
return isinstance(obj, bytes) or isinstance(obj, io.IOBase) or isinstance(
obj, os.PathLike
)


def is_file_content(obj: object) -> TypeGuard[FileContent]:
Expand Down
4 changes: 2 additions & 2 deletions google/genai/_interactions/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@
ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]]
ProxiesTypes = Union[str, Proxy, ProxiesDict]
if TYPE_CHECKING:
Base64FileInput = Union[IO[bytes], PathLike[str]]
Base64FileInput = Union[IO[bytes], bytes, PathLike[str]]
FileContent = Union[IO[bytes], bytes, PathLike[str]]
else:
Base64FileInput = Union[IO[bytes], PathLike]
Base64FileInput = Union[IO[bytes], bytes, PathLike]
FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8.


Expand Down
8 changes: 6 additions & 2 deletions google/genai/_interactions/_utils/_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ def _format_data(data: object, format_: PropertyFormat, format_template: str | N
if format_ == "base64" and is_base64_file_input(data):
binary: str | bytes | None = None

if isinstance(data, pathlib.Path):
if isinstance(data, bytes):
binary = data
elif isinstance(data, pathlib.Path):
binary = data.read_bytes()
elif isinstance(data, io.IOBase):
binary = data.read()
Expand Down Expand Up @@ -425,7 +427,9 @@ async def _async_format_data(data: object, format_: PropertyFormat, format_templ
if format_ == "base64" and is_base64_file_input(data):
binary: str | bytes | None = None

if isinstance(data, pathlib.Path):
if isinstance(data, bytes):
binary = data
elif isinstance(data, pathlib.Path):
binary = await anyio.Path(data).read_bytes()
elif isinstance(data, io.IOBase):
binary = data.read()
Expand Down
81 changes: 81 additions & 0 deletions google/genai/tests/interactions/test_base64_inputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import base64

import pytest

from ..._interactions._utils import async_maybe_transform
from ..._interactions._utils import maybe_transform
from ..._interactions._utils._json import openapi_dumps
from ..._interactions.types import interaction_create_params


@pytest.mark.parametrize(
'content_type,mime_type',
[
('image', 'image/png'),
('audio', 'audio/wav'),
('video', 'video/mp4'),
('document', 'application/pdf'),
],
)
def test_media_input_bytes_are_base64_encoded(content_type, mime_type):
body = maybe_transform(
{
'input': {
'type': content_type,
'data': b'media-bytes',
'mime_type': mime_type,
},
'model': 'gemini-2.5-flash',
},
interaction_create_params.CreateModelInteractionParamsNonStreaming,
)

assert body['input']['data'] == base64.b64encode(b'media-bytes').decode(
'ascii'
)
assert openapi_dumps(body)


@pytest.mark.parametrize(
'content_type,mime_type',
[
('image', 'image/png'),
('audio', 'audio/wav'),
('video', 'video/mp4'),
('document', 'application/pdf'),
],
)
@pytest.mark.asyncio
async def test_media_input_bytes_are_base64_encoded_async(
content_type, mime_type
):
body = await async_maybe_transform(
{
'input': {
'type': content_type,
'data': b'media-bytes',
'mime_type': mime_type,
},
'model': 'gemini-2.5-flash',
},
interaction_create_params.CreateModelInteractionParamsNonStreaming,
)

assert body['input']['data'] == base64.b64encode(b'media-bytes').decode(
'ascii'
)
assert openapi_dumps(body)