Clean stuff. Bindings for IFileTree.

This commit is contained in:
Mikaël Capelle
2022-04-21 23:57:07 +02:00
parent 8bd7d08d1a
commit 67ea7deb00
16 changed files with 825 additions and 1161 deletions
+4 -4
View File
@@ -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
+91
View File
@@ -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;
}
}
+27 -7
View File
@@ -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;
+248
View File
@@ -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)
+36
View File
@@ -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
-74
View File
@@ -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;
}
-13
View File
@@ -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
+84 -9
View File
@@ -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 {
}
}
}
}
+7 -246
View File
@@ -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
+2 -2
View File
@@ -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)();
-86
View File
@@ -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;
}
-12
View File
@@ -1,12 +0,0 @@
#ifndef SIPAPIACCESS_H
#define SIPAPIACCESS_H
#include <sip.h>
class sipAPIAccess
{
public:
static const sipAPIDef* sipAPI();
};
#endif // SIPAPIACCESS_H
-99
View File
@@ -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
-104
View File
@@ -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
+2 -2
View File
@@ -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)
;
}
}