diff options
author | Christian Tismer <tismer@stackless.com> | 2020-09-28 15:38:24 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2020-10-12 16:56:51 +0200 |
commit | d3883e9186892e673e8a66ad6651409d5eaf7cf3 (patch) | |
tree | 447c9927a19bb827caebbc03cffa49af86d43bc8 /sources/pyside2/libpyside/pysideproperty.cpp | |
parent | 8847a47aad95d7f85d5e184071bf95c44826c4c7 (diff) |
Update QtCore.Property to the current standard
WAS: Feature-select: Use QtCore.Property instead of Python's property
When successfully trying to use QtCore.Property for feature-select's
property, I suddenly realized that the implementation only works with
QtCore derived classes.
This is the reworked version of Property that behaves correctly.
The exhaustive Python test was adapted and is used to check the full
compatibility of this implementation.
In a later update, this implementation might be changed to no longer
be restricted to QObject.
Change-Id: If87b7b633a2c45e23a15c4d956f63e21d33af3dd
Task-number: PYSIDE-1402
Reviewed-by: Christian Tismer <tismer@stackless.com>
Diffstat (limited to 'sources/pyside2/libpyside/pysideproperty.cpp')
-rw-r--r-- | sources/pyside2/libpyside/pysideproperty.cpp | 225 |
1 files changed, 173 insertions, 52 deletions
diff --git a/sources/pyside2/libpyside/pysideproperty.cpp b/sources/pyside2/libpyside/pysideproperty.cpp index 8aaa81205..443098f2e 100644 --- a/sources/pyside2/libpyside/pysideproperty.cpp +++ b/sources/pyside2/libpyside/pysideproperty.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt for Python. @@ -47,6 +47,8 @@ #include <shiboken.h> #include <signature.h> +using namespace Shiboken; + extern "C" { @@ -55,25 +57,41 @@ static int qpropertyTpInit(PyObject *, PyObject *, PyObject *); static void qpropertyDeAlloc(PyObject *self); //methods -static PyObject *qPropertyCall(PyObject *, PyObject *, PyObject *); -static PyObject *qPropertySetter(PyObject *, PyObject *); static PyObject *qPropertyGetter(PyObject *, PyObject *); +static PyObject *qPropertySetter(PyObject *, PyObject *); +static PyObject *qPropertyResetter(PyObject *, PyObject *); +static PyObject *qPropertyDeleter(PyObject *, PyObject *); +static PyObject *qPropertyCall(PyObject *, PyObject *, PyObject *); static int qpropertyTraverse(PyObject *self, visitproc visit, void *arg); static int qpropertyClear(PyObject *self); // Attributes static PyObject *qPropertyDocGet(PyObject *, void *); +static int qPropertyDocSet(PyObject *, PyObject *, void *); +static PyObject *qProperty_fget(PyObject *, void *); +static PyObject *qProperty_fset(PyObject *, void *); +static PyObject *qProperty_freset(PyObject *, void *); +static PyObject *qProperty_fdel(PyObject *, void *); static PyMethodDef PySidePropertyMethods[] = { - {"setter", (PyCFunction)qPropertySetter, METH_O, 0}, - {"write", (PyCFunction)qPropertySetter, METH_O, 0}, {"getter", (PyCFunction)qPropertyGetter, METH_O, 0}, + {"setter", (PyCFunction)qPropertySetter, METH_O, 0}, + {"resetter", (PyCFunction)qPropertyResetter, METH_O, 0}, + {"deleter", (PyCFunction)qPropertyDeleter, METH_O, 0}, + // Synonyms from Qt {"read", (PyCFunction)qPropertyGetter, METH_O, 0}, + {"write", (PyCFunction)qPropertySetter, METH_O, 0}, {0, 0, 0, 0} }; static PyGetSetDef PySidePropertyType_getset[] = { - {const_cast<char *>("__doc__"), qPropertyDocGet, nullptr, nullptr, nullptr}, + // Note: we could not use `PyMemberDef` like Python's properties, + // because of the indirection of PySidePropertyPrivate. + {const_cast<char *>("fget"), qProperty_fget, nullptr, nullptr, nullptr}, + {const_cast<char *>("fset"), qProperty_fset, nullptr, nullptr, nullptr}, + {const_cast<char *>("freset"), qProperty_freset, nullptr, nullptr, nullptr}, + {const_cast<char *>("fdel"), qProperty_fdel, nullptr, nullptr, nullptr}, + {const_cast<char *>("__doc__"), qPropertyDocGet, qPropertyDocSet, nullptr, nullptr}, {nullptr, nullptr, nullptr, nullptr, nullptr} }; @@ -175,21 +193,26 @@ static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds) pData->metaCallHandler = &qpropertyMetaCall; static const char *kwlist[] = {"type", "fget", "fset", "freset", "fdel", "doc", "notify", - "designable", "scriptable", "stored", "user", - "constant", "final", 0}; + "designable", "scriptable", "stored", + "user", "constant", "final", 0}; char *doc{}; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "O|OOOOsObbbbbb:QtCore.QProperty", + "O|OOOOsObbbbbb:QtCore.Property", const_cast<char **>(kwlist), /*OO*/ &type, &(pData->fget), /*OOO*/ &(pData->fset), &(pData->freset), &(pData->fdel), /*s*/ &doc, /*O*/ &(pData->notify), - /*bbbbbb*/ &(pData->designable), &(pData->scriptable), &(pData->stored), &(pData->user), &(pData->constant), &(pData->final))) { + /*bbb*/ &(pData->designable), &(pData->scriptable), &(pData->stored), + /*bbb*/ &(pData->user), &(pData->constant), &(pData->final))) { return -1; } + // PYSIDE-1019: Fetching the default `__doc__` from fget would fail for inherited functions + // because we don't initialize the mro with signatures (and we will not!). + // But it is efficient and in-time to do that on demand in qPropertyDocGet. + pData->getter_doc = false; if (doc) pData->doc = doc; else @@ -229,53 +252,107 @@ static void qpropertyDeAlloc(PyObject *self) Py_TYPE(self)->tp_free(self); } -static PyObject *qPropertyCall(PyObject *self, PyObject *args, PyObject * /* kw */) +static PyObject * +_property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *reset, PyObject *del) { - PyObject *callback = PyTuple_GetItem(args, 0); - if (PyFunction_Check(callback)) { - auto prop = reinterpret_cast<PySideProperty *>(self); - PySidePropertyPrivate *pData = prop->d; + PySideProperty *pold = reinterpret_cast<PySideProperty *>(old); + PySidePropertyPrivate *pData = pold->d; - Py_INCREF(callback); - pData->fget = callback; + AutoDecRef type(PyObject_Type(old)); + QByteArray doc{}; + if (type.isNull()) + return nullptr; - Py_INCREF(self); - return self; + if (get == nullptr || get == Py_None) { + Py_XDECREF(get); + get = pData->fget ? pData->fget : Py_None; + } + if (set == nullptr || set == Py_None) { + Py_XDECREF(set); + set = pData->fset ? pData->fset : Py_None; + } + if (reset == nullptr || reset == Py_None) { + Py_XDECREF(reset); + reset = pData->freset ? pData->freset : Py_None; + } + if (del == nullptr || del == Py_None) { + Py_XDECREF(del); + del = pData->fdel ? pData->fdel : Py_None; + } + if (pData->getter_doc && get != Py_None) { + /* make _init use __doc__ from getter */ + doc = ""; + } + else { + doc = !pData->doc.isEmpty() ? pData->doc : ""; } - PyErr_SetString(PyExc_TypeError, "Invalid property usage."); - return nullptr; + auto notify = pData->notify ? pData->notify : Py_None; + + PyObject *typeName = String::fromCString(pData->typeName); + PyObject *obNew = PyObject_CallFunction(type, const_cast<char *>("OOOOOsO" "bbb" "bbb"), + typeName, get, set, reset, del, doc.data(), notify, + pData->designable, pData->scriptable, pData->stored, + pData->user, pData->constant, pData->final); + + return obNew; } -static PyObject *qPropertySetter(PyObject *self, PyObject *callback) +static PyObject *qPropertyGetter(PyObject *self, PyObject *getter) { - if (PyFunction_Check(callback)) { - PySideProperty *prop = reinterpret_cast<PySideProperty *>(self); - PySidePropertyPrivate *pData = prop->d; + return _property_copy(self, getter, nullptr, nullptr, nullptr); +} - Py_INCREF(callback); - pData->fset = callback; +static PyObject *qPropertySetter(PyObject *self, PyObject *setter) +{ + return _property_copy(self, nullptr, setter, nullptr, nullptr); +} - Py_INCREF(callback); - return callback; - } - PyErr_SetString(PyExc_TypeError, "Invalid property setter agument."); - return nullptr; +static PyObject *qPropertyResetter(PyObject *self, PyObject *resetter) +{ + return _property_copy(self, nullptr, nullptr, resetter, nullptr); } -static PyObject *qPropertyGetter(PyObject *self, PyObject *callback) +static PyObject *qPropertyDeleter(PyObject *self, PyObject *deleter) { - if (PyFunction_Check(callback)) { - PySideProperty *prop = reinterpret_cast<PySideProperty *>(self); - PySidePropertyPrivate *pData = prop->d; + return _property_copy(self, nullptr, nullptr, nullptr, deleter); +} - Py_INCREF(callback); - pData->fget = callback; +static PyObject *qPropertyCall(PyObject *self, PyObject *args, PyObject * /* kw */) +{ + PyObject *getter = PyTuple_GetItem(args, 0); + return _property_copy(self, getter, nullptr, nullptr, nullptr); +} - Py_INCREF(callback); - return callback; - } - PyErr_SetString(PyExc_TypeError, "Invalid property getter agument."); - return nullptr; +// PYSIDE-1019: Provide the same getters as Pythons `PyProperty`. +static PyObject *_property_func(PyObject *self, ssize_t offset) +{ + auto data = reinterpret_cast<PySideProperty *>(self); + PySidePropertyPrivate *pData = data->d; + auto funcptr = reinterpret_cast<char *>(pData) + offset; + auto func = *reinterpret_cast<PyObject **>(funcptr); + auto ret = func != nullptr ? func : Py_None; + Py_INCREF(ret); + return ret; +} + +static PyObject *qProperty_fget(PyObject *self, void *) +{ + return _property_func(self, offsetof(PySidePropertyPrivate, fget)); +} + +static PyObject *qProperty_fset(PyObject *self, void *) +{ + return _property_func(self, offsetof(PySidePropertyPrivate, fset)); +} + +static PyObject *qProperty_freset(PyObject *self, void *) +{ + return _property_func(self, offsetof(PySidePropertyPrivate, freset)); +} + +static PyObject *qProperty_fdel(PyObject *self, void *) +{ + return _property_func(self, offsetof(PySidePropertyPrivate, fdel)); } static PyObject *qPropertyDocGet(PyObject *self, void *) @@ -291,9 +368,40 @@ static PyObject *qPropertyDocGet(PyObject *self, void *) return PyString_FromString(doc); #endif } + if (pData->fget != nullptr) { + // PYSIDE-1019: Fetch the default `__doc__` from fget. We do it late. + AutoDecRef get_doc(PyObject_GetAttr(pData->fget, PyMagicName::doc())); + if (!get_doc.isNull()) { + pData->doc = String::toCString(get_doc); + pData->getter_doc = true; + if (Py_TYPE(self) == PySidePropertyTypeF()) + return qPropertyDocGet(self, nullptr); + /* + * If this is a property subclass, put __doc__ in dict of the + * subclass instance instead, otherwise it gets shadowed by + * __doc__ in the class's dict. + */ + auto get_doc_obj = get_doc.object(); + int err = PyObject_SetAttr(self, PyMagicName::doc(), get_doc); + return err < 0 ? nullptr : (Py_INCREF(get_doc_obj), get_doc_obj); + } + PyErr_Clear(); + } Py_RETURN_NONE; } +static int qPropertyDocSet(PyObject *self, PyObject *value, void *) +{ + auto data = reinterpret_cast<PySideProperty *>(self); + PySidePropertyPrivate *pData = data->d; + + if (String::check(value)) { + pData->doc = String::toCString(value); + return 0; + } + PyErr_SetString(PyExc_TypeError, "String argument expected."); + return -1; +} static int qpropertyTraverse(PyObject *self, visitproc visit, void *arg) { @@ -354,14 +462,20 @@ static PyObject *getFromType(PyTypeObject *type, PyObject *name) namespace PySide { namespace Property { static const char *Property_SignatureStrings[] = { - "PySide2.QtCore.Property(type:type,fget:typing.Callable=None,fset:typing.Callable=None," + "PySide2.QtCore.Property(self,type:type,fget:typing.Callable=None,fset:typing.Callable=None," "freset:typing.Callable=None,fdel:typing.Callable=None,doc:str=None," "notify:typing.Callable=None,designable:bool=True,scriptable:bool=True," - "stored:bool=True,user:bool=False,constant:bool=False,final:bool=False)", - "PySide2.QtCore.Property.getter(func:typing.Callable)", - "PySide2.QtCore.Property.read(func:typing.Callable)", - "PySide2.QtCore.Property.setter(func:typing.Callable)", - "PySide2.QtCore.Property.write(func:typing.Callable)", + "stored:bool=True,user:bool=False,constant:bool=False,final:bool=False)" + "->PySide2.QtCore.Property", + "PySide2.QtCore.Property.deleter(self,func:typing.Callable)", + "PySide2.QtCore.Property.fdel(self)->typing.Callable", + "PySide2.QtCore.Property.fget(self)->typing.Callable", + "PySide2.QtCore.Property.freset(self)->typing.Callable", + "PySide2.QtCore.Property.fset(self)->typing.Callable", + "PySide2.QtCore.Property.getter(self,func:typing.Callable)", + "PySide2.QtCore.Property.read(self,func:typing.Callable)", + "PySide2.QtCore.Property.setter(self,func:typing.Callable)", + "PySide2.QtCore.Property.write(self,func:typing.Callable)", nullptr}; // Sentinel void init(PyObject *module) @@ -384,7 +498,7 @@ bool checkType(PyObject *pyObj) int setValue(PySideProperty *self, PyObject *source, PyObject *value) { PyObject *fset = self->d->fset; - if (fset) { + if (fset && value) { Shiboken::AutoDecRef args(PyTuple_New(2)); PyTuple_SET_ITEM(args, 0, source); PyTuple_SET_ITEM(args, 1, value); @@ -392,9 +506,16 @@ int setValue(PySideProperty *self, PyObject *source, PyObject *value) Py_INCREF(value); Shiboken::AutoDecRef result(PyObject_CallObject(fset, args)); return (result.isNull() ? -1 : 0); - } else { - PyErr_SetString(PyExc_AttributeError, "Attibute read only"); } + PyObject *fdel = self->d->fdel; + if (fdel) { + Shiboken::AutoDecRef args(PyTuple_New(1)); + PyTuple_SET_ITEM(args, 0, source); + Py_INCREF(source); + Shiboken::AutoDecRef result(PyObject_CallObject(fdel, args)); + return (result.isNull() ? -1 : 0); + } + PyErr_SetString(PyExc_AttributeError, "Attibute read only"); return -1; } |