diff --git a/libsrc/core/CMakeLists.txt b/libsrc/core/CMakeLists.txt index bf4b0bff..7eefdacb 100644 --- a/libsrc/core/CMakeLists.txt +++ b/libsrc/core/CMakeLists.txt @@ -10,6 +10,10 @@ add_library(ngcore SHARED utils.cpp ) +if(USE_PYTHON) + target_sources(ngcore PRIVATE python_ngcore.cpp) +endif(USE_PYTHON) + target_compile_definitions(ngcore PRIVATE NGCORE_EXPORTS) if(NOT WIN32) target_compile_options(ngcore PRIVATE -fvisibility=hidden) @@ -53,7 +57,7 @@ if(ENABLE_CPP_CORE_GUIDELINES_CHECK) endif(ENABLE_CPP_CORE_GUIDELINES_CHECK) if(USE_PYTHON) - pybind11_add_module(pyngcore SHARED python_ngcore.cpp) + pybind11_add_module(pyngcore SHARED python_ngcore_export.cpp) target_link_libraries(pyngcore PUBLIC ngcore ${PYTHON_LIBRARIES}) set_target_properties(pyngcore PROPERTIES INSTALL_RPATH "${NG_RPATH_TOKEN}/${NETGEN_PYTHON_RPATH}") install(TARGETS pyngcore DESTINATION ${NG_INSTALL_DIR_PYTHON} COMPONENT netgen) diff --git a/libsrc/core/python_ngcore.cpp b/libsrc/core/python_ngcore.cpp index 47c16740..a0810ca6 100644 --- a/libsrc/core/python_ngcore.cpp +++ b/libsrc/core/python_ngcore.cpp @@ -1,30 +1,119 @@ - #include +#include "python_ngcore.hpp" + +#include "array.hpp" +#include "flags.hpp" #include "logging.hpp" namespace py = pybind11; -using namespace ngcore; +using std::string; -PYBIND11_MODULE(pyngcore, m) // NOLINT +namespace ngcore { - py::enum_(m, "LOG_LEVEL", "Logging level") - .value("Trace", level::trace) - .value("Debug", level::debug) - .value("Info", level::info) - .value("Warn", level::warn) - .value("Error", level::err) - .value("Critical", level::critical) - .value("Off", level::off); - m.def("SetLoggingLevel", &SetLoggingLevel, py::arg("level"), py::arg("logger")="", - "Set logging level, if name is given only to the specific logger, else set the global logging level"); - m.def("AddFileSink", &AddFileSink, py::arg("filename"), py::arg("level"), py::arg("logger")="", - "Add File sink, either only to logger specified or globally to all loggers"); - m.def("AddConsoleSink", &AddConsoleSink, py::arg("level"), py::arg("logger")="", - "Add console output for specific logger or all if none given"); - m.def("ClearLoggingSinks", &ClearLoggingSinks, py::arg("logger")="", - "Clear sinks of specific logger, or all if none given"); - m.def("FlushOnLoggingLevel", &FlushOnLoggingLevel, py::arg("level"), py::arg("logger")="", - "Flush every message with level at least `level` for specific logger or all loggers if none given."); -} + void SetFlag(Flags &flags, string s, py::object value) + { + if (py::isinstance(value)) + { + py::dict vdd(value); + // call recursively to set dictionary + for (auto item : vdd) { + string name = item.first.cast(); + py::object val = py::reinterpret_borrow(item.second); + SetFlag(flags, name, val); + } + return; + } + + if (py::isinstance(value)) + flags.SetFlag(s, value.cast()); + + if (py::isinstance(value)) + flags.SetFlag(s, value.cast()); + + if (py::isinstance(value)) + flags.SetFlag(s, double(value.cast())); + + if (py::isinstance(value)) + flags.SetFlag(s, value.cast()); + + if (py::isinstance(value)) + { + py::list vdl(value); + if (py::len(vdl) > 0) + { + if(py::isinstance(vdl[0])) + flags.SetFlag(s, makeCArray(vdl)); + if(py::isinstance(vdl[0])) + flags.SetFlag(s, makeCArray(vdl)); + } + else + { + Array dummystr; + Array dummydbl; + flags.SetFlag(s,dummystr); + flags.SetFlag(s,dummydbl); + } + } + + if (py::isinstance(value)) + { + py::tuple vdt(value); + if (py::isinstance(value)) + flags.SetFlag(s, makeCArray(vdt)); + if (py::isinstance(value)) + flags.SetFlag(s, makeCArray(vdt)); + if (py::isinstance(value)) + flags.SetFlag(s, makeCArray(vdt)); + } + } + + Flags CreateFlagsFromKwArgs(py::object pyclass, const py::kwargs& kwargs, py::list info) + { + static std::shared_ptr logger = GetLogger("Flags"); + auto flags_doc = pyclass.attr("__flags_doc__")(); + py::dict flags_dict; + + if (kwargs.contains("flags")) + { + logger->warn("WARNING: using flags as kwarg is deprecated in {}, use the flag arguments as kwargs instead!", + std::string(py::str(pyclass))); + auto addflags = py::cast(kwargs["flags"]); + for (auto item : addflags) + flags_dict[item.first.cast().c_str()] = item.second; + } + for (auto item : kwargs) + if (!flags_doc.contains(item.first.cast().c_str()) && + !(item.first.cast() == "flags")) + logger->warn("WARNING: kwarg '{}' is an undocumented flags option for class {}, maybe there is a typo?", + item.first.cast(), std::string(py::str(pyclass))); + + py::dict special; + if(py::hasattr(pyclass,"__special_treated_flags__")) + special = pyclass.attr("__special_treated_flags__")(); + for (auto item : kwargs) + { + auto name = item.first.cast(); + if (name != "flags") + { + if(!special.contains(name.c_str())) + flags_dict[name.c_str()] = item.second; + } + } + + auto flags = py::cast(flags_dict); + + for (auto item : kwargs) + { + auto name = item.first.cast(); + if (name != "flags") + { + if(special.contains(name.c_str())) + special[name.c_str()](item.second, &flags, info); + } + } + return flags; + } + +} // namespace ngcore diff --git a/libsrc/core/python_ngcore.hpp b/libsrc/core/python_ngcore.hpp index 07f348c7..9c283589 100644 --- a/libsrc/core/python_ngcore.hpp +++ b/libsrc/core/python_ngcore.hpp @@ -3,11 +3,37 @@ #include +#include "array.hpp" #include "archive.hpp" - +#include "flags.hpp" +#include "ngcore_api.hpp" namespace ngcore { + namespace py = pybind11; + + template + Array makeCArray(const py::object& obj) + { + Array arr; + arr.SetAllocSize(py::len(obj)); + if(py::isinstance(obj)) + for(auto& val : py::cast(obj)) + arr.Append(py::cast(val)); + else if(py::isinstance(obj)) + for(auto& val : py::cast(obj)) + arr.Append(py::cast(val)); + else + throw py::type_error("Cannot convert Python object to C Array"); + return arr; + } + + void NGCORE_API SetFlag(Flags &flags, std::string s, py::object value); + // Parse python kwargs to flags + Flags NGCORE_API CreateFlagsFromKwArgs(py::object pyclass, const py::kwargs& kwargs, py::list info = py::list()); + + // *************** Archiving functionality ************** + template Archive& Archive :: Shallow(T& val) { diff --git a/libsrc/core/python_ngcore_export.cpp b/libsrc/core/python_ngcore_export.cpp new file mode 100644 index 00000000..90dbf72f --- /dev/null +++ b/libsrc/core/python_ngcore_export.cpp @@ -0,0 +1,87 @@ + +#include "python_ngcore.hpp" + +using namespace ngcore; +using namespace std; + +PYBIND11_MODULE(pyngcore, m) // NOLINT +{ + py::class_(m, "Flags") + .def(py::init<>()) + .def("__str__", &ToString) + .def(py::init([](py::object & obj) { + Flags flags; + py::dict d(obj); + SetFlag (flags, "", d); + return flags; + }), py::arg("obj"), "Create Flags by given object") + .def(py::pickle([] (const Flags& self) + { + std::stringstream str; + self.SaveFlags(str); + return py::make_tuple(py::cast(str.str())); + }, + [] (py::tuple state) + { + string s = state[0].cast(); + std::stringstream str(s); + Flags flags; + flags.LoadFlags(str); + return flags; + } + )) + .def("Set",[](Flags & self,const py::dict & aflags)->Flags& + { + SetFlag(self, "", aflags); + return self; + }, py::arg("aflag"), "Set the flags by given dict") + + .def("Set",[](Flags & self, const char * akey, const py::object & value)->Flags& + { + SetFlag(self, akey, value); + return self; + }, py::arg("akey"), py::arg("value"), "Set flag by given value.") + + .def("__getitem__", [](Flags & self, const string& name) -> py::object { + + if(self.NumListFlagDefined(name)) + return py::cast(self.GetNumListFlag(name)); + + if(self.StringListFlagDefined(name)) + return py::cast(self.GetStringListFlag(name)); + + if(self.NumFlagDefined(name)) + return py::cast(*self.GetNumFlagPtr(name)); + + if(self.StringFlagDefined(name)) + return py::cast(self.GetStringFlag(name)); + + if(self.FlagsFlagDefined(name)) + return py::cast(self.GetFlagsFlag(name)); + + return py::cast(self.GetDefineFlag(name)); + }, py::arg("name"), "Return flag by given name") + ; + py::implicitly_convertible(); + + + py::enum_(m, "LOG_LEVEL", "Logging level") + .value("Trace", level::trace) + .value("Debug", level::debug) + .value("Info", level::info) + .value("Warn", level::warn) + .value("Error", level::err) + .value("Critical", level::critical) + .value("Off", level::off); + + m.def("SetLoggingLevel", &SetLoggingLevel, py::arg("level"), py::arg("logger")="", + "Set logging level, if name is given only to the specific logger, else set the global logging level"); + m.def("AddFileSink", &AddFileSink, py::arg("filename"), py::arg("level"), py::arg("logger")="", + "Add File sink, either only to logger specified or globally to all loggers"); + m.def("AddConsoleSink", &AddConsoleSink, py::arg("level"), py::arg("logger")="", + "Add console output for specific logger or all if none given"); + m.def("ClearLoggingSinks", &ClearLoggingSinks, py::arg("logger")="", + "Clear sinks of specific logger, or all if none given"); + m.def("FlushOnLoggingLevel", &FlushOnLoggingLevel, py::arg("level"), py::arg("logger")="", + "Flush every message with level at least `level` for specific logger or all loggers if none given."); +}