Skip to content

Commit ab652da

Browse files
committed
feat(convert): use built-in cache for type info before encoding
1 parent c91a51f commit ab652da

File tree

3 files changed

+44
-53
lines changed

3 files changed

+44
-53
lines changed

python/cocoindex/convert.py

Lines changed: 38 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import dataclasses
88
import datetime
9+
import functools
910
import inspect
1011
import warnings
1112
from enum import Enum
@@ -63,41 +64,44 @@ def _is_type_kind_convertible_to(src_type_kind: str, dst_type_kind: str) -> bool
6364
)
6465

6566

66-
def _get_cached_type_info(
67-
type_to_analyze: Any,
68-
cache: dict[Any, AnalyzedTypeInfo],
69-
) -> AnalyzedTypeInfo:
70-
"""Retrieve or compute and cache the type information for a given type."""
71-
if type_to_analyze not in cache:
72-
cache[type_to_analyze] = analyze_type_info(type_to_analyze)
73-
return cache[type_to_analyze]
67+
def _get_type_info_safe(type_to_analyze: Any) -> AnalyzedTypeInfo:
68+
"""Safely get type info, bypassing cache if type is not hashable."""
69+
70+
@functools.cache
71+
def _get_cached_type_info() -> AnalyzedTypeInfo:
72+
"""cache the computed type information for a given type."""
73+
return analyze_type_info(type_to_analyze)
74+
75+
try:
76+
return _get_cached_type_info(type_to_analyze)
77+
except TypeError:
78+
return analyze_type_info(type_to_analyze)
7479

7580

7681
def _encode_engine_value_core(
7782
value: Any,
78-
type_hint: Type[Any] | str | None = None,
79-
type_variant: AnalyzedTypeInfo | None = None,
80-
_elem_type_cache: dict[Any, AnalyzedTypeInfo] | None = None,
83+
type_info: AnalyzedTypeInfo | None = None,
8184
) -> Any:
8285
"""Core encoding logic for converting Python values to engine values."""
83-
_elem_type_cache = _elem_type_cache or {}
8486

8587
if dataclasses.is_dataclass(value):
8688
fields = dataclasses.fields(value)
8789
return [
88-
encode_engine_value(
90+
_encode_engine_value_core(
8991
getattr(value, f.name),
90-
type_hint=f.type,
92+
type_info=_get_type_info_safe(f.type),
9193
)
9294
for f in fields
9395
]
9496

9597
if is_namedtuple_type(type(value)):
9698
annotations = type(value).__annotations__
9799
return [
98-
encode_engine_value(
100+
_encode_engine_value_core(
99101
getattr(value, name),
100-
type_hint=annotations.get(name),
102+
type_info=_get_type_info_safe(annotations.get(name))
103+
if annotations.get(name)
104+
else None,
101105
)
102106
for name in value._fields
103107
]
@@ -110,66 +114,51 @@ def _encode_engine_value_core(
110114

111115
if isinstance(value, (list, tuple)):
112116
if (
113-
type_variant
114-
and isinstance(type_variant.variant, AnalyzedListType)
115-
and type_variant.variant.elem_type
117+
type_info
118+
and isinstance(type_info.variant, AnalyzedListType)
119+
and type_info.variant.elem_type
116120
):
117-
elem_type_info = _get_cached_type_info(
118-
type_variant.variant.elem_type, _elem_type_cache
119-
)
121+
elem_type_info = _get_type_info_safe(type_info.variant.elem_type)
120122
return [
121123
_encode_engine_value_core(
122124
v,
123-
type_hint=None,
124-
type_variant=elem_type_info,
125-
_elem_type_cache=_elem_type_cache,
125+
type_info=elem_type_info,
126126
)
127127
for v in value
128128
]
129129
else:
130-
return [encode_engine_value(v, type_hint) for v in value]
130+
return [_encode_engine_value_core(v, type_info=None) for v in value]
131131

132132
if isinstance(value, dict):
133133
# Determine if this is a JSON type
134134
is_json_type = False
135-
if type_variant and isinstance(type_variant.variant, AnalyzedBasicType):
136-
is_json_type = type_variant.variant.kind == "Json"
137-
elif type_hint:
138-
hint_type_info = type_variant or _get_cached_type_info(
139-
type_hint, _elem_type_cache
140-
)
141-
is_json_type = (
142-
isinstance(hint_type_info.variant, AnalyzedBasicType)
143-
and hint_type_info.variant.kind == "Json"
144-
)
135+
if type_info and isinstance(type_info.variant, AnalyzedBasicType):
136+
is_json_type = type_info.variant.kind == "Json"
145137

146138
# Handle empty dict
147139
if not value:
148-
return value if (not type_hint or is_json_type) else []
140+
return value if (not type_info or is_json_type) else []
149141

150142
# Handle KTable
151143
first_val = next(iter(value.values()))
152144
if is_struct_type(type(first_val)):
153145
return [
154-
[encode_engine_value(k, type_hint)] + encode_engine_value(v, type_hint)
146+
[_encode_engine_value_core(k, type_info=None)]
147+
+ _encode_engine_value_core(v, type_info=None)
155148
for k, v in value.items()
156149
]
157150

158151
# Handle regular dict
159152
if (
160-
type_variant
161-
and isinstance(type_variant.variant, AnalyzedDictType)
162-
and type_variant.variant.value_type
153+
type_info
154+
and isinstance(type_info.variant, AnalyzedDictType)
155+
and type_info.variant.value_type
163156
):
164-
value_type_info = _get_cached_type_info(
165-
type_variant.variant.value_type, _elem_type_cache
166-
)
157+
value_type_info = _get_type_info_safe(type_info.variant.value_type)
167158
return {
168159
k: _encode_engine_value_core(
169160
v,
170-
type_hint=None,
171-
type_variant=value_type_info,
172-
_elem_type_cache=_elem_type_cache,
161+
type_info=value_type_info,
173162
)
174163
for k, v in value.items()
175164
}
@@ -189,11 +178,11 @@ def encode_engine_value(value: Any, type_hint: Type[Any] | str) -> Any:
189178
The encoded engine value
190179
"""
191180
# Analyze type once and reuse the result
192-
type_info = _get_cached_type_info(type_hint, {})
181+
type_info = _get_type_info_safe(type_hint)
193182
if isinstance(type_info.variant, AnalyzedUnknownType):
194183
raise ValueError(f"Type annotation `{type_info.core_type}` is unsupported")
195184

196-
return _encode_engine_value_core(value, type_hint=type_hint, type_variant=type_info)
185+
return _encode_engine_value_core(value, type_info)
197186

198187

199188
def make_engine_value_decoder(

python/cocoindex/flow.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,9 +1100,11 @@ async def eval_async(self, *args: Any, **kwargs: Any) -> T:
11001100
self._flow_arg_types[i] if i < len(self._flow_arg_types) else None
11011101
)
11021102
if i < len(args):
1103-
params.append(encode_engine_value(args[i], type_hint=param_type))
1103+
params.append(encode_engine_value(args[i], type_hint=param_type or Any))
11041104
elif arg in kwargs:
1105-
params.append(encode_engine_value(kwargs[arg], type_hint=param_type))
1105+
params.append(
1106+
encode_engine_value(kwargs[arg], type_hint=param_type or Any)
1107+
)
11061108
else:
11071109
raise ValueError(f"Parameter {arg} is not provided")
11081110
engine_result = await flow_info.engine_flow.evaluate_async(params)

python/cocoindex/tests/test_convert.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def test_encode_engine_value_nested_struct() -> None:
226226

227227
def test_encode_engine_value_empty_list() -> None:
228228
assert encode_engine_value([], list) == []
229-
assert encode_engine_value([[]], list[list]) == [[]]
229+
assert encode_engine_value([[]], list[list[Any]]) == [[]]
230230

231231

232232
def test_encode_engine_value_tuple() -> None:
@@ -238,7 +238,7 @@ def test_encode_engine_value_tuple() -> None:
238238

239239

240240
def test_encode_engine_value_none() -> None:
241-
assert encode_engine_value(None, int | None) is None
241+
assert encode_engine_value(None, Any) is None
242242

243243

244244
def test_roundtrip_basic_types() -> None:

0 commit comments

Comments
 (0)