1"""
2A static version of getattr.
3This is a backport of the Python 3 code with a little bit of additional
4information returned to enable Jedi to make decisions.
5"""
6
7import types
8
9from jedi import debug
10
11_sentinel = object()
12
13
14def _check_instance(obj, attr):
15 instance_dict = {}
16 try:
17 instance_dict = object.__getattribute__(obj, "__dict__")
18 except AttributeError:
19 pass
20 return dict.get(instance_dict, attr, _sentinel)
21
22
23def _check_class(klass, attr):
24 for entry in _static_getmro(klass):
25 if _shadowed_dict(type(entry)) is _sentinel:
26 try:
27 return entry.__dict__[attr]
28 except KeyError:
29 pass
30 return _sentinel
31
32
33def _is_type(obj):
34 try:
35 _static_getmro(obj)
36 except TypeError:
37 return False
38 return True
39
40
41def _shadowed_dict(klass):
42 dict_attr = type.__dict__["__dict__"] # type: ignore[index]
43 for entry in _static_getmro(klass):
44 try:
45 class_dict = dict_attr.__get__(entry)["__dict__"]
46 except KeyError:
47 pass
48 else:
49 if not (type(class_dict) is types.GetSetDescriptorType
50 and class_dict.__name__ == "__dict__"
51 and class_dict.__objclass__ is entry):
52 return class_dict
53 return _sentinel
54
55
56def _static_getmro(klass):
57 mro = type.__dict__['__mro__'].__get__(klass) # type: ignore[index]
58 if not isinstance(mro, (tuple, list)):
59 # There are unfortunately no tests for this, I was not able to
60 # reproduce this in pure Python. However should still solve the issue
61 # raised in GH #1517.
62 debug.warning('mro of %s returned %s, should be a tuple' % (klass, mro))
63 return ()
64 return mro
65
66
67def _safe_hasattr(obj, name):
68 return _check_class(type(obj), name) is not _sentinel
69
70
71def _safe_is_data_descriptor(obj):
72 return _safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__')
73
74
75def getattr_static(obj, attr, default=_sentinel):
76 """Retrieve attributes without triggering dynamic lookup via the
77 descriptor protocol, __getattr__ or __getattribute__.
78
79 Note: this function may not be able to retrieve all attributes
80 that getattr can fetch (like dynamically created attributes)
81 and may find attributes that getattr can't (like descriptors
82 that raise AttributeError). It can also return descriptor objects
83 instead of instance members in some cases. See the
84 documentation for details.
85
86 Returns a tuple `(attr, is_get_descriptor)`. is_get_descripter means that
87 the attribute is a descriptor that has a `__get__` attribute.
88 """
89 instance_result = _sentinel
90 if not _is_type(obj):
91 klass = type(obj)
92 dict_attr = _shadowed_dict(klass)
93 # In Python 3.15+, __dict__ is a GetSetDescriptorType instead of being _sentinel
94 if (dict_attr is _sentinel
95 or type(dict_attr) is types.MemberDescriptorType
96 or type(dict_attr) is types.GetSetDescriptorType):
97 instance_result = _check_instance(obj, attr)
98 else:
99 klass = obj
100
101 klass_result = _check_class(klass, attr)
102
103 if instance_result is not _sentinel and klass_result is not _sentinel:
104 if _safe_hasattr(klass_result, '__get__') \
105 and _safe_is_data_descriptor(klass_result):
106 # A get/set descriptor has priority over everything.
107 return klass_result, True
108
109 if instance_result is not _sentinel:
110 return instance_result, False
111 if klass_result is not _sentinel:
112 return klass_result, _safe_hasattr(klass_result, '__get__')
113
114 if obj is klass:
115 # for types we check the metaclass too
116 for entry in _static_getmro(type(klass)):
117 if _shadowed_dict(type(entry)) is _sentinel:
118 try:
119 return entry.__dict__[attr], False
120 except KeyError:
121 pass
122 if default is not _sentinel:
123 return default, False
124 raise AttributeError(attr)