From efaea1edfeb23b8dea613827fbc3f859afd38d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 7 May 2022 13:58:59 +0200 Subject: [PATCH] Better implementation for FileWrapper / DirectoryWrapper. --- README.md | 22 +++ src/mobase/mobase.cpp | 6 - src/mobase/pybind11_all.h | 150 ++++++++++----- src/mobase/wrappers/game_features.cpp | 26 ++- src/mobase/wrappers/pyplugins.h | 8 +- src/mobase/wrappers/wrappers.cpp | 11 +- src/pybind11-utils/README.md | 10 +- .../include/pybind11_utils/arg_wrapper.h | 155 --------------- .../include/pybind11_utils/smart_variant.h | 53 +++++ .../pybind11_utils/smart_variant_wrapper.h | 181 ++++++++++++++++++ tests/python/CMakeLists.txt | 10 +- tests/python/test_argument_wrapper.cpp | 40 ++-- 12 files changed, 411 insertions(+), 261 deletions(-) delete mode 100644 src/pybind11-utils/include/pybind11_utils/arg_wrapper.h create mode 100644 src/pybind11-utils/include/pybind11_utils/smart_variant.h create mode 100644 src/pybind11-utils/include/pybind11_utils/smart_variant_wrapper.h diff --git a/README.md b/README.md index 059fa5b..9085360 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,25 @@ sub-directories: - [`tests/runner`](tests/runner/) contains C++ tests, using GTest Tests in this project instantiate a Python runner and then use it to check that plugins implemented in Python can be used properly on the C++ side. + +## Building & Running tests + +Tests are not built by default with `mob`, so you will need to run `cmake` manually +with the proper arguments. + +You need to define `PLUGIN_PYTHON_TESTS` with `-DPLUGIN_PYTHON_TESTS` when running +the configure step of cmake. + +You can then build the tests + +```bash +# replace vsbuild with your build folder +cmake --build vsbuild --config RelWithDebInfo --target "python-tests" "runner-tests" +``` + +To run the tests, use `ctest` + +```bash +# replace vsbuild with your build folder +ctest.exe --test-dir vsbuild -C RelWithDebInfo +``` diff --git a/src/mobase/mobase.cpp b/src/mobase/mobase.cpp index c24a00d..2415af6 100644 --- a/src/mobase/mobase.cpp +++ b/src/mobase/mobase.cpp @@ -68,7 +68,6 @@ PYBIND11_MODULE(mobase, m) // typing stuff to be consistent with stubs and allow plugin developers to properly // type their code if they want { - m.add_object("Path", py::module_::import("pathlib").attr("Path")); m.add_object("TypeVar", py::module_::import("typing").attr("TypeVar")); auto s = m.attr("__dict__"); @@ -83,11 +82,6 @@ PYBIND11_MODULE(mobase, m) "MoVariant", py::eval("None | bool | int | str | list[object] | dict[str, object]")); - // same things for FileWrapper and DirectoryWrapper - // - m.add_object("FileWrapper", py::eval("str | PyQt6.QtCore.QFileInfo | Path", s)); - m.add_object("DirectoryWrapper", py::eval("str | PyQt6.QtCore.QDir | Path", s)); - // same thing for GameFeatureType // m.add_object("GameFeatureType", py::eval("TypeVar('GameFeatureType')", s)); diff --git a/src/mobase/pybind11_all.h b/src/mobase/pybind11_all.h index 7dbba2b..096c13a 100644 --- a/src/mobase/pybind11_all.h +++ b/src/mobase/pybind11_all.h @@ -12,65 +12,72 @@ #include "pybind11_qt/pybind11_qt.h" -#include "pybind11_utils/arg_wrapper.h" #include "pybind11_utils/functional.h" #include "pybind11_utils/shared_cpp_owner.h" +#include "pybind11_utils/smart_variant_wrapper.h" #include #include namespace mo2::python { - // struct to wrap "path" object between C++ and Python, allowing to mix - // pathlib.Path, QString, QFileInfo and QDir when possible + namespace detail { + + template <> + struct smart_variant_converter { + + static QString from(std::filesystem::path const& path) + { + return QString::fromStdWString(path.native()); + } + + static QString from(QFileInfo const& fileInfo) + { + return fileInfo.filePath(); + } + + static QString from(QDir const& dir) { return dir.path(); } + }; + + template <> + struct smart_variant_converter { + + static std::filesystem::path from(QString const& value) + { + return {value.toStdWString()}; + } + + static std::filesystem::path from(QFileInfo const& fileInfo) + { + return fileInfo.filesystemFilePath(); + } + + static std::filesystem::path from(QDir const& dir) + { + return dir.filesystemPath(); + } + }; + + // we do not need specialization for QFileInfo and QDir because both of them can + // be constructed from std::filesystem::path and QString already + + } // namespace detail + + using FileWrapper = smart_variant; + using DirectoryWrapper = smart_variant; + + // wrap the given function to accept FileWrapper (str | PathLike | QFileInfo) at the + // given argument positions (or any valid positions if Is... is empty) // - class BasePathWrapper { - protected: - // we store a std::filesystem::path because it can be converted to most thing, - // even though we lose basic functionality on QDir (name filter, etc.) - std::filesystem::path path_; - - public: - BasePathWrapper() = default; - BasePathWrapper(BasePathWrapper const&) = default; - BasePathWrapper& operator=(BasePathWrapper const&) = default; - BasePathWrapper(BasePathWrapper&&) = default; - BasePathWrapper& operator=(BasePathWrapper&&) = default; - - BasePathWrapper(std::filesystem::path const& path) : path_{path} {} - BasePathWrapper(QString const& path) : path_{path.toStdWString()} {} - - operator QString() const { return QString::fromStdWString(path_.native()); } - operator std::filesystem::path() const { return path_; } - }; - - class FileWrapper : public BasePathWrapper { - public: - using BasePathWrapper::BasePathWrapper; - - FileWrapper(QFileInfo const& fileInfo) - : BasePathWrapper(fileInfo.filesystemFilePath()) - { - } - - operator QFileInfo() const { return QFileInfo(path_); } - }; - - class DirectoryWrapper : public BasePathWrapper { - public: - using BasePathWrapper::BasePathWrapper; - - DirectoryWrapper(QDir const& dir) : BasePathWrapper(dir.filesystemPath()) {} - - operator QDir() const { return QDir(path_); } - }; - template auto wrap_for_filepath(Fn&& fn) { return mo2::python::wrap_arguments(std::forward(fn)); } + // wrap the given function to accept DirectoryWrapper (str | PathLike | QDir) + // at the given argument positions (or any valid positions if Is... is empty) + // template auto wrap_for_directory(Fn&& fn) { @@ -78,12 +85,57 @@ namespace mo2::python { std::forward(fn)); } -} // namespace mo2::python + // wrap a function-like object to return a FileWrapper instead of its return type, + // useful to generate proper typing in Python + // + // note that QFileInfo has a __fspath__ in Python, so it is quite easy to convert + // from "FileWrapper", a.k.a., str | os.PathLike | QFileInfo to Path by simply + // calling Path() on the return type if necessary + // + // this should be combined with custom return-value in PYBIND11_OVERRIDE(_PURE), see + // ISaveGame binding for an example + // + template + auto wrap_return_for_filepath(Fn&& fn) + { + return mo2::python::wrap_return(std::forward(fn)); + } -MO2_PYBIND11_WRAP_ARGUMENT_CASTER(mo2::python::FileWrapper, "FileWrapper", QFileInfo, - std::filesystem::path, QString); -MO2_PYBIND11_WRAP_ARGUMENT_CASTER(mo2::python::DirectoryWrapper, "DirectoryWrapper", - QDir, std::filesystem::path, QString); + // similar to wrap_return_for_filepath, except it returns a DirectoryWrapper instead + // of its return type + // + // this is much less practical than wrap_return_for_filepath since QDir does not + // expose __fspath__, so more complex things need to be done in Python, which is why + // this should be used carefully (e.g., should not be used if the return type is + // already QDir) + // + template + auto wrap_return_for_directory(Fn&& fn) + { + return mo2::python::wrap_return(std::forward(fn)); + } + + // convert a QList to QStringList - QString must be constructible from QString + // + template + QStringList toQStringList(QList const& list) + { + static_assert(std::is_constructible_v, + "QString must be constructible from T."); + return {list.begin(), list.end()}; + } + + // convert a QStringList to a QList - T must be constructible from QString + // + template + QList toQList(QStringList const& list) + { + static_assert(std::is_constructible_v, + "T must be constructible from QString."); + return {list.begin(), list.end()}; + } + +} // namespace mo2::python MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement) MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame) diff --git a/src/mobase/wrappers/game_features.cpp b/src/mobase/wrappers/game_features.cpp index 18f7ca6..c643eb2 100644 --- a/src/mobase/wrappers/game_features.cpp +++ b/src/mobase/wrappers/game_features.cpp @@ -155,12 +155,12 @@ namespace mo2::python { public: QString BinaryName() const override { - PYBIND11_OVERRIDE_PURE(FileWrapper, ScriptExtender, BinaryName, ); + PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, binaryName, ); } QString PluginPath() const override { - PYBIND11_OVERRIDE_PURE(DirectoryWrapper, ScriptExtender, PluginPath, ); + PYBIND11_OVERRIDE_PURE(DirectoryWrapper, ScriptExtender, pluginPath, ); } QString loaderName() const override @@ -210,11 +210,10 @@ namespace mo2::python { } QStringList secondaryFiles(const QString& modName) const override { - auto result = [&] { + return toQStringList([&] { PYBIND11_OVERRIDE_PURE(QList, UnmanagedMods, secondaryFiles, modName); - }(); - return QList(result.begin(), result.end()); + }()); } }; @@ -297,10 +296,10 @@ namespace mo2::python { py::class_(m, "ScriptExtender") .def(py::init<>()) - .def("BinaryName", &ScriptExtender::BinaryName) - .def("PluginPath", &ScriptExtender::PluginPath) + .def("binaryName", &ScriptExtender::BinaryName) + .def("pluginPath", wrap_return_for_directory(&ScriptExtender::PluginPath)) .def("loaderName", &ScriptExtender::loaderName) - .def("loaderPath", &ScriptExtender::loaderPath) + .def("loaderPath", wrap_return_for_filepath(&ScriptExtender::loaderPath)) .def("savegameExtension", &ScriptExtender::savegameExtension) .def("isInstalled", &ScriptExtender::isInstalled) .def("getExtenderVersion", &ScriptExtender::getExtenderVersion) @@ -312,8 +311,15 @@ namespace mo2::python { .def(py::init<>()) .def("mods", &UnmanagedMods::mods, "official_only"_a) .def("displayName", &UnmanagedMods::displayName, "mod_name"_a) - .def("referenceFile", &UnmanagedMods::referenceFile, "mod_name"_a) - .def("secondaryFiles", &UnmanagedMods::secondaryFiles, "mod_name"_a); + .def("referenceFile", + wrap_return_for_filepath(&UnmanagedMods::referenceFile), "mod_name"_a) + .def( + "secondaryFiles", + [](UnmanagedMods* m, const QString& modName) -> QList { + auto result = m->secondaryFiles(modName); + return {result.begin(), result.end()}; + }, + "mod_name"_a); } } // namespace mo2::python diff --git a/src/mobase/wrappers/pyplugins.h b/src/mobase/wrappers/pyplugins.h index 00e25d3..57b7480 100644 --- a/src/mobase/wrappers/pyplugins.h +++ b/src/mobase/wrappers/pyplugins.h @@ -375,11 +375,11 @@ namespace mo2::python { } QDir gameDirectory() const override { - PYBIND11_OVERRIDE_PURE(DirectoryWrapper, IPluginGame, gameDirectory, ); + PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, gameDirectory, ); } QDir dataDirectory() const override { - PYBIND11_OVERRIDE_PURE(DirectoryWrapper, IPluginGame, dataDirectory, ); + PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, dataDirectory, ); } void setGamePath(const QString& path) override { @@ -387,11 +387,11 @@ namespace mo2::python { } QDir documentsDirectory() const override { - PYBIND11_OVERRIDE_PURE(DirectoryWrapper, IPluginGame, documentsDirectory, ); + PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, documentsDirectory, ); } QDir savesDirectory() const override { - PYBIND11_OVERRIDE_PURE(DirectoryWrapper, IPluginGame, savesDirectory, ); + PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, savesDirectory, ); } QList executables() const override { diff --git a/src/mobase/wrappers/wrappers.cpp b/src/mobase/wrappers/wrappers.cpp index d2cdf45..33d5804 100644 --- a/src/mobase/wrappers/wrappers.cpp +++ b/src/mobase/wrappers/wrappers.cpp @@ -54,7 +54,9 @@ namespace mo2::python { QStringList allFiles() const override { - PYBIND11_OVERRIDE_PURE(QStringList, ISaveGame, allFiles, ); + return toQStringList([&] { + PYBIND11_OVERRIDE_PURE(QList, ISaveGame, allFiles, ); + }()); } ~PySaveGame() { std::cout << "~PySaveGame()" << std::endl; } @@ -80,11 +82,14 @@ namespace mo2::python { py::class_>(m, "ISaveGame") .def(py::init<>()) - .def("getFilepath", &ISaveGame::getFilepath) + .def("getFilepath", wrap_return_for_filepath(&ISaveGame::getFilepath)) .def("getCreationTime", &ISaveGame::getCreationTime) .def("getName", &ISaveGame::getName) .def("getSaveGroupIdentifier", &ISaveGame::getSaveGroupIdentifier) - .def("allFiles", &ISaveGame::allFiles); + .def("allFiles", [](ISaveGame* s) -> QList { + const auto result = s->allFiles(); + return {result.begin(), result.end()}; + }); // ISaveGameInfoWidget - custom holder to keep the Python object alive alongside // the widget diff --git a/src/pybind11-utils/README.md b/src/pybind11-utils/README.md index d9c21af..46a9256 100644 --- a/src/pybind11-utils/README.md +++ b/src/pybind11-utils/README.md @@ -2,12 +2,12 @@ This library contains some utility stuff for `pybind11` -## arg_wrapper.h +## smart_variant_wrapper.h -Expose a function `mo2::python::wrap_arguments` and a macro -`MO2_PYBIND11_WRAP_ARGUMENT_CASTER`. -These can be used to convert C++ function when exposing them to Python to accept more -type than the C++ one. +Expose a function `mo2::python::wrap_arguments` and a template +`mo2::python::smart_variant` that can be used to expose more interesting types Python +than the C++ one, e.g., accept `os.PathLike` and `QFileInfo` when a simple `QString` is +expected. A toy example can be found in the test folder at [`tests/python/test_argument_wrapper.cpp`](../../tests/python/test_argument_wrapper.cpp). diff --git a/src/pybind11-utils/include/pybind11_utils/arg_wrapper.h b/src/pybind11-utils/include/pybind11_utils/arg_wrapper.h deleted file mode 100644 index 2debb2d..0000000 --- a/src/pybind11-utils/include/pybind11_utils/arg_wrapper.h +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef PYTHON_PYBIND11_UTILS_FUNCTION_WRAPPER_H -#define PYTHON_PYBIND11_UTILS_FUNCTION_WRAPPER_H - -#include -#include - -#include - -namespace mo2::python { - - namespace detail { - - // simple helper class that expose a ::type attribute which is U is I is in Is, - // V otherwise - template - struct wrap_arg; - - template - struct wrap_arg> { - using type = - std::conditional_t...>, - U, V>; - }; - - // helper type for wrap_arg - template - using wrap_arg_t = typename wrap_arg::type; - - template - auto wrap_fn_impl(std::index_sequence, Fn&& fn, R (*)(Args...), - std::index_sequence) - { - return [fn = std::forward(fn)]( - wrap_arg_t>... args) { - return std::invoke(fn, std::forward(args)...); - }; - } - template - auto make_convertible_index_sequence(std::index_sequence) - { - return std::index_sequence<(std::is_convertible_v ? Is : -1)...>{}; - } - - template - auto wrap_fn_impl(Fn&& fn, R (*sg)(Args...)) - { - if constexpr (sizeof...(Is) == 0) { - return wrap_fn_impl(make_convertible_index_sequence( - std::make_index_sequence{}), - std::forward(fn), sg, - std::make_index_sequence{}); - } - else { - return wrap_fn_impl(std::index_sequence{}, - std::forward(fn), sg, - std::make_index_sequence{}); - } - } - - // list of wrap_fn_impl for possible function types, the set of overload is from - // pybind11::cpp_function - - template - auto wrap_fn_impl(R (*fn)(Args...)) - { - return wrap_fn_impl(fn, fn); - } - - template - auto wrap_fn_impl(R (C::*fn)(Args...)) - { - return wrap_fn_impl(fn, (R(*)(C*, Args...)) nullptr); - } - - template - auto wrap_fn_impl(R (C::*fn)(Args...) &) - { - return wrap_fn_impl(fn, (R(*)(C*, Args...)) nullptr); - } - - template - auto wrap_fn_impl(R (C::*fn)(Args...) const) - { - return wrap_fn_impl(fn, (R(*)(const C*, Args...)) nullptr); - } - - template - auto wrap_fn_impl(R (C::*fn)(Args...) const&) - { - return wrap_fn_impl(fn, (R(*)(const C*, Args...)) nullptr); - } - - template - auto wrap_fn_impl(Fn&& fn) - { - return wrap_fn_impl( - std::forward(fn), - (pybind11::detail::function_signature_t*)nullptr); - } - - template - struct load_wrapped_argument_helper; - - template - struct load_wrapped_argument_helper { - static bool load(Type&, pybind11::handle, bool) { return false; } - }; - - template - struct load_wrapped_argument_helper { - static bool load(Type& value, pybind11::handle src, bool convert) - { - pybind11::detail::make_caster caster; - - if (caster.load(src, convert)) { - value = Type{static_cast(caster)}; - return true; - } - - return load_wrapped_argument_helper::load( - value, src, convert); - } - }; - - } // namespace detail - - // wrap the given function-like object to accept T instead of the specified - // arguments at the specified positions - // - // if the list of positions is empty, replace all arguments that can be converted to - // T - // - template - auto wrap_arguments(Fn&& fn) - { - return detail::wrap_fn_impl(std::forward(fn)); - } - -} // namespace mo2::python - -#define MO2_PYBIND11_WRAP_ARGUMENT_CASTER(Type, Name, ...) \ - namespace pybind11::detail { \ - template <> \ - struct type_caster { \ - PYBIND11_TYPE_CASTER(Type, const_name(Name)); \ - bool load(handle src, bool convert) \ - { \ - return mo2::python::detail::load_wrapped_argument_helper< \ - Type, __VA_ARGS__>::load(value, src, convert); \ - } \ - }; \ - } - -#endif diff --git a/src/pybind11-utils/include/pybind11_utils/smart_variant.h b/src/pybind11-utils/include/pybind11_utils/smart_variant.h new file mode 100644 index 0000000..bd7bd9b --- /dev/null +++ b/src/pybind11-utils/include/pybind11_utils/smart_variant.h @@ -0,0 +1,53 @@ +#ifndef PYTHON_PYBIND11_UTILS_SMART_VARIANT_H +#define PYTHON_PYBIND11_UTILS_SMART_VARIANT_H + +#include + +namespace mo2::python { + + namespace detail { + + // simple template class that should be specialized to expose proper fromXXX + // methods + // + template + struct smart_variant_converter { + template + static T from(U&& u) + { + return T{std::forward(u)}; + } + }; + + } // namespace detail + + // a smart_variant is a std::variant that can be automatically converted to any of + // its type via custom operator T() + // + // user should specialize detail::smart_variant_converter to provide proper + // conversions + // + template + struct smart_variant : std::variant { + using std::variant::variant; + + template ...>, int> = 0> + operator T() const + { + return std::visit( + [](auto const& t) -> T { + if constexpr (std::is_same_v, T>) { + return t; + } + else { + return detail::smart_variant_converter::from(t); + } + }, + *this); + } + }; + +} // namespace mo2::python + +#endif diff --git a/src/pybind11-utils/include/pybind11_utils/smart_variant_wrapper.h b/src/pybind11-utils/include/pybind11_utils/smart_variant_wrapper.h new file mode 100644 index 0000000..8bc5e6d --- /dev/null +++ b/src/pybind11-utils/include/pybind11_utils/smart_variant_wrapper.h @@ -0,0 +1,181 @@ +#ifndef PYTHON_PYBIND11_UTILS_SMART_VARIANT_WRAPPER_H +#define PYTHON_PYBIND11_UTILS_SMART_VARIANT_WRAPPER_H + +#include +#include + +#include +#include + +#include "smart_variant.h" + +namespace mo2::python { + + namespace detail { + + // simple helper class that expose a ::type attribute which is U is I is in Is, + // V otherwise + template + struct wrap_arg; + + template + struct wrap_arg> { + using type = + std::conditional_t...>, + U, V>; + }; + + // helper type for wrap_arg + template + using wrap_arg_t = typename wrap_arg::type; + + template + auto wrap_arguments_impl(std::index_sequence, Fn&& fn, R (*)(Args...), + std::index_sequence) + { + return [fn = std::forward(fn)]( + wrap_arg_t>... args) { + return std::invoke(fn, std::forward(args)...); + }; + } + + template + auto make_convertible_index_sequence(std::index_sequence) + { + return std::index_sequence<(std::is_convertible_v ? Is : -1)...>{}; + } + + template + auto wrap_arguments_impl(Fn&& fn, R (*sg)(Args...)) + { + if constexpr (sizeof...(Is) == 0) { + return wrap_arguments_impl( + make_convertible_index_sequence( + std::make_index_sequence{}), + std::forward(fn), sg, + std::make_index_sequence{}); + } + else { + return wrap_arguments_impl( + std::index_sequence{}, std::forward(fn), sg, + std::make_index_sequence{}); + } + } + + template + auto wrap_return_impl(Fn&& fn, R (*)(Args...)) + { + return [fn = std::forward(fn)](Args... args) { + return T{std::invoke(fn, std::forward(args)...)}; + }; + } + + // make_python_function_signature: return a null-pointer with the proper type + // for the given function + + template + struct function_signature { + using type = + pybind11::detail::function_signature_t>; + }; + + template + struct function_signature { + using type = R(Args...); + }; + + template + struct function_signature { + using type = R(C*, Args...); + }; + + template + struct function_signature { + using type = R(C*, Args...); + }; + + template + struct function_signature { + using type = R(const C*, Args...); + }; + + template + struct function_signature { + using type = R(const C*, Args...); + }; + + template + using function_signature_t = typename function_signature::type; + + template + class wrap_type_caster { + using variant_type = std::variant; + using variant_caster = pybind11::detail::make_caster; + + public: + PYBIND11_TYPE_CASTER(Type, variant_caster::name); + + bool load(pybind11::handle src, bool convert) + { + variant_caster caster; + + if (!caster.load(src, convert)) { + return false; + } + + value = std::visit( + [](auto const& fn) { + return Type(fn); + }, + static_cast(caster)); + return true; + } + + static pybind11::handle cast(const Type& src, + pybind11::return_value_policy policy, + pybind11::handle parent) + { + return variant_caster::cast(variant_type(std::in_place_index<0>, src), + policy, parent); + } + }; + + } // namespace detail + + // wrap the given function-like object to accept T instead of the specified + // arguments at the specified positions + // + // if the list of positions is empty, replace all arguments that can be converted to + // T + // + template + auto wrap_arguments(Fn&& fn) + { + return detail::wrap_arguments_impl( + std::forward(fn), + (mo2::python::detail::function_signature_t*)nullptr); + } + + // wrap the given function-like object to return T instead of the specified type + // + template + auto wrap_return(Fn&& fn) + { + return detail::wrap_return_impl( + std::forward(fn), + (mo2::python::detail::function_signature_t*)nullptr); + } + +} // namespace mo2::python + +namespace pybind11::detail { + + template + struct type_caster<::mo2::python::smart_variant> + : variant_caster<::mo2::python::smart_variant> { + }; + +} // namespace pybind11::detail + +#endif diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 0703ca7..dbff9f7 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -11,7 +11,7 @@ else() set(UIBASE_PATH "${MO2_INSTALL_PATH}/bin") endif() -add_custom_target(pytest-deps) +add_custom_target(python-tests) add_test(NAME pytest COMMAND ${CMAKE_CURRENT_BINARY_DIR}/pylibs/bin/pytest.exe ${CMAKE_CURRENT_SOURCE_DIR} -s @@ -20,7 +20,7 @@ add_test(NAME pytest set(extra_paths "${UIBASE_PATH}\\;${MO2_INSTALL_PATH}/bin/dlls") set_tests_properties(pytest PROPERTIES - DEPENDS pytest-deps + DEPENDS python-tests WORKING_DIRECTORY ${MO2_INSTALL_PATH}/bin ENVIRONMENT_MODIFICATION "PYTHONPATH=set:${PYLIB_DIR}\\;$;\ @@ -28,10 +28,10 @@ UIBASE_PATH=set:${UIBASE_PATH};\ QT_ROOT=set:${QT_ROOT}" ) -mo2_python_pip_install(pytest-deps +mo2_python_pip_install(python-tests DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pylibs PACKAGES pytest PyQt6==6.3.0) -add_dependencies(pytest-deps mobase) +add_dependencies(python-tests mobase) file(GLOB test_files CONFIGURE_DEPENDS "test_*.cpp") foreach (test_file ${test_files}) @@ -57,5 +57,5 @@ foreach (test_file ${test_files}) target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../mocks) - add_dependencies(pytest-deps ${target}) + add_dependencies(python-tests ${target}) endforeach() diff --git a/tests/python/test_argument_wrapper.cpp b/tests/python/test_argument_wrapper.cpp index 8c2a939..e87f516 100644 --- a/tests/python/test_argument_wrapper.cpp +++ b/tests/python/test_argument_wrapper.cpp @@ -1,34 +1,26 @@ -#include "pybind11_utils/arg_wrapper.h" +#include "pybind11_utils/smart_variant_wrapper.h" #include #include #include +namespace mo2::python::detail { + + template <> + struct smart_variant_converter { + static std::string from(int const& value) { return std::to_string(value); } + }; + + template <> + struct smart_variant_converter { + static int from(std::string const& value) { return std::stoi(value); } + }; + +} // namespace mo2::python::detail + // wrapper that can be constructed from -class Wrapper { - std::string value; - -public: - Wrapper() = default; - - Wrapper(Wrapper const&) = default; - Wrapper(Wrapper&&) = default; - Wrapper& operator=(Wrapper const&) = default; - Wrapper& operator=(Wrapper&&) = default; - - template , int> = 0> - Wrapper(U&& u) : value{std::forward(u)} - { - } - - Wrapper(int u) : value{std::to_string(u)} {} - - operator int() const { return std::stoi(value); } - operator std::string() const { return value; } -}; - -MO2_PYBIND11_WRAP_ARGUMENT_CASTER(Wrapper, "Wrapper", int, std::string); +using Wrapper = mo2::python::smart_variant; template auto wrap(Fn&& fn)