"""Astroid hooks for the Python 2 GObject introspection bindings. Helps with understanding everything imported from 'gi.repository' """ import inspect import itertools import sys import re from astroid import MANAGER, AstroidBuildingException from astroid.builder import AstroidBuilder _inspected_modules = {} _identifier_re = r'^[A-Za-z_]\w*$' def _gi_build_stub(parent): """ Inspect the passed module recursively and build stubs for functions, classes, etc. """ classes = {} functions = {} constants = {} methods = {} for name in dir(parent): if name.startswith("__"): continue # Check if this is a valid name in python if not re.match(_identifier_re, name): continue try: obj = getattr(parent, name) except: continue if inspect.isclass(obj): classes[name] = obj elif (inspect.isfunction(obj) or inspect.isbuiltin(obj)): functions[name] = obj elif (inspect.ismethod(obj) or inspect.ismethoddescriptor(obj)): methods[name] = obj elif type(obj) in [int, str]: constants[name] = obj elif (str(obj).startswith("<flags") or str(obj).startswith("<enum ") or str(obj).startswith("<GType ") or inspect.isdatadescriptor(obj)): constants[name] = 0 elif callable(obj): # Fall back to a function for anything callable functions[name] = obj else: # Assume everything else is some manner of constant constants[name] = 0 ret = "" if constants: ret += "# %s contants\n\n" % parent.__name__ for name in sorted(constants): if name[0].isdigit(): # GDK has some busted constant names like # Gdk.EventType.2BUTTON_PRESS continue val = constants[name] strval = str(val) if type(val) is str: strval = '"%s"' % str(val).replace("\\", "\\\\") ret += "%s = %s\n" % (name, strval) if ret: ret += "\n\n" if functions: ret += "# %s functions\n\n" % parent.__name__ for name in sorted(functions): func = functions[name] ret += "def %s(*args, **kwargs):\n" % name ret += " pass\n" if ret: ret += "\n\n" if methods: ret += "# %s methods\n\n" % parent.__name__ for name in sorted(methods): func = methods[name] ret += "def %s(self, *args, **kwargs):\n" % name ret += " pass\n" if ret: ret += "\n\n" if classes: ret += "# %s classes\n\n" % parent.__name__ for name in sorted(classes): ret += "class %s(object):\n" % name classret = _gi_build_stub(classes[name]) if not classret: classret = "pass\n" for line in classret.splitlines(): ret += " " + line + "\n" ret += "\n" return ret def _import_gi_module(modname): # we only consider gi.repository submodules if not modname.startswith('gi.repository.'): raise AstroidBuildingException() # build astroid representation unless we already tried so if modname not in _inspected_modules: modnames = [modname] optional_modnames = [] # GLib and GObject may have some special case handling # in pygobject that we need to cope with. However at # least as of pygobject3-3.13.91 the _glib module doesn't # exist anymore, so if treat these modules as optional. if modname == 'gi.repository.GLib': optional_modnames.append('gi._glib') elif modname == 'gi.repository.GObject': optional_modnames.append('gi._gobject') try: modcode = '' for m in itertools.chain(modnames, optional_modnames): try: __import__(m) modcode += _gi_build_stub(sys.modules[m]) except ImportError: if m not in optional_modnames: raise except ImportError: astng = _inspected_modules[modname] = None else: astng = AstroidBuilder(MANAGER).string_build(modcode, modname) _inspected_modules[modname] = astng else: astng = _inspected_modules[modname] if astng is None: raise AstroidBuildingException('Failed to import module %r' % modname) return astng MANAGER.register_failed_import_hook(_import_gi_module)