Skip to content

Commit f5c2042

Browse files
committed
WIP: add test
1 parent 5980e5b commit f5c2042

File tree

2 files changed

+48
-2
lines changed

2 files changed

+48
-2
lines changed

src/input/input_python.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ fn get_ordered_dict_type(py: Python<'_>) -> &Bound<'_, PyType> {
6262
.bind(py)
6363
}
6464

65+
// Lazy helper that only initializes OrderedDict type when actually needed
66+
fn check_if_ordered_dict(obj: &Bound<'_, PyAny>) -> bool {
67+
// Quick type name check first - avoid Python import if possible
68+
if let Ok(type_name) = obj.get_type().name() {
69+
if type_name.to_string() != "OrderedDict" {
70+
return false; // Fast path for non-OrderedDict objects
71+
}
72+
}
73+
74+
// Only now do we need the expensive type lookup
75+
let ordered_dict_type = get_ordered_dict_type(obj.py());
76+
obj.is_instance(ordered_dict_type).unwrap_or(false)
77+
}
78+
6579
static FRACTION_TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
6680

6781
pub fn get_fraction_type(py: Python<'_>) -> &Bound<'_, PyType> {
@@ -413,13 +427,14 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
413427
}
414428

415429
fn lax_dict<'a>(&'a self) -> ValResult<GenericPyMapping<'a, 'py>> {
416-
let ordered_dict_type = get_ordered_dict_type(self.py());
417-
if self.is_instance(ordered_dict_type).unwrap_or(false) {
430+
// Optimized: Only check for OrderedDict when needed
431+
if check_if_ordered_dict(self) {
418432
// OrderedDict is a subclass of dict, but we want to treat it as a mapping to preserve order
419433
if let Ok(mapping) = self.downcast::<PyMapping>() {
420434
return Ok(GenericPyMapping::Mapping(mapping));
421435
}
422436
}
437+
423438
if let Ok(dict) = self.downcast::<PyDict>() {
424439
Ok(GenericPyMapping::Dict(dict))
425440
} else if let Ok(mapping) = self.downcast::<PyMapping>() {

tests/validators/test_dict.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,34 @@ def test_json_dict_complex_key():
255255
assert v.validate_json('{"1+2j": 2, "infj": 4}') == {complex(1, 2): 2, complex(0, float('inf')): 4}
256256
with pytest.raises(ValidationError, match='Input should be a valid complex string'):
257257
v.validate_json('{"1+2j": 2, "": 4}') == {complex(1, 2): 2, complex(0, float('inf')): 4}
258+
259+
260+
...
261+
def test_ordered_dict_key_order_preservation():
262+
# GH 12273
263+
v = SchemaValidator(cs.dict_schema(keys_schema=cs.str_schema(), values_schema=cs.int_schema()))
264+
265+
# Test case from original issue
266+
foo = OrderedDict({"a": 1, "b": 2})
267+
foo.move_to_end("a")
268+
269+
result = v.validate_python(foo)
270+
assert list(result.keys()) == list(foo.keys()) == ['b', 'a']
271+
assert result == {'b': 2, 'a': 1}
272+
273+
# Test with more complex reordering
274+
foo2 = OrderedDict({"x": 1, "y": 2, "z": 3})
275+
foo2.move_to_end("x")
276+
277+
result2 = v.validate_python(foo2)
278+
assert list(result2.keys()) == list(foo2.keys()) == ['y', 'z', 'x']
279+
assert result2 == {'y': 2, 'z': 3, 'x': 1}
280+
281+
# Test popitem and re-insertion
282+
foo3 = OrderedDict({"p": 1, "q": 2})
283+
item = foo3.popitem(last=False)
284+
foo3[item[0]] = item[1]
285+
286+
result3 = v.validate_python(foo3)
287+
assert list(result3.keys()) == list(foo3.keys()) == ['q', 'p']
288+
assert result3 == {'q': 2, 'p': 1}

0 commit comments

Comments
 (0)