Skip to content

Commit ac1b3e9

Browse files
wanlin31copybara-github
authored andcommitted
chore: internal change
PiperOrigin-RevId: 902676924
1 parent d880f92 commit ac1b3e9

3 files changed

Lines changed: 53 additions & 19 deletions

File tree

google/genai/_interactions/_files.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import io
2020
import os
2121
import pathlib
22-
from typing import overload
23-
from typing_extensions import TypeGuard
22+
from typing import Sequence, cast, overload
23+
from typing_extensions import TypeVar, TypeGuard
2424

2525
import anyio
2626

@@ -33,7 +33,9 @@
3333
HttpxFileContent,
3434
HttpxRequestFiles,
3535
)
36-
from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
36+
from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t
37+
38+
_T = TypeVar("_T")
3739

3840

3941
def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
@@ -137,3 +139,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent:
137139
return await anyio.Path(file).read_bytes()
138140

139141
return file
142+
143+
144+
def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
145+
"""Copy only the containers along the given paths.
146+
147+
Used to guard against mutation by extract_files without copying the entire structure.
148+
Only dicts and lists that lie on a path are copied; everything else
149+
is returned by reference.
150+
151+
For example, given paths=[["foo", "files", "file"]] and the structure:
152+
{
153+
"foo": {
154+
"bar": {"baz": {}},
155+
"files": {"file": <content>}
156+
}
157+
}
158+
The root dict, "foo", and "files" are copied (they lie on the path).
159+
"bar" and "baz" are returned by reference (off the path).
160+
"""
161+
return _deepcopy_with_paths(item, paths, 0)
162+
163+
164+
def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
165+
if not paths:
166+
return item
167+
if is_mapping(item):
168+
key_to_paths: dict[str, list[Sequence[str]]] = {}
169+
for path in paths:
170+
if index < len(path):
171+
key_to_paths.setdefault(path[index], []).append(path)
172+
173+
# if no path continues through this mapping, it won't be mutated and copying it is redundant
174+
if not key_to_paths:
175+
return item
176+
177+
result = dict(item)
178+
for key, subpaths in key_to_paths.items():
179+
if key in result:
180+
result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
181+
return cast(_T, result)
182+
if is_list(item):
183+
array_paths = [path for path in paths if index < len(path) and path[index] == "<array>"]
184+
185+
# if no path expects a list here, nothing will be mutated inside it - return by reference
186+
if not array_paths:
187+
return cast(_T, item)
188+
return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
189+
return item

google/genai/_interactions/_utils/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
coerce_integer as coerce_integer,
4040
file_from_path as file_from_path,
4141
strip_not_given as strip_not_given,
42-
deepcopy_minimal as deepcopy_minimal,
4342
get_async_library as get_async_library,
4443
maybe_coerce_float as maybe_coerce_float,
4544
get_required_header as get_required_header,

google/genai/_interactions/_utils/_utils.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -193,21 +193,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
193193
return isinstance(obj, Iterable)
194194

195195

196-
def deepcopy_minimal(item: _T) -> _T:
197-
"""Minimal reimplementation of copy.deepcopy() that will only copy certain object types:
198-
199-
- mappings, e.g. `dict`
200-
- list
201-
202-
This is done for performance reasons.
203-
"""
204-
if is_mapping(item):
205-
return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()})
206-
if is_list(item):
207-
return cast(_T, [deepcopy_minimal(entry) for entry in item])
208-
return item
209-
210-
211196
# copied from https://github.com/Rapptz/RoboDanny
212197
def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
213198
size = len(seq)

0 commit comments

Comments
 (0)