diff --git a/aidge_core/unit_tests/test_operator_binding.py b/aidge_core/unit_tests/test_operator_binding.py index 5b25eb7975d439816dbf91cc95b462f217fd0227..384a82de8d821e45c1be7e61d451ceff0a2bf3c9 100644 --- a/aidge_core/unit_tests/test_operator_binding.py +++ b/aidge_core/unit_tests/test_operator_binding.py @@ -73,15 +73,26 @@ class test_operator_binding(unittest.TestCase): self.assertEqual(attrs.get_attr("b"), "test") self.assertEqual(attrs.has_attr("c"), True) self.assertEqual(attrs.get_attr("c"), [True, False, True]) - self.assertEqual(attrs.dict().keys(), {"a", "b", "c"}) + self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "mem", "impl"}) self.assertEqual(attrs.has_attr("d"), False) + self.assertEqual(attrs.has_attr("mem.a"), True) + self.assertEqual(attrs.get_attr("mem.a"), 1) + self.assertEqual(attrs.has_attr("mem.data.b"), True) + self.assertEqual(attrs.get_attr("mem.data.b"), 1.0) + self.assertEqual(attrs.get_attr("mem").get_attr("data").get_attr("b"), 1.0) + self.assertEqual(attrs.has_attr("impl.c"), True) + self.assertEqual(attrs.get_attr("impl.c"), "test") # Add Python attributes attrs.add_attr("d", 18.56) self.assertEqual(attrs.get_attr("d"), 18.56) self.assertEqual(attrs.has_attr("d"), True) - self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d"}) + self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d", "mem", "impl"}) self.assertEqual(attrs.has_attr("e"), False) + attrs.add_attr("mem.data.c", 19.36) + self.assertEqual(attrs.get_attr("mem.data.c"), 19.36) + self.assertEqual(attrs.has_attr("mem.data.c"), True) + self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d", "mem", "impl"}) # Check that added Python attribute is accessible in C++ # Return the value of an attribute named "d" of type float64 (double in C++) diff --git a/include/aidge/utils/DynamicAttributes.hpp b/include/aidge/utils/DynamicAttributes.hpp index 9c4c197e99406b0d4b4e9afdc5e4fb0c5479da62..5218def658acd164a8ce1c4a28b2a916d6cd930d 100644 --- a/include/aidge/utils/DynamicAttributes.hpp +++ b/include/aidge/utils/DynamicAttributes.hpp @@ -67,19 +67,8 @@ public: } else { const auto ns = name.substr(0, dot); + AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace for \"{}\".", ns); const auto nsName = name.substr(dot + 1); - -#ifdef PYBIND - // If attribute does not exist in C++, it might have been created or modified in Python - auto it = mAttrs.find(ns); - if (it == mAttrs.end()) { - auto itPy = mAttrsPy.find(ns); - if (itPy != mAttrsPy.end()) { - // Insert the attribute back in C++ - mAttrs.emplace(std::make_pair(ns, future_std::any(itPy->second.cast<DynamicAttributes>()))); - } - } -#endif return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(ns)).getAttr<T>(nsName); } } @@ -106,23 +95,16 @@ public: // We cannot handle Python object if the Python interpreter is not running if (Py_IsInitialized()) { // Keep a copy of the attribute in py::object that is updated everytime - mAttrsPy.emplace(std::make_pair(pascalToSnake(name), py::cast(value))); + const auto& resPy = mAttrsPy.emplace(std::make_pair(pascalToSnake(name), py::cast(value))); + AIDGE_ASSERT(resPy.second, "attribute \"{}\" already exists (added in Python)", name); } #endif } else { const auto ns = name.substr(0, dot); + AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace for \"{}\".", ns); const auto nsName = name.substr(dot + 1); const auto& res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes()))); - -#ifdef PYBIND - // We cannot handle Python object if the Python interpreter is not running - if (Py_IsInitialized()) { - // Keep a copy of the attribute in py::object that is updated everytime - mAttrsPy.emplace(std::make_pair(ns, py::cast(DynamicAttributes()))); - } -#endif - future_std::any_cast<DynamicAttributes&>(res.first->second).addAttr(nsName, value); } } @@ -135,6 +117,7 @@ public: { const auto dot = name.find('.'); if (dot == name.npos) { + AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes for \"{}\".", name); auto res = mAttrs.emplace(std::make_pair(name, future_std::any(value))); if (!res.second) res.first->second = future_std::any(value); @@ -151,17 +134,9 @@ public: } else { const auto ns = name.substr(0, dot); + AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace for \"{}\".", ns); const auto nsName = name.substr(dot + 1); auto res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes()))); - -#ifdef PYBIND - // We cannot handle Python object if the Python interpreter is not running - if (Py_IsInitialized()) { - // Keep a copy of the attribute in py::object that is updated everytime - auto resPy = mAttrsPy.emplace(std::make_pair(ns, py::cast(DynamicAttributes()))); - } -#endif - future_std::any_cast<DynamicAttributes&>(res.first->second).setAttr<T>(nsName, value); } } @@ -169,18 +144,17 @@ public: void delAttr(const std::string& name) { const auto dot = name.find('.'); if (dot == name.npos) { + AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes for \"{}\".", name); mAttrs.erase(name); #ifdef PYBIND - mAttrsPy.erase(name); + mAttrsPy.erase(pascalToSnake(name)); #endif } else { const auto ns = name.substr(0, dot); + AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace for \"{}\".", ns); const auto nsName = name.substr(dot + 1); future_std::any_cast<DynamicAttributes&>(mAttrs.at(ns)).delAttr(nsName); -#ifdef PYBIND - mAttrsPy.erase(name); -#endif } } @@ -191,15 +165,16 @@ public: if (dot == name.npos) { AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python for \"{}\".", name); auto it = mAttrs.find(snakeToPascal(name)); - AIDGE_ASSERT(it == mAttrs.end(), "attribute \"{}\" already exists", name); + AIDGE_ASSERT(it == mAttrs.end(), "attribute \"{}\" already exists (added in C++)", name); const auto& res = mAttrsPy.emplace(std::make_pair(name, value)); AIDGE_ASSERT(res.second, "attribute \"{}\" already exists", name); } else { const auto ns = name.substr(0, dot); + AIDGE_ASSERT(isSnakeCase(ns), "Aidge standard requires snake_case for Attributes namespace with Python for \"{}\".", ns); const auto nsName = name.substr(dot + 1); - const auto& res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes()))); + const auto& res = mAttrs.emplace(std::make_pair(snakeToPascal(ns), DynamicAttributes())); future_std::any_cast<DynamicAttributes&>(res.first->second).addAttrPy(nsName, std::move(value)); } @@ -220,8 +195,9 @@ public: } else { const auto ns = name.substr(0, dot); + AIDGE_ASSERT(isSnakeCase(ns), "Aidge standard requires snake_case for Attributes namespace with Python for \"{}\".", ns); const auto nsName = name.substr(dot + 1); - const auto& res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes()))); + const auto& res = mAttrs.emplace(std::make_pair(snakeToPascal(ns), DynamicAttributes())); future_std::any_cast<DynamicAttributes&>(res.first->second).setAttrPy(nsName, std::move(value)); } @@ -229,6 +205,12 @@ public: py::dict dict() const override { py::dict attributes; + for (const auto& elt : mAttrs) { + const std::string snakeName = pascalToSnake(elt.first); + if (elt.second.type() == typeid(DynamicAttributes)) { + attributes[snakeName.c_str()] = future_std::any_cast<const DynamicAttributes&>(elt.second).dict(); + } + } for (const auto& elt : mAttrsPy) { const std::string snakeName = pascalToSnake(elt.first); attributes[snakeName.c_str()] = elt.second; @@ -255,10 +237,16 @@ public: const auto dot = name.find('.'); if (dot == name.npos) { AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes for \"{}\".", name); +#ifdef PYBIND + return (mAttrs.find(name) != mAttrs.cend() || mAttrsPy.find(pascalToSnake(name)) != mAttrsPy.cend()); + +#else return (mAttrs.find(name) != mAttrs.cend()); +#endif } else { const auto ns = name.substr(0, dot); + AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace \"{}\".", ns); const auto it = mAttrs.find(ns); if (it != mAttrs.cend()) { const auto nsName = name.substr(dot + 1); @@ -280,19 +268,14 @@ public: } else { const auto ns = name.substr(0, dot); - const auto nsName = name.substr(dot + 1); - const auto it = mAttrs.find(ns); + AIDGE_ASSERT(isSnakeCase(ns), "Aidge standard requires snake_case for Attributes namespace with Python for \"{}\".", ns); + const auto it = mAttrs.find(snakeToPascal(ns)); if (it != mAttrs.cend()) { + const auto nsName = name.substr(dot + 1); return future_std::any_cast<const DynamicAttributes&>(it->second).hasAttrPy(nsName); } else { - const auto itPy = mAttrsPy.find(ns); - if (itPy != mAttrsPy.cend()) { - return itPy->second.cast<DynamicAttributes>().hasAttrPy(nsName); - } - else { - return false; - } + return false; } } } @@ -320,18 +303,6 @@ public: else { const auto ns = name.substr(0, dot); const auto nsName = name.substr(dot + 1); - -#ifdef PYBIND - // If attribute does not exist in C++, it might have been created in Python - auto it = mAttrs.find(ns); - if (it == mAttrs.end()) { - auto itPy = mAttrsPy.find(ns); - if (itPy != mAttrsPy.end()) { - return itPy->second.cast<DynamicAttributes>().getAttrType(nsName); - } - } -#endif - return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(ns)).getAttrType(nsName); } } @@ -357,12 +328,24 @@ public: inline py::object getAttrPy(const std::string& name) const override final { const auto dot = name.find('.'); if (dot == name.npos) { - return mAttrsPy.at(name); + AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python for \"{}\".", name); + auto itPy = mAttrsPy.find(name); + if (itPy == mAttrsPy.end()) { + // Attribute may be a namespace + auto it = mAttrs.find(snakeToPascal(name)); + AIDGE_ASSERT(it != mAttrs.end() && it->second.type() == typeid(DynamicAttributes), "attribute \"{}\" not found", name); + return py::cast(future_std::any_cast<const DynamicAttributes&>(it->second)); + } + else { + return itPy->second; + } } else { + // Namespace is stored in mAttrs, so convention should be pascal. const auto ns = name.substr(0, dot); + AIDGE_ASSERT(isSnakeCase(ns), "Aidge standard requires snake_case for Attributes namespace with Python for \"{}\".", ns); const auto nsName = name.substr(dot + 1); - return mAttrsPy.at(ns).cast<DynamicAttributes>().getAttrPy(nsName); + return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(snakeToPascal(ns))).getAttrPy(nsName); } }; #endif diff --git a/python_binding/utils/pybind_Attributes.cpp b/python_binding/utils/pybind_Attributes.cpp index 7f5dde63c4835eb694d5fd2d571d7c9c1fd5a9ac..d449feaad9a7234e6f67215737795672fc54e9ac 100644 --- a/python_binding/utils/pybind_Attributes.cpp +++ b/python_binding/utils/pybind_Attributes.cpp @@ -24,6 +24,9 @@ DynamicAttributes test_DynamicAttributes_binding() { attrs.addAttr<int>("A", 42); attrs.addAttr<std::string>("B", "test"); attrs.addAttr<std::vector<bool>>("C", {true, false, true}); + attrs.addAttr("Mem.A", 1); + attrs.addAttr("Mem.Data.B", 1.0f); + attrs.addAttr("Impl.C", std::string("test")); return attrs; }