OLD | NEW |
(Empty) | |
| 1 """Astroid hooks for the Python 2 GObject introspection bindings. |
| 2 |
| 3 Helps with understanding everything imported from 'gi.repository' |
| 4 """ |
| 5 |
| 6 import inspect |
| 7 import itertools |
| 8 import sys |
| 9 import re |
| 10 |
| 11 from astroid import MANAGER, AstroidBuildingException |
| 12 from astroid.builder import AstroidBuilder |
| 13 |
| 14 |
| 15 _inspected_modules = {} |
| 16 |
| 17 _identifier_re = r'^[A-Za-z_]\w*$' |
| 18 |
| 19 def _gi_build_stub(parent): |
| 20 """ |
| 21 Inspect the passed module recursively and build stubs for functions, |
| 22 classes, etc. |
| 23 """ |
| 24 classes = {} |
| 25 functions = {} |
| 26 constants = {} |
| 27 methods = {} |
| 28 for name in dir(parent): |
| 29 if name.startswith("__"): |
| 30 continue |
| 31 |
| 32 # Check if this is a valid name in python |
| 33 if not re.match(_identifier_re, name): |
| 34 continue |
| 35 |
| 36 try: |
| 37 obj = getattr(parent, name) |
| 38 except: |
| 39 continue |
| 40 |
| 41 if inspect.isclass(obj): |
| 42 classes[name] = obj |
| 43 elif (inspect.isfunction(obj) or |
| 44 inspect.isbuiltin(obj)): |
| 45 functions[name] = obj |
| 46 elif (inspect.ismethod(obj) or |
| 47 inspect.ismethoddescriptor(obj)): |
| 48 methods[name] = obj |
| 49 elif type(obj) in [int, str]: |
| 50 constants[name] = obj |
| 51 elif (str(obj).startswith("<flags") or |
| 52 str(obj).startswith("<enum ") or |
| 53 str(obj).startswith("<GType ") or |
| 54 inspect.isdatadescriptor(obj)): |
| 55 constants[name] = 0 |
| 56 elif callable(obj): |
| 57 # Fall back to a function for anything callable |
| 58 functions[name] = obj |
| 59 else: |
| 60 # Assume everything else is some manner of constant |
| 61 constants[name] = 0 |
| 62 |
| 63 ret = "" |
| 64 |
| 65 if constants: |
| 66 ret += "# %s contants\n\n" % parent.__name__ |
| 67 for name in sorted(constants): |
| 68 if name[0].isdigit(): |
| 69 # GDK has some busted constant names like |
| 70 # Gdk.EventType.2BUTTON_PRESS |
| 71 continue |
| 72 |
| 73 val = constants[name] |
| 74 |
| 75 strval = str(val) |
| 76 if type(val) is str: |
| 77 strval = '"%s"' % str(val).replace("\\", "\\\\") |
| 78 ret += "%s = %s\n" % (name, strval) |
| 79 |
| 80 if ret: |
| 81 ret += "\n\n" |
| 82 if functions: |
| 83 ret += "# %s functions\n\n" % parent.__name__ |
| 84 for name in sorted(functions): |
| 85 func = functions[name] |
| 86 ret += "def %s(*args, **kwargs):\n" % name |
| 87 ret += " pass\n" |
| 88 |
| 89 if ret: |
| 90 ret += "\n\n" |
| 91 if methods: |
| 92 ret += "# %s methods\n\n" % parent.__name__ |
| 93 for name in sorted(methods): |
| 94 func = methods[name] |
| 95 ret += "def %s(self, *args, **kwargs):\n" % name |
| 96 ret += " pass\n" |
| 97 |
| 98 if ret: |
| 99 ret += "\n\n" |
| 100 if classes: |
| 101 ret += "# %s classes\n\n" % parent.__name__ |
| 102 for name in sorted(classes): |
| 103 ret += "class %s(object):\n" % name |
| 104 |
| 105 classret = _gi_build_stub(classes[name]) |
| 106 if not classret: |
| 107 classret = "pass\n" |
| 108 |
| 109 for line in classret.splitlines(): |
| 110 ret += " " + line + "\n" |
| 111 ret += "\n" |
| 112 |
| 113 return ret |
| 114 |
| 115 def _import_gi_module(modname): |
| 116 # we only consider gi.repository submodules |
| 117 if not modname.startswith('gi.repository.'): |
| 118 raise AstroidBuildingException() |
| 119 # build astroid representation unless we already tried so |
| 120 if modname not in _inspected_modules: |
| 121 modnames = [modname] |
| 122 optional_modnames = [] |
| 123 |
| 124 # GLib and GObject may have some special case handling |
| 125 # in pygobject that we need to cope with. However at |
| 126 # least as of pygobject3-3.13.91 the _glib module doesn't |
| 127 # exist anymore, so if treat these modules as optional. |
| 128 if modname == 'gi.repository.GLib': |
| 129 optional_modnames.append('gi._glib') |
| 130 elif modname == 'gi.repository.GObject': |
| 131 optional_modnames.append('gi._gobject') |
| 132 |
| 133 try: |
| 134 modcode = '' |
| 135 for m in itertools.chain(modnames, optional_modnames): |
| 136 try: |
| 137 __import__(m) |
| 138 modcode += _gi_build_stub(sys.modules[m]) |
| 139 except ImportError: |
| 140 if m not in optional_modnames: |
| 141 raise |
| 142 except ImportError: |
| 143 astng = _inspected_modules[modname] = None |
| 144 else: |
| 145 astng = AstroidBuilder(MANAGER).string_build(modcode, modname) |
| 146 _inspected_modules[modname] = astng |
| 147 else: |
| 148 astng = _inspected_modules[modname] |
| 149 if astng is None: |
| 150 raise AstroidBuildingException('Failed to import module %r' % modname) |
| 151 return astng |
| 152 |
| 153 |
| 154 MANAGER.register_failed_import_hook(_import_gi_module) |
| 155 |
OLD | NEW |