33import datetime
44from dataclasses import dataclass
55import pytest
6- from cocoindex .convert import to_engine_value , make_engine_value_converter
76from cocoindex .typing import encode_enriched_type
7+ from cocoindex .convert import to_engine_value
8+ from cocoindex .convert import make_engine_value_converter
89
910@dataclass
1011class Order :
@@ -33,13 +34,13 @@ class NestedStruct:
3334 orders : list [Order ]
3435 count : int = 0
3536
36- def build_converter ( py_type , target_type = None ):
37+ def build_engine_value_converter ( engine_type_in_py , python_type = None ):
3738 """
38- Helper to build a converter for the given Python type.
39- If target_type is not specified, uses py_type as the target.
39+ Helper to build a converter for the given engine-side type (as represented in Python) .
40+ If python_type is not specified, uses engine_type_in_py as the target.
4041 """
41- engine_type = encode_enriched_type (py_type )["type" ]
42- return make_engine_value_converter ([], engine_type , target_type or py_type )
42+ engine_type = encode_enriched_type (engine_type_in_py )["type" ]
43+ return make_engine_value_converter ([], engine_type , python_type or engine_type_in_py )
4344
4445def test_to_engine_value_basic_types ():
4546 assert to_engine_value (123 ) == 123
@@ -90,18 +91,18 @@ def test_to_engine_value_none():
9091 assert to_engine_value (None ) is None
9192
9293def test_make_engine_value_converter_basic_types ():
93- for py_type , value in [
94+ for engine_type_in_py , value in [
9495 (int , 42 ),
9596 (float , 3.14 ),
9697 (str , "hello" ),
9798 (bool , True ),
9899 # (type(None), None), # Removed unsupported NoneType
99100 ]:
100- converter = build_converter ( py_type )
101+ converter = build_engine_value_converter ( engine_type_in_py )
101102 assert converter (value ) == value
102103
103104def test_make_engine_value_converter_struct ():
104- converter = build_converter (Order )
105+ converter = build_engine_value_converter (Order )
105106 # All fields match
106107 engine_val = ["O123" , "mixed nuts" , 25.0 , "default_extra" ]
107108 assert converter (engine_val ) == Order ("O123" , "mixed nuts" , 25.0 , "default_extra" )
@@ -121,25 +122,25 @@ def test_make_engine_value_converter_struct():
121122def test_make_engine_value_converter_struct_field_order ():
122123 # Engine fields in different order
123124 # Use encode_enriched_type to avoid manual mistakes
124- converter = build_converter (Order )
125+ converter = build_engine_value_converter (Order )
125126 # Provide all fields in the correct order
126127 engine_val = ["O123" , "mixed nuts" , 25.0 , "default_extra" ]
127128 assert converter (engine_val ) == Order ("O123" , "mixed nuts" , 25.0 , "default_extra" )
128129
129130def test_make_engine_value_converter_collections ():
130131 # List of structs
131- converter = build_converter (list [Order ])
132+ converter = build_engine_value_converter (list [Order ])
132133 engine_val = [
133134 ["O1" , "item1" , 10.0 , "default_extra" ],
134135 ["O2" , "item2" , 20.0 , "default_extra" ]
135136 ]
136137 assert converter (engine_val ) == [Order ("O1" , "item1" , 10.0 , "default_extra" ), Order ("O2" , "item2" , 20.0 , "default_extra" )]
137138 # Struct with list field
138- converter = build_converter (Customer )
139+ converter = build_engine_value_converter (Customer )
139140 engine_val = ["Alice" , ["O1" , "item1" , 10.0 , "default_extra" ], [["vip" ], ["premium" ]]]
140141 assert converter (engine_val ) == Customer ("Alice" , Order ("O1" , "item1" , 10.0 , "default_extra" ), [Tag ("vip" ), Tag ("premium" )])
141142 # Struct with struct field
142- converter = build_converter (NestedStruct )
143+ converter = build_engine_value_converter (NestedStruct )
143144 engine_val = [
144145 ["Alice" , ["O1" , "item1" , 10.0 , "default_extra" ], [["vip" ]]],
145146 [["O1" , "item1" , 10.0 , "default_extra" ], ["O2" , "item2" , 20.0 , "default_extra" ]],
@@ -153,9 +154,66 @@ def test_make_engine_value_converter_collections():
153154
154155def test_make_engine_value_converter_defaults_and_missing_fields ():
155156 # Missing optional field in engine value
156- converter = build_converter (Customer )
157+ converter = build_engine_value_converter (Customer )
157158 engine_val = ["Alice" , ["O1" , "item1" , 10.0 , "default_extra" ], None ] # tags explicitly None
158159 assert converter (engine_val ) == Customer ("Alice" , Order ("O1" , "item1" , 10.0 , "default_extra" ), None )
159160 # Extra field in engine value (should ignore)
160161 engine_val = ["Alice" , ["O1" , "item1" , 10.0 , "default_extra" ], [["vip" ]], "extra" ]
161162 assert converter (engine_val ) == Customer ("Alice" , Order ("O1" , "item1" , 10.0 , "default_extra" ), [Tag ("vip" )])
163+
164+ def test_engine_python_schema_field_order ():
165+ """
166+ Engine and Python dataclasses have the same fields but different order.
167+ Converter should map by field name, not order.
168+ """
169+ @dataclass
170+ class EngineOrder :
171+ id : str
172+ name : str
173+ price : float
174+ @dataclass
175+ class PythonOrder :
176+ name : str
177+ id : str
178+ price : float
179+ extra : str = "default"
180+ converter = build_engine_value_converter (EngineOrder , PythonOrder )
181+ engine_val = ["O123" , "mixed nuts" , 25.0 ] # matches EngineOrder order
182+ assert converter (engine_val ) == PythonOrder ("mixed nuts" , "O123" , 25.0 , "default" )
183+
184+ def test_engine_python_schema_extra_field ():
185+ """
186+ Python dataclass has an extra field not present in engine schema.
187+ Converter should fill with default value.
188+ """
189+ @dataclass
190+ class EngineOrder :
191+ id : str
192+ name : str
193+ @dataclass
194+ class PythonOrder :
195+ id : str
196+ name : str
197+ price : float = 0.0
198+ converter = build_engine_value_converter (EngineOrder , PythonOrder )
199+ engine_val = ["O123" , "mixed nuts" ]
200+ assert converter (engine_val ) == PythonOrder ("O123" , "mixed nuts" , 0.0 )
201+
202+ def test_engine_python_schema_missing_field ():
203+ """
204+ Engine dataclass has a field missing in Python dataclass.
205+ Converter should ignore the missing field.
206+ """
207+ from dataclasses import dataclass
208+ @dataclass
209+ class EngineOrder :
210+ id : str
211+ name : str
212+ price : float
213+ @dataclass
214+ class PythonOrder :
215+ id : str
216+ name : str
217+ converter = build_engine_value_converter (EngineOrder , PythonOrder )
218+ engine_val = ["O123" , "mixed nuts" , 25.0 ]
219+ assert converter (engine_val ) == PythonOrder ("O123" , "mixed nuts" )
0 commit comments