From 8e00226da3b01364064d29ba64b594ea567d79b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 30 Apr 2021 17:46:19 +0200 Subject: [PATCH] python: add a caster for Containers::Optional. --- doc/python/pages/changelog.rst | 3 + src/Corrade/Containers/CMakeLists.txt | 1 + .../Containers/OptionalPythonBindings.h | 62 +++++++++++++++ src/python/corrade/test/CMakeLists.txt | 14 ++-- src/python/corrade/test/test_containers.py | 14 ++++ src/python/corrade/test/test_optional.cpp | 75 +++++++++++++++++++ 6 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 src/Corrade/Containers/OptionalPythonBindings.h create mode 100644 src/python/corrade/test/test_optional.cpp diff --git a/doc/python/pages/changelog.rst b/doc/python/pages/changelog.rst index acdb944..fa62f53 100644 --- a/doc/python/pages/changelog.rst +++ b/doc/python/pages/changelog.rst @@ -55,6 +55,9 @@ Changelog - The Homebrew package now uses `std_cmake_args` instead of hardcoded build type and install prefix, which resolves certain build issues (see :gh:`mosra/homebrew-magnum#6`) +- Added a caster for :dox:`Containers::Optional`, allowing it to be used + directly in function signatures and showing up on the Python side as either + :py:`None` or the actual value `2020.06`_ ========== diff --git a/src/Corrade/Containers/CMakeLists.txt b/src/Corrade/Containers/CMakeLists.txt index 14c52ab..7cdecfe 100644 --- a/src/Corrade/Containers/CMakeLists.txt +++ b/src/Corrade/Containers/CMakeLists.txt @@ -25,6 +25,7 @@ if(WITH_PYTHON) set(CorradeContainersPython_HEADERS + OptionalPythonBindings.h PythonBindings.h StridedArrayViewPythonBindings.h) diff --git a/src/Corrade/Containers/OptionalPythonBindings.h b/src/Corrade/Containers/OptionalPythonBindings.h new file mode 100644 index 0000000..6063437 --- /dev/null +++ b/src/Corrade/Containers/OptionalPythonBindings.h @@ -0,0 +1,62 @@ +#ifndef Corrade_Containers_OptionalPythonBindings_h +#define Corrade_Containers_OptionalPythonBindings_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +namespace pybind11 { namespace detail { + +/* pybind11/stl.h has optional_caster for this, but that relies on a value_type + typedef that Optional doesn't have, so adapting a copy of it, also without + std::is_lvalue_reference::value, which is not a thing here */ +template struct type_caster> { + using value_conv = make_caster; + + template static handle cast(T_&& src, const return_value_policy policy, const handle parent) { + if(!src) return none{}.inc_ref(); + return value_conv::cast(*std::forward(src), return_value_policy_override::policy(policy), parent); + } + + bool load(const handle src, bool convert) { + if(!src) return false; + + /* default-constructed value is already empty */ + if(src.is_none()) return true; + + value_conv inner_caster; + if(!inner_caster.load(src, convert)) return false; + + value.emplace(cast_op(std::move(inner_caster))); + return true; + } + + PYBIND11_TYPE_CASTER(Corrade::Containers::Optional, _("Optional[") + value_conv::name + _("]")); +}; + +}} + +#endif diff --git a/src/python/corrade/test/CMakeLists.txt b/src/python/corrade/test/CMakeLists.txt index 9bcca69..c3a9982 100644 --- a/src/python/corrade/test/CMakeLists.txt +++ b/src/python/corrade/test/CMakeLists.txt @@ -23,9 +23,11 @@ # DEALINGS IN THE SOFTWARE. # -pybind11_add_module(test_stridedarrayview ${pybind11_add_module_SYSTEM} test_stridedarrayview.cpp) -target_include_directories(test_stridedarrayview PRIVATE ${PROJECT_SOURCE_DIR}/src) -target_link_libraries(test_stridedarrayview PRIVATE Corrade::Containers) -set_target_properties(test_stridedarrayview PROPERTIES - FOLDER "python" - LIBRARY_OUTPUT_DIRECTORY ${output_dir}) +foreach(_test optional stridedarrayview) + pybind11_add_module(test_${_test} ${pybind11_add_module_SYSTEM} test_${_test}.cpp) + target_include_directories(test_${_test} PRIVATE ${PROJECT_SOURCE_DIR}/src) + target_link_libraries(test_${_test} PRIVATE Corrade::Containers) + set_target_properties(test_${_test} PROPERTIES + FOLDER "python" + LIBRARY_OUTPUT_DIRECTORY ${output_dir}) +endforeach() diff --git a/src/python/corrade/test/test_containers.py b/src/python/corrade/test/test_containers.py index 62ecc6c..b786f02 100644 --- a/src/python/corrade/test/test_containers.py +++ b/src/python/corrade/test/test_containers.py @@ -29,6 +29,7 @@ import unittest from corrade import containers import test_stridedarrayview +import test_optional class ArrayView(unittest.TestCase): def test_init(self): @@ -900,3 +901,16 @@ class StridedArrayViewCustomType(unittest.TestCase): # mutable_vector3d and mutable_long_float tested in test_containers_numpy # as memoryview can't handle their types + +class TestOptional(unittest.TestCase): + def test_simple(self): + self.assertIsNone(test_optional.simple_type(False)) + self.assertEqual(test_optional.simple_type(True), 5) + self.assertEqual(test_optional.acquire_simple_type(None), -1) + self.assertEqual(test_optional.acquire_simple_type(15), 15) + + def test_nested(self): + self.assertIsNone(test_optional.nested_type(False)) + self.assertEqual(test_optional.nested_type(True).a, 15) + self.assertEqual(test_optional.acquire_nested_type(None), -1) + self.assertEqual(test_optional.acquire_nested_type(test_optional.Foo(25)), 25) diff --git a/src/python/corrade/test/test_optional.cpp b/src/python/corrade/test/test_optional.cpp new file mode 100644 index 0000000..2d5a200 --- /dev/null +++ b/src/python/corrade/test/test_optional.cpp @@ -0,0 +1,75 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include "../bootstrap.h" /* for module / _module alias */ + +#include "Corrade/Containers/OptionalPythonBindings.h" + +using namespace Corrade; +namespace py = pybind11; + +namespace { + +struct Foo { + Foo(int a): a{a} {} + int a; +}; + +Containers::Optional simpleType(bool set) { + return set ? Containers::optional(5) : Containers::NullOpt; +} + +Containers::Optional nestedType(bool set) { + return set ? Containers::optional(Foo{15}) : Containers::NullOpt; +} + +int acquireSimpleType(Containers::Optional value) { + return value ? *value : -1; +} + +int acquireNestedType(Containers::Optional value) { + return value ? value->a : -1; +} + +} + +/* TODO: remove declaration when https://github.com/pybind/pybind11/pull/1863 + is released */ +extern "C" PYBIND11_EXPORT PyObject* PyInit_test_optional(); +PYBIND11_MODULE(test_optional, m) { + py::module_::import("corrade.containers"); + + py::class_{m, "Foo"} + .def(py::init()) + .def_readwrite("a", &Foo::a); + + m.def("simple_type", simpleType); + m.def("nested_type", nestedType); + + m.def("acquire_simple_type", acquireSimpleType); + m.def("acquire_nested_type", acquireNestedType); +}