123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- # Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
- # Copyright (c) 2014 Google, Inc.
- # Copyright (c) 2014 Cole Robinson <crobinso@redhat.com>
- # Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
- # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
- # Copyright (c) 2015 David Shea <dshea@redhat.com>
- # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
- # Copyright (c) 2016 Giuseppe Scrivano <gscrivan@redhat.com>
- # Copyright (c) 2018 Christoph Reiter <reiter.christoph@gmail.com>
- # Copyright (c) 2019 Philipp Hörist <philipp@hoerist.com>
- # Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
- # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
- # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
- # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
- # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
- # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
- """Astroid hooks for the Python 2 GObject introspection bindings.
- Helps with understanding everything imported from 'gi.repository'
- """
- # pylint:disable=import-error,import-outside-toplevel
- import inspect
- import itertools
- import re
- import sys
- import warnings
- from astroid import nodes
- from astroid.builder import AstroidBuilder
- from astroid.exceptions import AstroidBuildingError
- from astroid.manager import AstroidManager
- _inspected_modules = {}
- _identifier_re = r"^[A-Za-z_]\w*$"
- _special_methods = frozenset(
- {
- "__lt__",
- "__le__",
- "__eq__",
- "__ne__",
- "__ge__",
- "__gt__",
- "__iter__",
- "__getitem__",
- "__setitem__",
- "__delitem__",
- "__len__",
- "__bool__",
- "__nonzero__",
- "__next__",
- "__str__",
- "__len__",
- "__contains__",
- "__enter__",
- "__exit__",
- "__repr__",
- "__getattr__",
- "__setattr__",
- "__delattr__",
- "__del__",
- "__hash__",
- }
- )
- def _gi_build_stub(parent):
- """
- Inspect the passed module recursively and build stubs for functions,
- classes, etc.
- """
- classes = {}
- functions = {}
- constants = {}
- methods = {}
- for name in dir(parent):
- if name.startswith("__") and name not in _special_methods:
- continue
- # Check if this is a valid name in python
- if not re.match(_identifier_re, name):
- continue
- try:
- obj = getattr(parent, name)
- except AttributeError:
- continue
- if inspect.isclass(obj):
- classes[name] = obj
- elif inspect.isfunction(obj) or inspect.isbuiltin(obj):
- functions[name] = obj
- elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj):
- methods[name] = obj
- elif (
- str(obj).startswith("<flags")
- or str(obj).startswith("<enum ")
- or str(obj).startswith("<GType ")
- or inspect.isdatadescriptor(obj)
- ):
- constants[name] = 0
- elif isinstance(obj, (int, str)):
- constants[name] = obj
- elif callable(obj):
- # Fall back to a function for anything callable
- functions[name] = obj
- else:
- # Assume everything else is some manner of constant
- constants[name] = 0
- ret = ""
- if constants:
- ret += f"# {parent.__name__} constants\n\n"
- for name in sorted(constants):
- if name[0].isdigit():
- # GDK has some busted constant names like
- # Gdk.EventType.2BUTTON_PRESS
- continue
- val = constants[name]
- strval = str(val)
- if isinstance(val, str):
- strval = '"%s"' % str(val).replace("\\", "\\\\")
- ret += f"{name} = {strval}\n"
- if ret:
- ret += "\n\n"
- if functions:
- ret += f"# {parent.__name__} functions\n\n"
- for name in sorted(functions):
- ret += f"def {name}(*args, **kwargs):\n"
- ret += " pass\n"
- if ret:
- ret += "\n\n"
- if methods:
- ret += f"# {parent.__name__} methods\n\n"
- for name in sorted(methods):
- ret += f"def {name}(self, *args, **kwargs):\n"
- ret += " pass\n"
- if ret:
- ret += "\n\n"
- if classes:
- ret += f"# {parent.__name__} classes\n\n"
- for name, obj in sorted(classes.items()):
- base = "object"
- if issubclass(obj, Exception):
- base = "Exception"
- ret += f"class {name}({base}):\n"
- classret = _gi_build_stub(obj)
- if not classret:
- classret = "pass\n"
- for line in classret.splitlines():
- ret += " " + line + "\n"
- ret += "\n"
- return ret
- def _import_gi_module(modname):
- # we only consider gi.repository submodules
- if not modname.startswith("gi.repository."):
- raise AstroidBuildingError(modname=modname)
- # build astroid representation unless we already tried so
- if modname not in _inspected_modules:
- modnames = [modname]
- optional_modnames = []
- # GLib and GObject may have some special case handling
- # in pygobject that we need to cope with. However at
- # least as of pygobject3-3.13.91 the _glib module doesn't
- # exist anymore, so if treat these modules as optional.
- if modname == "gi.repository.GLib":
- optional_modnames.append("gi._glib")
- elif modname == "gi.repository.GObject":
- optional_modnames.append("gi._gobject")
- try:
- modcode = ""
- for m in itertools.chain(modnames, optional_modnames):
- try:
- with warnings.catch_warnings():
- # Just inspecting the code can raise gi deprecation
- # warnings, so ignore them.
- try:
- from gi import ( # pylint:disable=import-error
- PyGIDeprecationWarning,
- PyGIWarning,
- )
- warnings.simplefilter("ignore", PyGIDeprecationWarning)
- warnings.simplefilter("ignore", PyGIWarning)
- except Exception: # pylint:disable=broad-except
- pass
- __import__(m)
- modcode += _gi_build_stub(sys.modules[m])
- except ImportError:
- if m not in optional_modnames:
- raise
- except ImportError:
- astng = _inspected_modules[modname] = None
- else:
- astng = AstroidBuilder(AstroidManager()).string_build(modcode, modname)
- _inspected_modules[modname] = astng
- else:
- astng = _inspected_modules[modname]
- if astng is None:
- raise AstroidBuildingError(modname=modname)
- return astng
- def _looks_like_require_version(node):
- # Return whether this looks like a call to gi.require_version(<name>, <version>)
- # Only accept function calls with two constant arguments
- if len(node.args) != 2:
- return False
- if not all(isinstance(arg, nodes.Const) for arg in node.args):
- return False
- func = node.func
- if isinstance(func, nodes.Attribute):
- if func.attrname != "require_version":
- return False
- if isinstance(func.expr, nodes.Name) and func.expr.name == "gi":
- return True
- return False
- if isinstance(func, nodes.Name):
- return func.name == "require_version"
- return False
- def _register_require_version(node):
- # Load the gi.require_version locally
- try:
- import gi
- gi.require_version(node.args[0].value, node.args[1].value)
- except Exception: # pylint:disable=broad-except
- pass
- return node
- AstroidManager().register_failed_import_hook(_import_gi_module)
- AstroidManager().register_transform(
- nodes.Call, _register_require_version, _looks_like_require_version
- )
|