GNOME Bugzilla – Bug 616453
Override somehow overrides internal parameter checks
Last modified: 2013-02-25 07:34:32 UTC
Created attachment 159291 [details] Tree model test case I have added a GenericTreeModel override to my local PyGI bindings to emulate the same PyGtk class. For some reason the GenericTreeModel now overrides TreeModel in internal parameter type checks even though it simply is derived from TreeModel in the override. This causes the test bellow to throw a TypeError - sort = Gtk.TreeModelSort() tree = Gtk.TreeView() tree.set_model(sort) # TypeError: argument 1: Must be Gtk.TreeModel, not TreeModelSort However when you look in gdb at the _pygi_g_registered_type_info_check_object method in pygi-argument.c it is calling isinstance on the sort python object and a GenericTreeModel type not a TreeModel type: (gdb) print py_type $2 = <GObjectMeta(get_iter=<function at remote 0x7fffef94c488>, __module__='gi.overrides.Gtk', __gtype_name__='GenericTreeModel', __gtype__=<gobject.GType at remote 0x7ffff7efb3d8>, __doc__=None, __init__=<function at remote 0x7fffef94c410>) at remote 0x7ade00> Not sure if my override is wrong or if something is wrong with the override mechanisms.
Created attachment 159292 [details] My yet to be committed override file You need to install this override to see the issue
(In reply to comment #0) > > Not sure if my override is wrong or if something is wrong with the override > mechanisms. To rule that out, we could run the class as a normal one, outside the override mechanism.
Comment on attachment 159292 [details] My yet to be committed override file ># -*- Mode: Python; py-indent-offset: 4 -*- ># vim: tabstop=4 shiftwidth=4 expandtab ># ># Copyright (C) 2009 Johan Dahlin <johan@gnome.org> ># 2010 Simon van der Linden <svdlinden@src.gnome.org> ># ># This library is free software; you can redistribute it and/or ># modify it under the terms of the GNU Lesser General Public ># License as published by the Free Software Foundation; either ># version 2.1 of the License, or (at your option) any later version. ># ># This library is distributed in the hope that it will be useful, ># but WITHOUT ANY WARRANTY; without even the implied warranty of ># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ># Lesser General Public License for more details. ># ># You should have received a copy of the GNU Lesser General Public ># License along with this library; if not, write to the Free Software ># Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 ># USA > >import gobject >from gi.repository import Gdk >from gi.repository import GObject >from ..types import override >from ..importer import modules > >Gtk = modules['Gtk'] > >class GenericTreeModel(gobject.GObject, Gtk.TreeModel): > __gtype_name__ = "GenericTreeModel" > > def __init__(self): > gobject.GObject.__init__(self) > > def get_iter(self, path): > result = None > success = False > iter = None > > if isinstance(path, basestring): > (success, iter) = Gtk.TreeModel.get_iter_from_string(self, path) > elif isinstance(path, Gtk.TreePath): > (success, iter) = Gtk.TreeModel.get_iter(self, path) > elif isinstance(path, (tuple, list)): > # FIXME: We should be doing this but PyGI doesn't support valists yet > # gtk_path = Gtk.TreePath.new_from_indices(*path, -1) > gtk_path = Gtk.TreePath.new() > for i in path: > gtk_path.append_index(i) > (success, iter) = Gtk.TreeModel.get_iter(self, gtk_path) > > else: > raise ValueError('Expected a string, Gtk.TreePath, tuple or list, got %s' % type(path)) > > if success: > result = iter > > return result > >class ListStore(Gtk.ListStore): > __gtype_name__ = "ListStore" > > def __init__(self): > gobject.GObject.__init__(self) > > def append(self, row): > iter_ = Gtk.TreeIter() > Gtk.ListStore.append(self, iter_) > if row is not None: > assert len(row) == self.get_n_columns() > for i in xrange(0, self.get_n_columns()): > self.set_value(iter_, i, row[i]) > return iter_ > >class GenericCellRenderer(Gtk.CellRenderer): > __gtype_name__ = "GenericCellRenderer" > def __init__(self): > gobject.GObject.__init__(self) > > def do_get_size(self, widget, cell_area): > if hasattr(self, 'on_get_size'): > return self.on_get_size(widget, cell_area) > else: > return 0, 0, 0, 0 > > def do_render(self, window, widget, background_area, cell_area, expose_area, flags): > if hasattr(self, 'on_render'): > self.on_render(window, widget, background_area, cell_area, expose_area, flags) > >class CellRenderer(GenericCellRenderer): > __gtype_name__ = "MyCellRenderer" > > def __init__(self): > gobject.GObject.__init__(self) > > self._text = None > > def set_text(self, value): > self._text = value > > def get_text(self, pspec): > return self._text > > text = gobject.property(type=str, setter=set_text, getter=get_text) > > def on_get_size(self, widget, cell_area): > return 20, 20, 0, 0 > > def on_render(self, window, widget, background_area, cell_area, expose_area, flags): > cr = Gdk.cairo_create(window) > > cr.set_line_width(1) > cr.set_source_rgb(0, 1, 0) > cr.rectangle(cell_area.x, cell_area.y, cell_area.width, cell_area.height) > cr.stroke() > > cr.set_source_rgb(1, 1, 0) > cr.rectangle(cell_area.x, cell_area.y, cell_area.width, cell_area.height) > cr.fill() > > layout = widget.create_pango_layout(self._text) > Gtk.paint_layout(widget.get_style(), window, widget.get_state(), True, expose_area, > widget, "mec", cell_area.x, cell_area.y, layout) > >class Builder(Gtk.Builder): > > def connect_signals(self, obj_or_map): > print "connecting signals" > def _full_callback(builder, gobj, signal_name, handler_name, connect_obj, flags, dict_or_obj): > print ("full callback") > handler = None > if isinstance(dict_or_obj, dict): > handler = dict_or_obj.get(handler_name, None) > > if not handler: > raise AttributeError('Handler %s not found' % handler_name) > > if not callable(handler): > raise TypeError('Handler %s is not a method or function' % handler_name) > > after = False #flags and GObject.ConnectFlags.AFTER > if connect_obj: > if after: > gobj.connect_object_after(signal_name, handler, connect_obj) > else: > gobj.connect_object(signal_name, handler, connect_obj) > else: > if after: > gobj.connect_after(signal_name, handler) > else: > print "conencting %s to %s" %(signal_name, handler) > gobj.connect(signal_name, handler) > > self.connect_signals_full(_full_callback, > obj_or_map); > >GenericTreeModel = override(GenericTreeModel) >ListStore = override(ListStore) >GenericCellRenderer = override(GenericCellRenderer) >CellRenderer = override(CellRenderer) >Builder = override(Builder) >__all__ = [GenericTreeModel, ListStore, GenericCellRenderer, CellRenderer, Builder] > >import sys > >initialized, argv = Gtk.init_check(sys.argv) >if not initialized: > raise RuntimeError("Gtk couldn't be initialized")
Comment on attachment 159292 [details] My yet to be committed override file >class ListStore(Gtk.ListStore): > def __init__(self): > gobject.GObject.__init__(self) No need to override this. > def append(self, row): > iter_ = Gtk.TreeIter() titer or treeiter. prepend/insert should also be implemented here, they are pretty similar. >class CellRenderer(GenericCellRenderer): > __gtype_name__ = "MyCellRenderer" [...] > def on_get_size(self, widget, cell_area): > return 20, 20, 0, 0 Is this really what pygtk does or just a test? >GenericTreeModel = override(GenericTreeModel) >ListStore = override(ListStore) >GenericCellRenderer = override(GenericCellRenderer) >CellRenderer = override(CellRenderer) >Builder = override(Builder) Put these close to the wrapper, if we can use 2.5 or higher, use class decorators (or is that only in 2.6?) >__all__ = [GenericTreeModel, ListStore, GenericCellRenderer, CellRenderer, Builder] Should be strings iirc.
Class decorators only appeared in 2.6 I believe.
The issue seems to be that the python type derived from the g_type is GenericTreeModel while the g_type by all accounts correctly points to GtkTreeModel.
I've further reduced the bug to within this function call: PyObject * _pygi_type_get_from_g_type(GType g_type) { PyObject *py_g_type; PyObject *py_type; py_g_type = pyg_type_wrapper_new(g_type); if (py_g_type == NULL) { return NULL; } py_type = PyObject_GetAttrString(py_g_type, "pytype"); if (py_type == Py_None) { py_type = pygi_type_import_by_g_type(g_type); } Py_DECREF(py_g_type); return py_type; } print PyObject_Print(py_g_type, stderr,0) prints out the correct type. However once we get to the call py_type = PyObject_GetAttrString(py_g_type, "pytype"); py_type is populated with the GenericTreeModel type. e.g. print PyObject_Print(py_type, stderr,0) prints out the GenericTreeModel type
I'm also guessing that the pytype is being filled in by module.py in the DynamicModule class' __get_attr__ method: # Register the new Python wrapper. if g_type != gobject.TYPE_NONE: g_type.pytype = value I'm not sure why value is evaluating to GenericTreeModel
I might not fully understand what you're trying to do. But if you override something, then it completely shadows the overridden class; it is registered in the GType system in place of the automagicaly generated wrapper (that's what the 'override' decorator does), while the latter is still an ancestor of the override class. If you want, you can also add derived classes in an override module, without registering them as override classes.
The consensus here is to not override when not needed. That fixes one issue but it doesn't fix the typecheck issue if we have to override a base class like GtkCellRenderer. In that case we need to fix the checks to check if the wrapped object passed in is an instance of the g_type by using the g_type methods.
I should clarify. The issue is that when we override a base class it is no longer a base in the class hierarchy. It becomes a branch or sibling of the classes it was once a parent of. In this case python's isinstance call will return False. If a parameter of a function is expecting an overridden class and you pass in a subclass of the original type it will throw an error. In this case we need to check if the object is an instance of the original type, not the overridden type.
This no longer seems to be valid. The meta class injects the overridden base class into sub-classes of the original base class as far as I can tell and marshaling has the smarts to properly return types with this consideration.
This can be observed as follows: In [1]: from gi.repository import Gtk In [2]: Gtk.Spinner.mro() Out[2]: [gi.repository.Gtk.Spinner, gi.overrides.Gtk.Widget, gi.repository.Gtk.Widget, gi.repository.GObject.InitiallyUnowned, gi.overrides.GObject.Object, gi.repository.GObject.Object, gi._gobject._gobject.GObject, gi.repository.Atk.ImplementorIface, gi.repository.Gtk.Buildable, gobject.GInterface, builtins.object] In this case we override the base Gtk.Widget class but not Gtk.Spinner. However, Gtk.Spinner shows the overridden base properly in the mro.