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

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

171 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 GObject introspection bindings. 

6 

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

8""" 

9 

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

11 

12import enum 

13import inspect 

14import itertools 

15import re 

16import sys 

17import warnings 

18 

19from astroid import nodes 

20from astroid.builder import AstroidBuilder 

21from astroid.exceptions import AstroidBuildingError 

22from astroid.manager import AstroidManager 

23 

24_inspected_modules = {} 

25 

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

27 

28_special_methods = frozenset( 

29 { 

30 "__lt__", 

31 "__le__", 

32 "__eq__", 

33 "__ne__", 

34 "__ge__", 

35 "__gt__", 

36 "__iter__", 

37 "__getitem__", 

38 "__setitem__", 

39 "__delitem__", 

40 "__len__", 

41 "__bool__", 

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_supports_inspect_signature(): 

58 """ 

59 Indicates if pygobject supports inspect.signature(). 

60 """ 

61 import gi 

62 

63 try: 

64 # inspect.signature() is supported since pygobject==3.51.0 (ee9558e4). 

65 gi.check_version((3, 51, 0)) 

66 return True 

67 except ValueError: 

68 pass 

69 return False 

70 

71 

72def _gi_is_method_call(obj): 

73 if _gi_supports_inspect_signature(): 

74 # Since inspect.signature() is supported, the workaround to use 

75 # inspect.ismethoddescriptor() was disabled and cannot be used anymore 

76 # to tell apart functions from methods. 

77 # See https://github.com/pylint-dev/astroid/issues/2594 

78 try: 

79 sig = str(inspect.signature(obj)) 

80 return sig == "(self)" or sig.startswith("(self, ") 

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

82 return False 

83 return inspect.ismethod(obj) or inspect.ismethoddescriptor(obj) 

84 

85 

86def _gi_build_stub(parent): # noqa: C901 

87 """ 

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

89 classes, etc. 

90 """ 

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

92 

93 classes = {} 

94 functions = {} 

95 constants = {} 

96 methods = {} 

97 for name in dir(parent): 

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

99 continue 

100 

101 # Check if this is a valid name in python 

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

103 continue 

104 

105 try: 

106 obj = getattr(parent, name) 

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

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

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

110 continue 

111 

112 if inspect.isclass(obj): 

113 classes[name] = obj 

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

115 functions[name] = obj 

116 elif _gi_is_method_call(obj): 

117 methods[name] = obj 

118 elif ( 

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

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

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

122 or inspect.isdatadescriptor(obj) 

123 ): 

124 constants[name] = 0 

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

126 constants[name] = obj 

127 elif callable(obj): 

128 # Fall back to a function for anything callable 

129 functions[name] = obj 

130 else: 

131 # Assume everything else is some manner of constant 

132 constants[name] = 0 

133 # iterating enum.IntFlag doesn't include the zero value 

134 if inspect.isclass(parent) and issubclass(parent, enum.IntFlag): 

135 try: 

136 zero_name = parent(0).name 

137 if zero_name: 

138 constants[zero_name] = 0 

139 except TypeError: 

140 pass 

141 

142 ret = "" 

143 

144 if constants: 

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

146 for name in sorted(constants): 

147 if name[0].isdigit(): 

148 # GDK has some busted constant names like 

149 # Gdk.EventType.2BUTTON_PRESS 

150 continue 

151 

152 val = constants[name] 

153 

154 if isinstance(val, str): # pragma: no cover 

155 val_repr = val.replace("\\", "\\\\") 

156 strval = f'"{val_repr}"' 

157 else: # pragma: no cover 

158 strval = str(val) 

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

160 

161 if ret: 

162 ret += "\n\n" 

163 if functions: 

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

165 for name in sorted(functions): 

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

167 ret += " pass\n" 

168 

169 if ret: 

170 ret += "\n\n" 

171 if methods: 

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

173 for name in sorted(methods): 

174 static = False 

175 try: 

176 if not methods[name].is_method(): 

177 static = True 

178 except AttributeError: 

179 pass 

180 if static: 

181 ret += "@staticmethod\n" 

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

183 else: 

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

185 ret += " pass\n" 

186 

187 if ret: 

188 ret += "\n\n" 

189 if classes: 

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

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

192 base = "object" 

193 if issubclass(obj, Exception): 

194 base = "Exception" 

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

196 

197 classret = _gi_build_stub(obj) 

198 if not classret: 

199 classret = "pass\n" 

200 

201 for line in classret.splitlines(): 

202 ret += " " + line + "\n" 

203 ret += "\n" 

204 

205 return ret 

206 

207 

208def _import_gi_module(modname): 

209 # we only consider gi.repository submodules 

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

211 raise AstroidBuildingError(modname=modname) 

212 # build astroid representation unless we already tried so 

213 if modname not in _inspected_modules: 

214 modnames = [modname] 

215 optional_modnames = [] 

216 

217 # GLib and GObject may have some special case handling 

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

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

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

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

222 optional_modnames.append("gi._glib") 

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

224 optional_modnames.append("gi._gobject") 

225 

226 try: 

227 modcode = "" 

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

229 try: 

230 with warnings.catch_warnings(): 

231 # Just inspecting the code can raise gi deprecation 

232 # warnings, so ignore them. 

233 try: 

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

235 PyGIDeprecationWarning, 

236 PyGIWarning, 

237 ) 

238 

239 warnings.simplefilter("ignore", PyGIDeprecationWarning) 

240 warnings.simplefilter("ignore", PyGIWarning) 

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

242 pass 

243 

244 __import__(m) 

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

246 except ImportError: 

247 if m not in optional_modnames: 

248 raise 

249 except ImportError: 

250 astng = _inspected_modules[modname] = None 

251 else: 

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

253 _inspected_modules[modname] = astng 

254 else: 

255 astng = _inspected_modules[modname] 

256 if astng is None: 

257 raise AstroidBuildingError(modname=modname) 

258 return astng 

259 

260 

261def _looks_like_require_version(node) -> bool: 

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

263 # Only accept function calls with two constant arguments 

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

265 return False 

266 

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

268 return False 

269 

270 func = node.func 

271 if isinstance(func, nodes.Attribute): 

272 if func.attrname != "require_version": 

273 return False 

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

275 return True 

276 

277 return False 

278 

279 if isinstance(func, nodes.Name): 

280 return func.name == "require_version" 

281 

282 return False 

283 

284 

285def _register_require_version(node): 

286 # Load the gi.require_version locally 

287 try: 

288 import gi 

289 

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

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

292 pass 

293 

294 return node 

295 

296 

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

298 manager.register_failed_import_hook(_import_gi_module) 

299 manager.register_transform( 

300 nodes.Call, _register_require_version, _looks_like_require_version 

301 )