1import types
2
3from pure_eval.utils import of_type, CannotEval
4
5_sentinel = object()
6
7
8def _static_getmro(klass):
9 return type.__dict__['__mro__'].__get__(klass)
10
11
12def _check_instance(obj, attr):
13 instance_dict = {}
14 try:
15 instance_dict = object.__getattribute__(obj, "__dict__")
16 except AttributeError:
17 pass
18 return dict.get(instance_dict, attr, _sentinel)
19
20
21def _check_class(klass, attr):
22 for entry in _static_getmro(klass):
23 if _shadowed_dict(type(entry)) is _sentinel:
24 try:
25 return entry.__dict__[attr]
26 except KeyError:
27 pass
28 else:
29 break
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__"]
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 and
50 class_dict.__name__ == "__dict__" and
51 class_dict.__objclass__ is entry):
52 return class_dict
53 return _sentinel
54
55
56def getattr_static(obj, attr):
57 """Retrieve attributes without triggering dynamic lookup via the
58 descriptor protocol, __getattr__ or __getattribute__.
59
60 Note: this function may not be able to retrieve all attributes
61 that getattr can fetch (like dynamically created attributes)
62 and may find attributes that getattr can't (like descriptors
63 that raise AttributeError). It can also return descriptor objects
64 instead of instance members in some cases. See the
65 documentation for details.
66 """
67 instance_result = _sentinel
68 if not _is_type(obj):
69 klass = type(obj)
70 dict_attr = _shadowed_dict(klass)
71 if (dict_attr is _sentinel or
72 type(dict_attr) is types.MemberDescriptorType):
73 instance_result = _check_instance(obj, attr)
74 else:
75 raise CannotEval
76 else:
77 klass = obj
78
79 klass_result = _check_class(klass, attr)
80
81 if instance_result is not _sentinel and klass_result is not _sentinel:
82 if _check_class(type(klass_result), "__get__") is not _sentinel and (
83 _check_class(type(klass_result), "__set__") is not _sentinel
84 or _check_class(type(klass_result), "__delete__") is not _sentinel
85 ):
86 return _resolve_descriptor(klass_result, obj, klass)
87
88 if instance_result is not _sentinel:
89 return instance_result
90 if klass_result is not _sentinel:
91 get = _check_class(type(klass_result), '__get__')
92 if get is _sentinel:
93 return klass_result
94 else:
95 if obj is klass:
96 instance = None
97 else:
98 instance = obj
99 return _resolve_descriptor(klass_result, instance, klass)
100
101 if obj is klass:
102 # for types we check the metaclass too
103 for entry in _static_getmro(type(klass)):
104 if _shadowed_dict(type(entry)) is _sentinel:
105 try:
106 result = entry.__dict__[attr]
107 get = _check_class(type(result), '__get__')
108 if get is not _sentinel:
109 raise CannotEval
110 return result
111 except KeyError:
112 pass
113 raise CannotEval
114
115
116class _foo:
117 __slots__ = ['foo']
118 method = lambda: 0
119
120
121slot_descriptor = _foo.foo
122wrapper_descriptor = str.__dict__['__add__']
123method_descriptor = str.__dict__['startswith']
124user_method_descriptor = _foo.__dict__['method']
125
126safe_descriptors_raw = [
127 slot_descriptor,
128 wrapper_descriptor,
129 method_descriptor,
130 user_method_descriptor,
131]
132
133safe_descriptor_types = list(map(type, safe_descriptors_raw))
134
135
136def _resolve_descriptor(d, instance, owner):
137 try:
138 return type(of_type(d, *safe_descriptor_types)).__get__(d, instance, owner)
139 except AttributeError as e:
140 raise CannotEval from e