| import types |
|
|
| from pure_eval.utils import of_type, CannotEval |
|
|
| _sentinel = object() |
|
|
|
|
| def _static_getmro(klass): |
| return type.__dict__['__mro__'].__get__(klass) |
|
|
|
|
| def _check_instance(obj, attr): |
| instance_dict = {} |
| try: |
| instance_dict = object.__getattribute__(obj, "__dict__") |
| except AttributeError: |
| pass |
| return dict.get(instance_dict, attr, _sentinel) |
|
|
|
|
| def _check_class(klass, attr): |
| for entry in _static_getmro(klass): |
| if _shadowed_dict(type(entry)) is _sentinel: |
| try: |
| return entry.__dict__[attr] |
| except KeyError: |
| pass |
| else: |
| break |
| return _sentinel |
|
|
|
|
| def _is_type(obj): |
| try: |
| _static_getmro(obj) |
| except TypeError: |
| return False |
| return True |
|
|
|
|
| def _shadowed_dict(klass): |
| dict_attr = type.__dict__["__dict__"] |
| for entry in _static_getmro(klass): |
| try: |
| class_dict = dict_attr.__get__(entry)["__dict__"] |
| except KeyError: |
| pass |
| else: |
| if not (type(class_dict) is types.GetSetDescriptorType and |
| class_dict.__name__ == "__dict__" and |
| class_dict.__objclass__ is entry): |
| return class_dict |
| return _sentinel |
|
|
|
|
| def getattr_static(obj, attr): |
| """Retrieve attributes without triggering dynamic lookup via the |
| descriptor protocol, __getattr__ or __getattribute__. |
| |
| Note: this function may not be able to retrieve all attributes |
| that getattr can fetch (like dynamically created attributes) |
| and may find attributes that getattr can't (like descriptors |
| that raise AttributeError). It can also return descriptor objects |
| instead of instance members in some cases. See the |
| documentation for details. |
| """ |
| instance_result = _sentinel |
| if not _is_type(obj): |
| klass = type(obj) |
| dict_attr = _shadowed_dict(klass) |
| if (dict_attr is _sentinel or |
| type(dict_attr) is types.MemberDescriptorType): |
| instance_result = _check_instance(obj, attr) |
| else: |
| raise CannotEval |
| else: |
| klass = obj |
|
|
| klass_result = _check_class(klass, attr) |
|
|
| if instance_result is not _sentinel and klass_result is not _sentinel: |
| if _check_class(type(klass_result), "__get__") is not _sentinel and ( |
| _check_class(type(klass_result), "__set__") is not _sentinel |
| or _check_class(type(klass_result), "__delete__") is not _sentinel |
| ): |
| return _resolve_descriptor(klass_result, obj, klass) |
|
|
| if instance_result is not _sentinel: |
| return instance_result |
| if klass_result is not _sentinel: |
| get = _check_class(type(klass_result), '__get__') |
| if get is _sentinel: |
| return klass_result |
| else: |
| if obj is klass: |
| instance = None |
| else: |
| instance = obj |
| return _resolve_descriptor(klass_result, instance, klass) |
|
|
| if obj is klass: |
| |
| for entry in _static_getmro(type(klass)): |
| if _shadowed_dict(type(entry)) is _sentinel: |
| try: |
| result = entry.__dict__[attr] |
| get = _check_class(type(result), '__get__') |
| if get is not _sentinel: |
| raise CannotEval |
| return result |
| except KeyError: |
| pass |
| raise CannotEval |
|
|
|
|
| class _foo: |
| __slots__ = ['foo'] |
| method = lambda: 0 |
|
|
|
|
| slot_descriptor = _foo.foo |
| wrapper_descriptor = str.__dict__['__add__'] |
| method_descriptor = str.__dict__['startswith'] |
| user_method_descriptor = _foo.__dict__['method'] |
|
|
| safe_descriptors_raw = [ |
| slot_descriptor, |
| wrapper_descriptor, |
| method_descriptor, |
| user_method_descriptor, |
| ] |
|
|
| safe_descriptor_types = list(map(type, safe_descriptors_raw)) |
|
|
|
|
| def _resolve_descriptor(d, instance, owner): |
| try: |
| return type(of_type(d, *safe_descriptor_types)).__get__(d, instance, owner) |
| except AttributeError as e: |
| raise CannotEval from e |
|
|