Better implementation for FileWrapper / DirectoryWrapper.

This commit is contained in:
Mikaël Capelle
2022-05-07 13:58:59 +02:00
parent e204c003ae
commit efaea1edfe
12 changed files with 411 additions and 261 deletions
+22
View File
@@ -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
```
-6
View File
@@ -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));
+101 -49
View File
@@ -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 <isavegame.h>
#include <pluginrequirements.h>
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<QString> {
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<std::filesystem::path> {
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<QString, std::filesystem::path, QFileInfo>;
using DirectoryWrapper = smart_variant<QString, std::filesystem::path, QDir>;
// 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 <std::size_t... Is, class Fn>
auto wrap_for_filepath(Fn&& fn)
{
return mo2::python::wrap_arguments<FileWrapper, Is...>(std::forward<Fn>(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 <std::size_t... Is, class Fn>
auto wrap_for_directory(Fn&& fn)
{
@@ -78,12 +85,57 @@ namespace mo2::python {
std::forward<Fn>(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 <class Fn>
auto wrap_return_for_filepath(Fn&& fn)
{
return mo2::python::wrap_return<FileWrapper>(std::forward<Fn>(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 <class Fn>
auto wrap_return_for_directory(Fn&& fn)
{
return mo2::python::wrap_return<DirectoryWrapper>(std::forward<Fn>(fn));
}
// convert a QList to QStringList - QString must be constructible from QString
//
template <class T>
QStringList toQStringList(QList<T> const& list)
{
static_assert(std::is_constructible_v<QString, T>,
"QString must be constructible from T.");
return {list.begin(), list.end()};
}
// convert a QStringList to a QList - T must be constructible from QString
//
template <class T>
QList<T> toQList(QStringList const& list)
{
static_assert(std::is_constructible_v<T, QString>,
"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)
+16 -10
View File
@@ -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<FileWrapper>, UnmanagedMods,
secondaryFiles, modName);
}();
return QList<QString>(result.begin(), result.end());
}());
}
};
@@ -297,10 +296,10 @@ namespace mo2::python {
py::class_<ScriptExtender, PyScriptExtender>(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<FileWrapper> {
auto result = m->secondaryFiles(modName);
return {result.begin(), result.end()};
},
"mod_name"_a);
}
} // namespace mo2::python
+4 -4
View File
@@ -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<ExecutableInfo> executables() const override
{
+8 -3
View File
@@ -54,7 +54,9 @@ namespace mo2::python {
QStringList allFiles() const override
{
PYBIND11_OVERRIDE_PURE(QStringList, ISaveGame, allFiles, );
return toQStringList([&] {
PYBIND11_OVERRIDE_PURE(QList<FileWrapper>, ISaveGame, allFiles, );
}());
}
~PySaveGame() { std::cout << "~PySaveGame()" << std::endl; }
@@ -80,11 +82,14 @@ namespace mo2::python {
py::class_<ISaveGame, PySaveGame, std::shared_ptr<ISaveGame>>(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<FileWrapper> {
const auto result = s->allFiles();
return {result.begin(), result.end()};
});
// ISaveGameInfoWidget - custom holder to keep the Python object alive alongside
// the widget
+5 -5
View File
@@ -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).
@@ -1,155 +0,0 @@
#ifndef PYTHON_PYBIND11_UTILS_FUNCTION_WRAPPER_H
#define PYTHON_PYBIND11_UTILS_FUNCTION_WRAPPER_H
#include <functional>
#include <type_traits>
#include <pybind11/pybind11.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 <class U, class V, std::size_t I, class Is>
struct wrap_arg;
template <class U, class V, std::size_t I, std::size_t... Is>
struct wrap_arg<U, V, I, std::index_sequence<Is...>> {
using type =
std::conditional_t<std::disjunction_v<std::bool_constant<I == Is>...>,
U, V>;
};
// helper type for wrap_arg
template <class U, class A, std::size_t I, class Is>
using wrap_arg_t = typename wrap_arg<U, A, I, Is>::type;
template <class T, std::size_t... Is, std::size_t... AIs, class Fn, class R,
class... Args>
auto wrap_fn_impl(std::index_sequence<Is...>, Fn&& fn, R (*)(Args...),
std::index_sequence<AIs...>)
{
return [fn = std::forward<Fn>(fn)](
wrap_arg_t<T, Args, AIs, std::index_sequence<Is...>>... args) {
return std::invoke(fn, std::forward<decltype(args)>(args)...);
};
}
template <class T, class... Args, std::size_t... Is>
auto make_convertible_index_sequence(std::index_sequence<Is...>)
{
return std::index_sequence<(std::is_convertible_v<T, Args> ? Is : -1)...>{};
}
template <class T, std::size_t... Is, class Fn, class R, class... Args>
auto wrap_fn_impl(Fn&& fn, R (*sg)(Args...))
{
if constexpr (sizeof...(Is) == 0) {
return wrap_fn_impl<T>(make_convertible_index_sequence<T, Args...>(
std::make_index_sequence<sizeof...(Args)>{}),
std::forward<Fn>(fn), sg,
std::make_index_sequence<sizeof...(Args)>{});
}
else {
return wrap_fn_impl<T>(std::index_sequence<Is...>{},
std::forward<Fn>(fn), sg,
std::make_index_sequence<sizeof...(Args)>{});
}
}
// list of wrap_fn_impl for possible function types, the set of overload is from
// pybind11::cpp_function
template <class T, std::size_t... Is, class R, class... Args>
auto wrap_fn_impl(R (*fn)(Args...))
{
return wrap_fn_impl<T, Is...>(fn, fn);
}
template <class T, std::size_t... Is, class R, class C, class... Args>
auto wrap_fn_impl(R (C::*fn)(Args...))
{
return wrap_fn_impl<T, Is...>(fn, (R(*)(C*, Args...)) nullptr);
}
template <class T, std::size_t... Is, class R, class C, class... Args>
auto wrap_fn_impl(R (C::*fn)(Args...) &)
{
return wrap_fn_impl<T, Is...>(fn, (R(*)(C*, Args...)) nullptr);
}
template <class T, std::size_t... Is, class R, class C, class... Args>
auto wrap_fn_impl(R (C::*fn)(Args...) const)
{
return wrap_fn_impl<T, Is...>(fn, (R(*)(const C*, Args...)) nullptr);
}
template <class T, std::size_t... Is, class R, class C, class... Args>
auto wrap_fn_impl(R (C::*fn)(Args...) const&)
{
return wrap_fn_impl<T, Is...>(fn, (R(*)(const C*, Args...)) nullptr);
}
template <class T, std::size_t... Is, class Fn>
auto wrap_fn_impl(Fn&& fn)
{
return wrap_fn_impl<T, Is...>(
std::forward<Fn>(fn),
(pybind11::detail::function_signature_t<Fn>*)nullptr);
}
template <class Type, class... WrappedTypes>
struct load_wrapped_argument_helper;
template <class Type>
struct load_wrapped_argument_helper<Type> {
static bool load(Type&, pybind11::handle, bool) { return false; }
};
template <class Type, class WrappedType, class... WrappedTypes>
struct load_wrapped_argument_helper<Type, WrappedType, WrappedTypes...> {
static bool load(Type& value, pybind11::handle src, bool convert)
{
pybind11::detail::make_caster<WrappedType> caster;
if (caster.load(src, convert)) {
value = Type{static_cast<WrappedType>(caster)};
return true;
}
return load_wrapped_argument_helper<Type, WrappedTypes...>::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 <class T, std::size_t... Is, class Fn>
auto wrap_arguments(Fn&& fn)
{
return detail::wrap_fn_impl<T, Is...>(std::forward<Fn>(fn));
}
} // namespace mo2::python
#define MO2_PYBIND11_WRAP_ARGUMENT_CASTER(Type, Name, ...) \
namespace pybind11::detail { \
template <> \
struct type_caster<Type> { \
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
@@ -0,0 +1,53 @@
#ifndef PYTHON_PYBIND11_UTILS_SMART_VARIANT_H
#define PYTHON_PYBIND11_UTILS_SMART_VARIANT_H
#include <variant>
namespace mo2::python {
namespace detail {
// simple template class that should be specialized to expose proper fromXXX
// methods
//
template <class T>
struct smart_variant_converter {
template <class U>
static T from(U&& u)
{
return T{std::forward<U>(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 <class... Args>
struct smart_variant : std::variant<Args...> {
using std::variant<Args...>::variant;
template <class T, std::enable_if_t<
std::disjunction_v<std::is_same<T, Args>...>, int> = 0>
operator T() const
{
return std::visit(
[](auto const& t) -> T {
if constexpr (std::is_same_v<std::decay_t<decltype(t)>, T>) {
return t;
}
else {
return detail::smart_variant_converter<T>::from(t);
}
},
*this);
}
};
} // namespace mo2::python
#endif
@@ -0,0 +1,181 @@
#ifndef PYTHON_PYBIND11_UTILS_SMART_VARIANT_WRAPPER_H
#define PYTHON_PYBIND11_UTILS_SMART_VARIANT_WRAPPER_H
#include <functional>
#include <type_traits>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#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 <class U, class V, std::size_t I, class Is>
struct wrap_arg;
template <class U, class V, std::size_t I, std::size_t... Is>
struct wrap_arg<U, V, I, std::index_sequence<Is...>> {
using type =
std::conditional_t<std::disjunction_v<std::bool_constant<I == Is>...>,
U, V>;
};
// helper type for wrap_arg
template <class U, class A, std::size_t I, class Is>
using wrap_arg_t = typename wrap_arg<U, A, I, Is>::type;
template <class T, std::size_t... Is, std::size_t... AIs, class Fn, class R,
class... Args>
auto wrap_arguments_impl(std::index_sequence<Is...>, Fn&& fn, R (*)(Args...),
std::index_sequence<AIs...>)
{
return [fn = std::forward<Fn>(fn)](
wrap_arg_t<T, Args, AIs, std::index_sequence<Is...>>... args) {
return std::invoke(fn, std::forward<decltype(args)>(args)...);
};
}
template <class T, class... Args, std::size_t... Is>
auto make_convertible_index_sequence(std::index_sequence<Is...>)
{
return std::index_sequence<(std::is_convertible_v<T, Args> ? Is : -1)...>{};
}
template <class T, std::size_t... Is, class Fn, class R, class... Args>
auto wrap_arguments_impl(Fn&& fn, R (*sg)(Args...))
{
if constexpr (sizeof...(Is) == 0) {
return wrap_arguments_impl<T>(
make_convertible_index_sequence<T, Args...>(
std::make_index_sequence<sizeof...(Args)>{}),
std::forward<Fn>(fn), sg,
std::make_index_sequence<sizeof...(Args)>{});
}
else {
return wrap_arguments_impl<T>(
std::index_sequence<Is...>{}, std::forward<Fn>(fn), sg,
std::make_index_sequence<sizeof...(Args)>{});
}
}
template <class T, class Fn, class R, class... Args>
auto wrap_return_impl(Fn&& fn, R (*)(Args...))
{
return [fn = std::forward<Fn>(fn)](Args... args) {
return T{std::invoke(fn, std::forward<decltype(args)>(args)...)};
};
}
// make_python_function_signature: return a null-pointer with the proper type
// for the given function
template <class Fn>
struct function_signature {
using type =
pybind11::detail::function_signature_t<std::remove_reference_t<Fn>>;
};
template <class R, class... Args>
struct function_signature<R (*)(Args...)> {
using type = R(Args...);
};
template <class R, class C, class... Args>
struct function_signature<R (C::*)(Args...)> {
using type = R(C*, Args...);
};
template <class R, class C, class... Args>
struct function_signature<R (C::*)(Args...)&> {
using type = R(C*, Args...);
};
template <class R, class C, class... Args>
struct function_signature<R (C::*)(Args...) const> {
using type = R(const C*, Args...);
};
template <class R, class C, class... Args>
struct function_signature<R (C::*)(Args...) const&> {
using type = R(const C*, Args...);
};
template <class Fn>
using function_signature_t = typename function_signature<Fn>::type;
template <class Type, class... WrappedTypes>
class wrap_type_caster {
using variant_type = std::variant<WrappedTypes...>;
using variant_caster = pybind11::detail::make_caster<variant_type>;
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<variant_type>(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 <class T, std::size_t... Is, class Fn>
auto wrap_arguments(Fn&& fn)
{
return detail::wrap_arguments_impl<T, Is...>(
std::forward<Fn>(fn),
(mo2::python::detail::function_signature_t<Fn>*)nullptr);
}
// wrap the given function-like object to return T instead of the specified type
//
template <class T, class Fn>
auto wrap_return(Fn&& fn)
{
return detail::wrap_return_impl<T>(
std::forward<Fn>(fn),
(mo2::python::detail::function_signature_t<Fn>*)nullptr);
}
} // namespace mo2::python
namespace pybind11::detail {
template <class... Args>
struct type_caster<::mo2::python::smart_variant<Args...>>
: variant_caster<::mo2::python::smart_variant<Args...>> {
};
} // namespace pybind11::detail
#endif
+5 -5
View File
@@ -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}\\;$<TARGET_FILE_DIR:mobase>;\
@@ -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()
+16 -24
View File
@@ -1,34 +1,26 @@
#include "pybind11_utils/arg_wrapper.h"
#include "pybind11_utils/smart_variant_wrapper.h"
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <string>
namespace mo2::python::detail {
template <>
struct smart_variant_converter<std::string> {
static std::string from(int const& value) { return std::to_string(value); }
};
template <>
struct smart_variant_converter<int> {
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 <class U, std::enable_if_t<std::is_convertible_v<U, std::string>, int> = 0>
Wrapper(U&& u) : value{std::forward<U>(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<int, std::string>;
template <std::size_t... Is, class Fn>
auto wrap(Fn&& fn)