mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
Clean stuff. Bindings for IFileTree.
This commit is contained in:
@@ -96,7 +96,7 @@ namespace pybind11::detail {
|
||||
* instance or return false upon failure. The second argument
|
||||
* indicates whether implicit conversions should be applied.
|
||||
*/
|
||||
bool load(handle src, bool) {
|
||||
inline bool load(handle src, bool) {
|
||||
|
||||
PyObject *objPtr = src.ptr();
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace pybind11::detail {
|
||||
* ``return_value_policy::reference_internal``) and are generally
|
||||
* ignored by implicit casters.
|
||||
*/
|
||||
static handle cast(QString src, return_value_policy /* policy */, handle /* parent */) {
|
||||
inline static handle cast(QString src, return_value_policy /* policy */, handle /* parent */) {
|
||||
static_assert(sizeof(QChar) == 2);
|
||||
return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, src.constData(), src.length());
|
||||
}
|
||||
@@ -143,7 +143,7 @@ namespace pybind11::detail {
|
||||
* instance or return false upon failure. The second argument
|
||||
* indicates whether implicit conversions should be applied.
|
||||
*/
|
||||
bool load(handle src, bool);
|
||||
inline bool load(handle src, bool);
|
||||
|
||||
/**
|
||||
* Conversion part 2 (C++ -> Python): convert an QString instance into
|
||||
@@ -152,7 +152,7 @@ namespace pybind11::detail {
|
||||
* ``return_value_policy::reference_internal``) and are generally
|
||||
* ignored by implicit casters.
|
||||
*/
|
||||
static handle cast(QVariant var, return_value_policy policy, handle parent);
|
||||
inline static handle cast(QVariant var, return_value_policy policy, handle parent);
|
||||
};
|
||||
|
||||
// QList
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "converters_qt_sip.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <utility.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace mo2::details {
|
||||
|
||||
const sipAPIDef* sipAPI()
|
||||
{
|
||||
QString exception;
|
||||
static const sipAPIDef* sipApi = nullptr;
|
||||
if (sipApi == nullptr) {
|
||||
#if defined(SIP_USE_PYCAPSULE)
|
||||
PyImport_ImportModule("PyQt5.sip");
|
||||
|
||||
auto errorObj = PyErr_Occurred();
|
||||
if (errorObj != NULL) {
|
||||
PyObject* type, * value, * traceback;
|
||||
PyErr_Fetch(&type, &value, &traceback);
|
||||
PyErr_NormalizeException(&type, &value, &traceback);
|
||||
if (traceback != NULL) {
|
||||
py::handle h_type(type);
|
||||
py::handle h_val(value);
|
||||
py::handle h_tb(traceback);
|
||||
py::module_ tb = py::module_::import("traceback");
|
||||
py::object fmt_exp(tb.attr("format_exception"));
|
||||
py::object exp_list(fmt_exp(h_type, h_val, h_tb));
|
||||
py::object exp_str(py::str("\n").attr("join")(exp_list));
|
||||
exception = QString::fromStdString(exp_str.cast<std::string>());
|
||||
}
|
||||
PyErr_Restore(type, value, traceback);
|
||||
throw MOBase::Exception(QString("Failed to load PyQt5: %1").arg(exception));
|
||||
}
|
||||
|
||||
sipApi = (const sipAPIDef*)PyCapsule_Import("PyQt5.sip._C_API", 0);
|
||||
if (sipApi == NULL) {
|
||||
auto errorObj = PyErr_Occurred();
|
||||
if (errorObj != NULL) {
|
||||
PyObject* type, * value, * traceback;
|
||||
PyErr_Fetch(&type, &value, &traceback);
|
||||
PyErr_NormalizeException(&type, &value, &traceback);
|
||||
if (traceback != NULL) {
|
||||
py::handle h_type(type);
|
||||
py::handle h_val(value);
|
||||
py::handle h_tb(traceback);
|
||||
py::module_ tb = py::module_::import("traceback");
|
||||
py::object fmt_exp(tb.attr("format_exception"));
|
||||
py::object exp_list(fmt_exp(h_type, h_val, h_tb));
|
||||
py::object exp_str(py::str("\n").attr("join")(exp_list));
|
||||
exception = QString::fromStdString(exp_str.cast<std::string>());
|
||||
}
|
||||
PyErr_Restore(type, value, traceback);
|
||||
}
|
||||
throw MOBase::Exception(QString("Failed to load SIP API: %1").arg(exception));
|
||||
}
|
||||
#else
|
||||
PyObject* sip_module;
|
||||
PyObject* sip_module_dict;
|
||||
PyObject* c_api;
|
||||
|
||||
/* Import the SIP module. */
|
||||
sip_module = PyImport_ImportModule("PyQt5.sip");
|
||||
|
||||
if (sip_module == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Get the module's dictionary. */
|
||||
sip_module_dict = PyModule_GetDict(sip_module);
|
||||
|
||||
/* Get the "_C_API" attribute. */
|
||||
c_api = PyDict_GetItemString(sip_module_dict, "_C_API");
|
||||
|
||||
if (c_api == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Sanity check that it is the right type. */
|
||||
if (!PyCObject_Check(c_api))
|
||||
return NULL;
|
||||
|
||||
/* Get the actual pointer from the object. */
|
||||
sipApi = (const sipAPIDef*)PyCObject_AsVoidPtr(c_api);
|
||||
#endif
|
||||
}
|
||||
|
||||
return sipApi;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,30 @@
|
||||
#ifndef PYTHON_CONVERTERS_QT_SIP_HPP
|
||||
#define PYTHON_CONVERTERS_QT_SIP_HPP
|
||||
|
||||
#include "sipapiaccess.h"
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
#include <QMainWindow>
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
#include <QSize>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
#include <sip.h>
|
||||
|
||||
namespace mo2::details {
|
||||
|
||||
/**
|
||||
* @brief Retrieve the SIP api.
|
||||
*
|
||||
* @return const sipAPIDef*
|
||||
*/
|
||||
const sipAPIDef* sipAPI();
|
||||
|
||||
template <typename T, class = void> struct MetaData;
|
||||
|
||||
template <typename T>
|
||||
@@ -43,10 +63,10 @@ namespace mo2::details {
|
||||
// sipAPI()->api_transfer_to(objPtr, Py_None);
|
||||
//
|
||||
void* data = nullptr;
|
||||
if (PyObject_TypeCheck(src.ptr(), sipAPIAccess::sipAPI()->api_simplewrapper_type)) {
|
||||
if (PyObject_TypeCheck(src.ptr(), mo2::details::sipAPI()->api_simplewrapper_type)) {
|
||||
data = reinterpret_cast<sipSimpleWrapper*>(src.ptr())->data;
|
||||
}
|
||||
else if (PyObject_TypeCheck(src.ptr(), sipAPIAccess::sipAPI()->api_wrapper_type)) {
|
||||
else if (PyObject_TypeCheck(src.ptr(), mo2::details::sipAPI()->api_wrapper_type)) {
|
||||
data = reinterpret_cast<sipWrapper*>(src.ptr())->super.data;
|
||||
}
|
||||
|
||||
@@ -68,7 +88,7 @@ namespace mo2::details {
|
||||
QtType src, pybind11::return_value_policy /* policy */, pybind11::handle /* parent */) {
|
||||
|
||||
const sipTypeDef* type =
|
||||
sipAPIAccess::sipAPI()->api_find_type(MetaData<QtType>::name);
|
||||
mo2::details::sipAPI()->api_find_type(MetaData<QtType>::name);
|
||||
if (type == nullptr) {
|
||||
return Py_None;
|
||||
}
|
||||
@@ -87,10 +107,10 @@ namespace mo2::details {
|
||||
}
|
||||
|
||||
if constexpr (std::is_pointer_v<QtType>) {
|
||||
sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(sipData, type, 0);
|
||||
sipObj = mo2::details::sipAPI()->api_convert_from_type(sipData, type, 0);
|
||||
}
|
||||
else {
|
||||
sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(sipData, type, 0);
|
||||
sipObj = mo2::details::sipAPI()->api_convert_from_type(sipData, type, 0);
|
||||
}
|
||||
|
||||
if (sipObj == nullptr) {
|
||||
@@ -99,7 +119,7 @@ namespace mo2::details {
|
||||
|
||||
if constexpr (!std::is_pointer_v<QtType> && std::is_copy_constructible_v<QtType>) {
|
||||
// ensure Python deletes the C++ component
|
||||
sipAPIAccess::sipAPI()->api_transfer_back(sipObj);
|
||||
mo2::details::sipAPI()->api_transfer_back(sipObj);
|
||||
}
|
||||
|
||||
return sipObj;
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "pyfiletree.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include <pybind11/functional.h>
|
||||
#include <pybind11/operators.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <ifiletree.h>
|
||||
#include <log.h>
|
||||
|
||||
#include "converters_qt.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace MOBase;
|
||||
|
||||
namespace mo2::details {
|
||||
|
||||
// filetree implementation for testing purpose
|
||||
//
|
||||
class PyFileTree : public IFileTree {
|
||||
public:
|
||||
|
||||
using callback_t = std::function<bool(QString, bool)>;
|
||||
|
||||
PyFileTree(std::shared_ptr<const IFileTree> parent, QString name, callback_t callback) :
|
||||
FileTreeEntry(parent, name), IFileTree(), m_Callback(callback){ }
|
||||
|
||||
std::shared_ptr<FileTreeEntry> addFile(QString name, bool) override {
|
||||
if (m_Callback && !m_Callback(name, false)) {
|
||||
throw UnsupportedOperationException("File rejected by callback.");
|
||||
}
|
||||
return IFileTree::addFile(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<IFileTree> addDirectory(QString name) override {
|
||||
if (m_Callback && !m_Callback(name, true)) {
|
||||
throw UnsupportedOperationException("Directory rejected by callback.");
|
||||
}
|
||||
return IFileTree::addDirectory(name);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
std::shared_ptr<IFileTree> makeDirectory(std::shared_ptr<const IFileTree> parent, QString name) const override {
|
||||
return std::make_shared<PyFileTree>(parent, name, m_Callback);
|
||||
}
|
||||
|
||||
bool doPopulate(std::shared_ptr<const IFileTree> parent, std::vector<std::shared_ptr<FileTreeEntry>>& entries) const override { return true; }
|
||||
std::shared_ptr<IFileTree> doClone() const override { return std::make_shared<PyFileTree>(nullptr, name(), m_Callback); }
|
||||
|
||||
private:
|
||||
callback_t m_Callback;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
namespace pybind11 {
|
||||
const void *polymorphic_type_hook<FileTreeEntry>::get(const FileTreeEntry *src, const std::type_info *&type) {
|
||||
if (auto p = dynamic_cast<const IFileTree*>(src)) {
|
||||
type = &typeid(IFileTree);
|
||||
return p;
|
||||
}
|
||||
return src;
|
||||
}
|
||||
} // namespace pybind11
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
void add_ifiletree_bindings(pybind11::module_ &m) {
|
||||
|
||||
// FileTreeEntry Scope:
|
||||
auto fileTreeEntryClass = py::class_<FileTreeEntry, std::shared_ptr<FileTreeEntry>>(m, "FileTreeEntry");
|
||||
|
||||
// we do not use the enum directly, we will mostly bind the FileTypes (with an S)
|
||||
py::enum_<FileTreeEntry::FileType>(fileTreeEntryClass, "FileType", py::arithmetic{});
|
||||
py::class_<FileTreeEntry::FileTypes>(fileTreeEntryClass, "FileTypes")
|
||||
.def_property_readonly_static("FILE", [](py::object) { return FileTreeEntry::FILE; })
|
||||
.def_property_readonly_static("DIRECTORY", [](py::object) { return FileTreeEntry::DIRECTORY; })
|
||||
.def_property_readonly_static("FILE_OR_DIRECTORY", [](py::object) { return FileTreeEntry::FILE_OR_DIRECTORY; })
|
||||
|
||||
.def(py::self == py::self)
|
||||
.def(py::self != py::self)
|
||||
.def(py::self | py::self)
|
||||
;
|
||||
py::implicitly_convertible<FileTreeEntry::FileType, FileTreeEntry::FileTypes>();
|
||||
|
||||
fileTreeEntryClass
|
||||
.def_property_readonly_static("FILE", [](py::object) { return FileTreeEntry::FILE; })
|
||||
.def_property_readonly_static("DIRECTORY", [](py::object) { return FileTreeEntry::DIRECTORY; })
|
||||
.def_property_readonly_static("FILE_OR_DIRECTORY", [](py::object) { return FileTreeEntry::FILE_OR_DIRECTORY; });
|
||||
|
||||
fileTreeEntryClass
|
||||
|
||||
.def("isFile", &FileTreeEntry::isFile)
|
||||
.def("isDir", &FileTreeEntry::isDir)
|
||||
// Forcing the conversion to FileTypeS to avoid having to expose FileType in python:
|
||||
.def("fileType", &FileTreeEntry::fileType)
|
||||
// This should probably not be exposed in python since we provide automatic downcast:
|
||||
// .def("getTree", static_cast<std::shared_ptr<IFileTree>(FileTreeEntry::*)()>(&FileTreeEntry::astree))
|
||||
.def("name", &FileTreeEntry::name)
|
||||
.def("suffix", &FileTreeEntry::suffix)
|
||||
.def("hasSuffix", [](FileTreeEntry* entry, QStringList suffixes) { return entry->hasSuffix(suffixes); }, py::arg("suffixes"))
|
||||
.def("hasSuffix", [](FileTreeEntry* entry, QString suffix) { return entry->hasSuffix(suffix); }, py::arg("suffix"))
|
||||
.def("parent", py::overload_cast<>(&FileTreeEntry::parent), "[optional]")
|
||||
.def("path", &FileTreeEntry::path, py::arg("sep") = "\\")
|
||||
.def("pathFrom", &FileTreeEntry::pathFrom, py::arg("tree"), py::arg("sep") = "\\")
|
||||
|
||||
// Mutable operation:
|
||||
.def("detach", &FileTreeEntry::detach)
|
||||
.def("moveTo", &FileTreeEntry::moveTo, py::arg("tree"))
|
||||
|
||||
// Special methods:
|
||||
.def("__eq__", [](const FileTreeEntry* entry, QString other) {
|
||||
return entry->compare(other) == 0;
|
||||
})
|
||||
.def("__eq__", [](const FileTreeEntry* entry, std::shared_ptr<FileTreeEntry> other) {
|
||||
return entry == other.get();
|
||||
})
|
||||
|
||||
// Special methods for debug:
|
||||
.def("__repr__", [](const FileTreeEntry* entry) { return "FileTreeEntry(\"" + entry->name() + "\")"; })
|
||||
;
|
||||
|
||||
// IFileTree scope:
|
||||
auto iFileTreeClass = py::class_<IFileTree, FileTreeEntry, std::shared_ptr<IFileTree>>(
|
||||
m, "IFileTree", py::multiple_inheritance());
|
||||
|
||||
py::enum_<IFileTree::InsertPolicy>(iFileTreeClass, "InsertPolicy")
|
||||
.value("FAIL_IF_EXISTS", IFileTree::InsertPolicy::FAIL_IF_EXISTS)
|
||||
.value("REPLACE", IFileTree::InsertPolicy::REPLACE)
|
||||
.value("MERGE", IFileTree::InsertPolicy::MERGE)
|
||||
.export_values()
|
||||
;
|
||||
|
||||
py::enum_<IFileTree::WalkReturn>(iFileTreeClass, "WalkReturn")
|
||||
.value("CONTINUE", IFileTree::WalkReturn::CONTINUE)
|
||||
.value("STOP", IFileTree::WalkReturn::STOP)
|
||||
.value("SKIP", IFileTree::WalkReturn::SKIP)
|
||||
.export_values()
|
||||
;
|
||||
|
||||
// Non-mutable operations:
|
||||
iFileTreeClass.def("exists",
|
||||
py::overload_cast<QString, IFileTree::FileTypes>(&IFileTree::exists, py::const_),
|
||||
py::arg("path"), py::arg("type") = IFileTree::FILE_OR_DIRECTORY);
|
||||
iFileTreeClass.def("find", py::overload_cast<QString, IFileTree::FileTypes>(&IFileTree::find),
|
||||
py::arg("path"), py::arg("type") = IFileTree::FILE_OR_DIRECTORY, "[optional]");
|
||||
iFileTreeClass.def("pathTo",
|
||||
&IFileTree::pathTo, py::arg("entry"), py::arg("sep") = "\\");
|
||||
|
||||
// Note: walk() would probably be better as a generator in python, but it is likely impossible to construct
|
||||
// from the C++ walk() method.
|
||||
iFileTreeClass.def("walk",
|
||||
&IFileTree::walk, py::arg("callback"), py::arg("sep") = "\\");
|
||||
|
||||
// Kind-of-static operations:
|
||||
iFileTreeClass.def("createOrphanTree",
|
||||
&IFileTree::createOrphanTree, py::arg("name") = "");
|
||||
|
||||
// addFile() and addDirectory throws exception instead of returning null pointer in order
|
||||
// to have better traces.
|
||||
iFileTreeClass.def("addFile", [](IFileTree* w, QString path, bool replaceIfExists) {
|
||||
auto result = w->addFile(path, replaceIfExists);
|
||||
if (result == nullptr) {
|
||||
throw std::logic_error("addFile failed");
|
||||
}
|
||||
return result;
|
||||
}, py::arg("path"), py::arg("replace_if_exists") = false);
|
||||
iFileTreeClass.def("addDirectory", [](IFileTree* w, QString path) {
|
||||
auto result = w->addDirectory(path);
|
||||
if (result == nullptr) {
|
||||
throw std::logic_error("addDirectory failed");
|
||||
}
|
||||
MOBase::log::warn("{}", result->fileType());
|
||||
return result;
|
||||
}, py::arg("path"));
|
||||
|
||||
// Merge needs custom return types depending if the user wants overrides or not. A failure is translated
|
||||
// into an exception for easier tracing and handling.
|
||||
iFileTreeClass.def("merge",
|
||||
[](IFileTree* p, std::shared_ptr<IFileTree> other, bool returnOverwrites)
|
||||
-> std::variant<IFileTree::OverwritesType, std::size_t> {
|
||||
IFileTree::OverwritesType overwrites;
|
||||
auto result = p->merge(other, returnOverwrites ? &overwrites : nullptr);
|
||||
if (result == IFileTree::MERGE_FAILED) {
|
||||
throw std::logic_error("merge failed");
|
||||
}
|
||||
if (returnOverwrites) {
|
||||
return { overwrites };
|
||||
}
|
||||
return { result };
|
||||
}, py::arg("other"), py::arg("overwrites") = false);
|
||||
|
||||
// Insert and erase returns an iterator, which makes no sense in python, so we convert it to bool. Erase is also
|
||||
// renamed "remove" since "erase" is very C++.
|
||||
iFileTreeClass.def("insert",
|
||||
[](IFileTree* p, std::shared_ptr<FileTreeEntry> entry, IFileTree::InsertPolicy insertPolicy) {
|
||||
return p->insert(entry, insertPolicy) == p->end();
|
||||
}, py::arg("entry"), py::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS);
|
||||
|
||||
iFileTreeClass.def("remove", [](IFileTree* p, QString name) {
|
||||
return p->erase(name).first != p->end(); }, py::arg("name"));
|
||||
iFileTreeClass.def("remove", [](IFileTree* p, std::shared_ptr<FileTreeEntry> entry) {
|
||||
return p->erase(entry) != p->end(); }, py::arg("entry"));
|
||||
|
||||
iFileTreeClass.def("move",
|
||||
&IFileTree::move, py::arg("entry"), py::arg("path"),
|
||||
py::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS);
|
||||
iFileTreeClass.def("copy",
|
||||
[](IFileTree* w, std::shared_ptr<FileTreeEntry> entry, QString path, IFileTree::InsertPolicy insertPolicy) {
|
||||
auto result = w->copy(entry, path, insertPolicy);
|
||||
if (result == nullptr) {
|
||||
throw std::logic_error("copy failed");
|
||||
}
|
||||
return result;
|
||||
}, py::arg("entry"), py::arg("path") = "",
|
||||
py::arg("insert_policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS);
|
||||
|
||||
iFileTreeClass.def("clear", &IFileTree::clear);
|
||||
iFileTreeClass.def("removeAll", &IFileTree::removeAll, py::arg("names"));
|
||||
iFileTreeClass.def("removeIf", &IFileTree::removeIf, py::arg("filter"));
|
||||
|
||||
// Special methods:
|
||||
iFileTreeClass.def("__getitem__", py::overload_cast<std::size_t>(&IFileTree::at));
|
||||
// , py::return_value_policy<utils::downcast_return<FileTreeEntry, IFileTree>>()
|
||||
|
||||
iFileTreeClass.def("__iter__", [](IFileTree *tree) {
|
||||
return py::make_iterator(*tree); });
|
||||
iFileTreeClass.def("__len__", &IFileTree::size);
|
||||
iFileTreeClass.def("__bool__", +[](const IFileTree* tree) {
|
||||
return !tree->empty(); });
|
||||
iFileTreeClass.def("__repr__", +[](const IFileTree* entry) {
|
||||
return "IFileTree(\"" + entry->name() + "\")"; });
|
||||
}
|
||||
|
||||
void add_make_tree_function(pybind11::module_ &m) {
|
||||
m.def("makeTree", [](mo2::details::PyFileTree::callback_t callback) -> std::shared_ptr<IFileTree> {
|
||||
return std::make_shared<mo2::details::PyFileTree>(nullptr, "", callback);
|
||||
}, py::arg("callback") = mo2::details::PyFileTree::callback_t{});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma optimize("", on)
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef MO2_PYTHON_FILETREE_H
|
||||
#define MO2_PYTHON_FILETREE_H
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <ifiletree.h>
|
||||
|
||||
namespace pybind11
|
||||
{
|
||||
template <>
|
||||
struct polymorphic_type_hook<MOBase::FileTreeEntry>
|
||||
{
|
||||
static const void *get(const MOBase::FileTreeEntry *src, const std::type_info *&type);
|
||||
};
|
||||
} // namespace pybind11
|
||||
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
/**
|
||||
* @brief Add bindings for FileTreeEntry andIFileTree to the given module.
|
||||
*
|
||||
* @param mobase Module to add the bindings to.
|
||||
*/
|
||||
void add_ifiletree_bindings(pybind11::module_ &m);
|
||||
|
||||
/**
|
||||
* @brief Add makeTree() function to the given module, useful for debugging.
|
||||
*
|
||||
* @param mobase Module to add the function to.
|
||||
*/
|
||||
void add_make_tree_function(pybind11::module_ &m);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,74 +0,0 @@
|
||||
#include "pylogger.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
// Small structure to hold the levels - There are copy paste from
|
||||
// my Python version and I assume these will not change soon:
|
||||
struct PyLogLevel {
|
||||
static constexpr int CRITICAL = 50;
|
||||
static constexpr int ERROR = 40;
|
||||
static constexpr int WARNING = 30;
|
||||
static constexpr int INFO = 20;
|
||||
static constexpr int DEBUG = 10;
|
||||
};
|
||||
|
||||
// This is the function we are going to use as our Handler .emit
|
||||
// method.
|
||||
void emit_function(py::object self, py::object record) {
|
||||
|
||||
// There are other parameters that could be used, but this is minimal for
|
||||
// now (filename, line number, etc.).
|
||||
const int level = record.attr("levelno").cast<int>();
|
||||
const std::wstring msg = py::str(record.attr("msg")).cast<std::wstring>();
|
||||
|
||||
switch (level) {
|
||||
case PyLogLevel::CRITICAL:
|
||||
case PyLogLevel::ERROR:
|
||||
MOBase::log::error("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::WARNING:
|
||||
MOBase::log::warn("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::INFO:
|
||||
MOBase::log::info("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::DEBUG:
|
||||
default: // There is a "NOTSET" level in theory:
|
||||
MOBase::log::debug("{}", msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
void configure_python_logging(py::module_ mobase)
|
||||
{
|
||||
// most of this is dealing with actual Python objects since it is not possible
|
||||
// to derive from logging.Handler in C++ using Boost.Python, and since a lot of
|
||||
// this would require extra register only for this.
|
||||
|
||||
// see also https://github.com/pybind/pybind11/issues/1193#issuecomment-429451094
|
||||
|
||||
// retrieve the logging module and the Handler class.
|
||||
auto logging = py::module_::import("logging");
|
||||
auto Handler = logging.attr("Handler");
|
||||
|
||||
// this is ugly but that's how it's done in C Python
|
||||
auto type = py::reinterpret_borrow<py::object>((PyObject*)&PyType_Type);
|
||||
|
||||
// Create the "MO2Handler" python class:
|
||||
auto methods = py::dict();
|
||||
methods["emit"] = py::cpp_function(emit_function);
|
||||
auto MO2Handler = type("LogHandler", py::make_tuple(Handler), methods);
|
||||
|
||||
// Create the default logger:
|
||||
auto handler = MO2Handler();
|
||||
handler.attr("setLevel")(PyLogLevel::DEBUG);
|
||||
auto logger = logging.attr("getLogger")(py::object(mobase.attr("__name__")));
|
||||
logger.attr("setLevel")(PyLogLevel::DEBUG);
|
||||
logger.attr("addHandler")(handler);
|
||||
|
||||
// Set mobase attributes:
|
||||
mobase.attr("LogHandler") = MO2Handler;
|
||||
mobase.attr("logger") = logger;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef MO2_PYTHON_LOGGER_H
|
||||
#define MO2_PYTHON_LOGGER_H
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
/**
|
||||
* @brief Configure logging for MO2 python plugin.
|
||||
*
|
||||
* @param mobase The mobase module.
|
||||
*/
|
||||
void configure_python_logging(pybind11::module_ mobase);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,24 +3,99 @@
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace utils {
|
||||
namespace py = pybind11;
|
||||
|
||||
void show_deprecation_warning(std::string_view name, std::string_view message, bool show_once) {
|
||||
namespace mo2::python {
|
||||
|
||||
// Small structure to hold the levels - There are copy paste from
|
||||
// my Python version and I assume these will not change soon:
|
||||
struct PyLogLevel {
|
||||
static constexpr int CRITICAL = 50;
|
||||
static constexpr int ERROR = 40;
|
||||
static constexpr int WARNING = 30;
|
||||
static constexpr int INFO = 20;
|
||||
static constexpr int DEBUG = 10;
|
||||
};
|
||||
|
||||
// This is the function we are going to use as our Handler .emit
|
||||
// method.
|
||||
void emit_function(py::object self, py::object record)
|
||||
{
|
||||
|
||||
// There are other parameters that could be used, but this is minimal for
|
||||
// now (filename, line number, etc.).
|
||||
const int level = record.attr("levelno").cast<int>();
|
||||
const std::wstring msg = py::str(record.attr("msg")).cast<std::wstring>();
|
||||
|
||||
switch (level) {
|
||||
case PyLogLevel::CRITICAL:
|
||||
case PyLogLevel::ERROR:
|
||||
MOBase::log::error("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::WARNING:
|
||||
MOBase::log::warn("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::INFO:
|
||||
MOBase::log::info("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::DEBUG:
|
||||
default: // There is a "NOTSET" level in theory:
|
||||
MOBase::log::debug("{}", msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
void configure_python_logging(py::module_ mobase)
|
||||
{
|
||||
// most of this is dealing with actual Python objects since it is not possible
|
||||
// to derive from logging.Handler in C++ using Boost.Python, and since a lot of
|
||||
// this would require extra register only for this.
|
||||
|
||||
// see also https://github.com/pybind/pybind11/issues/1193#issuecomment-429451094
|
||||
|
||||
// retrieve the logging module and the Handler class.
|
||||
auto logging = py::module_::import("logging");
|
||||
auto Handler = logging.attr("Handler");
|
||||
|
||||
// this is ugly but that's how it's done in C Python
|
||||
auto type = py::reinterpret_borrow<py::object>((PyObject*)&PyType_Type);
|
||||
|
||||
// Create the "MO2Handler" python class:
|
||||
auto methods = py::dict();
|
||||
methods["emit"] = py::cpp_function(emit_function);
|
||||
auto MO2Handler = type("LogHandler", py::make_tuple(Handler), methods);
|
||||
|
||||
// Create the default logger:
|
||||
auto handler = MO2Handler();
|
||||
handler.attr("setLevel")(PyLogLevel::DEBUG);
|
||||
auto logger = logging.attr("getLogger")(py::object(mobase.attr("__name__")));
|
||||
logger.attr("setLevel")(PyLogLevel::DEBUG);
|
||||
logger.attr("addHandler")(handler);
|
||||
|
||||
// Set mobase attributes:
|
||||
mobase.attr("LogHandler") = MO2Handler;
|
||||
mobase.attr("logger") = logger;
|
||||
}
|
||||
|
||||
void show_deprecation_warning(std::string_view name, std::string_view message, bool show_once)
|
||||
{
|
||||
|
||||
// Contains the list of filename / line number for which a deprecation warning has already been shown.
|
||||
static std::set<std::pair<std::string, int>> DeprecatedLines;
|
||||
|
||||
|
||||
// Find the caller:
|
||||
auto inspect = bpy::import("inspect");
|
||||
auto inspect = py::module_::import("inspect");
|
||||
auto current_frame = inspect.attr("currentframe")();
|
||||
auto callable_frame = inspect.attr("getouterframes")(current_frame, 2);
|
||||
auto filename = bpy::extract<std::string>(callable_frame[-1].attr("filename"))();
|
||||
auto function = bpy::extract<std::string>(callable_frame[-1].attr("function"))();
|
||||
auto lineno = bpy::extract<int>(callable_frame[-1].attr("lineno"));
|
||||
py::sequence callable_frame = inspect.attr("getouterframes")(current_frame, 2);
|
||||
auto filename = callable_frame[-1].attr("filename").cast<std::string>();
|
||||
auto function = callable_frame[-1].attr("function").cast<std::string>();
|
||||
auto lineno = callable_frame[-1].attr("lineno").cast<int>();
|
||||
|
||||
// Only show once if requested:
|
||||
if (show_once && DeprecatedLines.contains({ filename, lineno })) {
|
||||
@@ -43,4 +118,4 @@ namespace utils {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,257 +1,18 @@
|
||||
#ifndef PYTHONRUNNER_UTILS_H
|
||||
#define PYTHONRUNNER_UTILS_H
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <string_view>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
namespace bpy = boost::python;
|
||||
|
||||
namespace details {
|
||||
|
||||
template <class It, class = void>
|
||||
struct is_stdmap_iterator : std::false_type {};
|
||||
|
||||
template <class It>
|
||||
struct is_stdmap_iterator<It, std::void_t<decltype(std::declval<It>()->first)>> : std::true_type {};
|
||||
|
||||
// Note: QMap and standard maps do not have the same type of iterators:
|
||||
template <class It, std::enable_if_t<is_stdmap_iterator<It>{}, int> = 0>
|
||||
inline auto set_dict_entry(bpy::dict& result, It const& it) {
|
||||
result[bpy::object{ it->first }] = bpy::object{ it->second };
|
||||
}
|
||||
|
||||
template <class It, std::enable_if_t<!is_stdmap_iterator<It>{}, int > = 0>
|
||||
inline auto set_dict_entry(bpy::dict& result, It const& it) {
|
||||
result[bpy::object{ it.key() }] = bpy::object{ it.value() };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <class Map>
|
||||
struct map_to_python {
|
||||
static PyObject* convert(const Map& map) {
|
||||
bpy::dict result;
|
||||
for (auto it = map.begin(); it != map.end(); ++it) {
|
||||
details::set_dict_entry(result, it);
|
||||
}
|
||||
return bpy::incref(result.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
template <class Map>
|
||||
struct map_from_python {
|
||||
|
||||
using key_type = typename Map::key_type;
|
||||
using value_type = typename Map::mapped_type;
|
||||
|
||||
static void* convertible(PyObject* objPtr) {
|
||||
return PyDict_Check(objPtr) ? objPtr : nullptr;
|
||||
}
|
||||
|
||||
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
|
||||
void* storage = ((bpy::converter::rvalue_from_python_storage<Map>*)data)->storage.bytes;
|
||||
Map* result = new (storage) Map();
|
||||
bpy::dict source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
bpy::list keys = source.keys();
|
||||
int len = bpy::len(keys);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
bpy::object pyKey = keys[i];
|
||||
(*result)[bpy::extract<key_type>(pyKey)] = bpy::extract<value_type>(source[pyKey]);
|
||||
}
|
||||
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
struct container_to_python_list {
|
||||
static PyObject* convert(const Container& container) {
|
||||
bpy::list pyList;
|
||||
|
||||
try {
|
||||
for (auto& item : container) {
|
||||
pyList.append(item);
|
||||
}
|
||||
}
|
||||
catch (const bpy::error_already_set&) {
|
||||
throw pyexcept::PythonError();
|
||||
}
|
||||
|
||||
return bpy::incref(pyList.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class Container>
|
||||
struct container_from_python_list {
|
||||
|
||||
using value_type = typename Container::value_type;
|
||||
|
||||
static void* convertible(PyObject* objPtr) {
|
||||
// Check that the object can be iterated or is a sequence. There is no "clean"
|
||||
// way checking that an object is iterable apparently (PyIter_Check checks that
|
||||
// an object is an iterator, which is very different).
|
||||
if (objPtr->ob_type->tp_iter != 0 || PySequence_Check(objPtr)) return objPtr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
|
||||
void* storage = ((bpy::converter::rvalue_from_python_storage<Container>*)data)->storage.bytes;
|
||||
Container* result = new (storage) Container();
|
||||
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
bpy::stl_input_iterator<value_type> begin(source), end;
|
||||
std::copy(begin, end, std::back_inserter(*result));
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
struct set_to_python {
|
||||
static PyObject* convert(const Container& container) {
|
||||
bpy::list pyList;
|
||||
|
||||
try {
|
||||
for (auto& item : container)
|
||||
pyList.append(item);
|
||||
}
|
||||
catch (const bpy::error_already_set&) {
|
||||
throw pyexcept::PythonError();
|
||||
}
|
||||
|
||||
return bpy::incref(pyList.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class Container>
|
||||
struct set_from_python {
|
||||
|
||||
using value_type = typename Container::value_type;
|
||||
|
||||
static void* convertible(PyObject* objPtr) {
|
||||
// See container_from_python.
|
||||
if (objPtr->ob_type->tp_iter != 0 && PySequence_Check(objPtr)) return objPtr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
|
||||
void* storage = ((bpy::converter::rvalue_from_python_storage<Container>*)data)->storage.bytes;
|
||||
Container* result = new (storage) Container();
|
||||
bpy::list source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
bpy::stl_input_iterator<value_type> begin(source), end;
|
||||
std::copy(begin, end, std::inserter(*result, result->begin()));
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class Optional>
|
||||
struct optional_to_python {
|
||||
static PyObject* convert(const Optional& optional) {
|
||||
if (optional) {
|
||||
return bpy::incref(bpy::object(*optional).ptr());
|
||||
}
|
||||
else {
|
||||
return bpy::incref(Py_None);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class Optional>
|
||||
struct optional_from_python {
|
||||
|
||||
using value_type = typename Optional::value_type;
|
||||
|
||||
static void* convertible(PyObject* objPtr) {
|
||||
|
||||
if (objPtr == Py_None) {
|
||||
return objPtr;
|
||||
}
|
||||
|
||||
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
return bpy::extract<value_type>(source).check() ? objPtr : nullptr;
|
||||
}
|
||||
|
||||
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
|
||||
void* storage = ((bpy::converter::rvalue_from_python_storage<Optional>*)data)->storage.bytes;
|
||||
Optional* result = new (storage) Optional();
|
||||
|
||||
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
if (!source.is_none()) {
|
||||
*result = bpy::extract<value_type>(source)();
|
||||
}
|
||||
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
/**
|
||||
* @brief Register from and to python converters for (at least) map, unordered_map and QMap.
|
||||
* @brief Configure logging for MO2 python plugin.
|
||||
*
|
||||
* Any standard compliant associative container with key/value should work here.
|
||||
*
|
||||
* @tparam Map The container type to register.
|
||||
* @param mobase The mobase module.
|
||||
*/
|
||||
template <class Map>
|
||||
void register_associative_container() {
|
||||
bpy::to_python_converter<Map, map_to_python<Map>>();
|
||||
bpy::converter::registry::push_back(
|
||||
&map_from_python<Map>::convertible
|
||||
, &map_from_python<Map>::construct
|
||||
, bpy::type_id<Map>());
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Register from and to python converters for (at least) set, unordered_set and QSet.
|
||||
*
|
||||
* Any standard compliant associative container with key should work here.
|
||||
*
|
||||
* @tparam Map The container type to register.
|
||||
*/
|
||||
template <class Container>
|
||||
void register_set_container() {
|
||||
bpy::to_python_converter<Container, set_to_python<Container>>();
|
||||
bpy::converter::registry::push_back(
|
||||
&set_from_python<Container>::convertible
|
||||
, &set_from_python<Container>::construct
|
||||
, bpy::type_id<Container>());
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Register from and to python converters for sequence container.
|
||||
*
|
||||
* Any standard compliant container should work here.
|
||||
*
|
||||
* @tparam Map The container type to register.
|
||||
*/
|
||||
template <class Container>
|
||||
void register_sequence_container() {
|
||||
bpy::to_python_converter<Container, container_to_python_list<Container>>();
|
||||
bpy::converter::registry::push_back(
|
||||
&container_from_python_list<Container>::convertible
|
||||
, &container_from_python_list<Container>::construct
|
||||
, bpy::type_id<Container>());
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Register from and to python converters for optional.
|
||||
*
|
||||
* @tparam T The optional type (std::optional<X> or boost::optional<X>).
|
||||
*/
|
||||
template <class Optional>
|
||||
void register_optional() {
|
||||
bpy::to_python_converter<Optional, optional_to_python<Optional>>();
|
||||
bpy::converter::registry::push_back(
|
||||
&optional_from_python<Optional>::convertible
|
||||
, &optional_from_python<Optional>::construct
|
||||
, bpy::type_id<Optional>());
|
||||
};
|
||||
|
||||
void configure_python_logging(pybind11::module_ mobase);
|
||||
|
||||
/**
|
||||
* @brief Show a deprecation warning.
|
||||
@@ -268,4 +29,4 @@ namespace utils {
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <log.h>
|
||||
#include <utility.h>
|
||||
|
||||
#include "sipApiAccess.h"
|
||||
#include "converters_qt_sip.h"
|
||||
#include "error.h"
|
||||
#include "gilock.h"
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace details {
|
||||
*objPtr = result;
|
||||
}
|
||||
else if (apiTransfer) {
|
||||
sipAPIAccess::sipAPI()->api_transfer_to(result.ptr(), Py_None);
|
||||
mo2::details::sipAPI()->api_transfer_to(result.ptr(), Py_None);
|
||||
}
|
||||
if constexpr (!std::is_same_v<ReturnType, void>) {
|
||||
return boost::python::extract<ReturnType>(result)();
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#include "sipapiaccess.h"
|
||||
#include <boost/python.hpp>
|
||||
#include <QString>
|
||||
#include <utility.h>
|
||||
|
||||
const sipAPIDef* sipAPIAccess::sipAPI()
|
||||
{
|
||||
QString exception;
|
||||
static const sipAPIDef* sipApi = nullptr;
|
||||
if (sipApi == nullptr) {
|
||||
#if defined(SIP_USE_PYCAPSULE)
|
||||
PyImport_ImportModule("PyQt5.sip");
|
||||
|
||||
auto errorObj = PyErr_Occurred();
|
||||
if (errorObj != NULL) {
|
||||
PyObject* type, * value, * traceback;
|
||||
PyErr_Fetch(&type, &value, &traceback);
|
||||
PyErr_NormalizeException(&type, &value, &traceback);
|
||||
if (traceback != NULL) {
|
||||
boost::python::handle<> h_type(type);
|
||||
boost::python::handle<> h_val(value);
|
||||
boost::python::handle<> h_tb(traceback);
|
||||
boost::python::object tb(boost::python::import("traceback"));
|
||||
boost::python::object fmt_exp(tb.attr("format_exception"));
|
||||
boost::python::object exp_list(fmt_exp(h_type, h_val, h_tb));
|
||||
boost::python::object exp_str(boost::python::str("\n").join(exp_list));
|
||||
boost::python::extract<std::string> returned(exp_str);
|
||||
exception = QString::fromStdString(returned());
|
||||
}
|
||||
PyErr_Restore(type, value, traceback);
|
||||
throw MOBase::Exception(QString("Failed to load PyQt5: %1").arg(exception));
|
||||
}
|
||||
|
||||
sipApi = (const sipAPIDef*)PyCapsule_Import("PyQt5.sip._C_API", 0);
|
||||
if (sipApi == NULL) {
|
||||
auto errorObj = PyErr_Occurred();
|
||||
if (errorObj != NULL) {
|
||||
PyObject* type, * value, * traceback;
|
||||
PyErr_Fetch(&type, &value, &traceback);
|
||||
PyErr_NormalizeException(&type, &value, &traceback);
|
||||
if (traceback != NULL) {
|
||||
boost::python::handle<> h_type(type);
|
||||
boost::python::handle<> h_val(value);
|
||||
boost::python::handle<> h_tb(traceback);
|
||||
boost::python::object tb(boost::python::import("traceback"));
|
||||
boost::python::object fmt_exp(tb.attr("format_exception"));
|
||||
boost::python::object exp_list(fmt_exp(h_type, h_val, h_tb));
|
||||
boost::python::object exp_str(boost::python::str("\n").join(exp_list));
|
||||
boost::python::extract<std::string> returned(exp_str);
|
||||
exception = QString::fromStdString(returned());
|
||||
}
|
||||
PyErr_Restore(type, value, traceback);
|
||||
}
|
||||
throw MOBase::Exception(QString("Failed to load SIP API: %1").arg(exception));
|
||||
}
|
||||
#else
|
||||
PyObject* sip_module;
|
||||
PyObject* sip_module_dict;
|
||||
PyObject* c_api;
|
||||
|
||||
/* Import the SIP module. */
|
||||
sip_module = PyImport_ImportModule("PyQt5.sip");
|
||||
|
||||
if (sip_module == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Get the module's dictionary. */
|
||||
sip_module_dict = PyModule_GetDict(sip_module);
|
||||
|
||||
/* Get the "_C_API" attribute. */
|
||||
c_api = PyDict_GetItemString(sip_module_dict, "_C_API");
|
||||
|
||||
if (c_api == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Sanity check that it is the right type. */
|
||||
if (!PyCObject_Check(c_api))
|
||||
return NULL;
|
||||
|
||||
/* Get the actual pointer from the object. */
|
||||
sipApi = (const sipAPIDef*)PyCObject_AsVoidPtr(c_api);
|
||||
#endif
|
||||
}
|
||||
|
||||
return sipApi;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#ifndef SIPAPIACCESS_H
|
||||
#define SIPAPIACCESS_H
|
||||
|
||||
#include <sip.h>
|
||||
|
||||
class sipAPIAccess
|
||||
{
|
||||
public:
|
||||
static const sipAPIDef* sipAPI();
|
||||
};
|
||||
|
||||
#endif // SIPAPIACCESS_H
|
||||
@@ -1,99 +0,0 @@
|
||||
#ifndef TUPLE_HELPER_H
|
||||
#define TUPLE_HELPER_H
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/object.hpp> //len function
|
||||
|
||||
namespace boost {
|
||||
namespace python {
|
||||
|
||||
template <class TTuple>
|
||||
struct to_py_tuple {
|
||||
|
||||
static PyObject* convert(const TTuple& c_tuple) {
|
||||
list values;
|
||||
//add all c_tuple items to "values" list
|
||||
convert_impl(c_tuple, values, std::make_index_sequence<std::tuple_size_v<TTuple>>{});
|
||||
//create Python tuple from the list
|
||||
return incref(python::tuple(values).ptr());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template <std::size_t... Is>
|
||||
static void convert_impl(const TTuple& c_tuple, list& values, std::index_sequence<Is... >) {
|
||||
(values.append(std::get<Is>(c_tuple)), ...);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
template <class TTuple>
|
||||
struct from_py_sequence {
|
||||
|
||||
using tuple_type = TTuple;
|
||||
using index_sequence = std::make_index_sequence<std::tuple_size_v<TTuple>>;
|
||||
|
||||
static void* convertible(PyObject* py_obj) {
|
||||
|
||||
if (!PySequence_Check(py_obj)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!PyObject_HasAttrString(py_obj, "__len__")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
python::object py_sequence(handle<>(borrowed(py_obj)));
|
||||
|
||||
if (std::tuple_size_v<TTuple> != len(py_sequence)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (convertible_impl(py_sequence, index_sequence{})) {
|
||||
return py_obj;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void construct(PyObject* py_obj, converter::rvalue_from_python_stage1_data* data) {
|
||||
typedef converter::rvalue_from_python_storage<TTuple> storage_t;
|
||||
storage_t* the_storage = reinterpret_cast<storage_t*>(data);
|
||||
void* memory_chunk = the_storage->storage.bytes;
|
||||
TTuple* c_tuple = new (memory_chunk) TTuple();
|
||||
data->convertible = memory_chunk;
|
||||
|
||||
python::object py_sequence(handle<>(borrowed(py_obj)));
|
||||
construct_impl(py_sequence, *c_tuple, index_sequence{});
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template <std::size_t... Is>
|
||||
static bool convertible_impl(const python::object& py_sequence, std::index_sequence<Is... >) {
|
||||
return (... && extract<std::tuple_element_t<Is, tuple_type>>(py_sequence[Is]).check());
|
||||
}
|
||||
|
||||
template <std::size_t... Is>
|
||||
static void construct_impl(const python::object& py_sequence, TTuple& c_tuple, std::index_sequence<Is... >) {
|
||||
c_tuple = tuple_type{ extract<std::tuple_element_t<Is, tuple_type>>(py_sequence[Is])... };
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template< class TTuple>
|
||||
void register_tuple() {
|
||||
|
||||
to_python_converter< TTuple, to_py_tuple<TTuple> >();
|
||||
|
||||
converter::registry::push_back(&from_py_sequence<TTuple>::convertible
|
||||
, &from_py_sequence<TTuple>::construct
|
||||
, type_id<TTuple>());
|
||||
};
|
||||
|
||||
}
|
||||
} //boost::python
|
||||
|
||||
#endif
|
||||
@@ -1,104 +0,0 @@
|
||||
#ifndef VARIANT_HELPER_H
|
||||
#define VARIANT_HELPER_H
|
||||
|
||||
#include <boost/python.hpp>
|
||||
|
||||
/**
|
||||
* Register variant(s) from and to python object. Greatly inspired by register_tuple<>.
|
||||
*/
|
||||
|
||||
namespace boost {
|
||||
namespace python {
|
||||
|
||||
template <class TVariant>
|
||||
struct to_py_variant {
|
||||
|
||||
static PyObject* convert(const TVariant& c_variant) {
|
||||
object value = std::visit([](auto const& value) {
|
||||
return object{ value };
|
||||
}, c_variant);
|
||||
//create Python object from the list
|
||||
return incref(value.ptr());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <class TVariant>
|
||||
struct variant_from_python;
|
||||
|
||||
template <template <class... > class VTemplate, class... Args>
|
||||
struct variant_from_python<VTemplate<Args... >> {
|
||||
|
||||
using variant_type = VTemplate<Args... >;
|
||||
using index_sequence = std::make_index_sequence<std::variant_size_v<variant_type>>;
|
||||
|
||||
static void* convertible(PyObject* py_obj) {
|
||||
|
||||
python::object obj(handle<>(borrowed(py_obj)));
|
||||
|
||||
if (convertible_impl(obj)) {
|
||||
return py_obj;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void construct(PyObject* py_obj, converter::rvalue_from_python_stage1_data* data) {
|
||||
typedef converter::rvalue_from_python_storage<variant_type> storage_t;
|
||||
storage_t* the_storage = reinterpret_cast<storage_t*>(data);
|
||||
void* memory_chunk = the_storage->storage.bytes;
|
||||
variant_type* c_variant = new (memory_chunk) variant_type();
|
||||
data->convertible = memory_chunk;
|
||||
|
||||
python::object obj(handle<>(borrowed(py_obj)));
|
||||
impl<Args... >::construct(obj, *c_variant);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static bool convertible_impl(const python::object& obj) {
|
||||
return (... || extract<Args>(obj).check());
|
||||
}
|
||||
|
||||
template <class... >
|
||||
struct impl;
|
||||
|
||||
template <>
|
||||
struct impl<> {
|
||||
static void construct(const python::object& obj, variant_type& c_variant) {}
|
||||
};
|
||||
|
||||
template <class UArg, class... UArgs>
|
||||
struct impl<UArg, UArgs... > {
|
||||
|
||||
static void construct(const python::object& obj, variant_type& c_variant) {
|
||||
|
||||
extract<UArg> type_checker(obj);
|
||||
|
||||
if (type_checker.check()) {
|
||||
c_variant = type_checker();
|
||||
}
|
||||
else {
|
||||
impl<UArgs... >::construct(obj, c_variant);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
template< class TVariant>
|
||||
void register_variant() {
|
||||
|
||||
to_python_converter<TVariant, to_py_variant<TVariant>>();
|
||||
|
||||
converter::registry::push_back(&variant_from_python<TVariant>::convertible
|
||||
, &variant_from_python<TVariant>::construct
|
||||
, type_id<TVariant>());
|
||||
};
|
||||
|
||||
}
|
||||
} //boost::python
|
||||
|
||||
#endif
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <boost/python.hpp>
|
||||
|
||||
#include "report.h"
|
||||
#include "variant_helper.h"
|
||||
// #include "variant_helper.h"
|
||||
|
||||
namespace bpy = boost::python;
|
||||
|
||||
@@ -63,4 +63,4 @@ void register_widgets() {
|
||||
.def("exec", &TaskDialog::exec)
|
||||
;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user