diff --git a/examples/knxPython/pybind11/.github/workflows/emscripten.yaml b/examples/knxPython/pybind11/.github/workflows/emscripten.yaml new file mode 100644 index 0000000..14b2b9d --- /dev/null +++ b/examples/knxPython/pybind11/.github/workflows/emscripten.yaml @@ -0,0 +1,30 @@ +name: WASM + +on: + workflow_dispatch: + pull_request: + branches: + - master + - stable + - v* + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-wasm-emscripten: + name: Pyodide wheel + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + - uses: pypa/cibuildwheel@v2.20 + env: + PYODIDE_BUILD_EXPORTS: whole_archive + with: + package-dir: tests + only: cp312-pyodide_wasm32 diff --git a/examples/knxPython/pybind11/docs/advanced/cast/functional.rst b/examples/knxPython/pybind11/docs/advanced/cast/functional.rst new file mode 100644 index 0000000..d9b4605 --- /dev/null +++ b/examples/knxPython/pybind11/docs/advanced/cast/functional.rst @@ -0,0 +1,109 @@ +Functional +########## + +The following features must be enabled by including :file:`pybind11/functional.h`. + + +Callbacks and passing anonymous functions +========================================= + +The C++11 standard brought lambda functions and the generic polymorphic +function wrapper ``std::function<>`` to the C++ programming language, which +enable powerful new ways of working with functions. Lambda functions come in +two flavors: stateless lambda function resemble classic function pointers that +link to an anonymous piece of code, while stateful lambda functions +additionally depend on captured variables that are stored in an anonymous +*lambda closure object*. + +Here is a simple example of a C++ function that takes an arbitrary function +(stateful or stateless) with signature ``int -> int`` as an argument and runs +it with the value 10. + +.. code-block:: cpp + + int func_arg(const std::function &f) { + return f(10); + } + +The example below is more involved: it takes a function of signature ``int -> int`` +and returns another function of the same kind. The return value is a stateful +lambda function, which stores the value ``f`` in the capture object and adds 1 to +its return value upon execution. + +.. code-block:: cpp + + std::function func_ret(const std::function &f) { + return [f](int i) { + return f(i) + 1; + }; + } + +This example demonstrates using python named parameters in C++ callbacks which +requires using ``py::cpp_function`` as a wrapper. Usage is similar to defining +methods of classes: + +.. code-block:: cpp + + py::cpp_function func_cpp() { + return py::cpp_function([](int i) { return i+1; }, + py::arg("number")); + } + +After including the extra header file :file:`pybind11/functional.h`, it is almost +trivial to generate binding code for all of these functions. + +.. code-block:: cpp + + #include + + PYBIND11_MODULE(example, m) { + m.def("func_arg", &func_arg); + m.def("func_ret", &func_ret); + m.def("func_cpp", &func_cpp); + } + +The following interactive session shows how to call them from Python. + +.. code-block:: pycon + + $ python + >>> import example + >>> def square(i): + ... return i * i + ... + >>> example.func_arg(square) + 100L + >>> square_plus_1 = example.func_ret(square) + >>> square_plus_1(4) + 17L + >>> plus_1 = func_cpp() + >>> plus_1(number=43) + 44L + +.. warning:: + + Keep in mind that passing a function from C++ to Python (or vice versa) + will instantiate a piece of wrapper code that translates function + invocations between the two languages. Naturally, this translation + increases the computational cost of each function call somewhat. A + problematic situation can arise when a function is copied back and forth + between Python and C++ many times in a row, in which case the underlying + wrappers will accumulate correspondingly. The resulting long sequence of + C++ -> Python -> C++ -> ... roundtrips can significantly decrease + performance. + + There is one exception: pybind11 detects case where a stateless function + (i.e. a function pointer or a lambda function without captured variables) + is passed as an argument to another C++ function exposed in Python. In this + case, there is no overhead. Pybind11 will extract the underlying C++ + function pointer from the wrapped function to sidestep a potential C++ -> + Python -> C++ roundtrip. This is demonstrated in :file:`tests/test_callbacks.cpp`. + +.. note:: + + This functionality is very useful when generating bindings for callbacks in + C++ libraries (e.g. GUI libraries, asynchronous networking libraries, etc.). + + The file :file:`tests/test_callbacks.cpp` contains a complete example + that demonstrates how to work with callbacks and anonymous functions in + more detail. diff --git a/examples/knxPython/pybind11/docs/advanced/pycpp/index.rst b/examples/knxPython/pybind11/docs/advanced/pycpp/index.rst new file mode 100644 index 0000000..6885bdc --- /dev/null +++ b/examples/knxPython/pybind11/docs/advanced/pycpp/index.rst @@ -0,0 +1,13 @@ +Python C++ interface +#################### + +pybind11 exposes Python types and functions using thin C++ wrappers, which +makes it possible to conveniently call Python code from C++ without resorting +to Python's C API. + +.. toctree:: + :maxdepth: 2 + + object + numpy + utilities diff --git a/examples/knxPython/pybind11/docs/pybind11_vs_boost_python1.png b/examples/knxPython/pybind11/docs/pybind11_vs_boost_python1.png new file mode 100644 index 0000000..833231f Binary files /dev/null and b/examples/knxPython/pybind11/docs/pybind11_vs_boost_python1.png differ diff --git a/examples/knxPython/pybind11/docs/pybind11_vs_boost_python1.svg b/examples/knxPython/pybind11/docs/pybind11_vs_boost_python1.svg new file mode 100644 index 0000000..5bf950e Binary files /dev/null and b/examples/knxPython/pybind11/docs/pybind11_vs_boost_python1.svg differ diff --git a/examples/knxPython/pybind11/docs/pybind11_vs_boost_python2.png b/examples/knxPython/pybind11/docs/pybind11_vs_boost_python2.png new file mode 100644 index 0000000..9f17272 Binary files /dev/null and b/examples/knxPython/pybind11/docs/pybind11_vs_boost_python2.png differ diff --git a/examples/knxPython/pybind11/docs/pybind11_vs_boost_python2.svg b/examples/knxPython/pybind11/docs/pybind11_vs_boost_python2.svg new file mode 100644 index 0000000..5ed6530 Binary files /dev/null and b/examples/knxPython/pybind11/docs/pybind11_vs_boost_python2.svg differ diff --git a/examples/knxPython/pybind11/include/pybind11/common.h b/examples/knxPython/pybind11/include/pybind11/common.h new file mode 100644 index 0000000..6c8a4f1 --- /dev/null +++ b/examples/knxPython/pybind11/include/pybind11/common.h @@ -0,0 +1,2 @@ +#include "detail/common.h" +#warning "Including 'common.h' is deprecated. It will be removed in v3.0. Use 'pybind11.h'." diff --git a/examples/knxPython/pybind11/include/pybind11/detail/cpp_conduit.h b/examples/knxPython/pybind11/include/pybind11/detail/cpp_conduit.h new file mode 100644 index 0000000..b66c2d3 --- /dev/null +++ b/examples/knxPython/pybind11/include/pybind11/detail/cpp_conduit.h @@ -0,0 +1,77 @@ +// Copyright (c) 2024 The pybind Community. + +#pragma once + +#include + +#include "common.h" +#include "internals.h" + +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +// Forward declaration needed here: Refactoring opportunity. +extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); + +inline bool type_is_managed_by_our_internals(PyTypeObject *type_obj) { +#if defined(PYPY_VERSION) + auto &internals = get_internals(); + return bool(internals.registered_types_py.find(type_obj) + != internals.registered_types_py.end()); +#else + return bool(type_obj->tp_new == pybind11_object_new); +#endif +} + +inline bool is_instance_method_of_type(PyTypeObject *type_obj, PyObject *attr_name) { + PyObject *descr = _PyType_Lookup(type_obj, attr_name); + return bool((descr != nullptr) && PyInstanceMethod_Check(descr)); +} + +inline object try_get_cpp_conduit_method(PyObject *obj) { + if (PyType_Check(obj)) { + return object(); + } + PyTypeObject *type_obj = Py_TYPE(obj); + str attr_name("_pybind11_conduit_v1_"); + bool assumed_to_be_callable = false; + if (type_is_managed_by_our_internals(type_obj)) { + if (!is_instance_method_of_type(type_obj, attr_name.ptr())) { + return object(); + } + assumed_to_be_callable = true; + } + PyObject *method = PyObject_GetAttr(obj, attr_name.ptr()); + if (method == nullptr) { + PyErr_Clear(); + return object(); + } + if (!assumed_to_be_callable && PyCallable_Check(method) == 0) { + Py_DECREF(method); + return object(); + } + return reinterpret_steal(method); +} + +inline void *try_raw_pointer_ephemeral_from_cpp_conduit(handle src, + const std::type_info *cpp_type_info) { + object method = try_get_cpp_conduit_method(src.ptr()); + if (method) { + capsule cpp_type_info_capsule(const_cast(static_cast(cpp_type_info)), + typeid(std::type_info).name()); + object cpp_conduit = method(bytes(PYBIND11_PLATFORM_ABI_ID), + cpp_type_info_capsule, + bytes("raw_pointer_ephemeral")); + if (isinstance(cpp_conduit)) { + return reinterpret_borrow(cpp_conduit).get_pointer(); + } + } + return nullptr; +} + +#define PYBIND11_HAS_CPP_CONDUIT 1 + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/examples/knxPython/pybind11/include/pybind11/detail/exception_translation.h b/examples/knxPython/pybind11/include/pybind11/detail/exception_translation.h new file mode 100644 index 0000000..2764180 --- /dev/null +++ b/examples/knxPython/pybind11/include/pybind11/detail/exception_translation.h @@ -0,0 +1,71 @@ +/* + pybind11/detail/exception_translation.h: means to translate C++ exceptions to Python exceptions + + Copyright (c) 2024 The Pybind Development Team. + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "common.h" +#include "internals.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +// Apply all the extensions translators from a list +// Return true if one of the translators completed without raising an exception +// itself. Return of false indicates that if there are other translators +// available, they should be tried. +inline bool apply_exception_translators(std::forward_list &translators) { + auto last_exception = std::current_exception(); + + for (auto &translator : translators) { + try { + translator(last_exception); + return true; + } catch (...) { + last_exception = std::current_exception(); + } + } + return false; +} + +inline void try_translate_exceptions() { + /* When an exception is caught, give each registered exception + translator a chance to translate it to a Python exception. First + all module-local translators will be tried in reverse order of + registration. If none of the module-locale translators handle + the exception (or there are no module-locale translators) then + the global translators will be tried, also in reverse order of + registration. + + A translator may choose to do one of the following: + + - catch the exception and call py::set_error() + to set a standard (or custom) Python exception, or + - do nothing and let the exception fall through to the next translator, or + - delegate translation to the next translator by throwing a new type of exception. + */ + + bool handled = with_internals([&](internals &internals) { + auto &local_exception_translators = get_local_internals().registered_exception_translators; + if (detail::apply_exception_translators(local_exception_translators)) { + return true; + } + auto &exception_translators = internals.registered_exception_translators; + if (detail::apply_exception_translators(exception_translators)) { + return true; + } + return false; + }); + + if (!handled) { + set_error(PyExc_SystemError, "Exception escaped from default exception translator!"); + } +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/examples/knxPython/pybind11/include/pybind11/detail/value_and_holder.h b/examples/knxPython/pybind11/include/pybind11/detail/value_and_holder.h new file mode 100644 index 0000000..ca37d70 --- /dev/null +++ b/examples/knxPython/pybind11/include/pybind11/detail/value_and_holder.h @@ -0,0 +1,77 @@ +// Copyright (c) 2016-2024 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#pragma once + +#include "common.h" + +#include +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +struct value_and_holder { + instance *inst = nullptr; + size_t index = 0u; + const detail::type_info *type = nullptr; + void **vh = nullptr; + + // Main constructor for a found value/holder: + value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) + : inst{i}, index{index}, type{type}, + vh{inst->simple_layout ? inst->simple_value_holder + : &inst->nonsimple.values_and_holders[vpos]} {} + + // Default constructor (used to signal a value-and-holder not found by get_value_and_holder()) + value_and_holder() = default; + + // Used for past-the-end iterator + explicit value_and_holder(size_t index) : index{index} {} + + template + V *&value_ptr() const { + return reinterpret_cast(vh[0]); + } + // True if this `value_and_holder` has a non-null value pointer + explicit operator bool() const { return value_ptr() != nullptr; } + + template + H &holder() const { + return reinterpret_cast(vh[1]); + } + bool holder_constructed() const { + return inst->simple_layout + ? inst->simple_holder_constructed + : (inst->nonsimple.status[index] & instance::status_holder_constructed) != 0u; + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void set_holder_constructed(bool v = true) { + if (inst->simple_layout) { + inst->simple_holder_constructed = v; + } else if (v) { + inst->nonsimple.status[index] |= instance::status_holder_constructed; + } else { + inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed; + } + } + bool instance_registered() const { + return inst->simple_layout + ? inst->simple_instance_registered + : ((inst->nonsimple.status[index] & instance::status_instance_registered) != 0); + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void set_instance_registered(bool v = true) { + if (inst->simple_layout) { + inst->simple_instance_registered = v; + } else if (v) { + inst->nonsimple.status[index] |= instance::status_instance_registered; + } else { + inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered; + } + } +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/examples/knxPython/pybind11/tests/exo_planet_c_api.cpp b/examples/knxPython/pybind11/tests/exo_planet_c_api.cpp new file mode 100644 index 0000000..3bde0b2 --- /dev/null +++ b/examples/knxPython/pybind11/tests/exo_planet_c_api.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2024 The pybind Community. + +// THIS MUST STAY AT THE TOP! +#include // EXCLUSIVELY for PYBIND11_PLATFORM_ABI_ID +// Potential future direction to maximize reusability: +// (e.g. for use from SWIG, Cython, PyCLIF, nanobind): +// #include +// This would only depend on: +// 1. A C++ compiler, WITHOUT requiring -fexceptions. +// 2. Python.h + +#include "test_cpp_conduit_traveler_types.h" + +#include +#include + +namespace { + +void *get_cpp_conduit_void_ptr(PyObject *py_obj, const std::type_info *cpp_type_info) { + PyObject *cpp_type_info_capsule + = PyCapsule_New(const_cast(static_cast(cpp_type_info)), + typeid(std::type_info).name(), + nullptr); + if (cpp_type_info_capsule == nullptr) { + return nullptr; + } + PyObject *cpp_conduit = PyObject_CallMethod(py_obj, + "_pybind11_conduit_v1_", + "yOy", + PYBIND11_PLATFORM_ABI_ID, + cpp_type_info_capsule, + "raw_pointer_ephemeral"); + Py_DECREF(cpp_type_info_capsule); + if (cpp_conduit == nullptr) { + return nullptr; + } + void *void_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name()); + Py_DECREF(cpp_conduit); + if (PyErr_Occurred()) { + return nullptr; + } + return void_ptr; +} + +template +T *get_cpp_conduit_type_ptr(PyObject *py_obj) { + void *void_ptr = get_cpp_conduit_void_ptr(py_obj, &typeid(T)); + if (void_ptr == nullptr) { + return nullptr; + } + return static_cast(void_ptr); +} + +extern "C" PyObject *wrapGetLuggage(PyObject * /*self*/, PyObject *traveler) { + const auto *cpp_traveler + = get_cpp_conduit_type_ptr(traveler); + if (cpp_traveler == nullptr) { + return nullptr; + } + return PyUnicode_FromString(cpp_traveler->luggage.c_str()); +} + +extern "C" PyObject *wrapGetPoints(PyObject * /*self*/, PyObject *premium_traveler) { + const auto *cpp_premium_traveler + = get_cpp_conduit_type_ptr( + premium_traveler); + if (cpp_premium_traveler == nullptr) { + return nullptr; + } + return PyLong_FromLong(static_cast(cpp_premium_traveler->points)); +} + +PyMethodDef ThisMethodDef[] = {{"GetLuggage", wrapGetLuggage, METH_O, nullptr}, + {"GetPoints", wrapGetPoints, METH_O, nullptr}, + {nullptr, nullptr, 0, nullptr}}; + +struct PyModuleDef ThisModuleDef = { + PyModuleDef_HEAD_INIT, // m_base + "exo_planet_c_api", // m_name + nullptr, // m_doc + -1, // m_size + ThisMethodDef, // m_methods + nullptr, // m_slots + nullptr, // m_traverse + nullptr, // m_clear + nullptr // m_free +}; + +} // namespace + +#if defined(WIN32) || defined(_WIN32) +# define EXO_PLANET_C_API_EXPORT __declspec(dllexport) +#else +# define EXO_PLANET_C_API_EXPORT __attribute__((visibility("default"))) +#endif + +extern "C" EXO_PLANET_C_API_EXPORT PyObject *PyInit_exo_planet_c_api() { + PyObject *m = PyModule_Create(&ThisModuleDef); + if (m == nullptr) { + return nullptr; + } + return m; +} diff --git a/examples/knxPython/pybind11/tests/exo_planet_pybind11.cpp b/examples/knxPython/pybind11/tests/exo_planet_pybind11.cpp new file mode 100644 index 0000000..9d1a2b8 --- /dev/null +++ b/examples/knxPython/pybind11/tests/exo_planet_pybind11.cpp @@ -0,0 +1,19 @@ +// Copyright (c) 2024 The pybind Community. + +#if defined(PYBIND11_INTERNALS_VERSION) +# undef PYBIND11_INTERNALS_VERSION +#endif +#define PYBIND11_INTERNALS_VERSION 900000001 + +#include "test_cpp_conduit_traveler_bindings.h" + +namespace pybind11_tests { +namespace test_cpp_conduit { + +PYBIND11_MODULE(exo_planet_pybind11, m) { + wrap_traveler(m); + m.def("wrap_very_lonely_traveler", [m]() { wrap_very_lonely_traveler(m); }); +} + +} // namespace test_cpp_conduit +} // namespace pybind11_tests diff --git a/examples/knxPython/pybind11/tests/home_planet_very_lonely_traveler.cpp b/examples/knxPython/pybind11/tests/home_planet_very_lonely_traveler.cpp new file mode 100644 index 0000000..78d50cf --- /dev/null +++ b/examples/knxPython/pybind11/tests/home_planet_very_lonely_traveler.cpp @@ -0,0 +1,13 @@ +// Copyright (c) 2024 The pybind Community. + +#include "test_cpp_conduit_traveler_bindings.h" + +namespace pybind11_tests { +namespace test_cpp_conduit { + +PYBIND11_MODULE(home_planet_very_lonely_traveler, m) { + m.def("wrap_very_lonely_traveler", [m]() { wrap_very_lonely_traveler(m); }); +} + +} // namespace test_cpp_conduit +} // namespace pybind11_tests diff --git a/examples/knxPython/pybind11/tests/pyproject.toml b/examples/knxPython/pybind11/tests/pyproject.toml new file mode 100644 index 0000000..044bf15 --- /dev/null +++ b/examples/knxPython/pybind11/tests/pyproject.toml @@ -0,0 +1,21 @@ +# Warning: this is currently used for pyodide, and is not a general out-of-tree +# builder for the tests (yet). Specifically, wheels can't be built from SDists. + +[build-system] +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[project] +name = "pybind11_tests" +version = "0.0.1" +dependencies = ["pytest", "pytest-timeout", "numpy", "scipy"] + +[tool.scikit-build] +# Hide a warning while we also support CMake < 3.15 +cmake.version = ">=3.15" + +[tool.scikit-build.cmake.define] +PYBIND11_FINDPYTHON = true + +[tool.cibuildwheel] +test-command = "pytest -o timeout=0 -p no:cacheprovider {project}/tests/test_*.py" diff --git a/examples/knxPython/pybind11/tests/test_cpp_conduit.cpp b/examples/knxPython/pybind11/tests/test_cpp_conduit.cpp new file mode 100644 index 0000000..4ee4f06 --- /dev/null +++ b/examples/knxPython/pybind11/tests/test_cpp_conduit.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2024 The pybind Community. + +#include "pybind11_tests.h" +#include "test_cpp_conduit_traveler_bindings.h" + +#include + +namespace pybind11_tests { +namespace test_cpp_conduit { + +TEST_SUBMODULE(cpp_conduit, m) { + m.attr("PYBIND11_PLATFORM_ABI_ID") = py::bytes(PYBIND11_PLATFORM_ABI_ID); + m.attr("cpp_type_info_capsule_Traveler") + = py::capsule(&typeid(Traveler), typeid(std::type_info).name()); + m.attr("cpp_type_info_capsule_int") = py::capsule(&typeid(int), typeid(std::type_info).name()); + + wrap_traveler(m); + wrap_lonely_traveler(m); +} + +} // namespace test_cpp_conduit +} // namespace pybind11_tests diff --git a/examples/knxPython/pybind11/tests/test_cpp_conduit.py b/examples/knxPython/pybind11/tests/test_cpp_conduit.py new file mode 100644 index 0000000..51fcf69 --- /dev/null +++ b/examples/knxPython/pybind11/tests/test_cpp_conduit.py @@ -0,0 +1,162 @@ +# Copyright (c) 2024 The pybind Community. + +from __future__ import annotations + +import exo_planet_c_api +import exo_planet_pybind11 +import home_planet_very_lonely_traveler +import pytest + +from pybind11_tests import cpp_conduit as home_planet + + +def test_traveler_getattr_actually_exists(): + t_h = home_planet.Traveler("home") + assert t_h.any_name == "Traveler GetAttr: any_name luggage: home" + + +def test_premium_traveler_getattr_actually_exists(): + t_h = home_planet.PremiumTraveler("home", 7) + assert t_h.secret_name == "PremiumTraveler GetAttr: secret_name points: 7" + + +def test_call_cpp_conduit_success(): + t_h = home_planet.Traveler("home") + cap = t_h._pybind11_conduit_v1_( + home_planet.PYBIND11_PLATFORM_ABI_ID, + home_planet.cpp_type_info_capsule_Traveler, + b"raw_pointer_ephemeral", + ) + assert cap.__class__.__name__ == "PyCapsule" + + +def test_call_cpp_conduit_platform_abi_id_mismatch(): + t_h = home_planet.Traveler("home") + cap = t_h._pybind11_conduit_v1_( + home_planet.PYBIND11_PLATFORM_ABI_ID + b"MISMATCH", + home_planet.cpp_type_info_capsule_Traveler, + b"raw_pointer_ephemeral", + ) + assert cap is None + + +def test_call_cpp_conduit_cpp_type_info_capsule_mismatch(): + t_h = home_planet.Traveler("home") + cap = t_h._pybind11_conduit_v1_( + home_planet.PYBIND11_PLATFORM_ABI_ID, + home_planet.cpp_type_info_capsule_int, + b"raw_pointer_ephemeral", + ) + assert cap is None + + +def test_call_cpp_conduit_pointer_kind_invalid(): + t_h = home_planet.Traveler("home") + with pytest.raises( + RuntimeError, match='^Invalid pointer_kind: "raw_pointer_ephemreal"$' + ): + t_h._pybind11_conduit_v1_( + home_planet.PYBIND11_PLATFORM_ABI_ID, + home_planet.cpp_type_info_capsule_Traveler, + b"raw_pointer_ephemreal", + ) + + +def test_home_only_basic(): + t_h = home_planet.Traveler("home") + assert t_h.luggage == "home" + assert home_planet.get_luggage(t_h) == "home" + + +def test_home_only_premium(): + p_h = home_planet.PremiumTraveler("home", 2) + assert p_h.luggage == "home" + assert home_planet.get_luggage(p_h) == "home" + assert home_planet.get_points(p_h) == 2 + + +def test_exo_only_basic(): + t_e = exo_planet_pybind11.Traveler("exo") + assert t_e.luggage == "exo" + assert exo_planet_pybind11.get_luggage(t_e) == "exo" + + +def test_exo_only_premium(): + p_e = exo_planet_pybind11.PremiumTraveler("exo", 3) + assert p_e.luggage == "exo" + assert exo_planet_pybind11.get_luggage(p_e) == "exo" + assert exo_planet_pybind11.get_points(p_e) == 3 + + +def test_home_passed_to_exo_basic(): + t_h = home_planet.Traveler("home") + assert exo_planet_pybind11.get_luggage(t_h) == "home" + + +def test_exo_passed_to_home_basic(): + t_e = exo_planet_pybind11.Traveler("exo") + assert home_planet.get_luggage(t_e) == "exo" + + +def test_home_passed_to_exo_premium(): + p_h = home_planet.PremiumTraveler("home", 2) + assert exo_planet_pybind11.get_luggage(p_h) == "home" + assert exo_planet_pybind11.get_points(p_h) == 2 + + +def test_exo_passed_to_home_premium(): + p_e = exo_planet_pybind11.PremiumTraveler("exo", 3) + assert home_planet.get_luggage(p_e) == "exo" + assert home_planet.get_points(p_e) == 3 + + +@pytest.mark.parametrize( + "traveler_type", [home_planet.Traveler, exo_planet_pybind11.Traveler] +) +def test_exo_planet_c_api_traveler(traveler_type): + t = traveler_type("socks") + assert exo_planet_c_api.GetLuggage(t) == "socks" + + +@pytest.mark.parametrize( + "premium_traveler_type", + [home_planet.PremiumTraveler, exo_planet_pybind11.PremiumTraveler], +) +def test_exo_planet_c_api_premium_traveler(premium_traveler_type): + pt = premium_traveler_type("gucci", 5) + assert exo_planet_c_api.GetLuggage(pt) == "gucci" + assert exo_planet_c_api.GetPoints(pt) == 5 + + +def test_home_planet_wrap_very_lonely_traveler(): + # This does not exercise the cpp_conduit feature, but is here to + # demonstrate that the cpp_conduit feature does not solve all + # cross-extension interoperability issues. + # Here is the proof that the following works for extensions with + # matching `PYBIND11_INTERNALS_ID`s: + # test_cpp_conduit.cpp: + # py::class_ + # home_planet_very_lonely_traveler.cpp: + # py::class_ + # See test_exo_planet_pybind11_wrap_very_lonely_traveler() for the negative + # test. + assert home_planet.LonelyTraveler is not None # Verify that the base class exists. + home_planet_very_lonely_traveler.wrap_very_lonely_traveler() + # Ensure that the derived class exists. + assert home_planet_very_lonely_traveler.VeryLonelyTraveler is not None + + +def test_exo_planet_pybind11_wrap_very_lonely_traveler(): + # See comment under test_home_planet_wrap_very_lonely_traveler() first. + # Here the `PYBIND11_INTERNALS_ID`s don't match between: + # test_cpp_conduit.cpp: + # py::class_ + # exo_planet_pybind11.cpp: + # py::class_ + assert home_planet.LonelyTraveler is not None # Verify that the base class exists. + with pytest.raises( + RuntimeError, + match='^generic_type: type "VeryLonelyTraveler" referenced unknown base type ' + '"pybind11_tests::test_cpp_conduit::LonelyTraveler"$', + ): + exo_planet_pybind11.wrap_very_lonely_traveler() diff --git a/examples/knxPython/pybind11/tests/test_cpp_conduit_traveler_bindings.h b/examples/knxPython/pybind11/tests/test_cpp_conduit_traveler_bindings.h new file mode 100644 index 0000000..4e52c90 --- /dev/null +++ b/examples/knxPython/pybind11/tests/test_cpp_conduit_traveler_bindings.h @@ -0,0 +1,47 @@ +// Copyright (c) 2024 The pybind Community. + +#pragma once + +#include + +#include "test_cpp_conduit_traveler_types.h" + +#include + +namespace pybind11_tests { +namespace test_cpp_conduit { + +namespace py = pybind11; + +inline void wrap_traveler(py::module_ m) { + py::class_(m, "Traveler") + .def(py::init()) + .def_readwrite("luggage", &Traveler::luggage) + // See issue #3788: + .def("__getattr__", [](const Traveler &self, const std::string &key) { + return "Traveler GetAttr: " + key + " luggage: " + self.luggage; + }); + + m.def("get_luggage", [](const Traveler &person) { return person.luggage; }); + + py::class_(m, "PremiumTraveler") + .def(py::init()) + .def_readwrite("points", &PremiumTraveler::points) + // See issue #3788: + .def("__getattr__", [](const PremiumTraveler &self, const std::string &key) { + return "PremiumTraveler GetAttr: " + key + " points: " + std::to_string(self.points); + }); + + m.def("get_points", [](const PremiumTraveler &person) { return person.points; }); +} + +inline void wrap_lonely_traveler(py::module_ m) { + py::class_(std::move(m), "LonelyTraveler"); +} + +inline void wrap_very_lonely_traveler(py::module_ m) { + py::class_(std::move(m), "VeryLonelyTraveler"); +} + +} // namespace test_cpp_conduit +} // namespace pybind11_tests diff --git a/examples/knxPython/pybind11/tests/test_cpp_conduit_traveler_types.h b/examples/knxPython/pybind11/tests/test_cpp_conduit_traveler_types.h new file mode 100644 index 0000000..b8e6a5a --- /dev/null +++ b/examples/knxPython/pybind11/tests/test_cpp_conduit_traveler_types.h @@ -0,0 +1,25 @@ +// Copyright (c) 2024 The pybind Community. + +#pragma once + +#include + +namespace pybind11_tests { +namespace test_cpp_conduit { + +struct Traveler { + explicit Traveler(const std::string &luggage) : luggage(luggage) {} + std::string luggage; +}; + +struct PremiumTraveler : Traveler { + explicit PremiumTraveler(const std::string &luggage, int points) + : Traveler(luggage), points(points) {} + int points; +}; + +struct LonelyTraveler {}; +struct VeryLonelyTraveler : LonelyTraveler {}; + +} // namespace test_cpp_conduit +} // namespace pybind11_tests diff --git a/examples/knxPython/pybind11/tests/test_type_caster_std_function_specializations.cpp b/examples/knxPython/pybind11/tests/test_type_caster_std_function_specializations.cpp new file mode 100644 index 0000000..89213dd --- /dev/null +++ b/examples/knxPython/pybind11/tests/test_type_caster_std_function_specializations.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include "pybind11_tests.h" + +namespace py = pybind11; + +namespace { + +struct SpecialReturn { + int value = 99; +}; + +} // namespace + +namespace pybind11 { +namespace detail { +namespace type_caster_std_function_specializations { + +template +struct func_wrapper : func_wrapper_base { + using func_wrapper_base::func_wrapper_base; + SpecialReturn operator()(Args... args) const { + gil_scoped_acquire acq; + SpecialReturn result; + try { + result = hfunc.f(std::forward(args)...).template cast(); + } catch (error_already_set &) { + result.value += 1; + } + result.value += 100; + return result; + } +}; + +} // namespace type_caster_std_function_specializations +} // namespace detail +} // namespace pybind11 + +TEST_SUBMODULE(type_caster_std_function_specializations, m) { + py::class_(m, "SpecialReturn") + .def(py::init<>()) + .def_readwrite("value", &SpecialReturn::value); + m.def("call_callback_with_special_return", + [](const std::function &func) { return func(); }); +} diff --git a/examples/knxPython/pybind11/tests/test_type_caster_std_function_specializations.py b/examples/knxPython/pybind11/tests/test_type_caster_std_function_specializations.py new file mode 100644 index 0000000..9e45d4f --- /dev/null +++ b/examples/knxPython/pybind11/tests/test_type_caster_std_function_specializations.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from pybind11_tests import type_caster_std_function_specializations as m + + +def test_callback_with_special_return(): + def return_special(): + return m.SpecialReturn() + + def raise_exception(): + raise ValueError("called raise_exception.") + + assert return_special().value == 99 + assert m.call_callback_with_special_return(return_special).value == 199 + assert m.call_callback_with_special_return(raise_exception).value == 200