from __future__ import annotations import contextlib import sys import types import pytest import env from pybind11_tests import detailed_error_messages_enabled from pybind11_tests import pytypes as m def test_obj_class_name(): assert m.obj_class_name(None) == "NoneType" assert m.obj_class_name(list) == "list" assert m.obj_class_name([]) == "list" def test_handle_from_move_only_type_with_operator_PyObject(): assert m.handle_from_move_only_type_with_operator_PyObject_ncnst() assert m.handle_from_move_only_type_with_operator_PyObject_const() def test_bool(doc): assert doc(m.get_bool) == "get_bool() -> bool" def test_int(doc): assert doc(m.get_int) == "get_int() -> int" def test_iterator(doc): assert doc(m.get_iterator) == "get_iterator() -> collections.abc.Iterator" @pytest.mark.parametrize( ("pytype", "from_iter_func"), [ (frozenset, m.get_frozenset_from_iterable), (list, m.get_list_from_iterable), (set, m.get_set_from_iterable), (tuple, m.get_tuple_from_iterable), ], ) def test_from_iterable(pytype, from_iter_func): my_iter = iter(range(10)) s = from_iter_func(my_iter) assert type(s) == pytype assert s == pytype(range(10)) def test_iterable(doc): assert doc(m.get_iterable) == "get_iterable() -> collections.abc.Iterable" lst = [1, 2, 3] i = m.get_first_item_from_iterable(lst) assert i == 1 i = m.get_second_item_from_iterable(lst) assert i == 2 def test_float(doc): assert doc(m.get_float) == "get_float() -> float" def test_list(capture, doc): assert m.list_no_args() == [] assert m.list_ssize_t() == [] assert m.list_size_t() == [] lst = [1, 2] m.list_insert_ssize_t(lst) assert lst == [1, 83, 2] m.list_insert_size_t(lst) assert lst == [1, 83, 2, 57] m.list_clear(lst) assert lst == [] with capture: lst = m.get_list() assert lst == ["inserted-0", "overwritten", "inserted-2"] lst.append("value2") m.print_list(lst) assert ( capture.unordered == """ Entry at position 0: value list item 0: inserted-0 list item 1: overwritten list item 2: inserted-2 list item 3: value2 """ ) assert doc(m.get_list) == "get_list() -> list" assert doc(m.print_list) == "print_list(arg0: list) -> None" def test_none(doc): assert doc(m.get_none) == "get_none() -> None" assert doc(m.print_none) == "print_none(arg0: None) -> None" def test_set(capture, doc): s = m.get_set() assert isinstance(s, set) assert s == {"key1", "key2", "key3"} s.add("key4") with capture: m.print_anyset(s) assert ( capture.unordered == """ key: key1 key: key2 key: key3 key: key4 """ ) m.set_add(s, "key5") assert m.anyset_size(s) == 5 m.set_clear(s) assert m.anyset_empty(s) assert not m.anyset_contains(set(), 42) assert m.anyset_contains({42}, 42) assert m.anyset_contains({"foo"}, "foo") assert doc(m.get_set) == "get_set() -> set" assert doc(m.print_anyset) == "print_anyset(arg0: set | frozenset) -> None" def test_frozenset(capture, doc): s = m.get_frozenset() assert isinstance(s, frozenset) assert s == frozenset({"key1", "key2", "key3"}) with capture: m.print_anyset(s) assert ( capture.unordered == """ key: key1 key: key2 key: key3 """ ) assert m.anyset_size(s) == 3 assert not m.anyset_empty(s) assert not m.anyset_contains(frozenset(), 42) assert m.anyset_contains(frozenset({42}), 42) assert m.anyset_contains(frozenset({"foo"}), "foo") assert doc(m.get_frozenset) == "get_frozenset() -> frozenset" def test_dict(capture, doc): d = m.get_dict() assert d == {"key": "value"} with capture: d["key2"] = "value2" m.print_dict(d) assert ( capture.unordered == """ key: key, value=value key: key2, value=value2 """ ) assert not m.dict_contains({}, 42) assert m.dict_contains({42: None}, 42) assert m.dict_contains({"foo": None}, "foo") assert doc(m.get_dict) == "get_dict() -> dict" assert doc(m.print_dict) == "print_dict(arg0: dict) -> None" assert m.dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3} class CustomContains: d = {"key": None} def __contains__(self, m): return m in self.d @pytest.mark.parametrize( ("arg", "func"), [ (set(), m.anyset_contains), ({}, m.dict_contains), (CustomContains(), m.obj_contains), ], ) def test_unhashable_exceptions(arg, func): class Unhashable: __hash__ = None with pytest.raises(TypeError) as exc_info: func(arg, Unhashable()) assert "unhashable type:" in str(exc_info.value) def test_tuple(): assert m.tuple_no_args() == () assert m.tuple_ssize_t() == () assert m.tuple_size_t() == () assert m.get_tuple() == (42, None, "spam") def test_simple_namespace(): ns = m.get_simple_namespace() assert ns.attr == 42 assert ns.x == "foo" assert ns.right == 2 assert not hasattr(ns, "wrong") def test_str(doc): assert m.str_from_char_ssize_t().encode().decode() == "red" assert m.str_from_char_size_t().encode().decode() == "blue" assert m.str_from_string().encode().decode() == "baz" assert m.str_from_bytes().encode().decode() == "boo" assert doc(m.str_from_bytes) == "str_from_bytes() -> str" class A: def __str__(self): return "this is a str" def __repr__(self): return "this is a repr" assert m.str_from_object(A()) == "this is a str" assert m.repr_from_object(A()) == "this is a repr" assert m.str_from_handle(A()) == "this is a str" s1, s2 = m.str_format() assert s1 == "1 + 2 = 3" assert s1 == s2 malformed_utf8 = b"\x80" if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"): assert m.str_from_object(malformed_utf8) is malformed_utf8 else: assert m.str_from_object(malformed_utf8) == "b'\\x80'" assert m.str_from_handle(malformed_utf8) == "b'\\x80'" assert m.str_from_string_from_str("this is a str") == "this is a str" ucs_surrogates_str = "\udcc3" with pytest.raises(UnicodeEncodeError): m.str_from_string_from_str(ucs_surrogates_str) @pytest.mark.parametrize( "func", [ m.str_from_bytes_input, m.str_from_cstr_input, m.str_from_std_string_input, ], ) @pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side") def test_surrogate_pairs_unicode_error(func): input_str = "\ud83d\ude4f".encode("utf-8", "surrogatepass") with pytest.raises(UnicodeDecodeError): func(input_str) def test_bytes(doc): assert m.bytes_from_char_ssize_t().decode() == "green" assert m.bytes_from_char_size_t().decode() == "purple" assert m.bytes_from_string().decode() == "foo" assert m.bytes_from_str().decode() == "bar" assert doc(m.bytes_from_str) == "bytes_from_str() -> bytes" def test_bytearray(): assert m.bytearray_from_char_ssize_t().decode() == "$%" assert m.bytearray_from_char_size_t().decode() == "@$!" assert m.bytearray_from_string().decode() == "foo" assert m.bytearray_size() == len("foo") def test_capsule(capture): pytest.gc_collect() with capture: a = m.return_capsule_with_destructor() del a pytest.gc_collect() assert ( capture.unordered == """ creating capsule destructing capsule """ ) with capture: a = m.return_renamed_capsule_with_destructor() del a pytest.gc_collect() assert ( capture.unordered == """ creating capsule renaming capsule destructing capsule """ ) with capture: a = m.return_capsule_with_destructor_2() del a pytest.gc_collect() assert ( capture.unordered == """ creating capsule destructing capsule: 1234 """ ) with capture: a = m.return_capsule_with_destructor_3() del a pytest.gc_collect() assert ( capture.unordered == """ creating capsule destructing capsule: 1233 original name: oname """ ) with capture: a = m.return_renamed_capsule_with_destructor_2() del a pytest.gc_collect() assert ( capture.unordered == """ creating capsule renaming capsule destructing capsule: 1234 """ ) with capture: a = m.return_capsule_with_name_and_destructor() del a pytest.gc_collect() assert ( capture.unordered == """ created capsule (1234, 'pointer type description') destructing capsule (1234, 'pointer type description') """ ) with capture: a = m.return_capsule_with_explicit_nullptr_dtor() del a pytest.gc_collect() assert ( capture.unordered == """ creating capsule with explicit nullptr dtor """ ) def test_accessors(): class SubTestObject: attr_obj = 1 attr_char = 2 class TestObject: basic_attr = 1 begin_end = [1, 2, 3] d = {"operator[object]": 1, "operator[char *]": 2} sub = SubTestObject() def func(self, x, *args): return self.basic_attr + x + sum(args) d = m.accessor_api(TestObject()) assert d["basic_attr"] == 1 assert d["begin_end"] == [1, 2, 3] assert d["operator[object]"] == 1 assert d["operator[char *]"] == 2 assert d["attr(object)"] == 1 assert d["attr(char *)"] == 2 assert d["missing_attr_ptr"] == "raised" assert d["missing_attr_chain"] == "raised" assert d["is_none"] is False assert d["operator()"] == 2 assert d["operator*"] == 7 assert d["implicit_list"] == [1, 2, 3] assert all(x in TestObject.__dict__ for x in d["implicit_dict"]) assert m.tuple_accessor(()) == (0, 1, 2) d = m.accessor_assignment() assert d["get"] == 0 assert d["deferred_get"] == 0 assert d["set"] == 1 assert d["deferred_set"] == 1 assert d["var"] == 99 def test_accessor_moves(): inc_refs = m.accessor_moves() if inc_refs: assert inc_refs == [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] else: pytest.skip("Not defined: PYBIND11_HANDLE_REF_DEBUG") @pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side") def test_constructors(): """C++ default and converting constructors are equivalent to type calls in Python""" types = [bytes, bytearray, str, bool, int, float, tuple, list, dict, set] expected = {t.__name__: t() for t in types} assert m.default_constructors() == expected data = { bytes: b"41", # Currently no supported or working conversions. bytearray: bytearray(b"41"), str: 42, bool: "Not empty", int: "42", float: "+1e3", tuple: range(3), list: range(3), dict: [("two", 2), ("one", 1), ("three", 3)], set: [4, 4, 5, 6, 6, 6], frozenset: [4, 4, 5, 6, 6, 6], memoryview: b"abc", } inputs = {k.__name__: v for k, v in data.items()} expected = {k.__name__: k(v) for k, v in data.items()} assert m.converting_constructors(inputs) == expected assert m.cast_functions(inputs) == expected # Converting constructors and cast functions should just reference rather # than copy when no conversion is needed: noconv1 = m.converting_constructors(expected) for k in noconv1: assert noconv1[k] is expected[k] noconv2 = m.cast_functions(expected) for k in noconv2: assert noconv2[k] is expected[k] def test_non_converting_constructors(): non_converting_test_cases = [ ("bytes", range(10)), ("none", 42), ("ellipsis", 42), ("type", 42), ] for t, v in non_converting_test_cases: for move in [True, False]: with pytest.raises(TypeError) as excinfo: m.nonconverting_constructor(t, v, move) expected_error = ( f"Object of type '{type(v).__name__}' is not an instance of '{t}'" ) assert str(excinfo.value) == expected_error def test_pybind11_str_raw_str(): # specifically to exercise pybind11::str::raw_str cvt = m.convert_to_pybind11_str assert cvt("Str") == "Str" assert cvt(b"Bytes") == "b'Bytes'" assert cvt(None) == "None" assert cvt(False) == "False" assert cvt(True) == "True" assert cvt(42) == "42" assert cvt(2**65) == "36893488147419103232" assert cvt(-1.50) == "-1.5" assert cvt(()) == "()" assert cvt((18,)) == "(18,)" assert cvt([]) == "[]" assert cvt([28]) == "[28]" assert cvt({}) == "{}" assert cvt({3: 4}) == "{3: 4}" assert cvt(set()) == "set()" assert cvt({3}) == "{3}" valid_orig = "DZ" valid_utf8 = valid_orig.encode("utf-8") valid_cvt = cvt(valid_utf8) if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"): assert valid_cvt is valid_utf8 else: assert type(valid_cvt) is str assert valid_cvt == "b'\\xc7\\xb1'" malformed_utf8 = b"\x80" if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"): assert cvt(malformed_utf8) is malformed_utf8 else: malformed_cvt = cvt(malformed_utf8) assert type(malformed_cvt) is str assert malformed_cvt == "b'\\x80'" def test_implicit_casting(): """Tests implicit casting when assigning or appending to dicts and lists.""" z = m.get_implicit_casting() assert z["d"] == { "char*_i1": "abc", "char*_i2": "abc", "char*_e": "abc", "char*_p": "abc", "str_i1": "str", "str_i2": "str1", "str_e": "str2", "str_p": "str3", "int_i1": 42, "int_i2": 42, "int_e": 43, "int_p": 44, } assert z["l"] == [3, 6, 9, 12, 15] def test_print(capture): with capture: m.print_function() assert ( capture == """ Hello, World! 1 2.0 three True -- multiple args *args-and-a-custom-separator no new line here -- next print flush py::print + str.format = this """ ) assert capture.stderr == "this goes to stderr" with pytest.raises(RuntimeError) as excinfo: m.print_failure() assert str(excinfo.value) == "Unable to convert call argument " + ( "'1' of type 'UnregisteredType' to Python object" if detailed_error_messages_enabled else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" ) def test_hash(): class Hashable: def __init__(self, value): self.value = value def __hash__(self): return self.value class Unhashable: __hash__ = None assert m.hash_function(Hashable(42)) == 42 with pytest.raises(TypeError): m.hash_function(Unhashable()) def test_number_protocol(): for a, b in [(1, 1), (3, 5)]: li = [ a == b, a != b, a < b, a <= b, a > b, a >= b, a + b, a - b, a * b, a / b, a | b, a & b, a ^ b, a >> b, a << b, ] assert m.test_number_protocol(a, b) == li def test_list_slicing(): li = list(range(100)) assert li[0:-1:2] == m.test_list_slicing(li) assert li[::] == m.test_list_slicing_default(li) def test_issue2361(): # See issue #2361 assert m.issue2361_str_implicit_copy_none() == "None" with pytest.raises(TypeError) as excinfo: assert m.issue2361_dict_implicit_copy_none() assert "NoneType" in str(excinfo.value) assert "iterable" in str(excinfo.value) @pytest.mark.parametrize( ("method", "args", "fmt", "expected_view"), [ (m.test_memoryview_object, (b"red",), "B", b"red"), (m.test_memoryview_buffer_info, (b"green",), "B", b"green"), (m.test_memoryview_from_buffer, (False,), "h", [3, 1, 4, 1, 5]), (m.test_memoryview_from_buffer, (True,), "H", [2, 7, 1, 8]), (m.test_memoryview_from_buffer_nativeformat, (), "@i", [4, 7, 5]), ], ) def test_memoryview(method, args, fmt, expected_view): view = method(*args) assert isinstance(view, memoryview) assert view.format == fmt assert list(view) == list(expected_view) @pytest.mark.xfail("env.PYPY", reason="getrefcount is not available") @pytest.mark.parametrize( "method", [ m.test_memoryview_object, m.test_memoryview_buffer_info, ], ) def test_memoryview_refcount(method): # Avoiding a literal to avoid an immortal object in free-threaded builds buf = "\x0a\x0b\x0c\x0d".encode("ascii") ref_before = sys.getrefcount(buf) view = method(buf) ref_after = sys.getrefcount(buf) assert ref_before < ref_after assert list(view) == list(buf) def test_memoryview_from_buffer_empty_shape(): view = m.test_memoryview_from_buffer_empty_shape() assert isinstance(view, memoryview) assert view.format == "B" assert bytes(view) == b"" def test_test_memoryview_from_buffer_invalid_strides(): with pytest.raises(RuntimeError): m.test_memoryview_from_buffer_invalid_strides() def test_test_memoryview_from_buffer_nullptr(): with pytest.raises(ValueError): m.test_memoryview_from_buffer_nullptr() def test_memoryview_from_memory(): view = m.test_memoryview_from_memory() assert isinstance(view, memoryview) assert view.format == "B" assert bytes(view) == b"\xff\xe1\xab\x37" def test_builtin_functions(): assert m.get_len(list(range(42))) == 42 with pytest.raises(TypeError) as exc_info: m.get_len(i for i in range(42)) assert str(exc_info.value) in [ "object of type 'generator' has no len()", "'generator' has no length", ] # PyPy def test_isinstance_string_types(): assert m.isinstance_pybind11_bytes(b"") assert not m.isinstance_pybind11_bytes("") assert m.isinstance_pybind11_str("") if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"): assert m.isinstance_pybind11_str(b"") else: assert not m.isinstance_pybind11_str(b"") def test_pass_bytes_or_unicode_to_string_types(): assert m.pass_to_pybind11_bytes(b"Bytes") == 5 with pytest.raises(TypeError): m.pass_to_pybind11_bytes("Str") if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"): assert m.pass_to_pybind11_str(b"Bytes") == 5 else: with pytest.raises(TypeError): m.pass_to_pybind11_str(b"Bytes") assert m.pass_to_pybind11_str("Str") == 3 assert m.pass_to_std_string(b"Bytes") == 5 assert m.pass_to_std_string("Str") == 3 malformed_utf8 = b"\x80" if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"): assert m.pass_to_pybind11_str(malformed_utf8) == 1 else: with pytest.raises(TypeError): m.pass_to_pybind11_str(malformed_utf8) @pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") @pytest.mark.parametrize( ("create_weakref", "create_weakref_with_callback"), [ (m.weakref_from_handle, m.weakref_from_handle_and_function), (m.weakref_from_object, m.weakref_from_object_and_function), ], ) def test_weakref(create_weakref, create_weakref_with_callback): from weakref import getweakrefcount # Apparently, you cannot weakly reference an object() class WeaklyReferenced: pass callback_called = False def callback(_): nonlocal callback_called callback_called = True obj = WeaklyReferenced() assert getweakrefcount(obj) == 0 wr = create_weakref(obj) assert getweakrefcount(obj) == 1 obj = WeaklyReferenced() assert getweakrefcount(obj) == 0 wr = create_weakref_with_callback(obj, callback) # noqa: F841 assert getweakrefcount(obj) == 1 assert not callback_called del obj pytest.gc_collect() assert callback_called @pytest.mark.parametrize( ("create_weakref", "has_callback"), [ (m.weakref_from_handle, False), (m.weakref_from_object, False), (m.weakref_from_handle_and_function, True), (m.weakref_from_object_and_function, True), ], ) def test_weakref_err(create_weakref, has_callback): class C: __slots__ = [] def callback(_): pass ob = C() # Should raise TypeError on CPython cm = pytest.raises(TypeError) if env.PYPY or env.GRAALPY: cm = contextlib.nullcontext() with cm: _ = create_weakref(ob, callback) if has_callback else create_weakref(ob) def test_cpp_iterators(): assert m.tuple_iterator() == 12 assert m.dict_iterator() == 305 + 711 assert m.passed_iterator(iter((-7, 3))) == -4 def test_implementation_details(): lst = [39, 43, 92, 49, 22, 29, 93, 98, 26, 57, 8] tup = tuple(lst) assert m.sequence_item_get_ssize_t(lst) == 43 assert m.sequence_item_set_ssize_t(lst) is None assert lst[1] == "peppa" assert m.sequence_item_get_size_t(lst) == 92 assert m.sequence_item_set_size_t(lst) is None assert lst[2] == "george" assert m.list_item_get_ssize_t(lst) == 49 assert m.list_item_set_ssize_t(lst) is None assert lst[3] == "rebecca" assert m.list_item_get_size_t(lst) == 22 assert m.list_item_set_size_t(lst) is None assert lst[4] == "richard" assert m.tuple_item_get_ssize_t(tup) == 29 assert m.tuple_item_set_ssize_t() == ("emely", "edmond") assert m.tuple_item_get_size_t(tup) == 93 assert m.tuple_item_set_size_t() == ("candy", "cat") def test_external_float_(): r1 = m.square_float_(2.0) assert r1 == 4.0 def test_tuple_rvalue_getter(): pop = 1000 tup = tuple(range(pop)) m.tuple_rvalue_getter(tup) def test_list_rvalue_getter(): pop = 1000 my_list = list(range(pop)) m.list_rvalue_getter(my_list) def test_populate_dict_rvalue(): pop = 1000 my_dict = {i: i for i in range(pop)} assert m.populate_dict_rvalue(pop) == my_dict def test_populate_obj_str_attrs(): pop = 1000 o = types.SimpleNamespace(**{str(i): i for i in range(pop)}) new_o = m.populate_obj_str_attrs(o, pop) new_attrs = {k: v for k, v in new_o.__dict__.items() if not k.startswith("_")} assert all(isinstance(v, str) for v in new_attrs.values()) assert len(new_attrs) == pop @pytest.mark.parametrize( ("a", "b"), [("foo", "bar"), (1, 2), (1.0, 2.0), (list(range(3)), list(range(3, 6)))], ) def test_inplace_append(a, b): expected = a + b assert m.inplace_append(a, b) == expected @pytest.mark.parametrize( ("a", "b"), [(3, 2), (3.0, 2.0), (set(range(3)), set(range(2)))] ) def test_inplace_subtract(a, b): expected = a - b assert m.inplace_subtract(a, b) == expected @pytest.mark.parametrize(("a", "b"), [(3, 2), (3.0, 2.0), ([1], 3)]) def test_inplace_multiply(a, b): expected = a * b assert m.inplace_multiply(a, b) == expected @pytest.mark.parametrize(("a", "b"), [(6, 3), (6.0, 3.0)]) def test_inplace_divide(a, b): expected = a / b assert m.inplace_divide(a, b) == expected @pytest.mark.parametrize( ("a", "b"), [ (False, True), ( set(), { 1, }, ), ], ) def test_inplace_or(a, b): expected = a | b assert m.inplace_or(a, b) == expected @pytest.mark.parametrize( ("a", "b"), [ (True, False), ( {1, 2, 3}, { 1, }, ), ], ) def test_inplace_and(a, b): expected = a & b assert m.inplace_and(a, b) == expected @pytest.mark.parametrize(("a", "b"), [(8, 1), (-3, 2)]) def test_inplace_lshift(a, b): expected = a << b assert m.inplace_lshift(a, b) == expected @pytest.mark.parametrize(("a", "b"), [(8, 1), (-2, 2)]) def test_inplace_rshift(a, b): expected = a >> b assert m.inplace_rshift(a, b) == expected def test_tuple_nonempty_annotations(doc): assert ( doc(m.annotate_tuple_float_str) == "annotate_tuple_float_str(arg0: tuple[typing.SupportsFloat, str]) -> None" ) def test_tuple_empty_annotations(doc): assert ( doc(m.annotate_tuple_empty) == "annotate_tuple_empty(arg0: tuple[()]) -> None" ) def test_tuple_variable_length_annotations(doc): assert ( doc(m.annotate_tuple_variable_length) == "annotate_tuple_variable_length(arg0: tuple[typing.SupportsFloat, ...]) -> None" ) def test_dict_annotations(doc): assert ( doc(m.annotate_dict_str_int) == "annotate_dict_str_int(arg0: dict[str, typing.SupportsInt]) -> None" ) def test_list_annotations(doc): assert ( doc(m.annotate_list_int) == "annotate_list_int(arg0: list[typing.SupportsInt]) -> None" ) def test_set_annotations(doc): assert doc(m.annotate_set_str) == "annotate_set_str(arg0: set[str]) -> None" def test_iterable_annotations(doc): assert ( doc(m.annotate_iterable_str) == "annotate_iterable_str(arg0: collections.abc.Iterable[str]) -> None" ) def test_iterator_annotations(doc): assert ( doc(m.annotate_iterator_int) == "annotate_iterator_int(arg0: collections.abc.Iterator[typing.SupportsInt]) -> None" ) def test_fn_annotations(doc): assert ( doc(m.annotate_fn) == "annotate_fn(arg0: collections.abc.Callable[[list[str], str], int]) -> None" ) def test_fn_return_only(doc): assert ( doc(m.annotate_fn_only_return) == "annotate_fn_only_return(arg0: collections.abc.Callable[..., int]) -> None" ) def test_type_annotation(doc): assert ( doc(m.annotate_type) == "annotate_type(arg0: type[typing.SupportsInt]) -> type" ) def test_union_annotations(doc): assert ( doc(m.annotate_union) == "annotate_union(arg0: list[str | typing.SupportsInt | object], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[str | int | object]" ) def test_union_typing_only(doc): assert doc(m.union_typing_only) == "union_typing_only(arg0: list[str]) -> list[int]" def test_union_object_annotations(doc): assert ( doc(m.annotate_union_to_object) == "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object" ) def test_optional_annotations(doc): assert ( doc(m.annotate_optional) == "annotate_optional(arg0: list) -> list[str | None]" ) def test_type_guard_annotations(doc, backport_typehints): assert ( backport_typehints(doc(m.annotate_type_guard)) == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" ) def test_type_is_annotations(doc, backport_typehints): assert ( backport_typehints(doc(m.annotate_type_is)) == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" ) def test_no_return_annotation(doc): assert doc(m.annotate_no_return) == "annotate_no_return() -> typing.NoReturn" def test_never_annotation(doc, backport_typehints): assert ( backport_typehints(doc(m.annotate_never)) == "annotate_never() -> typing.Never" ) def test_optional_object_annotations(doc): assert ( doc(m.annotate_optional_to_object) == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" ) @pytest.mark.skipif( not m.defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL, reason="C++20 non-type template args feature not available.", ) def test_literal(doc): assert ( doc(m.annotate_literal) == 'annotate_literal(arg0: typing.Literal[26, 0x1A, "hello world", b"hello world", u"hello world", True, Color.RED, None]) -> object' ) # The characters !, @, %, {, } and -> are used in the signature parser as special characters, but Literal should escape those for the parser to work. assert ( doc(m.identity_literal_exclamation) == 'identity_literal_exclamation(arg0: typing.Literal["!"]) -> typing.Literal["!"]' ) assert ( doc(m.identity_literal_at) == 'identity_literal_at(arg0: typing.Literal["@"]) -> typing.Literal["@"]' ) assert ( doc(m.identity_literal_percent) == 'identity_literal_percent(arg0: typing.Literal["%"]) -> typing.Literal["%"]' ) assert ( doc(m.identity_literal_curly_open) == 'identity_literal_curly_open(arg0: typing.Literal["{"]) -> typing.Literal["{"]' ) assert ( doc(m.identity_literal_curly_close) == 'identity_literal_curly_close(arg0: typing.Literal["}"]) -> typing.Literal["}"]' ) assert ( doc(m.identity_literal_arrow_with_io_name) == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' ) assert ( doc(m.identity_literal_arrow_with_callable) == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], float | int], float]) -> collections.abc.Callable[[typing.Literal["->"], float | int], float]' ) assert ( doc(m.identity_literal_all_special_chars) == 'identity_literal_all_special_chars(arg0: typing.Literal["!@!!->{%}"]) -> typing.Literal["!@!!->{%}"]' ) @pytest.mark.skipif( not m.defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL, reason="C++20 non-type template args feature not available.", ) def test_typevar(doc): assert ( doc(m.annotate_generic_containers) == "annotate_generic_containers(arg0: list[T]) -> list[V]" ) assert doc(m.annotate_listT_to_T) == "annotate_listT_to_T(arg0: list[T]) -> T" assert doc(m.annotate_object_to_T) == "annotate_object_to_T(arg0: object) -> T" @pytest.mark.skipif( not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, reason=" not available.", ) @pytest.mark.parametrize( ("tested_tuple", "expected"), [((1,), [2]), ((3, 4), [4, 5]), ((7, 8, 9), [8, 9, 10])], ) def test_tuple_ranges(tested_tuple, expected): assert m.tuple_iterator_default_initialization() assert m.transform_tuple_plus_one(tested_tuple) == expected @pytest.mark.skipif( not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, reason=" not available.", ) @pytest.mark.parametrize( ("tested_list", "expected"), [([1], [2]), ([3, 4], [4, 5]), ([7, 8, 9], [8, 9, 10])] ) def test_list_ranges(tested_list, expected): assert m.list_iterator_default_initialization() assert m.transform_list_plus_one(tested_list) == expected @pytest.mark.skipif( not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, reason=" not available.", ) @pytest.mark.parametrize( ("tested_dict", "expected"), [ ({1: 2}, [(2, 3)]), ({3: 4, 5: 6}, [(4, 5), (6, 7)]), ({7: 8, 9: 10, 11: 12}, [(8, 9), (10, 11), (12, 13)]), ], ) def test_dict_ranges(tested_dict, expected): assert m.dict_iterator_default_initialization() assert m.transform_dict_plus_one(tested_dict) == expected # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older def get_annotations_helper(o): if sys.version_info >= (3, 14): import annotationlib return annotationlib.get_annotations(o) or None if isinstance(o, type): return o.__dict__.get("__annotations__", None) return getattr(o, "__annotations__", None) @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.", ) def test_module_attribute_types() -> None: module_annotations = get_annotations_helper(m) assert module_annotations["list_int"] == "list[typing.SupportsInt]" assert module_annotations["set_str"] == "set[str]" assert module_annotations["foo"] == "pybind11_tests.pytypes.foo" assert ( module_annotations["foo_union"] == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" ) @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.", ) @pytest.mark.skipif( sys.version_info < (3, 10), reason="get_annotations function does not exist until Python3.10", ) def test_get_annotations_compliance() -> None: from inspect import get_annotations module_annotations = get_annotations(m) assert module_annotations["list_int"] == "list[typing.SupportsInt]" assert module_annotations["set_str"] == "set[str]" @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.", ) def test_class_attribute_types() -> None: empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) static_annotations = get_annotations_helper(m.Static) instance_annotations = get_annotations_helper(m.Instance) assert empty_annotations is None assert static_annotations["x"] == "typing.ClassVar[typing.SupportsFloat]" assert ( static_annotations["dict_str_int"] == "typing.ClassVar[dict[str, typing.SupportsInt]]" ) assert m.Static.x == 1.0 m.Static.x = 3.0 static = m.Static() assert static.x == 3.0 static.dict_str_int["hi"] = 3 assert m.Static().dict_str_int == {"hi": 3} assert instance_annotations["y"] == "typing.SupportsFloat" instance1 = m.Instance() instance1.y = 4.0 instance2 = m.Instance() instance2.y = 5.0 assert instance1.y != instance2.y @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.", ) def test_redeclaration_attr_with_type_hint() -> None: obj = m.Instance() m.attr_with_type_hint_float_x(obj) assert get_annotations_helper(obj)["x"] == "typing.SupportsFloat" with pytest.raises( RuntimeError, match=r'^__annotations__\["x"\] was set already\.$' ): m.attr_with_type_hint_float_x(obj) @pytest.mark.skipif( not m.defined___cpp_inline_variables, reason="C++17 feature __cpp_inline_variables not available.", ) def test_final_annotation() -> None: module_annotations = get_annotations_helper(m) assert module_annotations["CONST_INT"] == "typing.Final[int]" def test_arg_return_type_hints(doc, backport_typehints): assert doc(m.half_of_number) == "half_of_number(arg0: float | int) -> float" assert ( doc(m.half_of_number_convert) == "half_of_number_convert(x: float | int) -> float" ) assert ( doc(m.half_of_number_noconvert) == "half_of_number_noconvert(x: float) -> float" ) assert m.half_of_number(2.0) == 1.0 assert m.half_of_number(2) == 1.0 assert m.half_of_number(0) == 0 assert isinstance(m.half_of_number(0), float) assert not isinstance(m.half_of_number(0), int) # std::vector assert ( doc(m.half_of_number_vector) == "half_of_number_vector(arg0: collections.abc.Sequence[float | int]) -> list[float]" ) # Tuple assert ( doc(m.half_of_number_tuple) == "half_of_number_tuple(arg0: tuple[float | int, float | int]) -> tuple[float, float]" ) # Tuple assert ( doc(m.half_of_number_tuple_ellipsis) == "half_of_number_tuple_ellipsis(arg0: tuple[float | int, ...]) -> tuple[float, ...]" ) # Dict assert ( doc(m.half_of_number_dict) == "half_of_number_dict(arg0: dict[str, float | int]) -> dict[str, float]" ) # List assert ( doc(m.half_of_number_list) == "half_of_number_list(arg0: list[float | int]) -> list[float]" ) # List> assert ( doc(m.half_of_number_nested_list) == "half_of_number_nested_list(arg0: list[list[float | int]]) -> list[list[float]]" ) # Set assert doc(m.identity_set) == "identity_set(arg0: set[float | int]) -> set[float]" # Iterable assert ( doc(m.identity_iterable) == "identity_iterable(arg0: collections.abc.Iterable[float | int]) -> collections.abc.Iterable[float]" ) # Iterator assert ( doc(m.identity_iterator) == "identity_iterator(arg0: collections.abc.Iterator[float | int]) -> collections.abc.Iterator[float]" ) # Callable identity assert ( doc(m.identity_callable) == "identity_callable(arg0: collections.abc.Callable[[float | int], float]) -> collections.abc.Callable[[float | int], float]" ) # Callable identity assert ( doc(m.identity_callable_ellipsis) == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" ) # Nested Callable identity assert ( doc(m.identity_nested_callable) == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]) -> collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]" ) # Callable assert ( doc(m.apply_callable) == "apply_callable(arg0: float | int, arg1: collections.abc.Callable[[float | int], float]) -> float" ) # Callable assert ( doc(m.apply_callable_ellipsis) == "apply_callable_ellipsis(arg0: float | int, arg1: collections.abc.Callable[..., float]) -> float" ) # Union assert ( doc(m.identity_union) == "identity_union(arg0: float | int | str) -> float | str" ) # Optional assert ( doc(m.identity_optional) == "identity_optional(arg0: float | int | None) -> float | None" ) # TypeIs assert ( backport_typehints(doc(m.check_type_is)) == "check_type_is(arg0: object) -> typing.TypeIs[float]" ) # TypeGuard assert ( backport_typehints(doc(m.check_type_guard)) == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" )