33import datetime
44from dataclasses import dataclass
55import 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
910class 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
1521class Basket :
@@ -19,6 +25,13 @@ class Basket:
1925class 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
2336def 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
4154def 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
4558def 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
4962def test_to_engine_value_struct_with_list ():
5063 basket = Basket (items = ["apple" , "banana" ])
5164 assert to_engine_value (basket ) == [["apple" , "banana" ]]
5265
5366def 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
5770def test_to_engine_value_empty_list ():
5871 assert to_engine_value ([]) == []
@@ -67,3 +80,81 @@ def test_to_engine_value_tuple():
6780
6881def 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