aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2020-09-28 15:38:24 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2020-10-12 16:56:51 +0200
commitd3883e9186892e673e8a66ad6651409d5eaf7cf3 (patch)
tree447c9927a19bb827caebbc03cffa49af86d43bc8
parent8847a47aad95d7f85d5e184071bf95c44826c4c7 (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>
-rw-r--r--sources/pyside2/libpyside/pysideproperty.cpp225
-rw-r--r--sources/pyside2/libpyside/pysideproperty_p.h1
-rw-r--r--sources/pyside2/tests/QtCore/qproperty_decorator.py4
-rw-r--r--sources/pyside2/tests/pysidetest/CMakeLists.txt1
-rw-r--r--sources/pyside2/tests/pysidetest/property_python_test.py232
5 files changed, 410 insertions, 53 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;
}
diff --git a/sources/pyside2/libpyside/pysideproperty_p.h b/sources/pyside2/libpyside/pysideproperty_p.h
index 4db638021..e7b6e4d77 100644
--- a/sources/pyside2/libpyside/pysideproperty_p.h
+++ b/sources/pyside2/libpyside/pysideproperty_p.h
@@ -56,6 +56,7 @@ struct PySidePropertyPrivate
PyObject *freset = nullptr;
PyObject *fdel = nullptr;
PyObject *notify = nullptr;
+ bool getter_doc = false;
QByteArray notifySignature;
QByteArray doc;
bool designable = true;
diff --git a/sources/pyside2/tests/QtCore/qproperty_decorator.py b/sources/pyside2/tests/QtCore/qproperty_decorator.py
index aa31e59c4..c845ac6d3 100644
--- a/sources/pyside2/tests/QtCore/qproperty_decorator.py
+++ b/sources/pyside2/tests/QtCore/qproperty_decorator.py
@@ -47,7 +47,9 @@ class MyObject(QObject):
return self._value
@value.setter
- def valueSet(self, value):
+ # Note: The name of property and setter must be the same, because the
+ # object changes its identity all the time. `valueSet` no longer works.
+ def value(self, value):
self._value = value
diff --git a/sources/pyside2/tests/pysidetest/CMakeLists.txt b/sources/pyside2/tests/pysidetest/CMakeLists.txt
index 577b51815..0dbd2b7cf 100644
--- a/sources/pyside2/tests/pysidetest/CMakeLists.txt
+++ b/sources/pyside2/tests/pysidetest/CMakeLists.txt
@@ -161,6 +161,7 @@ PYSIDE_TEST(modelview_test.py)
PYSIDE_TEST(new_inherited_functions_test.py)
PYSIDE_TEST(notify_id.py)
PYSIDE_TEST(properties_test.py)
+PYSIDE_TEST(property_python_test.py)
PYSIDE_TEST(qapp_like_a_macro_test.py)
PYSIDE_TEST(qvariant_test.py)
PYSIDE_TEST(repr_test.py)
diff --git a/sources/pyside2/tests/pysidetest/property_python_test.py b/sources/pyside2/tests/pysidetest/property_python_test.py
new file mode 100644
index 000000000..ec2940b14
--- /dev/null
+++ b/sources/pyside2/tests/pysidetest/property_python_test.py
@@ -0,0 +1,232 @@
+#############################################################################
+##
+## Copyright (C) 2020 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the test suite of Qt for Python.
+##
+## $QT_BEGIN_LICENSE:GPL-EXCEPT$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 3 as published by the Free Software
+## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""
+Test for PySide's Property
+==========================
+
+This test is copied from Python's `test_property.py` and adapted to
+the PySide Property implementation.
+
+This test is to ensure maximum compatibility.
+"""
+
+# Test case for property
+# more tests are in test_descr
+
+import os
+import sys
+import unittest
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide2.QtCore import Property, QObject
+#from PyQt5.QtCore import pyqtProperty as Property, QObject
+
+# This are the original imports.
+import sys
+import unittest
+has_test = False
+try:
+ if sys.version_info[0] >= 3: # This test has no support in Python 2
+ from test import support
+ has_test = True
+except:
+ pass
+
+class PropertyBase(Exception):
+ pass
+
+class PropertyGet(PropertyBase):
+ pass
+
+class PropertySet(PropertyBase):
+ pass
+
+class PropertyDel(PropertyBase):
+ pass
+
+class BaseClass(QObject):
+ def __init__(self):
+ QObject.__init__(self)
+
+ self._spam = 5
+
+ @Property(object)
+ def spam(self):
+ """BaseClass.getter"""
+ return self._spam
+
+ @spam.setter
+ def spam(self, value):
+ self._spam = value
+
+ @spam.deleter
+ def spam(self):
+ del self._spam
+
+class SubClass(BaseClass):
+
+ @BaseClass.spam.getter
+ def spam(self):
+ """SubClass.getter"""
+ raise PropertyGet(self._spam)
+
+ @spam.setter
+ def spam(self, value):
+ raise PropertySet(self._spam)
+
+ @spam.deleter
+ def spam(self):
+ raise PropertyDel(self._spam)
+
+class PropertyDocBase(object):
+ _spam = 1
+ def _get_spam(self):
+ return self._spam
+ spam = Property(object, _get_spam, doc="spam spam spam")
+
+class PropertyDocSub(PropertyDocBase):
+ @PropertyDocBase.spam.getter
+ def spam(self):
+ """The decorator does not use this doc string"""
+ return self._spam
+
+class PropertySubNewGetter(BaseClass):
+ @BaseClass.spam.getter
+ def spam(self):
+ """new docstring"""
+ return 5
+
+class PropertyNewGetter(QObject):
+ def __init__(self):
+ QObject.__init__(self)
+
+ @Property(object)
+ def spam(self):
+ """original docstring"""
+ return 1
+ @spam.getter
+ def spam(self):
+ """new docstring"""
+ return 8
+
+class PropertyTests(unittest.TestCase):
+ def test_property_decorator_baseclass(self):
+ # see #1620
+ base = BaseClass()
+ self.assertEqual(base.spam, 5)
+ self.assertEqual(base._spam, 5)
+ base.spam = 10
+ self.assertEqual(base.spam, 10)
+ self.assertEqual(base._spam, 10)
+ delattr(base, "spam")
+ self.assertTrue(not hasattr(base, "spam"))
+ self.assertTrue(not hasattr(base, "_spam"))
+ base.spam = 20
+ self.assertEqual(base.spam, 20)
+ self.assertEqual(base._spam, 20)
+
+ def test_property_decorator_subclass(self):
+ # see #1620
+ sub = SubClass()
+ self.assertRaises(PropertyGet, getattr, sub, "spam")
+ self.assertRaises(PropertySet, setattr, sub, "spam", None)
+ self.assertRaises(PropertyDel, delattr, sub, "spam")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_decorator_subclass_doc(self):
+ sub = SubClass()
+ self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_decorator_baseclass_doc(self):
+ base = BaseClass()
+ self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter")
+
+ def test_property_decorator_doc(self):
+ base = PropertyDocBase()
+ sub = PropertyDocSub()
+ self.assertEqual(base.__class__.spam.__doc__, "spam spam spam")
+ self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_getter_doc_override(self):
+ newgettersub = PropertySubNewGetter()
+ self.assertEqual(newgettersub.spam, 5)
+ self.assertEqual(newgettersub.__class__.spam.__doc__, "new docstring")
+ newgetter = PropertyNewGetter()
+ self.assertEqual(newgetter.spam, 8)
+ self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring")
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_builtin_doc_writable(self):
+ p = Property(object, doc='basic')
+ self.assertEqual(p.__doc__, 'basic')
+ p.__doc__ = 'extended'
+ self.assertEqual(p.__doc__, 'extended')
+
+ @unittest.skipIf(sys.flags.optimize >= 2,
+ "Docstrings are omitted with -O2 and above")
+ def test_property_decorator_doc_writable(self):
+ class PropertyWritableDoc(object):
+
+ @Property(object)
+ def spam(self):
+ """Eggs"""
+ return "eggs"
+
+ sub = PropertyWritableDoc()
+ self.assertEqual(sub.__class__.spam.__doc__, 'Eggs')
+ sub.__class__.spam.__doc__ = 'Spam'
+ self.assertEqual(sub.__class__.spam.__doc__, 'Spam')
+
+ if has_test: # This test has no support in Python 2
+ @support.refcount_test
+ def test_refleaks_in___init__(self):
+ gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
+ fake_prop = Property(object, 'fget', 'fset', "freset", 'fdel', 'doc')
+ refs_before = gettotalrefcount()
+ for i in range(100):
+ fake_prop.__init__(object, 'fget', 'fset', "freset", 'fdel', 'doc')
+ self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
+
+
+# Note: We ignore the whole subclass tests concerning __doc__ strings.
+# See the original Python test starting with:
+# "Issue 5890: subclasses of property do not preserve method __doc__ strings"
+
+
+if __name__ == '__main__':
+ unittest.main()