Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/astroid/brain/brain_gi.py: 22%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

140 statements  

1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html 

2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE 

3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt 

4 

5"""Astroid hooks for the Python 2 GObject introspection bindings. 

6 

7Helps with understanding everything imported from 'gi.repository' 

8""" 

9 

10# pylint:disable=import-error,import-outside-toplevel 

11 

12import inspect 

13import itertools 

14import re 

15import sys 

16import warnings 

17 

18from astroid import nodes 

19from astroid.builder import AstroidBuilder 

20from astroid.exceptions import AstroidBuildingError 

21from astroid.manager import AstroidManager 

22 

23_inspected_modules = {} 

24 

25_identifier_re = r"^[A-Za-z_]\w*$" 

26 

27_special_methods = frozenset( 

28 { 

29 "__lt__", 

30 "__le__", 

31 "__eq__", 

32 "__ne__", 

33 "__ge__", 

34 "__gt__", 

35 "__iter__", 

36 "__getitem__", 

37 "__setitem__", 

38 "__delitem__", 

39 "__len__", 

40 "__bool__", 

41 "__nonzero__", 

42 "__next__", 

43 "__str__", 

44 "__contains__", 

45 "__enter__", 

46 "__exit__", 

47 "__repr__", 

48 "__getattr__", 

49 "__setattr__", 

50 "__delattr__", 

51 "__del__", 

52 "__hash__", 

53 } 

54) 

55 

56 

57def _gi_build_stub(parent): # noqa: C901 

58 """ 

59 Inspect the passed module recursively and build stubs for functions, 

60 classes, etc. 

61 """ 

62 # pylint: disable = too-many-branches, too-many-statements 

63 

64 classes = {} 

65 functions = {} 

66 constants = {} 

67 methods = {} 

68 for name in dir(parent): 

69 if name.startswith("__") and name not in _special_methods: 

70 continue 

71 

72 # Check if this is a valid name in python 

73 if not re.match(_identifier_re, name): 

74 continue 

75 

76 try: 

77 obj = getattr(parent, name) 

78 except Exception: # pylint: disable=broad-except 

79 # gi.module.IntrospectionModule.__getattr__() can raise all kinds of things 

80 # like ValueError, TypeError, NotImplementedError, RepositoryError, etc 

81 continue 

82 

83 if inspect.isclass(obj): 

84 classes[name] = obj 

85 elif inspect.isfunction(obj) or inspect.isbuiltin(obj): 

86 functions[name] = obj 

87 elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj): 

88 methods[name] = obj 

89 elif ( 

90 str(obj).startswith("<flags") 

91 or str(obj).startswith("<enum ") 

92 or str(obj).startswith("<GType ") 

93 or inspect.isdatadescriptor(obj) 

94 ): 

95 constants[name] = 0 

96 elif isinstance(obj, (int, str)): 

97 constants[name] = obj 

98 elif callable(obj): 

99 # Fall back to a function for anything callable 

100 functions[name] = obj 

101 else: 

102 # Assume everything else is some manner of constant 

103 constants[name] = 0 

104 

105 ret = "" 

106 

107 if constants: 

108 ret += f"# {parent.__name__} constants\n\n" 

109 for name in sorted(constants): 

110 if name[0].isdigit(): 

111 # GDK has some busted constant names like 

112 # Gdk.EventType.2BUTTON_PRESS 

113 continue 

114 

115 val = constants[name] 

116 

117 strval = str(val) 

118 if isinstance(val, str): 

119 strval = '"%s"' % str(val).replace("\\", "\\\\") 

120 ret += f"{name} = {strval}\n" 

121 

122 if ret: 

123 ret += "\n\n" 

124 if functions: 

125 ret += f"# {parent.__name__} functions\n\n" 

126 for name in sorted(functions): 

127 ret += f"def {name}(*args, **kwargs):\n" 

128 ret += " pass\n" 

129 

130 if ret: 

131 ret += "\n\n" 

132 if methods: 

133 ret += f"# {parent.__name__} methods\n\n" 

134 for name in sorted(methods): 

135 ret += f"def {name}(self, *args, **kwargs):\n" 

136 ret += " pass\n" 

137 

138 if ret: 

139 ret += "\n\n" 

140 if classes: 

141 ret += f"# {parent.__name__} classes\n\n" 

142 for name, obj in sorted(classes.items()): 

143 base = "object" 

144 if issubclass(obj, Exception): 

145 base = "Exception" 

146 ret += f"class {name}({base}):\n" 

147 

148 classret = _gi_build_stub(obj) 

149 if not classret: 

150 classret = "pass\n" 

151 

152 for line in classret.splitlines(): 

153 ret += " " + line + "\n" 

154 ret += "\n" 

155 

156 return ret 

157 

158 

159def _import_gi_module(modname): 

160 # we only consider gi.repository submodules 

161 if not modname.startswith("gi.repository."): 

162 raise AstroidBuildingError(modname=modname) 

163 # build astroid representation unless we already tried so 

164 if modname not in _inspected_modules: 

165 modnames = [modname] 

166 optional_modnames = [] 

167 

168 # GLib and GObject may have some special case handling 

169 # in pygobject that we need to cope with. However at 

170 # least as of pygobject3-3.13.91 the _glib module doesn't 

171 # exist anymore, so if treat these modules as optional. 

172 if modname == "gi.repository.GLib": 

173 optional_modnames.append("gi._glib") 

174 elif modname == "gi.repository.GObject": 

175 optional_modnames.append("gi._gobject") 

176 

177 try: 

178 modcode = "" 

179 for m in itertools.chain(modnames, optional_modnames): 

180 try: 

181 with warnings.catch_warnings(): 

182 # Just inspecting the code can raise gi deprecation 

183 # warnings, so ignore them. 

184 try: 

185 from gi import ( # pylint:disable=import-error 

186 PyGIDeprecationWarning, 

187 PyGIWarning, 

188 ) 

189 

190 warnings.simplefilter("ignore", PyGIDeprecationWarning) 

191 warnings.simplefilter("ignore", PyGIWarning) 

192 except Exception: # pylint:disable=broad-except 

193 pass 

194 

195 __import__(m) 

196 modcode += _gi_build_stub(sys.modules[m]) 

197 except ImportError: 

198 if m not in optional_modnames: 

199 raise 

200 except ImportError: 

201 astng = _inspected_modules[modname] = None 

202 else: 

203 astng = AstroidBuilder(AstroidManager()).string_build(modcode, modname) 

204 _inspected_modules[modname] = astng 

205 else: 

206 astng = _inspected_modules[modname] 

207 if astng is None: 

208 raise AstroidBuildingError(modname=modname) 

209 return astng 

210 

211 

212def _looks_like_require_version(node) -> bool: 

213 # Return whether this looks like a call to gi.require_version(<name>, <version>) 

214 # Only accept function calls with two constant arguments 

215 if len(node.args) != 2: 

216 return False 

217 

218 if not all(isinstance(arg, nodes.Const) for arg in node.args): 

219 return False 

220 

221 func = node.func 

222 if isinstance(func, nodes.Attribute): 

223 if func.attrname != "require_version": 

224 return False 

225 if isinstance(func.expr, nodes.Name) and func.expr.name == "gi": 

226 return True 

227 

228 return False 

229 

230 if isinstance(func, nodes.Name): 

231 return func.name == "require_version" 

232 

233 return False 

234 

235 

236def _register_require_version(node): 

237 # Load the gi.require_version locally 

238 try: 

239 import gi 

240 

241 gi.require_version(node.args[0].value, node.args[1].value) 

242 except Exception: # pylint:disable=broad-except 

243 pass 

244 

245 return node 

246 

247 

248def register(manager: AstroidManager) -> None: 

249 manager.register_failed_import_hook(_import_gi_module) 

250 manager.register_transform( 

251 nodes.Call, _register_require_version, _looks_like_require_version 

252 )