diff --git a/property-lookup.patch b/property-lookup.patch new file mode 100644 index 0000000..395f85b --- /dev/null +++ b/property-lookup.patch @@ -0,0 +1,497 @@ +From 901c1b6e3722a9cd999e4948297e2c460f101d20 Mon Sep 17 00:00:00 2001 +From: Daniel Drake +Date: Thu, 01 Nov 2012 14:46:22 +0000 +Subject: Fix property lookup in class hierarchy + +Commit 4bfe7972546413f46f5c36737ff03bb5612c1921 introduced a bug where +a Python subclass of a gi-provided base class overrides a property from the +base class. + +The new behaviour in the above commit causes pygobject to seek the property +in the base class and try to read it from there (resulting in confusion) +rather than noticing that the property is overridden and present in the +Python object instance. + +To provide a nicer solution here, we can exploit the fact that +g_object_class_find_property() will traverse the hierarchy in order to +find the right GParamSpec, and the returned GParamSpec can tell us exactly +which GType introduces the property. The strategy is: + + 1. Find pspec with g_object_class_find_property() + 2. Find the class that owns that property (pspec->owner_type) + 3. See if girepository owns that class. + 3a. If yes, get property from there. + 3b. If not, get property "directly" + +And the same for property setting. + +Now that _pygi_lookup_property_from_g_type is always passed the type that +implements the property, it no longer has to go recursing through parent +classes, which was the original cause of confusion. + +https://bugzilla.gnome.org/show_bug.cgi?id=686942 +--- +diff --git a/gi/_gobject/pygobject.c b/gi/_gobject/pygobject.c +index 6dfffef..23a4284 100644 +--- a/gi/_gobject/pygobject.c ++++ b/gi/_gobject/pygobject.c +@@ -51,6 +51,25 @@ GQuark pygobject_wrapper_key; + GQuark pygobject_has_updated_constructor_key; + GQuark pygobject_instance_data_key; + ++/* Copied from glib. gobject uses hyphens in property names, but in Python ++ * we can only represent hyphens as underscores. Convert underscores to ++ * hyphens for glib compatibility. */ ++static void ++canonicalize_key (gchar *key) ++{ ++ gchar *p; ++ ++ for (p = key; *p != 0; p++) ++ { ++ gchar c = *p; ++ ++ if (c != '-' && ++ (c < '0' || c > '9') && ++ (c < 'A' || c > 'Z') && ++ (c < 'a' || c > 'z')) ++ *p = '-'; ++ } ++} + + /* -------------- class <-> wrapper manipulation --------------- */ + +@@ -237,7 +256,7 @@ build_parameter_list(GObjectClass *class) + static PyObject* + PyGProps_getattro(PyGProps *self, PyObject *attr) + { +- char *attr_name; ++ char *attr_name, *property_name; + GObjectClass *class; + GParamSpec *pspec; + GValue value = { 0, }; +@@ -257,15 +276,13 @@ PyGProps_getattro(PyGProps *self, PyObject *attr) + return ret; + } + +- if (self->pygobject != NULL) { +- ret = pygi_get_property_value (self->pygobject, attr_name); +- if (ret != NULL) { +- g_type_class_unref(class); +- return ret; +- } +- } +- +- pspec = g_object_class_find_property(class, attr_name); ++ /* g_object_class_find_property recurses through the class hierarchy, ++ * so the resulting pspec tells us the owner_type that owns the property ++ * we're dealing with. */ ++ property_name = g_strdup(attr_name); ++ canonicalize_key(property_name); ++ pspec = g_object_class_find_property(class, property_name); ++ g_free(property_name); + g_type_class_unref(class); + + if (!pspec) { +@@ -278,14 +295,23 @@ PyGProps_getattro(PyGProps *self, PyObject *attr) + return NULL; + } + +- /* If we're doing it without an instance, return a GParamSpec */ + if (!self->pygobject) { ++ /* If we're doing it without an instance, return a GParamSpec */ + return pyg_param_spec_new(pspec); + } ++ ++ /* See if the property's class is from the gi repository. If so, ++ * use gi to correctly read the property value. */ ++ ret = pygi_get_property_value (self->pygobject, pspec); ++ if (ret != NULL) { ++ return ret; ++ } + ++ /* If we reach here, it must be a property defined outside of gi. ++ * Just do a straightforward read. */ + g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec)); + pyg_begin_allow_threads; +- g_object_get_property(self->pygobject->obj, attr_name, &value); ++ g_object_get_property(self->pygobject->obj, pspec->name, &value); + pyg_end_allow_threads; + ret = pyg_param_gvalue_as_pyobject(&value, TRUE, pspec); + g_value_unset(&value); +@@ -295,7 +321,6 @@ PyGProps_getattro(PyGProps *self, PyObject *attr) + + static gboolean + set_property_from_pspec(GObject *obj, +- char *attr_name, + GParamSpec *pspec, + PyObject *pvalue) + { +@@ -304,13 +329,13 @@ set_property_from_pspec(GObject *obj, + if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) { + PyErr_Format(PyExc_TypeError, + "property '%s' can only be set in constructor", +- attr_name); ++ pspec->name); + return FALSE; + } + + if (!(pspec->flags & G_PARAM_WRITABLE)) { + PyErr_Format(PyExc_TypeError, +- "property '%s' is not writable", attr_name); ++ "property '%s' is not writable", pspec->name); + return FALSE; + } + +@@ -322,7 +347,7 @@ set_property_from_pspec(GObject *obj, + } + + pyg_begin_allow_threads; +- g_object_set_property(obj, attr_name, &value); ++ g_object_set_property(obj, pspec->name, &value); + pyg_end_allow_threads; + + g_value_unset(&value); +@@ -336,7 +361,7 @@ static int + PyGProps_setattro(PyGProps *self, PyObject *attr, PyObject *pvalue) + { + GParamSpec *pspec; +- char *attr_name; ++ char *attr_name, *property_name; + GObject *obj; + int ret = -1; + +@@ -358,20 +383,33 @@ PyGProps_setattro(PyGProps *self, PyObject *attr, PyObject *pvalue) + return -1; + } + +- ret = pygi_set_property_value (self->pygobject, attr_name, pvalue); ++ obj = self->pygobject->obj; ++ ++ property_name = g_strdup(attr_name); ++ canonicalize_key(property_name); ++ ++ /* g_object_class_find_property recurses through the class hierarchy, ++ * so the resulting pspec tells us the owner_type that owns the property ++ * we're dealing with. */ ++ pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), ++ property_name); ++ g_free(property_name); ++ if (!pspec) { ++ return PyObject_GenericSetAttr((PyObject *)self, attr, pvalue); ++ } ++ ++ /* See if the property's class is from the gi repository. If so, ++ * use gi to correctly read the property value. */ ++ ret = pygi_set_property_value (self->pygobject, pspec, pvalue); + if (ret == 0) + return 0; + else if (ret == -1) + if (PyErr_Occurred()) + return -1; + +- obj = self->pygobject->obj; +- pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), attr_name); +- if (!pspec) { +- return PyObject_GenericSetAttr((PyObject *)self, attr, pvalue); +- } +- +- if (!set_property_from_pspec(obj, attr_name, pspec, pvalue)) ++ /* If we reach here, it must be a property defined outside of gi. ++ * Just do a straightforward set. */ ++ if (!set_property_from_pspec(obj, pspec, pvalue)) + return -1; + + return 0; +@@ -1458,7 +1496,7 @@ pygobject_set_property(PyGObject *self, PyObject *args) + return NULL; + } + +- if (!set_property_from_pspec(self->obj, param_name, pspec, pvalue)) ++ if (!set_property_from_pspec(self->obj, pspec, pvalue)) + return NULL; + + Py_INCREF(Py_None); +@@ -1496,7 +1534,7 @@ pygobject_set_properties(PyGObject *self, PyObject *args, PyObject *kwargs) + goto exit; + } + +- if (!set_property_from_pspec(G_OBJECT(self->obj), key_str, pspec, value)) ++ if (!set_property_from_pspec(G_OBJECT(self->obj), pspec, value)) + goto exit; + } + +diff --git a/gi/pygi-property.c b/gi/pygi-property.c +index 14564d0..4f09e70 100644 +--- a/gi/pygi-property.c ++++ b/gi/pygi-property.c +@@ -25,92 +25,95 @@ + + #include + +-/* Copied from glib */ +-static void +-canonicalize_key (gchar *key) ++static GIPropertyInfo * ++lookup_property_from_object_info (GIObjectInfo *info, const gchar *attr_name) + { +- gchar *p; ++ gssize n_infos; ++ gssize i; + +- for (p = key; *p != 0; p++) +- { +- gchar c = *p; ++ n_infos = g_object_info_get_n_properties (info); ++ for (i = 0; i < n_infos; i++) { ++ GIPropertyInfo *property_info; + +- if (c != '-' && +- (c < '0' || c > '9') && +- (c < 'A' || c > 'Z') && +- (c < 'a' || c > 'z')) +- *p = '-'; ++ property_info = g_object_info_get_property (info, i); ++ g_assert (info != NULL); ++ ++ if (strcmp (attr_name, g_base_info_get_name (property_info)) == 0) { ++ return property_info; ++ } ++ ++ g_base_info_unref (property_info); + } ++ ++ return NULL; + } + + static GIPropertyInfo * +-_pygi_lookup_property_from_g_type (GType g_type, const gchar *attr_name) ++lookup_property_from_interface_info (GIInterfaceInfo *info, ++ const gchar *attr_name) + { +- GIRepository *repository; +- GIBaseInfo *info; + gssize n_infos; + gssize i; +- GType parent; + +- repository = g_irepository_get_default(); +- info = g_irepository_find_by_gtype (repository, g_type); +- if (info != NULL) { ++ n_infos = g_interface_info_get_n_properties (info); ++ for (i = 0; i < n_infos; i++) { ++ GIPropertyInfo *property_info; + +- n_infos = g_object_info_get_n_properties ( (GIObjectInfo *) info); +- for (i = 0; i < n_infos; i++) { +- GIPropertyInfo *property_info; ++ property_info = g_interface_info_get_property (info, i); ++ g_assert (info != NULL); + +- property_info = g_object_info_get_property ( (GIObjectInfo *) info, +- i); +- g_assert (info != NULL); +- +- if (strcmp (attr_name, g_base_info_get_name (property_info)) == 0) { +- g_base_info_unref (info); +- return property_info; +- } +- +- g_base_info_unref (property_info); ++ if (strcmp (attr_name, g_base_info_get_name (property_info)) == 0) { ++ return property_info; + } + +- g_base_info_unref (info); ++ g_base_info_unref (property_info); + } + +- parent = g_type_parent (g_type); +- if (parent > 0) +- return _pygi_lookup_property_from_g_type (parent, attr_name); +- + return NULL; + } + ++static GIPropertyInfo * ++_pygi_lookup_property_from_g_type (GType g_type, const gchar *attr_name) ++{ ++ GIPropertyInfo *ret = NULL; ++ GIRepository *repository; ++ GIBaseInfo *info; ++ ++ repository = g_irepository_get_default(); ++ info = g_irepository_find_by_gtype (repository, g_type); ++ if (info == NULL) ++ return NULL; ++ ++ if (GI_IS_OBJECT_INFO (info)) ++ ret = lookup_property_from_object_info ((GIObjectInfo *) info, ++ attr_name); ++ else if (GI_IS_INTERFACE_INFO (info)) ++ ret = lookup_property_from_interface_info ((GIInterfaceInfo *) info, ++ attr_name); ++ ++ g_base_info_unref (info); ++ return ret; ++} ++ + PyObject * +-pygi_get_property_value_real (PyGObject *instance, +- const gchar *attr_name) ++pygi_get_property_value_real (PyGObject *instance, GParamSpec *pspec) + { +- GType g_type; + GIPropertyInfo *property_info = NULL; +- char *property_name = g_strdup (attr_name); +- GParamSpec *pspec = NULL; + GValue value = { 0, }; + GIArgument arg = { 0, }; + PyObject *py_value = NULL; + GITypeInfo *type_info = NULL; + GITransfer transfer; + +- canonicalize_key (property_name); +- +- g_type = pyg_type_from_object ((PyObject *)instance); +- property_info = _pygi_lookup_property_from_g_type (g_type, property_name); ++ /* The owner_type of the pspec gives us the exact type that introduced the ++ * property, even if it is a parent class of the instance in question. */ ++ property_info = _pygi_lookup_property_from_g_type (pspec->owner_type, pspec->name); + + if (property_info == NULL) + goto out; + +- pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (instance->obj), +- attr_name); +- if (pspec == NULL) +- goto out; +- + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); +- g_object_get_property (instance->obj, attr_name, &value); ++ g_object_get_property (instance->obj, pspec->name, &value); + + type_info = g_property_info_get_type (property_info); + transfer = g_property_info_get_ownership_transfer (property_info); +@@ -243,7 +246,6 @@ pygi_get_property_value_real (PyGObject *instance, + py_value = _pygi_argument_to_object (&arg, type_info, transfer); + + out: +- g_free (property_name); + if (property_info != NULL) + g_base_info_unref (property_info); + if (type_info != NULL) +@@ -254,33 +256,24 @@ out: + + gint + pygi_set_property_value_real (PyGObject *instance, +- const gchar *attr_name, ++ GParamSpec *pspec, + PyObject *py_value) + { +- GType g_type; + GIPropertyInfo *property_info = NULL; +- char *property_name = g_strdup (attr_name); + GITypeInfo *type_info = NULL; + GITypeTag type_tag; + GITransfer transfer; + GValue value = { 0, }; + GIArgument arg = { 0, }; +- GParamSpec *pspec = NULL; + gint ret_value = -1; + +- canonicalize_key (property_name); +- +- g_type = pyg_type_from_object ((PyObject *)instance); +- property_info = _pygi_lookup_property_from_g_type (g_type, property_name); +- ++ /* The owner_type of the pspec gives us the exact type that introduced the ++ * property, even if it is a parent class of the instance in question. */ ++ property_info = _pygi_lookup_property_from_g_type (pspec->owner_type, ++ pspec->name); + if (property_info == NULL) + goto out; + +- pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (instance->obj), +- attr_name); +- if (pspec == NULL) +- goto out; +- + if (! (pspec->flags & G_PARAM_WRITABLE)) + goto out; + +@@ -413,12 +406,11 @@ pygi_set_property_value_real (PyGObject *instance, + goto out; + } + +- g_object_set_property (instance->obj, attr_name, &value); ++ g_object_set_property (instance->obj, pspec->name, &value); + + ret_value = 0; + + out: +- g_free (property_name); + if (property_info != NULL) + g_base_info_unref (property_info); + if (type_info != NULL) +diff --git a/gi/pygi-property.h b/gi/pygi-property.h +index 31d0e42..875d21e 100644 +--- a/gi/pygi-property.h ++++ b/gi/pygi-property.h +@@ -30,10 +30,10 @@ + #include "pygi.h" + + PyObject *pygi_get_property_value_real (PyGObject *instance, +- const gchar *attr_name); ++ GParamSpec *pspec); + + gint pygi_set_property_value_real (PyGObject *instance, +- const gchar *attr_name, ++ GParamSpec *pspec, + PyObject *py_value); + + #endif /* __PYGI_PROPERTY_H__ */ +diff --git a/gi/pygi.h b/gi/pygi.h +index 121eae5..86da07f 100644 +--- a/gi/pygi.h ++++ b/gi/pygi.h +@@ -77,9 +77,9 @@ typedef PyObject * (*PyGIArgOverrideReleaseFunc) (GITypeInfo *type_info, + struct PyGI_API { + PyObject* (*type_import_by_g_type) (GType g_type); + PyObject* (*get_property_value) (PyGObject *instance, +- const gchar *attr_name); ++ GParamSpec *pspec); + gint (*set_property_value) (PyGObject *instance, +- const gchar *attr_name, ++ GParamSpec *pspec, + PyObject *value); + GClosure * (*signal_closure_new) (PyGObject *instance, + const gchar *sig_name, +@@ -124,23 +124,23 @@ pygi_type_import_by_g_type (GType g_type) + + static inline PyObject * + pygi_get_property_value (PyGObject *instance, +- const gchar *attr_name) ++ GParamSpec *pspec) + { + if (_pygi_import() < 0) { + return NULL; + } +- return PyGI_API->get_property_value(instance, attr_name); ++ return PyGI_API->get_property_value(instance, pspec); + } + + static inline gint + pygi_set_property_value (PyGObject *instance, +- const gchar *attr_name, ++ GParamSpec *pspec, + PyObject *value) + { + if (_pygi_import() < 0) { + return -1; + } +- return PyGI_API->set_property_value(instance, attr_name, value); ++ return PyGI_API->set_property_value(instance, pspec, value); + } + + static inline GClosure * diff --git a/pygobject3.spec b/pygobject3.spec index 6a8ed67..0c5749d 100644 --- a/pygobject3.spec +++ b/pygobject3.spec @@ -22,7 +22,7 @@ Name: pygobject3 Version: 3.4.1.1 -Release: 1%{?dist} +Release: 2%{?dist} License: LGPLv2+ and MIT Group: Development/Languages Summary: Python 2 bindings for GObject Introspection @@ -69,6 +69,9 @@ Patch2: pygobject-3.3.4-known-failures.patch # Not yet sent upstream: Patch3: test-list-marshalling.patch +# upstream fix for property type lookup, needed for basic Sugar operation +Patch4: property-lookup.patch + ### Build Dependencies ### BuildRequires: glib2-devel >= %{glib2_version} @@ -140,6 +143,7 @@ for use in Python 3 programs. %patch1 -p1 -b .ignore-more-pep8-errors %patch2 -p1 -b .known-failures %patch3 -p1 -b .test-list-marshalling +%patch4 -p1 -b .property-lookup %if 0%{?with_python3} rm -rf %{py3dir} @@ -238,6 +242,9 @@ xvfb-run make DESTDIR=$RPM_BUILD_ROOT check %{verbosity} %endif # with_python3 %changelog +* Tue Nov 6 2012 Daniel Drake - 3.4.1.1-2 +- Upstream fix for property lookup; needed for basic Sugar operation. + * Wed Oct 17 2012 Kalev Lember - 3.4.1.1-1 - Update to 3.4.1.1