Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/brain/brain_gi.py: 17%
139 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:53 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:53 +0000
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
5"""Astroid hooks for the Python 2 GObject introspection bindings.
7Helps with understanding everything imported from 'gi.repository'
8"""
10# pylint:disable=import-error,import-outside-toplevel
12import inspect
13import itertools
14import re
15import sys
16import warnings
18from astroid import nodes
19from astroid.builder import AstroidBuilder
20from astroid.exceptions import AstroidBuildingError
21from astroid.manager import AstroidManager
23_inspected_modules = {}
25_identifier_re = r"^[A-Za-z_]\w*$"
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)
57def _gi_build_stub(parent): # noqa: C901
58 """
59 Inspect the passed module recursively and build stubs for functions,
60 classes, etc.
61 """
62 classes = {}
63 functions = {}
64 constants = {}
65 methods = {}
66 for name in dir(parent):
67 if name.startswith("__") and name not in _special_methods:
68 continue
70 # Check if this is a valid name in python
71 if not re.match(_identifier_re, name):
72 continue
74 try:
75 obj = getattr(parent, name)
76 except Exception: # pylint: disable=broad-except
77 # gi.module.IntrospectionModule.__getattr__() can raise all kinds of things
78 # like ValueError, TypeError, NotImplementedError, RepositoryError, etc
79 continue
81 if inspect.isclass(obj):
82 classes[name] = obj
83 elif inspect.isfunction(obj) or inspect.isbuiltin(obj):
84 functions[name] = obj
85 elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj):
86 methods[name] = obj
87 elif (
88 str(obj).startswith("<flags")
89 or str(obj).startswith("<enum ")
90 or str(obj).startswith("<GType ")
91 or inspect.isdatadescriptor(obj)
92 ):
93 constants[name] = 0
94 elif isinstance(obj, (int, str)):
95 constants[name] = obj
96 elif callable(obj):
97 # Fall back to a function for anything callable
98 functions[name] = obj
99 else:
100 # Assume everything else is some manner of constant
101 constants[name] = 0
103 ret = ""
105 if constants:
106 ret += f"# {parent.__name__} constants\n\n"
107 for name in sorted(constants):
108 if name[0].isdigit():
109 # GDK has some busted constant names like
110 # Gdk.EventType.2BUTTON_PRESS
111 continue
113 val = constants[name]
115 strval = str(val)
116 if isinstance(val, str):
117 strval = '"%s"' % str(val).replace("\\", "\\\\")
118 ret += f"{name} = {strval}\n"
120 if ret:
121 ret += "\n\n"
122 if functions:
123 ret += f"# {parent.__name__} functions\n\n"
124 for name in sorted(functions):
125 ret += f"def {name}(*args, **kwargs):\n"
126 ret += " pass\n"
128 if ret:
129 ret += "\n\n"
130 if methods:
131 ret += f"# {parent.__name__} methods\n\n"
132 for name in sorted(methods):
133 ret += f"def {name}(self, *args, **kwargs):\n"
134 ret += " pass\n"
136 if ret:
137 ret += "\n\n"
138 if classes:
139 ret += f"# {parent.__name__} classes\n\n"
140 for name, obj in sorted(classes.items()):
141 base = "object"
142 if issubclass(obj, Exception):
143 base = "Exception"
144 ret += f"class {name}({base}):\n"
146 classret = _gi_build_stub(obj)
147 if not classret:
148 classret = "pass\n"
150 for line in classret.splitlines():
151 ret += " " + line + "\n"
152 ret += "\n"
154 return ret
157def _import_gi_module(modname):
158 # we only consider gi.repository submodules
159 if not modname.startswith("gi.repository."):
160 raise AstroidBuildingError(modname=modname)
161 # build astroid representation unless we already tried so
162 if modname not in _inspected_modules:
163 modnames = [modname]
164 optional_modnames = []
166 # GLib and GObject may have some special case handling
167 # in pygobject that we need to cope with. However at
168 # least as of pygobject3-3.13.91 the _glib module doesn't
169 # exist anymore, so if treat these modules as optional.
170 if modname == "gi.repository.GLib":
171 optional_modnames.append("gi._glib")
172 elif modname == "gi.repository.GObject":
173 optional_modnames.append("gi._gobject")
175 try:
176 modcode = ""
177 for m in itertools.chain(modnames, optional_modnames):
178 try:
179 with warnings.catch_warnings():
180 # Just inspecting the code can raise gi deprecation
181 # warnings, so ignore them.
182 try:
183 from gi import ( # pylint:disable=import-error
184 PyGIDeprecationWarning,
185 PyGIWarning,
186 )
188 warnings.simplefilter("ignore", PyGIDeprecationWarning)
189 warnings.simplefilter("ignore", PyGIWarning)
190 except Exception: # pylint:disable=broad-except
191 pass
193 __import__(m)
194 modcode += _gi_build_stub(sys.modules[m])
195 except ImportError:
196 if m not in optional_modnames:
197 raise
198 except ImportError:
199 astng = _inspected_modules[modname] = None
200 else:
201 astng = AstroidBuilder(AstroidManager()).string_build(modcode, modname)
202 _inspected_modules[modname] = astng
203 else:
204 astng = _inspected_modules[modname]
205 if astng is None:
206 raise AstroidBuildingError(modname=modname)
207 return astng
210def _looks_like_require_version(node) -> bool:
211 # Return whether this looks like a call to gi.require_version(<name>, <version>)
212 # Only accept function calls with two constant arguments
213 if len(node.args) != 2:
214 return False
216 if not all(isinstance(arg, nodes.Const) for arg in node.args):
217 return False
219 func = node.func
220 if isinstance(func, nodes.Attribute):
221 if func.attrname != "require_version":
222 return False
223 if isinstance(func.expr, nodes.Name) and func.expr.name == "gi":
224 return True
226 return False
228 if isinstance(func, nodes.Name):
229 return func.name == "require_version"
231 return False
234def _register_require_version(node):
235 # Load the gi.require_version locally
236 try:
237 import gi
239 gi.require_version(node.args[0].value, node.args[1].value)
240 except Exception: # pylint:disable=broad-except
241 pass
243 return node
246AstroidManager().register_failed_import_hook(_import_gi_module)
247AstroidManager().register_transform(
248 nodes.Call, _register_require_version, _looks_like_require_version
249)