diff --git a/src/input/input_python.rs b/src/input/input_python.rs index 0f4ceb672..5a1b9a4b0 100644 --- a/src/input/input_python.rs +++ b/src/input/input_python.rs @@ -395,15 +395,17 @@ impl<'py> Input<'py> for Bound<'py, PyAny> { Self: 'a; fn strict_dict<'a>(&'a self) -> ValResult> { - if let Ok(dict) = self.downcast::() { + if let Ok(dict) = self.downcast_exact::() { Ok(GenericPyMapping::Dict(dict)) + } else if self.is_instance_of::() { + Ok(GenericPyMapping::Mapping(self.downcast::()?)) } else { Err(ValError::new(ErrorTypeDefaults::DictType, self)) } } fn lax_dict<'a>(&'a self) -> ValResult> { - if let Ok(dict) = self.downcast::() { + if let Ok(dict) = self.downcast_exact::() { Ok(GenericPyMapping::Dict(dict)) } else if let Ok(mapping) = self.downcast::() { Ok(GenericPyMapping::Mapping(mapping)) diff --git a/tests/validators/test_dict.py b/tests/validators/test_dict.py index 939d089d9..193c56ac6 100644 --- a/tests/validators/test_dict.py +++ b/tests/validators/test_dict.py @@ -1,4 +1,5 @@ import re +import sys from collections import OrderedDict from collections.abc import Mapping from typing import Any @@ -313,3 +314,29 @@ def test_dict_fail_fast(fail_fast, expected): v.validate_python({'a': 'b', 'c': 'd'}) assert exc_info.value.errors(include_url=False) == expected + + +@pytest.mark.skipif( + sys.implementation.name == 'graalpy', + reason='GraalPy has a bug where PyMapping.items() does not preserve OrderedDict order. See: https://github.com/oracle/graalpython/issues/553', +) +@pytest.mark.parametrize('strict', [True, False]) +def test_ordered_dict_key_order_preservation(strict): + # GH 12273 + v = SchemaValidator(cs.dict_schema(keys_schema=cs.str_schema(), values_schema=cs.int_schema())) + + # Original issue + foo = OrderedDict({'a': 1, 'b': 2}) + foo.move_to_end('a') + + result = v.validate_python(foo, strict=strict) + assert list(result.keys()) == list(foo.keys()) == ['b', 'a'] + assert result == {'b': 2, 'a': 1} + + # More complex case + foo2 = OrderedDict({'x': 1, 'y': 2, 'z': 3}) + foo2.move_to_end('x') + + result2 = v.validate_python(foo2, strict=strict) + assert list(result2.keys()) == list(foo2.keys()) == ['y', 'z', 'x'] + assert result2 == {'y': 2, 'z': 3, 'x': 1}