From 83b92ceb3537666fb0188f564e1d53bf8c80b0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20=C5=A0im=C3=A1=C4=8Dek?= Date: Mon, 18 Nov 2024 23:39:59 +0100 Subject: [PATCH 01/10] Try to fix reentrant write transient failures in tests (#5447) * Disable print_destroyed in tests on GraalPy * Reenable test_iostream on GraalPy --- tests/constructor_stats.h | 8 ++++++++ tests/test_iostream.py | 6 ------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/constructor_stats.h b/tests/constructor_stats.h index 9a5754fed9..352b1b6ca5 100644 --- a/tests/constructor_stats.h +++ b/tests/constructor_stats.h @@ -312,8 +312,16 @@ void print_created(T *inst, Values &&...values) { } template void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values + /* + * On GraalPy, destructors can trigger anywhere and this can cause random + * failures in unrelated tests. + */ +#if !defined(GRAALVM_PYTHON) print_constr_details(inst, "destroyed", values...); track_destroyed(inst); +#else + py::detail::silence_unused_warnings(inst, values...); +#endif } template void print_values(T *inst, Values &&...values) { diff --git a/tests/test_iostream.py b/tests/test_iostream.py index 606028d6fe..c3d987787a 100644 --- a/tests/test_iostream.py +++ b/tests/test_iostream.py @@ -6,14 +6,8 @@ import pytest -import env # noqa: F401 from pybind11_tests import iostream as m -pytestmark = pytest.mark.skipif( - "env.GRAALPY", - reason="Delayed prints from finalizers from other tests can end up in the output", -) - def test_captured(capsys): msg = "I've been redirected to Python, I hope!" From e7c9b907394ef241da7f5a8f93d3faec6adb2df5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:01:42 -0800 Subject: [PATCH 02/10] chore(deps): bump pypa/cibuildwheel in the actions group (#5451) Bumps the actions group with 1 update: [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel). Updates `pypa/cibuildwheel` from 2.21 to 2.22 - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.21...v2.22) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/emscripten.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/emscripten.yaml b/.github/workflows/emscripten.yaml index 79c783e7fe..5eac089e5c 100644 --- a/.github/workflows/emscripten.yaml +++ b/.github/workflows/emscripten.yaml @@ -22,7 +22,7 @@ jobs: submodules: true fetch-depth: 0 - - uses: pypa/cibuildwheel@v2.21 + - uses: pypa/cibuildwheel@v2.22 env: PYODIDE_BUILD_EXPORTS: whole_archive with: From a6d1ff2460aa092c393520e46357999d137c53ce Mon Sep 17 00:00:00 2001 From: James Morris <6653392+J-M0@users.noreply.github.com> Date: Sun, 8 Dec 2024 10:16:41 -0500 Subject: [PATCH 03/10] fix: make PYBIND11_WARNING_POP actually pop clang diagnostics (#5448) * fix: make PYBIND11_WARNING_POP actually pop clang diagnostics * fix: ignore -Wgnu-zero-variadic-macro-arguments on clang * Revert "fix: ignore -Wgnu-zero-variadic-macro-arguments on clang" This reverts commit d310959bf5e6194033aa1825ab181a76289d790f. * C++20 modernization: Use `__VA_OPT__(, ) __VA_ARGS__` in `PYBIND11_DECLARE_HOLDER_TYPE()` * Disable `__VA_OPT__(, ) __VA_ARGS__` usage for MSVC (it is unclear to rwgk why it does not work). This is the beginning of the error message: ``` D:\a\pybind11\pybind11\tests\test_smart_ptr.cpp(285,1): error C2143: syntax error: missing ')' before ',' [D:\a\pybind11\pybind11\build\tests\pybind11_tests.vcxproj] D:\a\pybind11\pybind11\tests\test_smart_ptr.cpp(285,1): error C2059: syntax error: ')' [D:\a\pybind11\pybind11\build\tests\pybind11_tests.vcxproj] ``` * Add `PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")` in test_smart_ptr.cpp This is the error message: ``` /__w/pybind11/pybind11/tests/test_smart_ptr.cpp:287:51: error: must specify at least one argument for '...' parameter of variadic macro [-Werror,-Wgnu-zero-variadic-macro-arguments] PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) ^ /__w/pybind11/pybind11/include/pybind11/cast.h:885:13: note: macro 'PYBIND11_DECLARE_HOLDER_TYPE' defined here ^ ``` * Also add `PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")` in test_virtual_functions.cpp * Also add `PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")` in test_embed/test_interpreter.cpp * Undo all changes except the original push -> pop fix. * 1. Add `PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")` near the top of pybind11/pybind11.h; 2. Change `PYBIND11_DECLARE_HOLDER_TYPE` macro to side-step the only remaining clang warning-as-error (this is still needed even for clang 18). Alternatively the warning suppression could be moved into pybind11/cast.h, but this commit limits the warning suppression to smaller scope within include/pybind11. --------- Co-authored-by: Ralf W. Grosse-Kunstleve --- include/pybind11/cast.h | 10 ++++++---- include/pybind11/detail/common.h | 2 +- include/pybind11/pybind11.h | 6 ++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 2ae25c2ebf..7ae9fa037f 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -863,18 +863,20 @@ using type_caster_holder = conditional_t::val copyable_holder_caster, move_only_holder_caster>; -template -struct always_construct_holder { +template +struct always_construct_holder_value { static constexpr bool value = Value; }; +template +struct always_construct_holder : always_construct_holder_value {}; + /// Create a specialization for custom holder types (silently ignores std::shared_ptr) #define PYBIND11_DECLARE_HOLDER_TYPE(type, holder_type, ...) \ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) \ namespace detail { \ template \ - struct always_construct_holder : always_construct_holder { \ - }; \ + struct always_construct_holder : always_construct_holder_value<__VA_ARGS__> {}; \ template \ class type_caster::value>> \ : public type_caster_holder {}; \ diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 310c3c3947..78173cad30 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -46,7 +46,7 @@ # define PYBIND11_COMPILER_CLANG # define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) # define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(clang diagnostic push) -# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic push) +# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic pop) #elif defined(__GNUC__) # define PYBIND11_COMPILER_GCC # define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index fe8384e57f..721808701f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -26,6 +26,12 @@ #include #include +// See PR #5448. This warning suppression is needed for the PYBIND11_OVERRIDE macro family. +// NOTE that this is NOT embedded in a push/pop pair because that is very difficult to achieve. +#if defined(__clang_major__) && __clang_major__ < 14 +PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments") +#endif + #if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1914)) # define PYBIND11_STD_LAUNDER std::launder # define PYBIND11_HAS_STD_LAUNDER 1 From 1d09fc8300d327ff03fc868feb51a7c92d05677b Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Sun, 8 Dec 2024 20:30:49 +0100 Subject: [PATCH 04/10] Option for arg/return type hints and correct typing for std::filesystem::path (#5450) * Added arg/return type handling. * Added support for nested arg/return type in py::typing::List * Added support for arg/return type in stl/filesystem * Added tests for arg/return type in stl/filesystem and py::typing::List * Added arg/return name to more py::typing classes * Added arg/return type to Callable[...] * Added tests for typing container classes (also nested) * Changed typing classes to avoid using C++14 auto return type deduction. * Fixed clang-tidy errors. * Changed Enable to SFINAE * Added test for Tuple[T, ...] * Added RealNumber with custom caster for testing typing classes. * Added tests for Set, Iterable, Iterator, Union, and Optional * Added tests for Callable * Fixed Callable with ellipsis test * Changed TypeGuard/TypeIs to use return type (being the narrower type) + Tests * Added test for use of fallback type name with stl vector * Updated documentation. * Fixed unnecessary constructor call in test. * Fixed reference counting in example type caster. * Fixed clang-tidy issues. * Fix for clang-tidy * Updated cast method to use pybind11 API rather than Python C API in custom caster example * Updated load to use pybind11 API rather than Python C API in custom caster example * Changed test of arg/return name to use pybind11 API instead of Python C API * Updated code in adcanced/cast example and improved documentation text * Fixed references in custom type caster docs * Fixed wrong logical and operator in test * Fixed wrong logical operator in doc example * Added comment to test about `float` vs `float | int` * Updated std::filesystem::path docs in cast/overview section * Remove one stray dot. --------- Co-authored-by: Ralf W. Grosse-Kunstleve --- docs/advanced/cast/custom.rst | 160 +++++++++++++++-------- docs/advanced/cast/overview.rst | 4 +- include/pybind11/cast.h | 37 +++++- include/pybind11/pybind11.h | 4 +- include/pybind11/stl/filesystem.h | 2 + include/pybind11/typing.h | 63 ++++++++- tests/CMakeLists.txt | 1 + tests/test_docs_advanced_cast_custom.cpp | 70 ++++++++++ tests/test_docs_advanced_cast_custom.py | 37 ++++++ tests/test_pytypes.cpp | 129 ++++++++++++++++++ tests/test_pytypes.py | 81 ++++++++++++ tests/test_stl.cpp | 53 +++++++- tests/test_stl.py | 50 ++++++- 13 files changed, 620 insertions(+), 71 deletions(-) create mode 100644 tests/test_docs_advanced_cast_custom.cpp create mode 100644 tests/test_docs_advanced_cast_custom.py diff --git a/docs/advanced/cast/custom.rst b/docs/advanced/cast/custom.rst index 8138cac619..065d09a6dd 100644 --- a/docs/advanced/cast/custom.rst +++ b/docs/advanced/cast/custom.rst @@ -1,35 +1,53 @@ Custom type casters =================== -In very rare cases, applications may require custom type casters that cannot be -expressed using the abstractions provided by pybind11, thus requiring raw -Python C API calls. This is fairly advanced usage and should only be pursued by -experts who are familiar with the intricacies of Python reference counting. - -The following snippets demonstrate how this works for a very simple ``inty`` -type that that should be convertible from Python types that provide a -``__int__(self)`` method. +Some applications may prefer custom type casters that convert between existing +Python types and C++ types, similar to the ``list`` ↔ ``std::vector`` +and ``dict`` ↔ ``std::map`` conversions which are built into pybind11. +Implementing custom type casters is fairly advanced usage. +While it is recommended to use the pybind11 API as much as possible, more complex examples may +require familiarity with the intricacies of the Python C API. +You can refer to the `Python/C API Reference Manual `_ +for more information. + +The following snippets demonstrate how this works for a very simple ``Point2D`` type. +We want this type to be convertible to C++ from Python types implementing the +``Sequence`` protocol and having two elements of type ``float``. +When returned from C++ to Python, it should be converted to a Python ``tuple[float, float]``. +For this type we could provide Python bindings for different arithmetic functions implemented +in C++ (here demonstrated by a simple ``negate`` function). + +.. + PLEASE KEEP THE CODE BLOCKS IN SYNC WITH + tests/test_docs_advanced_cast_custom.cpp + tests/test_docs_advanced_cast_custom.py + Ideally, change the test, run pre-commit (incl. clang-format), + then copy the changed code back here. + Also use TEST_SUBMODULE in tests, but PYBIND11_MODULE in docs. .. code-block:: cpp - struct inty { long long_value; }; + namespace user_space { - void print(inty s) { - std::cout << s.long_value << std::endl; - } + struct Point2D { + double x; + double y; + }; -The following Python snippet demonstrates the intended usage from the Python side: + Point2D negate(const Point2D &point) { return Point2D{-point.x, -point.y}; } -.. code-block:: python + } // namespace user_space - class A: - def __int__(self): - return 123 +The following Python snippet demonstrates the intended usage of ``negate`` from the Python side: + +.. code-block:: python - from example import print + from my_math_module import docs_advanced_cast_custom as m - print(A()) + point1 = [1.0, -1.0] + point2 = m.negate(point1) + assert point2 == (-1.0, 1.0) To register the necessary conversion routines, it is necessary to add an instantiation of the ``pybind11::detail::type_caster`` template. @@ -38,47 +56,59 @@ type is explicitly allowed. .. code-block:: cpp - namespace PYBIND11_NAMESPACE { namespace detail { - template <> struct type_caster { - public: - /** - * This macro establishes the name 'inty' in - * function signatures and declares a local variable - * 'value' of type inty - */ - PYBIND11_TYPE_CASTER(inty, const_name("inty")); - - /** - * Conversion part 1 (Python->C++): convert a PyObject into a inty - * instance or return false upon failure. The second argument - * indicates whether implicit conversions should be applied. - */ - bool load(handle src, bool) { - /* Extract PyObject from handle */ - PyObject *source = src.ptr(); - /* Try converting into a Python integer value */ - PyObject *tmp = PyNumber_Long(source); - if (!tmp) + namespace pybind11 { + namespace detail { + + template <> + struct type_caster { + // This macro inserts a lot of boilerplate code and sets the default type hint to `tuple` + PYBIND11_TYPE_CASTER(user_space::Point2D, const_name("tuple")); + // `arg_name` and `return_name` may optionally be used to specify type hints separately for + // arguments and return values. + // The signature of our negate function would then look like: + // `negate(Sequence[float]) -> tuple[float, float]` + static constexpr auto arg_name = const_name("Sequence[float]"); + static constexpr auto return_name = const_name("tuple[float, float]"); + + // C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments + // are used to indicate the return value policy and parent object (for + // return_value_policy::reference_internal) and are often ignored by custom casters. + // The return value should reflect the type hint specified by `return_name`. + static handle + cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) { + return py::make_tuple(number.x, number.y).release(); + } + + // Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The + // second argument indicates whether implicit conversions should be allowed. + // The accepted types should reflect the type hint specified by `arg_name`. + bool load(handle src, bool /*convert*/) { + // Check if handle is a Sequence + if (!py::isinstance(src)) { + return false; + } + auto seq = py::reinterpret_borrow(src); + // Check if exactly two values are in the Sequence + if (seq.size() != 2) { + return false; + } + // Check if each element is either a float or an int + for (auto item : seq) { + if (!py::isinstance(item) && !py::isinstance(item)) { return false; - /* Now try to convert into a C++ int */ - value.long_value = PyLong_AsLong(tmp); - Py_DECREF(tmp); - /* Ensure return code was OK (to avoid out-of-range errors etc) */ - return !(value.long_value == -1 && !PyErr_Occurred()); + } } + value.x = seq[0].cast(); + value.y = seq[1].cast(); + return true; + } + }; - /** - * Conversion part 2 (C++ -> Python): convert an inty instance into - * a Python object. The second and third arguments are used to - * indicate the return value policy and parent object (for - * ``return_value_policy::reference_internal``) and are generally - * ignored by implicit casters. - */ - static handle cast(inty src, return_value_policy /* policy */, handle /* parent */) { - return PyLong_FromLong(src.long_value); - } - }; - }} // namespace PYBIND11_NAMESPACE::detail + } // namespace detail + } // namespace pybind11 + + // Bind the negate function + PYBIND11_MODULE(docs_advanced_cast_custom, m) { m.def("negate", user_space::negate); } .. note:: @@ -86,8 +116,22 @@ type is explicitly allowed. that ``T`` is default-constructible (``value`` is first default constructed and then ``load()`` assigns to it). +.. note:: + For further information on the ``return_value_policy`` argument of ``cast`` refer to :ref:`return_value_policies`. + To learn about the ``convert`` argument of ``load`` see :ref:`nonconverting_arguments`. + .. warning:: When using custom type casters, it's important to declare them consistently - in every compilation unit of the Python extension module. Otherwise, + in every compilation unit of the Python extension module to satisfy the C++ One Definition Rule + (`ODR `_). Otherwise, undefined behavior can ensue. + +.. note:: + + Using the type hint ``Sequence[float]`` signals to static type checkers, that not only tuples may be + passed, but any type implementing the Sequence protocol, e.g., ``list[float]``. + Unfortunately, that loses the length information ``tuple[float, float]`` provides. + One way of still providing some length information in type hints is using ``typing.Annotated``, e.g., + ``Annotated[Sequence[float], 2]``, or further add libraries like + `annotated-types `_. diff --git a/docs/advanced/cast/overview.rst b/docs/advanced/cast/overview.rst index 011bd4c7a3..d5a34ef942 100644 --- a/docs/advanced/cast/overview.rst +++ b/docs/advanced/cast/overview.rst @@ -151,7 +151,7 @@ as arguments and return values, refer to the section on binding :ref:`classes`. +------------------------------------+---------------------------+-----------------------------------+ | ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` | +------------------------------------+---------------------------+-----------------------------------+ -| ``std::filesystem::path`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` | +| ``std::filesystem::path`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` | +------------------------------------+---------------------------+-----------------------------------+ | ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` | +------------------------------------+---------------------------+-----------------------------------+ @@ -167,4 +167,4 @@ as arguments and return values, refer to the section on binding :ref:`classes`. +------------------------------------+---------------------------+-----------------------------------+ .. [#] ``std::filesystem::path`` is converted to ``pathlib.Path`` and - ``os.PathLike`` is converted to ``std::filesystem::path``. + can be loaded from ``os.PathLike``, ``str``, and ``bytes``. diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 7ae9fa037f..3e15d8d0c1 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -34,6 +34,39 @@ PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) +// Type trait checker for `descr` +template +struct is_descr : std::false_type {}; + +template +struct is_descr> : std::true_type {}; + +template +struct is_descr> : std::true_type {}; + +// Use arg_name instead of name when available +template +struct as_arg_type { + static constexpr auto name = T::name; +}; + +template +struct as_arg_type::value>::type> { + static constexpr auto name = T::arg_name; +}; + +// Use return_name instead of name when available +template +struct as_return_type { + static constexpr auto name = T::name; +}; + +template +struct as_return_type::value>::type> { + static constexpr auto name = T::return_name; +}; + template class type_caster : public type_caster_base {}; template @@ -1080,6 +1113,8 @@ struct pyobject_caster { return src.inc_ref(); } PYBIND11_TYPE_CASTER(type, handle_type_name::name); + static constexpr auto arg_name = as_arg_type>::name; + static constexpr auto return_name = as_return_type>::name; }; template @@ -1608,7 +1643,7 @@ class argument_loader { "py::args cannot be specified more than once"); static constexpr auto arg_names - = ::pybind11::detail::concat(type_descr(make_caster::name)...); + = ::pybind11::detail::concat(type_descr(as_arg_type>::name)...); bool load_args(function_call &call) { return load_impl_sequence(call, indices{}); } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 721808701f..7fc7dbf723 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -336,8 +336,8 @@ class cpp_function : public function { /* Generate a readable signature describing the function's arguments and return value types */ - static constexpr auto signature - = const_name("(") + cast_in::arg_names + const_name(") -> ") + cast_out::name; + static constexpr auto signature = const_name("(") + cast_in::arg_names + + const_name(") -> ") + as_return_type::name; PYBIND11_DESCR_CONSTEXPR auto types = decltype(signature)::types(); /* Register the function with Python from generic (non-templated) code */ diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index c16a9ae5c2..ecfb9cf0dc 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -107,6 +107,8 @@ struct path_caster { } PYBIND11_TYPE_CASTER(T, const_name("os.PathLike")); + static constexpr auto arg_name = const_name("Union[os.PathLike, str, bytes]"); + static constexpr auto return_name = const_name("Path"); }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 84aaf9f702..405ff8714a 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -131,6 +131,13 @@ struct handle_type_name> { static constexpr auto name = const_name("tuple[") + ::pybind11::detail::concat(make_caster::name...) + const_name("]"); + static constexpr auto arg_name + = const_name("tuple[") + + ::pybind11::detail::concat(as_arg_type>::name...) + const_name("]"); + static constexpr auto return_name + = const_name("tuple[") + + ::pybind11::detail::concat(as_return_type>::name...) + + const_name("]"); }; template <> @@ -144,48 +151,76 @@ struct handle_type_name> { // PEP 484 specifies this syntax for a variable-length tuple static constexpr auto name = const_name("tuple[") + make_caster::name + const_name(", ...]"); + static constexpr auto arg_name + = const_name("tuple[") + as_arg_type>::name + const_name(", ...]"); + static constexpr auto return_name + = const_name("tuple[") + as_return_type>::name + const_name(", ...]"); }; template struct handle_type_name> { static constexpr auto name = const_name("dict[") + make_caster::name + const_name(", ") + make_caster::name + const_name("]"); + static constexpr auto arg_name = const_name("dict[") + as_arg_type>::name + + const_name(", ") + as_arg_type>::name + + const_name("]"); + static constexpr auto return_name = const_name("dict[") + as_return_type>::name + + const_name(", ") + as_return_type>::name + + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("list[") + make_caster::name + const_name("]"); + static constexpr auto arg_name + = const_name("list[") + as_arg_type>::name + const_name("]"); + static constexpr auto return_name + = const_name("list[") + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("set[") + make_caster::name + const_name("]"); + static constexpr auto arg_name + = const_name("set[") + as_arg_type>::name + const_name("]"); + static constexpr auto return_name + = const_name("set[") + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("Iterable[") + make_caster::name + const_name("]"); + static constexpr auto arg_name + = const_name("Iterable[") + as_arg_type>::name + const_name("]"); + static constexpr auto return_name + = const_name("Iterable[") + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("Iterator[") + make_caster::name + const_name("]"); + static constexpr auto arg_name + = const_name("Iterator[") + as_arg_type>::name + const_name("]"); + static constexpr auto return_name + = const_name("Iterator[") + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { using retval_type = conditional_t::value, void_type, Return>; static constexpr auto name - = const_name("Callable[[") + ::pybind11::detail::concat(make_caster::name...) - + const_name("], ") + make_caster::name + const_name("]"); + = const_name("Callable[[") + + ::pybind11::detail::concat(as_arg_type>::name...) + const_name("], ") + + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { // PEP 484 specifies this syntax for defining only return types of callables using retval_type = conditional_t::value, void_type, Return>; - static constexpr auto name - = const_name("Callable[..., ") + make_caster::name + const_name("]"); + static constexpr auto name = const_name("Callable[..., ") + + as_return_type>::name + + const_name("]"); }; template @@ -198,21 +233,37 @@ struct handle_type_name> { static constexpr auto name = const_name("Union[") + ::pybind11::detail::concat(make_caster::name...) + const_name("]"); + static constexpr auto arg_name + = const_name("Union[") + + ::pybind11::detail::concat(as_arg_type>::name...) + const_name("]"); + static constexpr auto return_name + = const_name("Union[") + + ::pybind11::detail::concat(as_return_type>::name...) + + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("Optional[") + make_caster::name + const_name("]"); + static constexpr auto arg_name + = const_name("Optional[") + as_arg_type>::name + const_name("]"); + static constexpr auto return_name + = const_name("Optional[") + as_return_type>::name + const_name("]"); }; +// TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually +// the narrower type. + template struct handle_type_name> { - static constexpr auto name = const_name("TypeGuard[") + make_caster::name + const_name("]"); + static constexpr auto name + = const_name("TypeGuard[") + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { - static constexpr auto name = const_name("TypeIs[") + make_caster::name + const_name("]"); + static constexpr auto name + = const_name("TypeIs[") + as_return_type>::name + const_name("]"); }; template <> diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 01b6c0a3e6..315a5bad04 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -122,6 +122,7 @@ set(PYBIND11_TEST_FILES test_custom_type_casters test_custom_type_setup test_docstring_options + test_docs_advanced_cast_custom test_eigen_matrix test_eigen_tensor test_enum diff --git a/tests/test_docs_advanced_cast_custom.cpp b/tests/test_docs_advanced_cast_custom.cpp new file mode 100644 index 0000000000..a6f8a212ef --- /dev/null +++ b/tests/test_docs_advanced_cast_custom.cpp @@ -0,0 +1,70 @@ +// ######################################################################### +// PLEASE UPDATE docs/advanced/cast/custom.rst IF ANY CHANGES ARE MADE HERE. +// ######################################################################### + +#include "pybind11_tests.h" + +namespace user_space { + +struct Point2D { + double x; + double y; +}; + +Point2D negate(const Point2D &point) { return Point2D{-point.x, -point.y}; } + +} // namespace user_space + +namespace pybind11 { +namespace detail { + +template <> +struct type_caster { + // This macro inserts a lot of boilerplate code and sets the default type hint to `tuple` + PYBIND11_TYPE_CASTER(user_space::Point2D, const_name("tuple")); + // `arg_name` and `return_name` may optionally be used to specify type hints separately for + // arguments and return values. + // The signature of our negate function would then look like: + // `negate(Sequence[float]) -> tuple[float, float]` + static constexpr auto arg_name = const_name("Sequence[float]"); + static constexpr auto return_name = const_name("tuple[float, float]"); + + // C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments + // are used to indicate the return value policy and parent object (for + // return_value_policy::reference_internal) and are often ignored by custom casters. + // The return value should reflect the type hint specified by `return_name`. + static handle + cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) { + return py::make_tuple(number.x, number.y).release(); + } + + // Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The + // second argument indicates whether implicit conversions should be allowed. + // The accepted types should reflect the type hint specified by `arg_name`. + bool load(handle src, bool /*convert*/) { + // Check if handle is a Sequence + if (!py::isinstance(src)) { + return false; + } + auto seq = py::reinterpret_borrow(src); + // Check if exactly two values are in the Sequence + if (seq.size() != 2) { + return false; + } + // Check if each element is either a float or an int + for (auto item : seq) { + if (!py::isinstance(item) && !py::isinstance(item)) { + return false; + } + } + value.x = seq[0].cast(); + value.y = seq[1].cast(); + return true; + } +}; + +} // namespace detail +} // namespace pybind11 + +// Bind the negate function +TEST_SUBMODULE(docs_advanced_cast_custom, m) { m.def("negate", user_space::negate); } diff --git a/tests/test_docs_advanced_cast_custom.py b/tests/test_docs_advanced_cast_custom.py new file mode 100644 index 0000000000..8018b8f576 --- /dev/null +++ b/tests/test_docs_advanced_cast_custom.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Sequence + +if TYPE_CHECKING: + from conftest import SanitizedString + +from pybind11_tests import docs_advanced_cast_custom as m + + +def assert_negate_function( + input_sequence: Sequence[float], + target: tuple[float, float], +) -> None: + output = m.negate(input_sequence) + assert isinstance(output, tuple) + assert len(output) == 2 + assert isinstance(output[0], float) + assert isinstance(output[1], float) + assert output == target + + +def test_negate(doc: SanitizedString) -> None: + assert doc(m.negate) == "negate(arg0: Sequence[float]) -> tuple[float, float]" + assert_negate_function([1.0, -1.0], (-1.0, 1.0)) + assert_negate_function((1.0, -1.0), (-1.0, 1.0)) + assert_negate_function([1, -1], (-1.0, 1.0)) + assert_negate_function((1, -1), (-1.0, 1.0)) + + +def test_docs() -> None: + ########################################################################### + # PLEASE UPDATE docs/advanced/cast/custom.rst IF ANY CHANGES ARE MADE HERE. + ########################################################################### + point1 = [1.0, -1.0] + point2 = m.negate(point1) + assert point2 == (-1.0, 1.0) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 8df4cdd3f6..1764ccda02 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -7,6 +7,7 @@ BSD-style license that can be found in the LICENSE file. */ +#include #include #include "pybind11_tests.h" @@ -137,6 +138,44 @@ typedef py::typing::TypeVar<"V"> TypeVarV; } // namespace typevar #endif +// Custom type for testing arg_name/return_name type hints +// RealNumber: +// * in arguments -> float | int +// * in return -> float +// * fallback -> complex +// The choice of types is not really useful, but just made different for testing purposes. +// According to `PEP 484 – Type Hints` annotating with `float` also allows `int`, +// so using `float | int` could be replaced by just `float`. + +struct RealNumber { + double value; +}; + +namespace pybind11 { +namespace detail { + +template <> +struct type_caster { + PYBIND11_TYPE_CASTER(RealNumber, const_name("complex")); + static constexpr auto arg_name = const_name("Union[float, int]"); + static constexpr auto return_name = const_name("float"); + + static handle cast(const RealNumber &number, return_value_policy, handle) { + return py::float_(number.value).release(); + } + + bool load(handle src, bool) { + if (!py::isinstance(src) && !py::isinstance(src)) { + return false; + } + value.value = src.cast(); + return true; + } +}; + +} // namespace detail +} // namespace pybind11 + TEST_SUBMODULE(pytypes, m) { m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); }); @@ -998,4 +1037,94 @@ TEST_SUBMODULE(pytypes, m) { #else m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false; #endif + m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; }); + // std::vector + m.def("half_of_number_vector", [](const std::vector &x) { + std::vector result; + result.reserve(x.size()); + for (auto num : x) { + result.push_back(RealNumber{num.value / 2}); + } + return result; + }); + // Tuple + m.def("half_of_number_tuple", [](const py::typing::Tuple &x) { + py::typing::Tuple result + = py::make_tuple(RealNumber{x[0].cast().value / 2}, + RealNumber{x[1].cast().value / 2}); + return result; + }); + // Tuple + m.def("half_of_number_tuple_ellipsis", + [](const py::typing::Tuple &x) { + py::typing::Tuple result(x.size()); + for (size_t i = 0; i < x.size(); ++i) { + result[i] = x[i].cast().value / 2; + } + return result; + }); + // Dict + m.def("half_of_number_dict", [](const py::typing::Dict &x) { + py::typing::Dict result; + for (auto it : x) { + result[it.first] = RealNumber{it.second.cast().value / 2}; + } + return result; + }); + // List + m.def("half_of_number_list", [](const py::typing::List &x) { + py::typing::List result; + for (auto num : x) { + result.append(RealNumber{num.cast().value / 2}); + } + return result; + }); + // List> + m.def("half_of_number_nested_list", + [](const py::typing::List> &x) { + py::typing::List> result_lists; + for (auto nums : x) { + py::typing::List result; + for (auto num : nums) { + result.append(RealNumber{num.cast().value / 2}); + } + result_lists.append(result); + } + return result_lists; + }); + // Set + m.def("identity_set", [](const py::typing::Set &x) { return x; }); + // Iterable + m.def("identity_iterable", [](const py::typing::Iterable &x) { return x; }); + // Iterator + m.def("identity_iterator", [](const py::typing::Iterator &x) { return x; }); + // Callable + m.def("apply_callable", + [](const RealNumber &x, const py::typing::Callable &f) { + return f(x).cast(); + }); + // Callable + m.def("apply_callable_ellipsis", + [](const RealNumber &x, const py::typing::Callable &f) { + return f(x).cast(); + }); + // Union + m.def("identity_union", [](const py::typing::Union &x) { return x; }); + // Optional + m.def("identity_optional", [](const py::typing::Optional &x) { return x; }); + // TypeGuard + m.def("check_type_guard", + [](const py::typing::List &x) + -> py::typing::TypeGuard> { + for (const auto &item : x) { + if (!py::isinstance(item)) { + return false; + } + } + return true; + }); + // TypeIs + m.def("check_type_is", [](const py::object &x) -> py::typing::TypeIs { + return py::isinstance(x); + }); } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 9fd24b34f1..b6e64b9bf6 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1101,3 +1101,84 @@ def test_list_ranges(tested_list, expected): def test_dict_ranges(tested_dict, expected): assert m.dict_iterator_default_initialization() assert m.transform_dict_plus_one(tested_dict) == expected + + +def test_arg_return_type_hints(doc): + assert doc(m.half_of_number) == "half_of_number(arg0: Union[float, int]) -> 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 should use fallback type (complex is not really useful but just used for testing) + assert ( + doc(m.half_of_number_vector) + == "half_of_number_vector(arg0: list[complex]) -> list[complex]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple) + == "half_of_number_tuple(arg0: tuple[Union[float, int], Union[float, int]]) -> tuple[float, float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple_ellipsis) + == "half_of_number_tuple_ellipsis(arg0: tuple[Union[float, int], ...]) -> tuple[float, ...]" + ) + # Dict + assert ( + doc(m.half_of_number_dict) + == "half_of_number_dict(arg0: dict[str, Union[float, int]]) -> dict[str, float]" + ) + # List + assert ( + doc(m.half_of_number_list) + == "half_of_number_list(arg0: list[Union[float, int]]) -> list[float]" + ) + # List> + assert ( + doc(m.half_of_number_nested_list) + == "half_of_number_nested_list(arg0: list[list[Union[float, int]]]) -> list[list[float]]" + ) + # Set + assert ( + doc(m.identity_set) + == "identity_set(arg0: set[Union[float, int]]) -> set[float]" + ) + # Iterable + assert ( + doc(m.identity_iterable) + == "identity_iterable(arg0: Iterable[Union[float, int]]) -> Iterable[float]" + ) + # Iterator + assert ( + doc(m.identity_iterator) + == "identity_iterator(arg0: Iterator[Union[float, int]]) -> Iterator[float]" + ) + # Callable + assert ( + doc(m.apply_callable) + == "apply_callable(arg0: Union[float, int], arg1: Callable[[Union[float, int]], float]) -> float" + ) + # Callable + assert ( + doc(m.apply_callable_ellipsis) + == "apply_callable_ellipsis(arg0: Union[float, int], arg1: Callable[..., float]) -> float" + ) + # Union + assert ( + doc(m.identity_union) + == "identity_union(arg0: Union[Union[float, int], str]) -> Union[float, str]" + ) + # Optional + assert ( + doc(m.identity_optional) + == "identity_optional(arg0: Optional[Union[float, int]]) -> Optional[float]" + ) + # TypeGuard + assert ( + doc(m.check_type_guard) + == "check_type_guard(arg0: list[object]) -> TypeGuard[list[float]]" + ) + # TypeIs + assert doc(m.check_type_is) == "check_type_is(arg0: object) -> TypeIs[float]" diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index dd93d51d0a..9ddd951e0c 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -16,6 +16,7 @@ # define PYBIND11_HAS_FILESYSTEM_IS_OPTIONAL #endif #include +#include #include #include @@ -453,7 +454,57 @@ TEST_SUBMODULE(stl, m) { #ifdef PYBIND11_HAS_FILESYSTEM // test_fs_path m.attr("has_filesystem") = true; - m.def("parent_path", [](const std::filesystem::path &p) { return p.parent_path(); }); + m.def("parent_path", [](const std::filesystem::path &path) { return path.parent_path(); }); + m.def("parent_paths", [](const std::vector &paths) { + std::vector result; + result.reserve(paths.size()); + for (const auto &path : paths) { + result.push_back(path.parent_path()); + } + return result; + }); + m.def("parent_paths_list", [](const py::typing::List &paths) { + py::typing::List result; + for (auto path : paths) { + result.append(path.cast().parent_path()); + } + return result; + }); + m.def("parent_paths_nested_list", + [](const py::typing::List> &paths_lists) { + py::typing::List> result_lists; + for (auto paths : paths_lists) { + py::typing::List result; + for (auto path : paths) { + result.append(path.cast().parent_path()); + } + result_lists.append(result); + } + return result_lists; + }); + m.def("parent_paths_tuple", + [](const py::typing::Tuple &paths) { + py::typing::Tuple result + = py::make_tuple(paths[0].cast().parent_path(), + paths[1].cast().parent_path()); + return result; + }); + m.def("parent_paths_tuple_ellipsis", + [](const py::typing::Tuple &paths) { + py::typing::Tuple result(paths.size()); + for (size_t i = 0; i < paths.size(); ++i) { + result[i] = paths[i].cast().parent_path(); + } + return result; + }); + m.def("parent_paths_dict", + [](const py::typing::Dict &paths) { + py::typing::Dict result; + for (auto it : paths) { + result[it.first] = it.second.cast().parent_path(); + } + return result; + }); #endif #ifdef PYBIND11_TEST_VARIANT diff --git a/tests/test_stl.py b/tests/test_stl.py index d1a9ff08b0..14c7da312a 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -246,7 +246,7 @@ def test_reference_sensitive_optional(): @pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") -def test_fs_path(): +def test_fs_path(doc): from pathlib import Path class PseudoStrPath: @@ -257,11 +257,59 @@ class PseudoBytesPath: def __fspath__(self): return b"foo/bar" + # Single argument assert m.parent_path(Path("foo/bar")) == Path("foo") assert m.parent_path("foo/bar") == Path("foo") assert m.parent_path(b"foo/bar") == Path("foo") assert m.parent_path(PseudoStrPath()) == Path("foo") assert m.parent_path(PseudoBytesPath()) == Path("foo") + assert ( + doc(m.parent_path) + == "parent_path(arg0: Union[os.PathLike, str, bytes]) -> Path" + ) + # std::vector should use name (for arg_name/return_name typing classes must be used) + assert m.parent_paths(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] + assert ( + doc(m.parent_paths) + == "parent_paths(arg0: list[os.PathLike]) -> list[os.PathLike]" + ) + # py::typing::List + assert m.parent_paths_list(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] + assert ( + doc(m.parent_paths_list) + == "parent_paths_list(arg0: list[Union[os.PathLike, str, bytes]]) -> list[Path]" + ) + # Nested py::typing::List + assert m.parent_paths_nested_list([["foo/bar"], ["foo/baz", "foo/buzz"]]) == [ + [Path("foo")], + [Path("foo"), Path("foo")], + ] + assert ( + doc(m.parent_paths_nested_list) + == "parent_paths_nested_list(arg0: list[list[Union[os.PathLike, str, bytes]]]) -> list[list[Path]]" + ) + # py::typing::Tuple + assert m.parent_paths_tuple(("foo/bar", "foo/baz")) == (Path("foo"), Path("foo")) + assert ( + doc(m.parent_paths_tuple) + == "parent_paths_tuple(arg0: tuple[Union[os.PathLike, str, bytes], Union[os.PathLike, str, bytes]]) -> tuple[Path, Path]" + ) + # py::typing::Dict + assert m.parent_paths_dict( + { + "key1": Path("foo/bar"), + "key2": "foo/baz", + "key3": b"foo/buzz", + } + ) == { + "key1": Path("foo"), + "key2": Path("foo"), + "key3": Path("foo"), + } + assert ( + doc(m.parent_paths_dict) + == "parent_paths_dict(arg0: dict[str, Union[os.PathLike, str, bytes]]) -> dict[str, Path]" + ) @pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no ") From b17555f3403e8494bd83c4c9d5f153c261b3136c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 15:21:49 -0800 Subject: [PATCH 05/10] chore(deps): update pre-commit hooks (#5459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update pre-commit hooks updates: - [github.com/pre-commit/mirrors-clang-format: v19.1.3 → v19.1.4](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.3...v19.1.4) - [github.com/astral-sh/ruff-pre-commit: v0.7.2 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.2...v0.8.1) - [github.com/sirosen/texthooks: 0.6.7 → 0.6.8](https://github.com/sirosen/texthooks/compare/0.6.7...0.6.8) - [github.com/PyCQA/pylint: v3.3.1 → v3.3.2](https://github.com/PyCQA/pylint/compare/v3.3.1...v3.3.2) - [github.com/python-jsonschema/check-jsonschema: 0.29.4 → 0.30.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.29.4...0.30.0) * Resolve ruff pre-commit errors: ``` ruff.....................................................................Failed - hook id: ruff - exit code: 1 warning: The following rules have been removed and ignoring them has no effect: - PT004 docs/benchmark.py:51:17: UP031 Use format specifiers instead of percent format | 50 | for cl in range(nclasses): 51 | decl += "class cl%03i {\n" % cl | ^^^^^^^^^^^^^^^^^^ UP031 52 | decl += "public:\n" 53 | bindings += f' py::class_("cl{cl:03}")\n' | = help: Replace with format specifiers docs/benchmark.py:88:15: UP031 Use format specifiers instead of percent format | 86 | elapsed = (n2 - n1).total_seconds() 87 | size = os.stat("test.so").st_size 88 | print(" {%i, %f, %i}," % (nclasses * nfns, elapsed, size)) | ^^^^^^^^^^^^^^^^^^ UP031 89 | print("}") | = help: Replace with format specifiers tools/make_changelog.py:62:9: PLC0206 Extracting value from dictionary without calling `.items()` | 61 | msg += f"\n `#{issue.number} <{issue.html_url}>`_" 62 | for cat in cats: | _________^ 63 | | if issue.title.lower().startswith(f"{cat}:"): 64 | | cats[cat].append(msg) 65 | | break 66 | | else: 67 | | cats["unknown"].append(msg) | |_______________________________________^ PLC0206 68 | 69 | for cat, msgs in cats.items(): | Found 3 errors. ``` --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ralf W. Grosse-Kunstleve --- .pre-commit-config.yaml | 10 +++++----- docs/benchmark.py | 4 ++-- pyproject.toml | 1 - tools/make_changelog.py | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 88f82eea8f..a28ae3b634 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,14 +25,14 @@ repos: # Clang format the codebase automatically - repo: https://github.com/pre-commit/mirrors-clang-format - rev: "v19.1.3" + rev: "v19.1.4" hooks: - id: clang-format types_or: [c++, c, cuda] # Ruff, the Python auto-correcting linter/formatter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.2 + rev: v0.8.1 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -93,7 +93,7 @@ repos: # Avoid directional quotes - repo: https://github.com/sirosen/texthooks - rev: "0.6.7" + rev: "0.6.8" hooks: - id: fix-ligatures - id: fix-smartquotes @@ -142,14 +142,14 @@ repos: # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v3.3.1" + rev: "v3.3.2" hooks: - id: pylint files: ^pybind11 # Check schemas on some of our YAML files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.4 + rev: 0.30.0 hooks: - id: check-readthedocs - id: check-github-workflows diff --git a/docs/benchmark.py b/docs/benchmark.py index a273674f45..26e390eb4e 100644 --- a/docs/benchmark.py +++ b/docs/benchmark.py @@ -48,7 +48,7 @@ def generate_dummy_code_boost(nclasses=10): decl += "\n" for cl in range(nclasses): - decl += "class cl%03i {\n" % cl + decl += f"class cl{cl:03} {{\n" decl += "public:\n" bindings += f' py::class_("cl{cl:03}")\n' for fn in range(nfns): @@ -85,5 +85,5 @@ def generate_dummy_code_boost(nclasses=10): n2 = dt.datetime.now() elapsed = (n2 - n1).total_seconds() size = os.stat("test.so").st_size - print(" {%i, %f, %i}," % (nclasses * nfns, elapsed, size)) + print(f" {{{nclasses * nfns}, {elapsed:.6f}, {size}}},") print("}") diff --git a/pyproject.toml b/pyproject.toml index c5e2651d6a..13dd04a518 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,6 @@ ignore = [ "PLR", # Design related pylint "E501", # Line too long (Black is enough) "PT011", # Too broad with raises in pytest - "PT004", # Fixture that doesn't return needs underscore (no, it is fine) "SIM118", # iter(x) is not always the same as iter(x.keys()) ] unfixable = ["T20"] diff --git a/tools/make_changelog.py b/tools/make_changelog.py index daa966f204..b499d06ba6 100755 --- a/tools/make_changelog.py +++ b/tools/make_changelog.py @@ -59,9 +59,9 @@ msg += "." msg += f"\n `#{issue.number} <{issue.html_url}>`_" - for cat in cats: + for cat, cat_list in cats.items(): if issue.title.lower().startswith(f"{cat}:"): - cats[cat].append(msg) + cat_list.append(msg) break else: cats["unknown"].append(msg) From 3ebdc503d29c7f089b9a0bc1823add0dda76f40d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:24:06 -0800 Subject: [PATCH 06/10] chore(deps): bump actions/attest-build-provenance in the actions group (#5461) Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 1.4.4 to 2.0.1 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/ef244123eb79f2f7a7e75d99086184180e6d0018...c4fbc648846ca6f503a13a2281a5e7b98aa57202) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 6d17993509..c50f06f945 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -103,7 +103,7 @@ jobs: - uses: actions/download-artifact@v4 - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 + uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 with: subject-path: "*/pybind11*" From 741d86f2e3527b667ba85d273a5eea19a0978ef5 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 12 Dec 2024 11:15:21 -0800 Subject: [PATCH 07/10] Drop Clang dev CI job (#5464) * Drop clang dev job for now. --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9782eed84..72fb9594dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -336,10 +336,6 @@ jobs: strategy: fail-fast: false matrix: - clang: - - dev - std: - - 11 container_suffix: - "" include: From 3e419485c3ba125fcd65c544a1f5f6be1bea79cd Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 20 Dec 2024 01:39:32 -0800 Subject: [PATCH 08/10] `PYBIND11_PLATFORM_ABI_ID` Modernization Continued (platforms other than MSVC) (#5439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * THIS IS JUST A START: First attempt to combine information from PR #4953 and PR #5437 * Include GXX_ABI and USE_CXX in the identifier Further constrain to GXX_ABI 1002 or greater and less than 2000, hopefully future proof by summarizing that as `1` along with CXX11 on or off. * style: pre-commit fixes * Use `gxx_abi_1xxx` and simplify the Clang string After discussions with Ralf Grosse-Kunstleve we think these would make good identifiers that are concise and clear. * Error if `_GLIBCXX_USE_CXX11_ABI` is not defined Within the `__GXX_ABI_VERSION` block this should always be defined, guard against unexpected defines and make the error obvious. * Change `usecxx11` to `use_cxx11_abi` for correspondence with `_GLIBCXX_USE_CXX11_ABI` (similarly to `gxx_abi` for `__GXX_ABI_VERSION`). * `PYBIND11_COMPILER_TYPE` overhaul, mainly: replace `_icc`, `_clang`, `_gcc` with `_system` * Add NVHPC (__PGI) to the list of compilers compatible with system compiler. See comment by @robertmaynard: https://github.com/pybind/pybind11/pull/5439#issuecomment-2498839010 * Fix oversight: remove __NVCOMPILER elif branch in PYBIND11_BUILD_ABI block. Also add comment pointing to this PR (#5439). * Revert "Fix oversight: remove __NVCOMPILER elif branch in PYBIND11_BUILD_ABI block." This reverts commit d412303e727923571bfd9b907809b1d73a045b34. * Revert "Add NVHPC (__PGI) to the list of compilers compatible with system compiler." This reverts commit 9fc951588594c48d4bfd440cd985ffee7be57ae4. * Define NVHPC PYBIND11_BUILD_ABI using __GNUC__, __GNUC_MINOR__, _GLIBCXX_USE_CXX11_ABI * Use _GLIBCXX_USE_CXX11_ABI to detect libstdc++, then assume that NVHPC is always in the 1xxx ABI family. * Enhance NVHPC comment and limited future proofing. * The `PYBIND11_STDLIB` is obsolete but kept around to maintain backward compatibility. * Move `PYBIND11_BUILD_TYPE` down in the file, so that the order of macro definitions is the same as in the list defining `PYBIND11_PLATFORM_ABI_ID` * Introduce `PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE`: This makes it possible to achieve these two goals: * Avoid the leading underscore in `PYBIND11_PLATFORM_ABI_ID` (see https://github.com/pybind/pybind11/pull/5439#issuecomment-2503762161) * Maintain backward compatibility for use cases as reported under https://github.com/pybind/pybind11/pull/5439#issuecomment-2510212677 `PYBIND11_INTERNALS_KIND` is removed in this commit to ensure that `PYBIND11_COMPILER_TYPE` is the first element of the `PYBIND11_PLATFORM_ABI_ID`, so that `PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE` can meaningfully be used as a prefix for `PYBIND11_PLATFORM_ABI_ID` in pybind11/detail/internals.h. * Apply suggestion by @isuruf, with revised comments (code is as suggested). * Make determination of `PYBIND11_COMPILER_TYPE` `"macos"` or `"glibc"` more general. The main motivation is to resolve these "Manylinux on 🐍 3.13t • GIL" and "Pyodide wheel" failures: ``` /__w/pybind11/pybind11/include/pybind11/conduit/pybind11_platform_abi_id.h:35:10: error: #error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE." 35 | # error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE." | ^~~~~ ``` (All other CI jobs succeeded.) Further thought: Essentially, under Linux and macOS the `PYBIND11_COMPILER_TYPE` is only for informational purposes. ABI compatibility is determined by the libstdc++ or libc++ ABI version. * Add `PYBIND11_COMPILER_TYPE` `emscripten` * Add `PYBIND11_COMPILER_TYPE` `graalvm` * Revert "Add `PYBIND11_COMPILER_TYPE` `graalvm`" This reverts commit 75da5fbfd95cb6f431123a92d9fb5885e6c71fc2. * Revert "Add `PYBIND11_COMPILER_TYPE` `emscripten`" This reverts commit e34dc8b511d9f4dba0d128f9cf043f23fd155894. * Revert "Make determination of `PYBIND11_COMPILER_TYPE` `"macos"` or `"glibc"` more general." This reverts commit 41daaa41fadd03ede9bdd9479c8ddf4d143330bf. * Revert "Apply suggestion by @isuruf, with revised comments (code is as suggested)." This reverts commit ca9e6990deb9a57f21cba0879499600addeadf06. * Remove `defined(__INTEL_COMPILER)` as suggested by @hpkfft under https://github.com/pybind/pybind11/pull/5439#discussion_r1889156543 --------- Co-authored-by: Marcus D. Hanwell Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../conduit/pybind11_platform_abi_id.h | 81 +++++++++---------- include/pybind11/detail/internals.h | 4 +- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/include/pybind11/conduit/pybind11_platform_abi_id.h b/include/pybind11/conduit/pybind11_platform_abi_id.h index a7733bcf5c..d21fdc56d8 100644 --- a/include/pybind11/conduit/pybind11_platform_abi_id.h +++ b/include/pybind11/conduit/pybind11_platform_abi_id.h @@ -12,51 +12,32 @@ #define PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) #x #define PYBIND11_PLATFORM_ABI_ID_TOSTRING(x) PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) -// On MSVC, debug and release builds are not ABI-compatible! -#if defined(_MSC_VER) && defined(_DEBUG) -# define PYBIND11_BUILD_TYPE "_debug" +#ifdef PYBIND11_COMPILER_TYPE +// // To maintain backward compatibility (see PR #5439). +# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "" #else -# define PYBIND11_BUILD_TYPE "" -#endif - -// Let's assume that different compilers are ABI-incompatible. -// A user can manually set this string if they know their -// compiler is compatible. -#ifndef PYBIND11_COMPILER_TYPE -# if defined(_MSC_VER) -# define PYBIND11_COMPILER_TYPE "_msvc" -# elif defined(__INTEL_COMPILER) -# define PYBIND11_COMPILER_TYPE "_icc" -# elif defined(__clang__) -# define PYBIND11_COMPILER_TYPE "_clang" -# elif defined(__PGI) -# define PYBIND11_COMPILER_TYPE "_pgi" -# elif defined(__MINGW32__) -# define PYBIND11_COMPILER_TYPE "_mingw" +# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "_" +# if defined(__MINGW32__) +# define PYBIND11_COMPILER_TYPE "mingw" # elif defined(__CYGWIN__) -# define PYBIND11_COMPILER_TYPE "_gcc_cygwin" -# elif defined(__GNUC__) -# define PYBIND11_COMPILER_TYPE "_gcc" +# define PYBIND11_COMPILER_TYPE "gcc_cygwin" +# elif defined(_MSC_VER) +# define PYBIND11_COMPILER_TYPE "msvc" +# elif defined(__clang__) || defined(__GNUC__) +# define PYBIND11_COMPILER_TYPE "system" // Assumed compatible with system compiler. # else -# define PYBIND11_COMPILER_TYPE "_unknown" +# error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE." # endif #endif -// Also standard libs +// PR #5439 made this macro obsolete. However, there are many manipulations of this macro in the +// wild. Therefore, to maintain backward compatibility, it is kept around. #ifndef PYBIND11_STDLIB -# if defined(_LIBCPP_VERSION) -# define PYBIND11_STDLIB "_libcpp" -# elif defined(__GLIBCXX__) || defined(__GLIBCPP__) -# define PYBIND11_STDLIB "_libstdcpp" -# else -# define PYBIND11_STDLIB "" -# endif +# define PYBIND11_STDLIB "" #endif #ifndef PYBIND11_BUILD_ABI -# if defined(__GXX_ABI_VERSION) // Linux/OSX. -# define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(__GXX_ABI_VERSION) -# elif defined(_MSC_VER) // See PR #4953. +# if defined(_MSC_VER) // See PR #4953. # if defined(_MT) && defined(_DLL) // Corresponding to CL command line options /MD or /MDd. # if (_MSC_VER) / 100 == 19 # define PYBIND11_BUILD_ABI "_md_mscver19" @@ -72,17 +53,35 @@ # error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE." # endif # endif -# elif defined(__NVCOMPILER) // NVHPC (PGI-based). -# define PYBIND11_BUILD_ABI "" // TODO: What should be here, to prevent UB? +# elif defined(_LIBCPP_ABI_VERSION) // https://libcxx.llvm.org/DesignDocs/ABIVersioning.html +# define PYBIND11_BUILD_ABI \ + "_libcpp_abi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_LIBCPP_ABI_VERSION) +# elif defined(_GLIBCXX_USE_CXX11_ABI) // See PR #5439. +# if defined(__NVCOMPILER) +// // Assume that NVHPC is in the 1xxx ABI family. +// // THIS ASSUMPTION IS NOT FUTURE PROOF but apparently the best we can do. +// // Please let us know if there is a way to validate the assumption here. +# elif !defined(__GXX_ABI_VERSION) +# error \ + "Unknown platform or compiler (_GLIBCXX_USE_CXX11_ABI): PLEASE REVISE THIS CODE." +# endif +# if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION < 1002 || __GXX_ABI_VERSION >= 2000 +# error "Unknown platform or compiler (__GXX_ABI_VERSION): PLEASE REVISE THIS CODE." +# endif +# define PYBIND11_BUILD_ABI \ + "_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_" PYBIND11_PLATFORM_ABI_ID_TOSTRING( \ + _GLIBCXX_USE_CXX11_ABI) # else # error "Unknown platform or compiler: PLEASE REVISE THIS CODE." # endif #endif -#ifndef PYBIND11_INTERNALS_KIND -# define PYBIND11_INTERNALS_KIND "" +// On MSVC, debug and release builds are not ABI-compatible! +#if defined(_MSC_VER) && defined(_DEBUG) +# define PYBIND11_BUILD_TYPE "_debug" +#else +# define PYBIND11_BUILD_TYPE "" #endif #define PYBIND11_PLATFORM_ABI_ID \ - PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \ - PYBIND11_BUILD_TYPE + PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 278f35bbaa..5fcaf9b9c1 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -272,11 +272,11 @@ struct type_info { #define PYBIND11_INTERNALS_ID \ "__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_PLATFORM_ABI_ID "__" + PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__" #define PYBIND11_MODULE_LOCAL_ID \ "__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_PLATFORM_ABI_ID "__" + PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__" /// Each module locally stores a pointer to the `internals` data. The data /// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`. From 5b503f7ec973fcf4ba96a2f522a0692be5c4864f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 01:42:32 -0800 Subject: [PATCH 09/10] chore(deps): bump actions/attest-build-provenance in the actions group (#5468) Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 2.0.1 to 2.1.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/c4fbc648846ca6f503a13a2281a5e7b98aa57202...7668571508540a607bdfd90a87a560489fe372eb) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index c50f06f945..4291592ead 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -103,7 +103,7 @@ jobs: - uses: actions/download-artifact@v4 - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 + uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 with: subject-path: "*/pybind11*" From 58042d4270d056f15417ecc5b9e4f7d8a2aeb183 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 20 Dec 2024 01:51:16 -0800 Subject: [PATCH 10/10] Resolve new ruff error. --- tests/test_class_sh_disowning.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_class_sh_disowning.py b/tests/test_class_sh_disowning.py index b23a685159..95fd30c556 100644 --- a/tests/test_class_sh_disowning.py +++ b/tests/test_class_sh_disowning.py @@ -60,10 +60,8 @@ def test_mixed(): assert is_disowned_results.count(True) == 1 if first_pass: first_pass = False - print( - "\nC++ function argument %d is evaluated first." - % (is_disowned_results.index(True) + 1) - ) + ix = is_disowned_results.index(True) + 1 + print(f"\nC++ function argument {ix} is evaluated first.") return # Comment out for manual leak checking (use `top` command).