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

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

154 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_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 

134 ret = "" 

135 

136 if constants: 

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

138 for name in sorted(constants): 

139 if name[0].isdigit(): 

140 # GDK has some busted constant names like 

141 # Gdk.EventType.2BUTTON_PRESS 

142 continue 

143 

144 val = constants[name] 

145 

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

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

148 strval = f'"{val_repr}"' 

149 else: # pragma: no cover 

150 strval = str(val) 

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

152 

153 if ret: 

154 ret += "\n\n" 

155 if functions: 

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

157 for name in sorted(functions): 

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

159 ret += " pass\n" 

160 

161 if ret: 

162 ret += "\n\n" 

163 if methods: 

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

165 for name in sorted(methods): 

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

167 ret += " pass\n" 

168 

169 if ret: 

170 ret += "\n\n" 

171 if classes: 

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

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

174 base = "object" 

175 if issubclass(obj, Exception): 

176 base = "Exception" 

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

178 

179 classret = _gi_build_stub(obj) 

180 if not classret: 

181 classret = "pass\n" 

182 

183 for line in classret.splitlines(): 

184 ret += " " + line + "\n" 

185 ret += "\n" 

186 

187 return ret 

188 

189 

190def _import_gi_module(modname): 

191 # we only consider gi.repository submodules 

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

193 raise AstroidBuildingError(modname=modname) 

194 # build astroid representation unless we already tried so 

195 if modname not in _inspected_modules: 

196 modnames = [modname] 

197 optional_modnames = [] 

198 

199 # GLib and GObject may have some special case handling 

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

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

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

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

204 optional_modnames.append("gi._glib") 

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

206 optional_modnames.append("gi._gobject") 

207 

208 try: 

209 modcode = "" 

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

211 try: 

212 with warnings.catch_warnings(): 

213 # Just inspecting the code can raise gi deprecation 

214 # warnings, so ignore them. 

215 try: 

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

217 PyGIDeprecationWarning, 

218 PyGIWarning, 

219 ) 

220 

221 warnings.simplefilter("ignore", PyGIDeprecationWarning) 

222 warnings.simplefilter("ignore", PyGIWarning) 

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

224 pass 

225 

226 __import__(m) 

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

228 except ImportError: 

229 if m not in optional_modnames: 

230 raise 

231 except ImportError: 

232 astng = _inspected_modules[modname] = None 

233 else: 

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

235 _inspected_modules[modname] = astng 

236 else: 

237 astng = _inspected_modules[modname] 

238 if astng is None: 

239 raise AstroidBuildingError(modname=modname) 

240 return astng 

241 

242 

243def _looks_like_require_version(node) -> bool: 

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

245 # Only accept function calls with two constant arguments 

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

247 return False 

248 

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

250 return False 

251 

252 func = node.func 

253 if isinstance(func, nodes.Attribute): 

254 if func.attrname != "require_version": 

255 return False 

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

257 return True 

258 

259 return False 

260 

261 if isinstance(func, nodes.Name): 

262 return func.name == "require_version" 

263 

264 return False 

265 

266 

267def _register_require_version(node): 

268 # Load the gi.require_version locally 

269 try: 

270 import gi 

271 

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

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

274 pass 

275 

276 return node 

277 

278 

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

280 manager.register_failed_import_hook(_import_gi_module) 

281 manager.register_transform( 

282 nodes.Call, _register_require_version, _looks_like_require_version 

283 )