# -*- Mode: Python; py-indent-offset: 4 -*- # vim: tabstop=4 shiftwidth=4 expandtab # # Copyright (C) 2005-2009 Johan Dahlin # # types.py: base types for introspected items. # # 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 from __future__ import absolute_import import sys import gobject from ._gi import \ InterfaceInfo, \ ObjectInfo, \ StructInfo, \ VFuncInfo, \ set_object_has_new_constructor, \ register_interface_info, \ hook_up_vfunc_implementation if sys.version_info > (3, 0): def callable(obj): return hasattr(obj, '__call__') def Function(info): def function(*args): return info.invoke(*args) function.__info__ = info function.__name__ = info.get_name() function.__module__ = info.get_namespace() return function def NativeVFunc(info, cls): def native_vfunc(*args): return info.invoke(*args, **dict(gtype=cls.__gtype__)) native_vfunc.__info__ = info native_vfunc.__name__ = info.get_name() native_vfunc.__module__ = info.get_namespace() return native_vfunc def Constructor(info): def constructor(cls, *args): cls_name = info.get_container().get_name() if cls.__name__ != cls_name: raise TypeError('%s constructor cannot be used to create instances of a subclass' % cls_name) return info.invoke(cls, *args) constructor.__info__ = info constructor.__name__ = info.get_name() constructor.__module__ = info.get_namespace() return constructor class MetaClassHelper(object): def _setup_constructors(cls): for method_info in cls.__info__.get_methods(): if method_info.is_constructor(): name = method_info.get_name() constructor = classmethod(Constructor(method_info)) setattr(cls, name, constructor) def _setup_methods(cls): for method_info in cls.__info__.get_methods(): name = method_info.get_name() function = Function(method_info) if method_info.is_method(): method = function elif method_info.is_constructor(): continue else: method = staticmethod(function) setattr(cls, name, method) def _setup_fields(cls): for field_info in cls.__info__.get_fields(): name = field_info.get_name().replace('-', '_') setattr(cls, name, property(field_info.get_value, field_info.set_value)) def _setup_constants(cls): for constant_info in cls.__info__.get_constants(): name = constant_info.get_name() value = constant_info.get_value() setattr(cls, name, value) def _setup_vfuncs(cls): for vfunc_name, py_vfunc in cls.__dict__.items(): if not vfunc_name.startswith("do_") or not callable(py_vfunc): continue # If a method name starts with "do_" assume it is a vfunc, and search # in the base classes for a method with the same name to override. # Recursion is not necessary here because getattr() searches all # super class attributes as well. vfunc_info = None for base in cls.__bases__: method = getattr(base, vfunc_name, None) if method is not None and hasattr(method, '__info__') and \ isinstance(method.__info__, VFuncInfo): vfunc_info = method.__info__ break # If we did not find a matching method name in the bases, we might # be overriding an interface virtual method. Since interfaces do not # provide implementations, there will be no method attribute installed # on the object. Instead we have to search through # InterfaceInfo.get_vfuncs(). Note that the infos returned by # get_vfuncs() use the C vfunc name (ie. there is no "do_" prefix). if vfunc_info is None: vfunc_info = find_vfunc_info_in_interface(cls.__bases__, vfunc_name[len("do_"):]) if vfunc_info is not None: assert vfunc_name == ('do_' + vfunc_info.get_name()) # Check to see if there are vfuncs with the same name in the bases. # We have no way of specifying which one we are supposed to override. ambiguous_base = find_vfunc_conflict_in_bases(vfunc_info, cls.__bases__) if ambiguous_base is not None: base_info = vfunc_info.get_container() raise TypeError('Method %s() on class %s.%s is ambiguous ' 'with methods in base classes %s.%s and %s.%s' % (vfunc_name, cls.__info__.get_namespace(), cls.__info__.get_name(), base_info.get_namespace(), base_info.get_name(), ambiguous_base.__info__.get_namespace(), ambiguous_base.__info__.get_name())) hook_up_vfunc_implementation(vfunc_info, cls.__gtype__, py_vfunc) def _setup_native_vfuncs(cls): # Only InterfaceInfo and ObjectInfo have the get_vfuncs() method. # We skip InterfaceInfo because interfaces have no implementations for vfuncs. # Also check if __info__ in __dict__, not hasattr('__info__', ...) # because we do not want to accidentally retrieve __info__ from a base class. class_info = cls.__dict__.get('__info__') if class_info is None or not isinstance(class_info, ObjectInfo): return for vfunc_info in class_info.get_vfuncs(): name = 'do_%s' % vfunc_info.get_name() value = NativeVFunc(vfunc_info, cls) setattr(cls, name, value) def find_vfunc_info_in_interface(bases, vfunc_name): for base in bases: # All wrapped interfaces inherit from GInterface. # This can be seen in IntrospectionModule.__getattr__() in module.py. # We do not need to search regular classes here, only wrapped interfaces. # We also skip GInterface, because it is not wrapped and has no __info__ attr. if base is gobject.GInterface or\ not issubclass(base, gobject.GInterface) or\ not isinstance(base.__info__, InterfaceInfo): continue for vfunc in base.__info__.get_vfuncs(): if vfunc.get_name() == vfunc_name: return vfunc vfunc = find_vfunc_info_in_interface(base.__bases__, vfunc_name) if vfunc is not None: return vfunc return None def find_vfunc_conflict_in_bases(vfunc, bases): for klass in bases: if not hasattr(klass, '__info__') or \ not hasattr(klass.__info__, 'get_vfuncs'): continue vfuncs = klass.__info__.get_vfuncs() vfunc_name = vfunc.get_name() for v in vfuncs: if v.get_name() == vfunc_name and v != vfunc: return klass aklass = find_vfunc_conflict_in_bases(vfunc, klass.__bases__) if aklass is not None: return aklass return None class GObjectMeta(gobject.GObjectMeta, MetaClassHelper): def __init__(cls, name, bases, dict_): super(GObjectMeta, cls).__init__(name, bases, dict_) is_gi_defined = False if cls.__module__ == 'gi.repository.' + cls.__info__.get_namespace(): is_gi_defined = True is_python_defined = False if not is_gi_defined and cls.__module__ != GObjectMeta.__module__: is_python_defined = True if is_python_defined: cls._setup_vfuncs() elif is_gi_defined: cls._setup_methods() cls._setup_constants() cls._setup_native_vfuncs() if isinstance(cls.__info__, ObjectInfo): cls._setup_fields() cls._setup_constructors() set_object_has_new_constructor(cls.__info__.get_g_type()) elif isinstance(cls.__info__, InterfaceInfo): register_interface_info(cls.__info__.get_g_type()) def mro(cls): return mro(cls) def _must_register_type(cls, namespace): ## don't register the class if already registered if '__gtype__' in namespace: return False # Do not register a new GType for the overrides, as this would sort of # defeat the purpose of overrides... return not cls.__module__.startswith('gi.overrides.') def mro(C): """Compute the class precedence list (mro) according to C3 Based on http://www.python.org/download/releases/2.3/mro/ Modified to consider that interfaces don't create the diamond problem """ # TODO: If this turns out being too slow, consider using generators bases = [] bases_of_subclasses = [[C]] if C.__bases__: bases_of_subclasses += list(map(mro, C.__bases__)) + [list(C.__bases__)] while bases_of_subclasses: for subclass_bases in bases_of_subclasses: candidate = subclass_bases[0] not_head = [s for s in bases_of_subclasses if candidate in s[1:]] if not_head and gobject.GInterface not in candidate.__bases__: candidate = None # conflict, reject candidate else: break if candidate is None: raise TypeError('Cannot create a consistent method resolution ' 'order (MRO)') bases.append(candidate) for subclass_bases in bases_of_subclasses[:]: # remove candidate if subclass_bases and subclass_bases[0] == candidate: del subclass_bases[0] if not subclass_bases: bases_of_subclasses.remove(subclass_bases) return bases class StructMeta(type, MetaClassHelper): def __init__(cls, name, bases, dict_): super(StructMeta, cls).__init__(name, bases, dict_) # Avoid touching anything else than the base class. g_type = cls.__info__.get_g_type() if g_type != gobject.TYPE_INVALID and g_type.pytype is not None: return cls._setup_fields() cls._setup_methods() cls._setup_constructors() for method_info in cls.__info__.get_methods(): if method_info.is_constructor() and \ method_info.get_name() == 'new' and \ not method_info.get_arguments(): cls.__new__ = staticmethod(Constructor(method_info)) break