diff --git a/src/App/DocumentObjectPyImp.cpp b/src/App/DocumentObjectPyImp.cpp index b4b99fa4ab..677f8b3e24 100644 --- a/src/App/DocumentObjectPyImp.cpp +++ b/src/App/DocumentObjectPyImp.cpp @@ -257,13 +257,42 @@ PyObject* DocumentObjectPy::setExpression(PyObject * args) } -PyObject *DocumentObjectPy::getCustomAttributes(const char* /*attr*/) const +PyObject *DocumentObjectPy::getCustomAttributes(const char* attr) const { - return 0; + // search for dynamic property + Property* prop = getDocumentObjectPtr()->getDynamicPropertyByName(attr); + if (prop) + return prop->getPyObject(); + else + return 0; } int DocumentObjectPy::setCustomAttributes(const char* attr, PyObject *obj) { + // explicitly search for dynamic property + try { + Property* prop = getDocumentObjectPtr()->getDynamicPropertyByName(attr); + if (prop) { + prop->setPyObject(obj); + return 1; + } + } + catch (Base::ValueError &exc) { + std::stringstream s; + s << "Property '" << attr << "': " << exc.what(); + throw Py::ValueError(s.str()); + } + catch (Base::Exception &exc) { + std::stringstream s; + s << "Attribute (Name: " << attr << ") error: '" << exc.what() << "' "; + throw Py::AttributeError(s.str()); + } + catch (...) { + std::stringstream s; + s << "Unknown error in attribute " << attr; + throw Py::AttributeError(s.str()); + } + // search in PropertyList Property *prop = getDocumentObjectPtr()->getPropertyByName(attr); if (prop) { diff --git a/src/App/FeaturePython.cpp b/src/App/FeaturePython.cpp index bcd06b459e..77094557ad 100644 --- a/src/App/FeaturePython.cpp +++ b/src/App/FeaturePython.cpp @@ -31,8 +31,9 @@ #include #include +#include #include "FeaturePython.h" -#include "FeaturePythonPyImp.h" +#include "FeaturePythonPyImp.h" using namespace App; @@ -192,7 +193,7 @@ PyObject *FeaturePythonImp::getPyObject(void) // --------------------------------------------------------- namespace App { -PROPERTY_SOURCE_TEMPLATE(App::FeaturePython, App::DocumentObject) +PROPERTY_SOURCE_TEMPLATE(App::FeaturePython, App::DocumentObject) template<> const char* App::FeaturePython::getViewProviderName(void) const { return "Gui::ViewProviderPythonFeature"; } @@ -208,11 +209,11 @@ template class AppExport FeaturePythonT; } // --------------------------------------------------------- - + namespace App { -PROPERTY_SOURCE_TEMPLATE(App::GeometryPython, App::GeoFeature) +PROPERTY_SOURCE_TEMPLATE(App::GeometryPython, App::GeoFeature) template<> const char* App::GeometryPython::getViewProviderName(void) const { return "Gui::ViewProviderPythonGeometry"; } // explicit template instantiation -template class AppExport FeaturePythonT; } +template class AppExport FeaturePythonT;} diff --git a/src/App/FeaturePythonPyImp.h b/src/App/FeaturePythonPyImp.h index 3ee30c4717..e56d4fa3fe 100644 --- a/src/App/FeaturePythonPyImp.h +++ b/src/App/FeaturePythonPyImp.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include namespace App { @@ -39,21 +39,15 @@ class FeaturePythonPyT : public FeaturePyT { public: static PyTypeObject Type; - static PyMethodDef Methods[]; public: - FeaturePythonPyT(DocumentObject *pcObject, PyTypeObject *T = &Type); + FeaturePythonPyT(PropertyContainer *pcObject, PyTypeObject *T = &Type); virtual ~FeaturePythonPyT(); /** @name callbacks and implementers for the python object methods */ //@{ static int __setattr(PyObject *PyObj, char *attr, PyObject *value); //@} - - /// getter method for special attributes (e.g. dynamic ones) - PyObject *getCustomAttributes(const char* attr) const; - /// setter for special attributes (e.g. dynamic ones) - int setCustomAttributes(const char* attr, PyObject *obj); PyObject *_getattr(char *attr); // __getattr__ function int _setattr(char *attr, PyObject *value); // __setattr__ function diff --git a/src/App/FeaturePythonPyImp.inl b/src/App/FeaturePythonPyImp.inl index 871b91c5d3..922f1c0797 100644 --- a/src/App/FeaturePythonPyImp.inl +++ b/src/App/FeaturePythonPyImp.inl @@ -58,7 +58,7 @@ PyTypeObject FeaturePythonPyT::Type = { 0, /*tp_weaklistoffset */ 0, /*tp_iter */ 0, /*tp_iternext */ - App::FeaturePythonPyT::Methods, /*tp_methods */ + 0, /*tp_methods */ 0, /*tp_members */ 0, /*tp_getset */ &FeaturePyT::Type, /*tp_base */ @@ -80,14 +80,8 @@ PyTypeObject FeaturePythonPyT::Type = { 0 /*tp_version_tag */ }; -/// Methods structure of FeaturePythonPyT template -PyMethodDef FeaturePythonPyT::Methods[] = { - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -template -FeaturePythonPyT::FeaturePythonPyT(DocumentObject *pcObject, PyTypeObject *T) +FeaturePythonPyT::FeaturePythonPyT(PropertyContainer *pcObject, PyTypeObject *T) : FeaturePyT(reinterpret_cast(pcObject), T) { } @@ -100,6 +94,8 @@ FeaturePythonPyT::~FeaturePythonPyT() template int FeaturePythonPyT::__setattr(PyObject *obj, char *attr, PyObject *value) { + // This overwrites PyObjectBase::__setattr because this actively disallows to delete an attribute + // if (!static_cast(obj)->isValid()){ PyErr_Format(PyExc_ReferenceError, "Cannot access attribute '%s' of deleted object", attr); return -1; @@ -112,68 +108,9 @@ int FeaturePythonPyT::__setattr(PyObject *obj, char *attr, PyObject return ret; } -template -PyObject *FeaturePythonPyT::_getattr(char *attr) -{ - try { - // getter method for special Attributes (e.g. dynamic ones) - PyObject *r = getCustomAttributes(attr); - if(r) return r; - } - catch(const Base::Exception& e) {// catch the FreeCAD exceptions - std::string str; - str += "FreeCAD exception thrown ("; - str += e.what(); - str += ")"; - e.ReportException(); - PyErr_SetString(Base::BaseExceptionFreeCADError,str.c_str()); - return NULL; - } - catch(const Py::Exception&) { - // The exception text is already set - return NULL; - } - - PyObject *rvalue = Py_FindMethod(Methods, this, attr); - if (rvalue == NULL) { - std::map::iterator it = dyn_methods.find(attr); - if (it != dyn_methods.end()) { - Py_INCREF(it->second); - rvalue = it->second; - PyErr_Clear(); - } - } - if (rvalue == NULL) { - PyErr_Clear(); - return FeaturePyT::_getattr(attr); - } - else { - return rvalue; - } -} - template int FeaturePythonPyT::_setattr(char *attr, PyObject *value) { - try { - // setter for special Attributes (e.g. dynamic ones) - int r = setCustomAttributes(attr, value); - if(r==1) return 0; - } - catch(const Base::Exception& e) { // catch the FreeCAD exceptions - std::string str; - str += "FreeCAD exception thrown ("; - str += e.what(); - str += ")"; - e.ReportException(); - PyErr_SetString(Base::BaseExceptionFreeCADError,str.c_str()); - return -1; - } - catch(const Py::Exception&) { - // The exception text is already set - return -1; - } - int returnValue = FeaturePyT::_setattr(attr, value); if (returnValue == -1) { if (value) { @@ -201,64 +138,49 @@ int FeaturePythonPyT::_setattr(char *attr, PyObject *value) return returnValue; } -// ------------------------------------------------------------- - template -PyObject *FeaturePythonPyT::getCustomAttributes(const char* attr) const +PyObject *FeaturePythonPyT::_getattr(char *attr) { - PY_TRY{ - if (Base::streq(attr, "__dict__")){ - // Return the default dict - PyTypeObject *tp = this->ob_type; - // register type if needed - if (tp->tp_dict == NULL) { - if (PyType_Ready(tp) < 0) - return 0; - } - - PyObject* dict = PyDict_Copy(tp->tp_dict); - std::map Map; - FeaturePyT::getPropertyContainerPtr()->getPropertyMap(Map); - for (std::map::iterator it = Map.begin(); it != Map.end(); ++it) - PyDict_SetItem(dict, PyString_FromString(it->first.c_str()), PyString_FromString("")); - for (std::map::const_iterator it = dyn_methods.begin(); it != dyn_methods.end(); ++it) - PyDict_SetItem(dict, PyString_FromString(it->first.c_str()), PyString_FromString("")); - if (PyErr_Occurred()) { - Py_DECREF(dict); - dict = 0; - } - return dict; - } - - // search for dynamic property - Property* prop = FeaturePyT::getDocumentObjectPtr()->getDynamicPropertyByName(attr); - if (prop) - return prop->getPyObject(); - else - return 0; - } PY_CATCH -} - -template -int FeaturePythonPyT::setCustomAttributes(const char* attr, PyObject *value) -{ - // search for dynamic property - Property* prop = FeaturePyT::getDocumentObjectPtr()->getDynamicPropertyByName(attr); - - if (!prop) - return FeaturePyT::setCustomAttributes(attr, value); - else { - try { - prop->setPyObject(value); - return 1; - } catch (Base::Exception &exc) { - PyErr_Format(PyExc_AttributeError, "Attribute (Name: %s) error: '%s' ", attr, exc.what()); - return -1; - } catch (...) { - PyErr_Format(PyExc_AttributeError, "Unknown error in attribute %s", attr); - return -1; - } + // See CallTipsList::extractTips + if (Base::streq(attr, "__fc_template__")) { + Py_INCREF(Py_None); + return Py_None; } + + if (Base::streq(attr, "__dict__")) { + // Return the default dict + PyTypeObject *tp = this->ob_type; + // register type if needed + if (tp->tp_dict == NULL) { + if (PyType_Ready(tp) < 0) + return 0; + } + + PyObject* dict = PyDict_Copy(tp->tp_dict); + std::map Map; + FeaturePyT::getPropertyContainerPtr()->getPropertyMap(Map); + for (std::map::iterator it = Map.begin(); it != Map.end(); ++it) + PyDict_SetItem(dict, PyString_FromString(it->first.c_str()), PyString_FromString("")); + for (std::map::const_iterator it = dyn_methods.begin(); it != dyn_methods.end(); ++it) + PyDict_SetItem(dict, PyString_FromString(it->first.c_str()), PyString_FromString("")); + if (PyErr_Occurred()) { + Py_DECREF(dict); + dict = 0; + } + return dict; + } + + PyObject *rvalue = NULL; + std::map::iterator it = dyn_methods.find(attr); + if (it != dyn_methods.end()) { + Py_INCREF(it->second); + rvalue = it->second; + PyErr_Clear(); + return rvalue; + } + + PyErr_Clear(); + return FeaturePyT::_getattr(attr); } } //namespace App diff --git a/src/Gui/CallTips.cpp b/src/Gui/CallTips.cpp index d1b5565dc9..f76dc9cc5b 100644 --- a/src/Gui/CallTips.cpp +++ b/src/Gui/CallTips.cpp @@ -261,8 +261,9 @@ QMap CallTipsList::extractTips(const QString& context) const // From the template Python object we don't query its type object because there we keep // a list of additional methods that we won't see otherwise. But to get the correct doc // strings we query the type's dict in the class itself. - // To see if we have a template Python object we check for the existence of supportedProperties - if (!type.hasAttr("supportedProperties")) { + // To see if we have a template Python object we check for the existence of '__fc_template__' + // See also: FeaturePythonPyT + if (!obj.hasAttr("__fc_template__")) { obj = type; } } diff --git a/src/Mod/Test/Document.py b/src/Mod/Test/Document.py index f2b4b3a181..d6741ff33a 100644 --- a/src/Mod/Test/Document.py +++ b/src/Mod/Test/Document.py @@ -150,7 +150,7 @@ class DocumentBasicCases(unittest.TestCase): enumeration_choices = ["one", "two"] obj = self.Doc.addObject("App::FeaturePython","Label_2") obj.addProperty("App::PropertyEnumeration", "myEnumeration", "Enum", "mytest") - with self.assertRaises(FreeCAD.Base.FreeCADError): + with self.assertRaises(ValueError): obj.myEnumeration = enumeration_choices[0] def testMem(self):