Skip to content

Commit d50b10e

Browse files
committed
Fix mypy errors
1 parent 94ed65e commit d50b10e

File tree

3 files changed

+29
-19
lines changed

3 files changed

+29
-19
lines changed

src/agents/util/_safe_copy.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
from decimal import Decimal
66
from fractions import Fraction
77
from pathlib import PurePath
8-
from typing import Any
8+
from typing import Any, TypeVar
99
from uuid import UUID
1010

11+
T = TypeVar("T")
1112

12-
def safe_copy(obj: Any) -> Any:
13+
14+
def safe_copy(obj: T) -> T:
1315
"""
1416
Copy 'obj' without triggering deepcopy on complex/fragile objects.
1517
@@ -56,46 +58,46 @@ def _is_simple_atom(o: Any) -> bool:
5658
return isinstance(o, _SIMPLE_ATOMS)
5759

5860

59-
def _safe_copy_internal(obj: Any, memo: dict[int, Any]) -> Any:
61+
def _safe_copy_internal(obj: T, memo: dict[int, Any]) -> T:
6062
oid = id(obj)
6163
if oid in memo:
62-
return memo[oid]
64+
return memo[oid] # type: ignore [no-any-return]
6365

6466
# 1) Simple "atoms": safe to deepcopy (cheap, predictable).
6567
if _is_simple_atom(obj):
6668
return copy.deepcopy(obj)
6769

6870
# 2) Containers: rebuild and recurse.
6971
if isinstance(obj, dict):
70-
new_dict = {}
72+
new_dict: dict[Any, Any] = {}
7173
memo[oid] = new_dict
7274
for k, v in obj.items():
7375
# preserve key identity/value, only copy the value
7476
new_dict[k] = _safe_copy_internal(v, memo)
75-
return new_dict
77+
return new_dict # type: ignore [return-value]
7678

7779
if isinstance(obj, list):
7880
new_list: list[Any] = []
7981
memo[oid] = new_list
8082
new_list.extend(_safe_copy_internal(x, memo) for x in obj)
81-
return new_list
83+
return new_list # type: ignore [return-value]
8284

8385
if isinstance(obj, tuple):
8486
new_tuple = tuple(_safe_copy_internal(x, memo) for x in obj)
8587
memo[oid] = new_tuple
86-
return new_tuple
88+
return new_tuple # type: ignore [return-value]
8789

8890
if isinstance(obj, set):
8991
new_set: set[Any] = set()
9092
memo[oid] = new_set
9193
for x in obj:
9294
new_set.add(_safe_copy_internal(x, memo))
93-
return new_set
95+
return new_set # type: ignore [return-value]
9496

9597
if isinstance(obj, frozenset):
9698
new_fset = frozenset(_safe_copy_internal(x, memo) for x in obj)
9799
memo[oid] = new_fset
98-
return new_fset
100+
return new_fset # type: ignore
99101

100102
# 3) Unknown/complex leaf: return as-is (identity preserved).
101103
memo[oid] = obj

tests/test_items_helpers.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from openai.types.responses.response_output_message_param import ResponseOutputMessageParam
2323
from openai.types.responses.response_output_refusal import ResponseOutputRefusal
2424
from openai.types.responses.response_output_text import ResponseOutputText
25+
from openai.types.responses.response_output_text_param import ResponseOutputTextParam
2526
from openai.types.responses.response_reasoning_item import ResponseReasoningItem, Summary
2627
from openai.types.responses.response_reasoning_item_param import ResponseReasoningItemParam
2728
from pydantic import TypeAdapter
@@ -116,7 +117,13 @@ def test_input_to_new_input_list_copies_the_ones_produced_by_pydantic() -> None:
116117
# Given a list of message dictionaries, ensure the returned list is a deep copy.
117118
original = ResponseOutputMessageParam(
118119
id="a75654dc-7492-4d1c-bce0-89e8312fbdd7",
119-
content=[{"type": "text", "text": "Hey, what's up?"}],
120+
content=[
121+
ResponseOutputTextParam(
122+
type="output_text",
123+
text="Hey, what's up?",
124+
annotations=[],
125+
)
126+
],
120127
role="assistant",
121128
status="completed",
122129
type="message",
@@ -125,15 +132,15 @@ def test_input_to_new_input_list_copies_the_ones_produced_by_pydantic() -> None:
125132
output_item = TypeAdapter(ResponseOutputMessageParam).validate_json(original_json)
126133
new_list = ItemHelpers.input_to_new_input_list([output_item])
127134
assert len(new_list) == 1
128-
assert new_list[0]["id"] == original["id"]
135+
assert new_list[0]["id"] == original["id"] # type: ignore
129136
size = 0
130137
for i, item in enumerate(original["content"]):
131138
size += 1 # pydantic_core._pydantic_core.ValidatorIterator does not support len()
132-
assert item["type"] == original["content"][i]["type"]
133-
assert item["text"] == original["content"][i]["text"]
139+
assert item["type"] == original["content"][i]["type"] # type: ignore
140+
assert item["text"] == original["content"][i]["text"] # type: ignore
134141
assert size == 1
135-
assert new_list[0]["role"] == original["role"]
136-
assert new_list[0]["status"] == original["status"]
142+
assert new_list[0]["role"] == original["role"] # type: ignore
143+
assert new_list[0]["status"] == original["status"] # type: ignore
137144
assert new_list[0]["type"] == original["type"]
138145

139146

tests/utils/test_safe_copy.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io
44
from decimal import Decimal
55
from fractions import Fraction
6+
from typing import Any
67
from uuid import UUID
78

89
import pytest
@@ -73,7 +74,7 @@ def test_deep_copy_for_nested_containers_of_primitives():
7374
cpy = safe_copy(orig)
7475

7576
# mutate original deeply
76-
orig["a"][2]["z"] = (99, 100)
77+
orig["a"][2]["z"] = (99, 100) # type: ignore
7778

7879
assert cpy == {"a": [1, 2, {"z": (3, 4)}]} # unaffected
7980

@@ -96,7 +97,7 @@ def __init__(self):
9697

9798
# mutating the leaf reflects in the copied structure
9899
leaf.val = 42
99-
assert cpy["k"].val == 42
100+
assert cpy["k"].val == 42 # type: ignore [attr-defined]
100101

101102

102103
def test_generator_is_preserved_and_not_consumed():
@@ -146,7 +147,7 @@ class Marker:
146147

147148
def test_cycles_are_handled_without_recursion_error():
148149
# a -> (a,)
149-
a = []
150+
a: list[Any] = []
150151
t = (a,)
151152
a.append(t)
152153

0 commit comments

Comments
 (0)