mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-20 14:59:27 +00:00
Make stl.h list|set|map_caster more user friendly. (#4686)
* Add `test_pass_std_vector_int()`, `test_pass_std_set_int()` in test_stl * Change `list_caster` to also accept generator objects (`PyGen_Check(src.ptr()`). Note for completeness: This is a more conservative change than https://github.com/google/pywrapcc/pull/30042 * Drop in (currently unpublished) PyCLIF code, use in `list_caster`, adjust tests. * Use `PyObjectTypeIsConvertibleToStdSet()` in `set_caster`, adjust tests. * Use `PyObjectTypeIsConvertibleToStdMap()` in `map_caster`, add tests. * Simplify `list_caster` `load()` implementation, push str/bytes check into `PyObjectTypeIsConvertibleToStdVector()`. * clang-tidy cleanup with a few extra `(... != 0)` to be more consistent. * Also use `PyObjectTypeIsConvertibleToStdVector()` in `array_caster`. * Update comment pointing to clif/python/runtime.cc (code is unchanged). * Comprehensive test coverage, enhanced set_caster load implementation. * Resolve clang-tidy eror. * Add a long C++ comment explaining what led to the `PyObjectTypeIsConvertibleTo*()` implementations. * Minor function name change in test. * strcmp -> std::strcmp (thanks @Skylion007 for catching this) * Add `PyCallable_Check(items)` in `PyObjectTypeIsConvertibleToStdMap()` * Resolve clang-tidy error * Use `PyMapping_Items()` instead of `src.attr("items")()`, to be internally consistent with `PyMapping_Check()` * Update link to PyCLIF sources. * Fix typo (thanks @wangxf123456 for catching this) * Add `test_pass_std_vector_int()`, `test_pass_std_set_int()` in test_stl * Change `list_caster` to also accept generator objects (`PyGen_Check(src.ptr()`). Note for completeness: This is a more conservative change than https://github.com/google/pywrapcc/pull/30042 * Drop in (currently unpublished) PyCLIF code, use in `list_caster`, adjust tests. * Use `PyObjectTypeIsConvertibleToStdSet()` in `set_caster`, adjust tests. * Use `PyObjectTypeIsConvertibleToStdMap()` in `map_caster`, add tests. * Simplify `list_caster` `load()` implementation, push str/bytes check into `PyObjectTypeIsConvertibleToStdVector()`. * clang-tidy cleanup with a few extra `(... != 0)` to be more consistent. * Also use `PyObjectTypeIsConvertibleToStdVector()` in `array_caster`. * Update comment pointing to clif/python/runtime.cc (code is unchanged). * Comprehensive test coverage, enhanced set_caster load implementation. * Resolve clang-tidy eror. * Add a long C++ comment explaining what led to the `PyObjectTypeIsConvertibleTo*()` implementations. * Minor function name change in test. * strcmp -> std::strcmp (thanks @Skylion007 for catching this) * Add `PyCallable_Check(items)` in `PyObjectTypeIsConvertibleToStdMap()` * Resolve clang-tidy error * Use `PyMapping_Items()` instead of `src.attr("items")()`, to be internally consistent with `PyMapping_Check()` * Update link to PyCLIF sources. * Fix typo (thanks @wangxf123456 for catching this) * Fix typo discovered by new version of codespell.
This commit is contained in:
committed by
GitHub
parent
4a06eca591
commit
0d44d720cb
@@ -381,3 +381,129 @@ def test_return_vector_bool_raw_ptr():
|
||||
v = m.return_vector_bool_raw_ptr()
|
||||
assert isinstance(v, list)
|
||||
assert len(v) == 4513
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("fn", "offset"), [(m.pass_std_vector_int, 0), (m.pass_std_array_int_2, 1)]
|
||||
)
|
||||
def test_pass_std_vector_int(fn, offset):
|
||||
assert fn([7, 13]) == 140 + offset
|
||||
assert fn({6, 2}) == 116 + offset
|
||||
assert fn({"x": 8, "y": 11}.values()) == 138 + offset
|
||||
assert fn({3: None, 9: None}.keys()) == 124 + offset
|
||||
assert fn(i for i in [4, 17]) == 142 + offset
|
||||
assert fn(map(lambda i: i * 3, [8, 7])) == 190 + offset # noqa: C417
|
||||
with pytest.raises(TypeError):
|
||||
fn({"x": 0, "y": 1})
|
||||
with pytest.raises(TypeError):
|
||||
fn({})
|
||||
|
||||
|
||||
def test_pass_std_vector_pair_int():
|
||||
fn = m.pass_std_vector_pair_int
|
||||
assert fn({1: 2, 3: 4}.items()) == 406
|
||||
assert fn(zip([5, 17], [13, 9])) == 2222
|
||||
|
||||
|
||||
def test_list_caster_fully_consumes_generator_object():
|
||||
def gen_invalid():
|
||||
yield from [1, 2.0, 3]
|
||||
|
||||
gen_obj = gen_invalid()
|
||||
with pytest.raises(TypeError):
|
||||
m.pass_std_vector_int(gen_obj)
|
||||
assert not tuple(gen_obj)
|
||||
|
||||
|
||||
def test_pass_std_set_int():
|
||||
fn = m.pass_std_set_int
|
||||
assert fn({3, 15}) == 254
|
||||
assert fn({5: None, 12: None}.keys()) == 251
|
||||
with pytest.raises(TypeError):
|
||||
fn([])
|
||||
with pytest.raises(TypeError):
|
||||
fn({})
|
||||
with pytest.raises(TypeError):
|
||||
fn({}.values())
|
||||
with pytest.raises(TypeError):
|
||||
fn(i for i in [])
|
||||
|
||||
|
||||
def test_set_caster_dict_keys_failure():
|
||||
dict_keys = {1: None, 2.0: None, 3: None}.keys()
|
||||
# The asserts does not really exercise anything in pybind11, but if one of
|
||||
# them fails in some future version of Python, the set_caster load
|
||||
# implementation may need to be revisited.
|
||||
assert tuple(dict_keys) == (1, 2.0, 3)
|
||||
assert tuple(dict_keys) == (1, 2.0, 3)
|
||||
with pytest.raises(TypeError):
|
||||
m.pass_std_set_int(dict_keys)
|
||||
assert tuple(dict_keys) == (1, 2.0, 3)
|
||||
|
||||
|
||||
class FakePyMappingMissingItems:
|
||||
def __getitem__(self, _):
|
||||
raise RuntimeError("Not expected to be called.")
|
||||
|
||||
|
||||
class FakePyMappingWithItems(FakePyMappingMissingItems):
|
||||
def items(self):
|
||||
return ((1, 3), (2, 4))
|
||||
|
||||
|
||||
class FakePyMappingBadItems(FakePyMappingMissingItems):
|
||||
def items(self):
|
||||
return ((1, 2), (3, "x"))
|
||||
|
||||
|
||||
class FakePyMappingItemsNotCallable(FakePyMappingMissingItems):
|
||||
@property
|
||||
def items(self):
|
||||
return ((1, 2), (3, 4))
|
||||
|
||||
|
||||
class FakePyMappingItemsWithArg(FakePyMappingMissingItems):
|
||||
def items(self, _):
|
||||
return ((1, 2), (3, 4))
|
||||
|
||||
|
||||
class FakePyMappingGenObj(FakePyMappingMissingItems):
|
||||
def __init__(self, gen_obj):
|
||||
super().__init__()
|
||||
self.gen_obj = gen_obj
|
||||
|
||||
def items(self):
|
||||
yield from self.gen_obj
|
||||
|
||||
|
||||
def test_pass_std_map_int():
|
||||
fn = m.pass_std_map_int
|
||||
assert fn({1: 2, 3: 4}) == 4506
|
||||
with pytest.raises(TypeError):
|
||||
fn([])
|
||||
assert fn(FakePyMappingWithItems()) == 3507
|
||||
with pytest.raises(TypeError):
|
||||
fn(FakePyMappingMissingItems())
|
||||
with pytest.raises(TypeError):
|
||||
fn(FakePyMappingBadItems())
|
||||
with pytest.raises(TypeError):
|
||||
fn(FakePyMappingItemsNotCallable())
|
||||
with pytest.raises(TypeError):
|
||||
fn(FakePyMappingItemsWithArg())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("items", "expected_exception"),
|
||||
[
|
||||
(((1, 2), (3, "x"), (4, 5)), TypeError),
|
||||
(((1, 2), (3, 4, 5), (6, 7)), ValueError),
|
||||
],
|
||||
)
|
||||
def test_map_caster_fully_consumes_generator_object(items, expected_exception):
|
||||
def gen_invalid():
|
||||
yield from items
|
||||
|
||||
gen_obj = gen_invalid()
|
||||
with pytest.raises(expected_exception):
|
||||
m.pass_std_map_int(FakePyMappingGenObj(gen_obj))
|
||||
assert not tuple(gen_obj)
|
||||
|
||||
Reference in New Issue
Block a user