brain_gi.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. # Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  2. # Copyright (c) 2014 Google, Inc.
  3. # Copyright (c) 2014 Cole Robinson <crobinso@redhat.com>
  4. # Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
  5. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
  6. # Copyright (c) 2015 David Shea <dshea@redhat.com>
  7. # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
  8. # Copyright (c) 2016 Giuseppe Scrivano <gscrivan@redhat.com>
  9. # Copyright (c) 2018 Christoph Reiter <reiter.christoph@gmail.com>
  10. # Copyright (c) 2019 Philipp Hörist <philipp@hoerist.com>
  11. # Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
  12. # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  13. # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  14. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  15. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  16. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  17. """Astroid hooks for the Python 2 GObject introspection bindings.
  18. Helps with understanding everything imported from 'gi.repository'
  19. """
  20. # pylint:disable=import-error,import-outside-toplevel
  21. import inspect
  22. import itertools
  23. import re
  24. import sys
  25. import warnings
  26. from astroid import nodes
  27. from astroid.builder import AstroidBuilder
  28. from astroid.exceptions import AstroidBuildingError
  29. from astroid.manager import AstroidManager
  30. _inspected_modules = {}
  31. _identifier_re = r"^[A-Za-z_]\w*$"
  32. _special_methods = frozenset(
  33. {
  34. "__lt__",
  35. "__le__",
  36. "__eq__",
  37. "__ne__",
  38. "__ge__",
  39. "__gt__",
  40. "__iter__",
  41. "__getitem__",
  42. "__setitem__",
  43. "__delitem__",
  44. "__len__",
  45. "__bool__",
  46. "__nonzero__",
  47. "__next__",
  48. "__str__",
  49. "__len__",
  50. "__contains__",
  51. "__enter__",
  52. "__exit__",
  53. "__repr__",
  54. "__getattr__",
  55. "__setattr__",
  56. "__delattr__",
  57. "__del__",
  58. "__hash__",
  59. }
  60. )
  61. def _gi_build_stub(parent):
  62. """
  63. Inspect the passed module recursively and build stubs for functions,
  64. classes, etc.
  65. """
  66. classes = {}
  67. functions = {}
  68. constants = {}
  69. methods = {}
  70. for name in dir(parent):
  71. if name.startswith("__") and name not in _special_methods:
  72. continue
  73. # Check if this is a valid name in python
  74. if not re.match(_identifier_re, name):
  75. continue
  76. try:
  77. obj = getattr(parent, name)
  78. except AttributeError:
  79. continue
  80. if inspect.isclass(obj):
  81. classes[name] = obj
  82. elif inspect.isfunction(obj) or inspect.isbuiltin(obj):
  83. functions[name] = obj
  84. elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj):
  85. methods[name] = obj
  86. elif (
  87. str(obj).startswith("<flags")
  88. or str(obj).startswith("<enum ")
  89. or str(obj).startswith("<GType ")
  90. or inspect.isdatadescriptor(obj)
  91. ):
  92. constants[name] = 0
  93. elif isinstance(obj, (int, str)):
  94. constants[name] = obj
  95. elif callable(obj):
  96. # Fall back to a function for anything callable
  97. functions[name] = obj
  98. else:
  99. # Assume everything else is some manner of constant
  100. constants[name] = 0
  101. ret = ""
  102. if constants:
  103. ret += f"# {parent.__name__} constants\n\n"
  104. for name in sorted(constants):
  105. if name[0].isdigit():
  106. # GDK has some busted constant names like
  107. # Gdk.EventType.2BUTTON_PRESS
  108. continue
  109. val = constants[name]
  110. strval = str(val)
  111. if isinstance(val, str):
  112. strval = '"%s"' % str(val).replace("\\", "\\\\")
  113. ret += f"{name} = {strval}\n"
  114. if ret:
  115. ret += "\n\n"
  116. if functions:
  117. ret += f"# {parent.__name__} functions\n\n"
  118. for name in sorted(functions):
  119. ret += f"def {name}(*args, **kwargs):\n"
  120. ret += " pass\n"
  121. if ret:
  122. ret += "\n\n"
  123. if methods:
  124. ret += f"# {parent.__name__} methods\n\n"
  125. for name in sorted(methods):
  126. ret += f"def {name}(self, *args, **kwargs):\n"
  127. ret += " pass\n"
  128. if ret:
  129. ret += "\n\n"
  130. if classes:
  131. ret += f"# {parent.__name__} classes\n\n"
  132. for name, obj in sorted(classes.items()):
  133. base = "object"
  134. if issubclass(obj, Exception):
  135. base = "Exception"
  136. ret += f"class {name}({base}):\n"
  137. classret = _gi_build_stub(obj)
  138. if not classret:
  139. classret = "pass\n"
  140. for line in classret.splitlines():
  141. ret += " " + line + "\n"
  142. ret += "\n"
  143. return ret
  144. def _import_gi_module(modname):
  145. # we only consider gi.repository submodules
  146. if not modname.startswith("gi.repository."):
  147. raise AstroidBuildingError(modname=modname)
  148. # build astroid representation unless we already tried so
  149. if modname not in _inspected_modules:
  150. modnames = [modname]
  151. optional_modnames = []
  152. # GLib and GObject may have some special case handling
  153. # in pygobject that we need to cope with. However at
  154. # least as of pygobject3-3.13.91 the _glib module doesn't
  155. # exist anymore, so if treat these modules as optional.
  156. if modname == "gi.repository.GLib":
  157. optional_modnames.append("gi._glib")
  158. elif modname == "gi.repository.GObject":
  159. optional_modnames.append("gi._gobject")
  160. try:
  161. modcode = ""
  162. for m in itertools.chain(modnames, optional_modnames):
  163. try:
  164. with warnings.catch_warnings():
  165. # Just inspecting the code can raise gi deprecation
  166. # warnings, so ignore them.
  167. try:
  168. from gi import ( # pylint:disable=import-error
  169. PyGIDeprecationWarning,
  170. PyGIWarning,
  171. )
  172. warnings.simplefilter("ignore", PyGIDeprecationWarning)
  173. warnings.simplefilter("ignore", PyGIWarning)
  174. except Exception: # pylint:disable=broad-except
  175. pass
  176. __import__(m)
  177. modcode += _gi_build_stub(sys.modules[m])
  178. except ImportError:
  179. if m not in optional_modnames:
  180. raise
  181. except ImportError:
  182. astng = _inspected_modules[modname] = None
  183. else:
  184. astng = AstroidBuilder(AstroidManager()).string_build(modcode, modname)
  185. _inspected_modules[modname] = astng
  186. else:
  187. astng = _inspected_modules[modname]
  188. if astng is None:
  189. raise AstroidBuildingError(modname=modname)
  190. return astng
  191. def _looks_like_require_version(node):
  192. # Return whether this looks like a call to gi.require_version(<name>, <version>)
  193. # Only accept function calls with two constant arguments
  194. if len(node.args) != 2:
  195. return False
  196. if not all(isinstance(arg, nodes.Const) for arg in node.args):
  197. return False
  198. func = node.func
  199. if isinstance(func, nodes.Attribute):
  200. if func.attrname != "require_version":
  201. return False
  202. if isinstance(func.expr, nodes.Name) and func.expr.name == "gi":
  203. return True
  204. return False
  205. if isinstance(func, nodes.Name):
  206. return func.name == "require_version"
  207. return False
  208. def _register_require_version(node):
  209. # Load the gi.require_version locally
  210. try:
  211. import gi
  212. gi.require_version(node.args[0].value, node.args[1].value)
  213. except Exception: # pylint:disable=broad-except
  214. pass
  215. return node
  216. AstroidManager().register_failed_import_hook(_import_gi_module)
  217. AstroidManager().register_transform(
  218. nodes.Call, _register_require_version, _looks_like_require_version
  219. )