Skip to content
Snippets Groups Projects
Commit 9a5e0331 authored by Olivier BICHLER's avatar Olivier BICHLER
Browse files

Universal Python binding

parent 54c59352
No related branches found
No related tags found
No related merge requests found
......@@ -22,6 +22,13 @@
#include "aidge/utils/Any.hpp"
#include "aidge/utils/Attributes.hpp"
#ifdef PYBIND
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
#endif
namespace Aidge {
......@@ -37,16 +44,40 @@ public:
* exist
* \note at() throws if the Attribute does not exist, using find to test for Attribute existance
*/
template<class T> T& getAttr(const std::string& name)
template<class T> T getAttr(const std::string& name) const
{
return libany::any_cast<T&>(mAttrs.at(name));
#ifdef PYBIND
// If attribute does not exist in C++, it might have been created in Python
auto it = mAttrs.find(name);
if (it == mAttrs.end()) {
auto itPy = mAttrsPy.find(name);
if (itPy != mAttrsPy.end()) {
return itPy->second.cast<T>();
}
}
#endif
return libany::any_cast<T>(mAttrs.at(name));
}
// Note: return by reference is not possible because py::object::cast() returns a temporary
/*
template<class T> const T& getAttr(const std::string& name) const
{
#ifdef PYBIND
// If attribute does not exist in C++, it might have been created in Python
auto it = mAttrs.find(name);
if (it == mAttrs.end()) {
auto itPy = mAttrsPy.find(name);
if (itPy != mAttrsPy.end()) {
return itPy->second.cast<T>();
}
}
#endif
return libany::any_cast<const T&>(mAttrs.at(name));
}
*/
///\brief Add a new Attribute, identified by its name. If it already exists, asserts.
///\tparam T expected Attribute type
///\param name Attribute name
......@@ -55,6 +86,11 @@ public:
{
const auto& res = mAttrs.emplace(std::make_pair(name, libany::any(std::forward<T>(value))));
assert(res.second && "attribute already exists");
#ifdef PYBIND
// Keep a copy of the attribute in py::object that is updated everytime
mAttrsPy.emplace(std::make_pair(name, py::cast(value)));
#endif
}
///\brief Set an Attribute value, identified by its name. If it already exists, its value (and type, if different) is changed.
......@@ -66,59 +102,89 @@ public:
auto res = mAttrs.emplace(std::make_pair(name, libany::any(std::forward<T>(value))));
if (!res.second)
res.first->second = std::move(libany::any(std::forward<T>(value)));
#ifdef PYBIND
// Keep a copy of the attribute in py::object that is updated everytime
auto resPy = mAttrsPy.emplace(std::make_pair(name, py::cast(value)));
if (!resPy.second)
resPy.first->second = std::move(py::cast(value));
#endif
}
#ifdef PYBIND
void addAttrPy(const std::string& name, py::object&& value)
{
mAttrsPy.emplace(std::make_pair(name, value));
}
void setAttrPy(const std::string& name, py::object&& value)
{
auto resPy = mAttrsPy.emplace(std::make_pair(name, value));
if (!resPy.second)
resPy.first->second = std::move(value);
}
#endif
//////////////////////////////////////
/// Generic Attributes API
//////////////////////////////////////
bool hasAttr(const std::string& name) const override final {
#ifdef PYBIND
return (mAttrsPy.find(name) != mAttrsPy.end());
#else
return (mAttrs.find(name) != mAttrs.end());
#endif
}
std::string getAttrType(const std::string& name) const override final {
// In order to remain consistent between C++ and Python, with or without PyBind, the name of the type is:
// - C-style for C++ created attributes
// - Python-style for Python created attributes
#ifdef PYBIND
// If attribute does not exist in C++, it might have been created in Python
auto it = mAttrs.find(name);
if (it == mAttrs.end()) {
auto itPy = mAttrsPy.find(name);
if (itPy != mAttrsPy.end()) {
return std::string(Py_TYPE(itPy->second.ptr())->tp_name);
}
}
#endif
return mAttrs.at(name).type().name();
}
std::vector<std::string> getAttrsName() const override final {
std::vector<std::string> attrsName;
#ifdef PYBIND
for(auto const& it: mAttrsPy)
attrsName.push_back(it.first);
#else
for(auto const& it: mAttrs)
attrsName.push_back(it.first);
#endif
return attrsName;
}
#ifdef PYBIND
#ifdef PYBIND
/**
* @detail See https://github.com/pybind/pybind11/issues/1590 as to why a
* generic type caster for std::any is not feasable.
* The strategy here is to keep a copy of each attribute in py::object that is updated everytime.
*/
py::object getAttrPy(const std::string& name) const {
py::object res = py::none();
const auto& attrType = mAttrs.at(name).type();
if(attrType == typeid(int))
res = py::cast(getAttr<int>(name));
else if(attrType == typeid(float))
res = py::cast(getAttr<float>(name));
else if(attrType == typeid(bool))
res = py::cast(getAttr<bool>(name));
else if(attrType == typeid(std::string))
res = py::cast(getAttr<std::string>(name));
else if(attrType == typeid(std::vector<bool>))
res = py::cast(getAttr<std::vector<bool>>(name));
else if(attrType == typeid(std::vector<int>))
res = py::cast(getAttr<std::vector<int>>(name));
else if(attrType == typeid(std::vector<float>))
res = py::cast(getAttr<std::vector<float>>(name));
else if(attrType == typeid(std::vector<std::string>))
res = py::cast(getAttr<std::vector<std::string>>(name));
else {
throw py::key_error("Failed to convert attribute type " + name + ", this issue may come from typeid function which gave an unknown key : [" + attrType.name() + "]. Please open an issue asking to add the support for this key.");
}
return res;
return mAttrsPy.at(name);
};
#endif
#endif
private:
// Stores C++ attributes only
std::map<std::string, libany::any> mAttrs;
#ifdef PYBIND
// Stores C++ attributes (copy) and Python-only attributes
std::map<std::string, py::object> mAttrsPy;
#endif
};
}
......
......@@ -6,30 +6,14 @@ namespace py = pybind11;
namespace Aidge {
void init_Attributes(py::module& m){
py::class_<Attributes, std::shared_ptr<Attributes>>(m, "Attributes")
.def("has_attr", &Attributes::hasAttr)
.def("get_attr_type", &Attributes::getAttrType)
.def("has_attr", &Attributes::hasAttr, py::arg("name"))
.def("get_attr_type", &Attributes::getAttrType, py::arg("name"))
.def("get_attrs_name", &Attributes::getAttrsName)
.def("get_attr", &Attributes::getAttrPy, py::arg("name"));
py::class_<DynamicAttributes, std::shared_ptr<DynamicAttributes>, Attributes>(m, "DynamicAttributes")
// add
.def("add_attr", &DynamicAttributes::addAttr<bool>)
.def("add_attr", &DynamicAttributes::addAttr<int>)
.def("add_attr", &DynamicAttributes::addAttr<float>)
.def("add_attr", &DynamicAttributes::addAttr<std::string>)
.def("add_attr", &DynamicAttributes::addAttr<std::vector<bool>>)
.def("add_attr", &DynamicAttributes::addAttr<std::vector<int>>)
.def("add_attr", &DynamicAttributes::addAttr<std::vector<float>>)
.def("add_attr", &DynamicAttributes::addAttr<std::vector<std::string>>)
// set
.def("set_attr", &DynamicAttributes::setAttr<bool>)
.def("set_attr", &DynamicAttributes::setAttr<int>)
.def("set_attr", &DynamicAttributes::setAttr<float>)
.def("set_attr", &DynamicAttributes::setAttr<std::string>)
.def("set_attr", &DynamicAttributes::setAttr<std::vector<bool>>)
.def("set_attr", &DynamicAttributes::setAttr<std::vector<int>>)
.def("set_attr", &DynamicAttributes::setAttr<std::vector<float>>)
.def("set_attr", &DynamicAttributes::setAttr<std::vector<std::string>>);
.def("add_attr", &DynamicAttributes::addAttrPy, py::arg("name"), py::arg("value"))
.def("set_attr", &DynamicAttributes::setAttrPy, py::arg("name"), py::arg("value"));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment