feat(python): support validation of dict subclasses
Currently, attemping to validate an instance of a dict subclass will raise a `ValueError`. This should not be happening, since the instance is still dict. Achieve compatibility by checking the subclasses' inheritance tree, and treat the instance like a dict if that check passes.
This commit is contained in:
parent
9771bc227c
commit
829ca19963
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Support subclasses of Python `dict`s [#427](https://github.com/Stranger6667/jsonschema-rs/issues/427)
|
||||
|
||||
## [0.17.2] - 2024-03-03
|
||||
|
||||
### Added
|
||||
|
|
|
@ -67,6 +67,19 @@ fn is_enum_subclass(object_type: *mut pyo3::ffi::PyTypeObject) -> bool {
|
|||
unsafe { (*(object_type.cast::<ffi::PyTypeObject>())).ob_type == types::ENUM_TYPE }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_dict_subclass(object_type: *mut pyo3::ffi::PyTypeObject) -> bool {
|
||||
// traverse the object's inheritance chain to check if it's a dict subclass
|
||||
let mut current_type = object_type;
|
||||
while !current_type.is_null() {
|
||||
if current_type == unsafe { types::DICT_TYPE } {
|
||||
return true;
|
||||
}
|
||||
current_type = unsafe { (*current_type).tp_base }
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_object_type_from_object(object: *mut pyo3::ffi::PyObject) -> ObjectType {
|
||||
unsafe {
|
||||
let object_type = Py_TYPE(object);
|
||||
|
@ -110,6 +123,8 @@ pub fn get_object_type(object_type: *mut pyo3::ffi::PyTypeObject) -> ObjectType
|
|||
ObjectType::Dict
|
||||
} else if is_enum_subclass(object_type) {
|
||||
ObjectType::Enum
|
||||
} else if is_dict_subclass(object_type) {
|
||||
ObjectType::Dict
|
||||
} else {
|
||||
ObjectType::Unknown(get_type_name(object_type).to_string())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sys
|
||||
import uuid
|
||||
from collections import namedtuple
|
||||
from collections import namedtuple, OrderedDict
|
||||
from contextlib import suppress
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
|
@ -248,3 +248,30 @@ def test_dict_with_non_str_keys():
|
|||
with pytest.raises(ValueError) as exec_info:
|
||||
validate(schema, instance)
|
||||
assert exec_info.value.args[0] == "Dict key must be str. Got 'UUID'"
|
||||
|
||||
|
||||
class MyDict(dict):
|
||||
pass
|
||||
|
||||
|
||||
class MyDict2(MyDict):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"type_, value, expected",
|
||||
(
|
||||
(dict, 1, True),
|
||||
(dict, "bar", False),
|
||||
(OrderedDict, 1, True),
|
||||
(OrderedDict, "bar", False),
|
||||
(MyDict, 1, True),
|
||||
(MyDict, "bar", False),
|
||||
(MyDict2, 1, True),
|
||||
(MyDict2, "bar", False),
|
||||
),
|
||||
)
|
||||
def test_dict_subclasses(type_, value, expected):
|
||||
schema = {"type": "object", "properties": {"foo": {"type": "integer"}}}
|
||||
document = type_({"foo": value})
|
||||
assert is_valid(schema, document) is expected
|
||||
|
|
Loading…
Reference in New Issue