2022-04-21 23:57:05 +02:00
|
|
|
#include "pythonutils.h"
|
|
|
|
|
|
|
|
|
|
#include <filesystem>
|
|
|
|
|
#include <set>
|
2022-04-23 21:39:10 +02:00
|
|
|
#include <sstream>
|
2022-04-21 23:57:05 +02:00
|
|
|
|
2022-04-18 22:40:36 +02:00
|
|
|
#include <pybind11/pybind11.h>
|
|
|
|
|
|
2022-04-21 23:57:05 +02:00
|
|
|
#include <QCoreApplication>
|
|
|
|
|
|
|
|
|
|
#include "log.h"
|
|
|
|
|
|
2022-04-18 22:40:36 +02:00
|
|
|
namespace py = pybind11;
|
2022-04-21 23:57:05 +02:00
|
|
|
|
2022-04-18 22:40:36 +02:00
|
|
|
namespace mo2::python {
|
|
|
|
|
|
2022-04-23 21:39:10 +02:00
|
|
|
class PrintWrapper {
|
|
|
|
|
MOBase::log::Levels level_;
|
|
|
|
|
std::stringstream buffer_;
|
2022-04-18 22:40:36 +02:00
|
|
|
|
2022-04-23 21:39:10 +02:00
|
|
|
public:
|
|
|
|
|
PrintWrapper(MOBase::log::Levels level) : level_{level} {}
|
2022-04-18 22:40:36 +02:00
|
|
|
|
2022-04-23 21:39:10 +02:00
|
|
|
void write(std::string_view message)
|
|
|
|
|
{
|
|
|
|
|
buffer_ << message;
|
|
|
|
|
if (buffer_.tellp() != 0 && buffer_.str().back() == '\n') {
|
|
|
|
|
const auto full_message = buffer_.str();
|
|
|
|
|
MOBase::log::log(
|
|
|
|
|
level_, full_message.substr(0, full_message.length() - 1));
|
|
|
|
|
buffer_ = std::stringstream{};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2022-04-18 22:40:36 +02:00
|
|
|
|
2022-04-23 21:39:10 +02:00
|
|
|
/**
|
|
|
|
|
* @brief Construct a dynamic Python type.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
template <class... Args>
|
|
|
|
|
pybind11::object make_python_type(std::string_view name,
|
|
|
|
|
pybind11::tuple base_classes,
|
|
|
|
|
Args&&... args)
|
|
|
|
|
{
|
|
|
|
|
// this is ugly but that's how it's done in C Python
|
|
|
|
|
auto type = py::reinterpret_borrow<py::object>((PyObject*)&PyType_Type);
|
2022-04-18 22:40:36 +02:00
|
|
|
|
2022-04-23 21:39:10 +02:00
|
|
|
// create the python class
|
|
|
|
|
return type(name, base_classes, py::dict(std::forward<Args>(args)...));
|
2022-04-21 23:57:05 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-23 21:39:10 +02:00
|
|
|
void configure_python_stream()
|
|
|
|
|
{
|
|
|
|
|
// create the "MO2Handler" python class
|
|
|
|
|
auto printWrapper = make_python_type(
|
|
|
|
|
"MO2PrintWrapper", py::make_tuple(),
|
|
|
|
|
py::arg("write") = py::cpp_function([](std::string_view message) {
|
|
|
|
|
static PrintWrapper wrapper(MOBase::log::Debug);
|
|
|
|
|
wrapper.write(message);
|
|
|
|
|
}));
|
|
|
|
|
auto errorWrapper = make_python_type(
|
|
|
|
|
"MO2ErrorWrapper", py::make_tuple(),
|
|
|
|
|
py::arg("write") = py::cpp_function([](std::string_view message) {
|
|
|
|
|
static PrintWrapper wrapper(MOBase::log::Error);
|
|
|
|
|
wrapper.write(message);
|
|
|
|
|
}));
|
|
|
|
|
py::module_ sys = py::module_::import("sys");
|
|
|
|
|
sys.attr("stdout") = printWrapper();
|
|
|
|
|
sys.attr("stderr") = errorWrapper();
|
2022-04-21 23:57:05 +02:00
|
|
|
|
2022-04-23 21:39:10 +02:00
|
|
|
// not sure if still needed for pybind11?
|
|
|
|
|
sys.attr("excepthook") = sys.attr("__excepthook__");
|
2022-04-21 23:57:05 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-23 21:39:10 +02:00
|
|
|
// 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");
|
|
|
|
|
|
|
|
|
|
// create the "MO2Handler" python class
|
|
|
|
|
auto MO2Handler =
|
|
|
|
|
make_python_type("LogHandler", py::make_tuple(Handler),
|
|
|
|
|
py::arg("emit") = py::cpp_function(emit_function));
|
|
|
|
|
|
|
|
|
|
// 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 = py::module_::import("inspect");
|
|
|
|
|
auto current_frame = inspect.attr("currentframe")();
|
|
|
|
|
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})) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Register the deprecation:
|
|
|
|
|
DeprecatedLines.emplace(filename, lineno);
|
|
|
|
|
|
|
|
|
|
auto path =
|
|
|
|
|
relative(std::filesystem::path(filename),
|
|
|
|
|
QCoreApplication::applicationDirPath().toStdWString());
|
|
|
|
|
|
|
|
|
|
// Show the message:
|
|
|
|
|
if (message.empty()) {
|
|
|
|
|
MOBase::log::warn("[deprecated] {} in {} [{}:{}].", name, function,
|
|
|
|
|
path.native(), lineno);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
MOBase::log::warn("[deprecated] {} in {} [{}:{}]: {}", name,
|
|
|
|
|
function, path.native(), lineno, message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace mo2::python
|