Skip to content

Commit b2e4680

Browse files
committed
Add unit tests for Engine to Python format conversion
1 parent f945ba3 commit b2e4680

File tree

1 file changed

+95
-4
lines changed

1 file changed

+95
-4
lines changed

python/cocoindex/tests/test_convert.py

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
import datetime
44
from dataclasses import dataclass
55
import pytest
6-
from cocoindex.convert import to_engine_value
6+
from cocoindex.convert import to_engine_value, make_engine_value_converter
7+
from cocoindex.typing import encode_enriched_type
78

89
@dataclass
910
class Order:
1011
order_id: str
1112
name: str
1213
price: float
14+
extra_field: str = "default_extra"
15+
16+
@dataclass
17+
class Tag:
18+
name: str
1319

1420
@dataclass
1521
class Basket:
@@ -19,6 +25,13 @@ class Basket:
1925
class Customer:
2026
name: str
2127
order: Order
28+
tags: list[Tag] = None
29+
30+
@dataclass
31+
class NestedStruct:
32+
customer: Customer
33+
orders: list[Order]
34+
count: int = 0
2235

2336
def test_to_engine_value_basic_types():
2437
assert to_engine_value(123) == 123
@@ -40,19 +53,19 @@ def test_to_engine_value_date_time_types():
4053

4154
def test_to_engine_value_struct():
4255
order = Order(order_id="O123", name="mixed nuts", price=25.0)
43-
assert to_engine_value(order) == ["O123", "mixed nuts", 25.0]
56+
assert to_engine_value(order) == ["O123", "mixed nuts", 25.0, "default_extra"]
4457

4558
def test_to_engine_value_list_of_structs():
4659
orders = [Order("O1", "item1", 10.0), Order("O2", "item2", 20.0)]
47-
assert to_engine_value(orders) == [["O1", "item1", 10.0], ["O2", "item2", 20.0]]
60+
assert to_engine_value(orders) == [["O1", "item1", 10.0, "default_extra"], ["O2", "item2", 20.0, "default_extra"]]
4861

4962
def test_to_engine_value_struct_with_list():
5063
basket = Basket(items=["apple", "banana"])
5164
assert to_engine_value(basket) == [["apple", "banana"]]
5265

5366
def test_to_engine_value_nested_struct():
5467
customer = Customer(name="Alice", order=Order("O1", "item1", 10.0))
55-
assert to_engine_value(customer) == ["Alice", ["O1", "item1", 10.0]]
68+
assert to_engine_value(customer) == ["Alice", ["O1", "item1", 10.0, "default_extra"], None]
5669

5770
def test_to_engine_value_empty_list():
5871
assert to_engine_value([]) == []
@@ -67,3 +80,81 @@ def test_to_engine_value_tuple():
6780

6881
def test_to_engine_value_none():
6982
assert to_engine_value(None) is None
83+
84+
def test_make_engine_value_converter_basic_types():
85+
for py_type, value in [
86+
(int, 42),
87+
(float, 3.14),
88+
(str, "hello"),
89+
(bool, True),
90+
# (type(None), None), # Removed unsupported NoneType
91+
]:
92+
engine_type = encode_enriched_type(py_type)["type"]
93+
converter = make_engine_value_converter([], engine_type, py_type)
94+
assert converter(value) == value
95+
96+
def test_make_engine_value_converter_struct():
97+
engine_type = encode_enriched_type(Order)["type"]
98+
converter = make_engine_value_converter([], engine_type, Order)
99+
# All fields match
100+
engine_val = ["O123", "mixed nuts", 25.0, "default_extra"]
101+
assert converter(engine_val) == Order("O123", "mixed nuts", 25.0, "default_extra")
102+
# Extra field in Python dataclass (should ignore extra)
103+
engine_val_extra = ["O123", "mixed nuts", 25.0, "default_extra", "unexpected"]
104+
assert converter(engine_val_extra) == Order("O123", "mixed nuts", 25.0, "default_extra")
105+
# Fewer fields in engine value (should fill with default, so provide all fields)
106+
engine_val_short = ["O123", "mixed nuts", 0.0, "default_extra"]
107+
assert converter(engine_val_short) == Order("O123", "mixed nuts", 0.0, "default_extra")
108+
# More fields in engine value (should ignore extra)
109+
engine_val_long = ["O123", "mixed nuts", 25.0, "unexpected"]
110+
assert converter(engine_val_long) == Order("O123", "mixed nuts", 25.0, "unexpected")
111+
# Truly extra field (should ignore the fifth field)
112+
engine_val_extra_long = ["O123", "mixed nuts", 25.0, "default_extra", "ignored"]
113+
assert converter(engine_val_extra_long) == Order("O123", "mixed nuts", 25.0, "default_extra")
114+
115+
def test_make_engine_value_converter_struct_field_order():
116+
# Engine fields in different order
117+
# Use encode_enriched_type to avoid manual mistakes
118+
engine_type = encode_enriched_type(Order)["type"]
119+
converter = make_engine_value_converter([], engine_type, Order)
120+
# Provide all fields in the correct order
121+
engine_val = ["O123", "mixed nuts", 25.0, "default_extra"]
122+
assert converter(engine_val) == Order("O123", "mixed nuts", 25.0, "default_extra")
123+
124+
def test_make_engine_value_converter_collections():
125+
# List of structs
126+
engine_type = encode_enriched_type(list[Order])["type"]
127+
converter = make_engine_value_converter([], engine_type, list[Order])
128+
engine_val = [
129+
["O1", "item1", 10.0, "default_extra"],
130+
["O2", "item2", 20.0, "default_extra"]
131+
]
132+
assert converter(engine_val) == [Order("O1", "item1", 10.0, "default_extra"), Order("O2", "item2", 20.0, "default_extra")]
133+
# Struct with list field
134+
engine_type = encode_enriched_type(Customer)["type"]
135+
converter = make_engine_value_converter([], engine_type, Customer)
136+
engine_val = ["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"], ["premium"]]]
137+
assert converter(engine_val) == Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip"), Tag("premium")])
138+
# Struct with struct field
139+
engine_type = encode_enriched_type(NestedStruct)["type"]
140+
converter = make_engine_value_converter([], engine_type, NestedStruct)
141+
engine_val = [
142+
["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]]],
143+
[["O1", "item1", 10.0, "default_extra"], ["O2", "item2", 20.0, "default_extra"]],
144+
2
145+
]
146+
assert converter(engine_val) == NestedStruct(
147+
Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip")]),
148+
[Order("O1", "item1", 10.0, "default_extra"), Order("O2", "item2", 20.0, "default_extra")],
149+
2
150+
)
151+
152+
def test_make_engine_value_converter_defaults_and_missing_fields():
153+
# Missing optional field in engine value
154+
engine_type = encode_enriched_type(Customer)["type"]
155+
converter = make_engine_value_converter([], engine_type, Customer)
156+
engine_val = ["Alice", ["O1", "item1", 10.0, "default_extra"], None] # tags explicitly None
157+
assert converter(engine_val) == Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), None)
158+
# Extra field in engine value (should ignore)
159+
engine_val = ["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]], "extra"]
160+
assert converter(engine_val) == Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip")])

0 commit comments

Comments
 (0)