aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside2
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2020-10-14 09:59:10 +0200
committerChristian Tismer <tismer@stackless.com>2020-10-27 13:13:46 +0000
commitdb03fa66435949c4c75658bbdfdaacbeedfa0413 (patch)
tree5c94ce063981982c7a50b3b190e758f202b063a3 /sources/pyside2
parent1d044f467070a040713c9566a8a8de3a56c571e7 (diff)
feature-select: implement class properties
After implementing property objects for PySide, the static properties (properties for static functions) were quite missing, for instance from QtCore.QCoreApplication and QtWidgets.QApplication . This implementation uses the normal Python properties and derives a PySide.ClassProperty class which works almost the same on classes. The static methods had to be mutated to class methods explicitly. That would be automated by PyType_Ready, but here we are doing this after class initialization. Task-number: PYSIDE-1019 Change-Id: Iabe00be18e25881cc7a97507b6fdae3e2d57ff7a Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io> Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'sources/pyside2')
-rw-r--r--sources/pyside2/libpyside/CMakeLists.txt2
-rw-r--r--sources/pyside2/libpyside/class_property.cpp158
-rw-r--r--sources/pyside2/libpyside/class_property.h69
-rw-r--r--sources/pyside2/libpyside/feature_select.cpp71
-rw-r--r--sources/pyside2/libpyside/feature_select.h1
-rw-r--r--sources/pyside2/tests/QtCore/snake_prop_feature_test.py19
6 files changed, 299 insertions, 21 deletions
diff --git a/sources/pyside2/libpyside/CMakeLists.txt b/sources/pyside2/libpyside/CMakeLists.txt
index e31c87eef..5e6ee86ba 100644
--- a/sources/pyside2/libpyside/CMakeLists.txt
+++ b/sources/pyside2/libpyside/CMakeLists.txt
@@ -39,6 +39,7 @@ else()
endif()
set(libpyside_SRC
+ class_property.cpp
dynamicqmetaobject.cpp
feature_select.cpp
signalmanager.cpp
@@ -125,6 +126,7 @@ endif()
#
set(libpyside_HEADERS
+ class_property.h
dynamicqmetaobject.h
feature_select.h
pysideclassinfo.h
diff --git a/sources/pyside2/libpyside/class_property.cpp b/sources/pyside2/libpyside/class_property.cpp
new file mode 100644
index 000000000..90ce0d2f3
--- /dev/null
+++ b/sources/pyside2/libpyside/class_property.cpp
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt for Python.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "pyside.h"
+#include "pysidestaticstrings.h"
+#include "feature_select.h"
+#include "class_property.h"
+
+#include <shiboken.h>
+#include <sbkstaticstrings.h>
+
+extern "C" {
+
+/*
+ * A `classproperty` is the same as a `property` but the `__get__()` and `__set__()`
+ * methods are modified to always use the object class instead of a concrete instance.
+ *
+ * Note: A "static property" as it is often called does not exist per se.
+ * Static methods do not receive anything when created. Static methods which
+ * should participate in a property must be turned into class methods, before.
+ * See function `createProperty` in `feature_select.cpp`.
+ */
+
+// `class_property.__get__()`: Always pass the class instead of the instance.
+static PyObject *PyClassProperty_get(PyObject *self, PyObject * /*ob*/, PyObject *cls)
+{
+ return PyProperty_Type.tp_descr_get(self, cls, cls);
+}
+
+// `class_property.__set__()`: Just like the above `__get__()`.
+static int PyClassProperty_set(PyObject *self, PyObject *obj, PyObject *value)
+{
+ PyObject *cls = PyType_Check(obj) ? obj : reinterpret_cast<PyObject *>(Py_TYPE(obj));
+ return PyProperty_Type.tp_descr_set(self, cls, value);
+}
+
+// The property `__doc__` default does not work for class properties
+// because PyProperty_Type.tp_init thinks this is a subclass which needs PyObject_SetAttr.
+// We call `__init__` while pretending to be a PyProperty_Type instance.
+static int PyClassProperty_init(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ auto hold = Py_TYPE(self);
+ Py_TYPE(self) = &PyProperty_Type;
+ auto ret = PyProperty_Type.tp_init(self, args, kwargs);
+ Py_TYPE(self) = hold;
+ return ret;
+}
+
+static PyType_Slot PyClassProperty_slots[] = {
+ {Py_tp_getset, nullptr}, // will be set below
+ {Py_tp_base, reinterpret_cast<void *>(&PyProperty_Type)},
+ {Py_tp_descr_get, reinterpret_cast<void *>(PyClassProperty_get)},
+ {Py_tp_descr_set, reinterpret_cast<void *>(PyClassProperty_set)},
+ {Py_tp_init, reinterpret_cast<void *>(PyClassProperty_init)},
+ {0, 0}
+};
+
+static PyType_Spec PyClassProperty_spec = {
+ "PySide2.PyClassProperty",
+ sizeof(propertyobject),
+ 0,
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ PyClassProperty_slots,
+};
+
+PyTypeObject *PyClassPropertyTypeF()
+{
+ static PyTypeObject *type = nullptr;
+ if (type == nullptr) {
+ // Provide the same `tp_getset`, which is not inherited.
+ PyClassProperty_slots[0].pfunc = PyProperty_Type.tp_getset;
+ type = reinterpret_cast<PyTypeObject *>(
+ PyType_FromSpec(&PyClassProperty_spec));
+ }
+ return type;
+}
+
+/*
+ * Types with class properties need to handle `Type.class_prop = x` in a specific way.
+ * By default, Python replaces the `class_property` itself, but for wrapped C++ types
+ * we need to call `class_property.__set__()` in order to propagate the new value to
+ * the underlying C++ data structure.
+ */
+static int SbkObjectType_meta_setattro(PyObject *obj, PyObject *name, PyObject *value)
+{
+ // Use `_PepType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw
+ // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`).
+ auto type = reinterpret_cast<PyTypeObject *>(obj);
+ PyObject *descr = _PepType_Lookup(type, name);
+
+ // The following assignment combinations are possible:
+ // 1. `Type.class_prop = value` --> descr_set: `Type.class_prop.__set__(value)`
+ // 2. `Type.class_prop = other_class_prop` --> setattro: replace existing `class_prop`
+ // 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment
+ const auto class_prop = reinterpret_cast<PyObject *>(PyClassPropertyTypeF());
+ const auto call_descr_set = descr && PyObject_IsInstance(descr, class_prop)
+ && !PyObject_IsInstance(value, class_prop);
+ if (call_descr_set) {
+ // Call `class_property.__set__()` instead of replacing the `class_property`.
+ return Py_TYPE(descr)->tp_descr_set(descr, obj, value);
+ } else {
+ // Replace existing attribute.
+ return PyType_Type.tp_setattro(obj, name, value);
+ }
+}
+
+} // extern "C"
+
+/*
+ * These functions are added to the SbkObjectType_TypeF() dynamically.
+ */
+namespace PySide { namespace ClassProperty {
+
+void init()
+{
+ PyTypeObject *type = SbkObjectType_TypeF();
+ type->tp_setattro = SbkObjectType_meta_setattro;
+ Py_TYPE(PyClassPropertyTypeF()) = type;
+}
+
+} // namespace ClassProperty
+} // namespace PySide
diff --git a/sources/pyside2/libpyside/class_property.h b/sources/pyside2/libpyside/class_property.h
new file mode 100644
index 000000000..f94fdde31
--- /dev/null
+++ b/sources/pyside2/libpyside/class_property.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt for Python.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef CLASS_PROPERTY_H
+#define CLASS_PROPERTY_H
+
+#include "pysidemacros.h"
+#include <sbkpython.h>
+
+extern "C" {
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *prop_get;
+ PyObject *prop_set;
+ PyObject *prop_del;
+ PyObject *prop_doc;
+ int getter_doc;
+} propertyobject;
+
+PYSIDE_API PyTypeObject *PyClassPropertyTypeF();
+
+} // extern "C"
+
+namespace PySide {
+namespace ClassProperty {
+
+PYSIDE_API void init();
+
+} // namespace ClassProperty
+} // namespace PySide
+
+#endif // CLASS_PROPERTY_H
diff --git a/sources/pyside2/libpyside/feature_select.cpp b/sources/pyside2/libpyside/feature_select.cpp
index 6a21d168d..660547d99 100644
--- a/sources/pyside2/libpyside/feature_select.cpp
+++ b/sources/pyside2/libpyside/feature_select.cpp
@@ -40,12 +40,11 @@
#include "feature_select.h"
#include "pyside.h"
#include "pysidestaticstrings.h"
+#include "class_property.h"
#include <shiboken.h>
#include <sbkstaticstrings.h>
-#include <QtCore/QtGlobal>
-
//////////////////////////////////////////////////////////////////////////////
//
// PYSIDE-1019: Support switchable extensions
@@ -418,6 +417,13 @@ void Select(PyObject *obj)
type->tp_dict = SelectFeatureSet(type);
}
+PyObject *Select(PyTypeObject *type)
+{
+ if (featurePointer != nullptr)
+ type->tp_dict = SelectFeatureSet(type);
+ return type->tp_dict;
+}
+
static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id);
static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id);
static bool feature_04_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id);
@@ -460,6 +466,7 @@ void init()
initSelectableFeature(SelectFeatureSet);
registerCleanupFunction(finalize);
patch_property_impl();
+ PySide::ClassProperty::init();
is_initialized = true;
}
// Reset the cache. This is called at any "from __feature__ import".
@@ -549,12 +556,47 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, in
// Feature 0x02: Use true properties instead of getters and setters
//
-static PyObject *createProperty(PyObject *getter, PyObject *setter)
+// This is the Python 2 version for inspection of m_ml, only.
+// The actual Python 3 version is larget.
+
+typedef struct {
+ PyObject_HEAD
+ PyMethodDef *m_ml; /* Description of the C function to call */
+ PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
+ PyObject *m_module; /* The __module__ attribute, can be anything */
+} PyCFunctionObject;
+
+static PyObject *modifyStaticToClassMethod(PyTypeObject *type, PyObject *sm)
{
+ AutoDecRef func_ob(PyObject_GetAttr(sm, PyMagicName::func()));
+ if (func_ob.isNull())
+ return nullptr;
+ auto func = reinterpret_cast<PyCFunctionObject *>(func_ob.object());
+ auto new_func = new PyMethodDef;
+ new_func->ml_name = func->m_ml->ml_name;
+ new_func->ml_meth = func->m_ml->ml_meth;
+ new_func->ml_flags = (func->m_ml->ml_flags & ~METH_STATIC) | METH_CLASS;
+ new_func->ml_doc = func->m_ml->ml_doc;
+ auto cfunc = PyCFunction_NewEx(new_func, nullptr, nullptr);
+ cfunc = PyDescr_NewClassMethod(type, new_func);
+ return cfunc;
+}
+
+static PyObject *createProperty(PyTypeObject *type, PyObject *getter, PyObject *setter)
+{
+ bool chassprop = false;
assert(getter != nullptr);
if (setter == nullptr)
setter = Py_None;
- auto obtype = reinterpret_cast<PyObject *>(&PyProperty_Type);
+ auto ptype = &PyProperty_Type;
+ if (Py_TYPE(getter) == PepStaticMethod_TypePtr) {
+ ptype = PyClassPropertyTypeF();
+ chassprop = true;
+ getter = modifyStaticToClassMethod(type, getter);
+ if (setter != Py_None)
+ setter = modifyStaticToClassMethod(type, setter);
+ }
+ auto obtype = reinterpret_cast<PyObject *>(ptype);
PyObject *prop = PyObject_CallFunctionObjArgs(obtype, getter, setter, nullptr);
return prop;
}
@@ -612,13 +654,12 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in
PyObject *read = make_snake_case(fields[1], lower);
PyObject *write = haveWrite ? make_snake_case(fields[2], lower) : nullptr;
PyObject *getter = PyDict_GetItem(prev_dict, read);
- if (getter == nullptr || Py_TYPE(getter) != PepMethodDescr_TypePtr)
+ if (getter == nullptr || !(Py_TYPE(getter) == PepMethodDescr_TypePtr ||
+ Py_TYPE(getter) == PepStaticMethod_TypePtr))
continue;
PyObject *setter = haveWrite ? PyDict_GetItem(prev_dict, write) : nullptr;
- if (setter != nullptr && Py_TYPE(setter) != PepMethodDescr_TypePtr)
- continue;
- AutoDecRef PyProperty(createProperty(getter, setter));
+ AutoDecRef PyProperty(createProperty(type, getter, setter));
if (PyProperty.isNull())
return false;
if (PyDict_SetItem(prop_dict, name, PyProperty) < 0)
@@ -641,19 +682,10 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in
// suitable for us.
// We turn `__doc__` into a lazy attribute saving signature initialization.
//
-// Currently, there is no static extension planned, because _PyType_Lookup
-// and Limited_API are hard to use at the same time.
+// There is now also a class property implementation which inherits
+// from this one.
//
-typedef struct {
- PyObject_HEAD
- PyObject *prop_get;
- PyObject *prop_set;
- PyObject *prop_del;
- PyObject *prop_doc;
- int getter_doc;
-} propertyobject;
-
static PyObject *property_doc_get(PyObject *self, void *)
{
auto po = reinterpret_cast<propertyobject *>(self);
@@ -702,7 +734,6 @@ static bool patch_property_impl()
return false;
if (PyDict_SetItemString(dict, gsp->name, descr) < 0)
return false;
- // Replace property_descr_get/set by slightly changed versions
return true;
}
diff --git a/sources/pyside2/libpyside/feature_select.h b/sources/pyside2/libpyside/feature_select.h
index 32abffac6..845828a4c 100644
--- a/sources/pyside2/libpyside/feature_select.h
+++ b/sources/pyside2/libpyside/feature_select.h
@@ -48,6 +48,7 @@ namespace Feature {
PYSIDE_API void init();
PYSIDE_API void Select(PyObject *obj);
+PYSIDE_API PyObject *Select(PyTypeObject *type);
} // namespace Feature
} // namespace PySide
diff --git a/sources/pyside2/tests/QtCore/snake_prop_feature_test.py b/sources/pyside2/tests/QtCore/snake_prop_feature_test.py
index 779b8a408..b53a1f0a5 100644
--- a/sources/pyside2/tests/QtCore/snake_prop_feature_test.py
+++ b/sources/pyside2/tests/QtCore/snake_prop_feature_test.py
@@ -54,7 +54,7 @@ snake_prop_feature_test.py
Test the snake_case and true_property feature.
-This works now. More tests needed!
+This works now, including class properties.
"""
class Window(QtWidgets.QWidget):
@@ -68,6 +68,7 @@ class FeatureTest(unittest.TestCase):
__feature__.set_selection(0)
def tearDown(self):
+ __feature__.set_selection(0)
qApp.shutdown()
def testRenamedFunctions(self):
@@ -101,6 +102,22 @@ class FeatureTest(unittest.TestCase):
with self.assertRaises(AttributeError):
window.modal
+ def testClassProperty(self):
+ from __feature__ import snake_case, true_property
+ # We check the class...
+ self.assertEqual(type(QtWidgets.QApplication.quit_on_last_window_closed), bool)
+ x = QtWidgets.QApplication.quit_on_last_window_closed
+ QtWidgets.QApplication.quit_on_last_window_closed = not x
+ self.assertEqual(QtWidgets.QApplication.quit_on_last_window_closed, not x)
+ # ... and now the instance.
+ self.assertEqual(type(qApp.quit_on_last_window_closed), bool)
+ x = qApp.quit_on_last_window_closed
+ qApp.quit_on_last_window_closed = not x
+ self.assertEqual(qApp.quit_on_last_window_closed, not x)
+ # make sure values are equal
+ self.assertEqual(qApp.quit_on_last_window_closed,
+ QtWidgets.QApplication.quit_on_last_window_closed)
+
if __name__ == '__main__':
unittest.main()