#! /usr/bin/env python3 import unittest from static import getattr_static class TestGetattrStatic(unittest.TestCase): def test_basic(self): class Thing(object): x = object() thing = Thing() self.assertEqual(getattr_static(thing, 'x'), Thing.x) self.assertEqual(getattr_static(thing, 'x', None), Thing.x) with self.assertRaises(AttributeError): getattr_static(thing, 'y') self.assertEqual(getattr_static(thing, 'y', 3), 3) def test_inherited(self): class Thing(object): x = object() class OtherThing(Thing): pass something = OtherThing() self.assertEqual(getattr_static(something, 'x'), Thing.x) def test_instance_attr(self): class Thing(object): x = 2 def __init__(self, x): self.x = x thing = Thing(3) self.assertEqual(getattr_static(thing, 'x'), 3) del thing.x self.assertEqual(getattr_static(thing, 'x'), 2) def test_property(self): class Thing(object): @property def x(self): raise AttributeError("I'm pretending not to exist") thing = Thing() self.assertEqual(getattr_static(thing, 'x'), Thing.x) def test_descriptor(self): class descriptor(object): def __get__(*_): raise AttributeError("I'm pretending not to exist") desc = descriptor() class Thing(object): x = desc thing = Thing() self.assertEqual(getattr_static(thing, 'x'), desc) def test_classAttribute(self): class Thing(object): x = object() self.assertEqual(getattr_static(Thing, 'x'), Thing.x) def test_inherited_classattribute(self): class Thing(object): x = object() class OtherThing(Thing): pass self.assertEqual(getattr_static(OtherThing, 'x'), Thing.x) def test_slots(self): class Thing(object): y = 'bar' __slots__ = ['x'] def __init__(self): self.x = 'foo' thing = Thing() self.assertEqual(getattr_static(thing, 'x'), Thing.x) self.assertEqual(getattr_static(thing, 'y'), 'bar') del thing.x self.assertEqual(getattr_static(thing, 'x'), Thing.x) def test_metaclass(self): class meta(type): attr = 'foo' class Thing(object, metaclass=meta): pass self.assertEqual(getattr_static(Thing, 'attr'), 'foo') class sub(meta): pass class OtherThing(object, metaclass=sub): x = 3 self.assertEqual(getattr_static(OtherThing, 'attr'), 'foo') class OtherOtherThing(OtherThing): pass # this test is odd, but it was added as it exposed a bug self.assertEqual(getattr_static(OtherOtherThing, 'x'), 3) def test_no_dict_no_slots(self): self.assertEqual(getattr_static(1, 'foo', None), None) self.assertNotEqual(getattr_static('foo', 'lower'), None) def test_no_dict_no_slots_instance_member(self): # returns descriptor with open(__file__) as handle: self.assertEqual(getattr_static(handle, 'name'), type(handle).name) def test_inherited_slots(self): # returns descriptor class Thing(object): __slots__ = ['x'] def __init__(self): self.x = 'foo' class OtherThing(Thing): pass # it would be nice if this worked... # we get the descriptor instead of the instance attribute self.assertEqual(getattr_static(OtherThing(), 'x'), Thing.x) def test_descriptor(self): class descriptor(object): def __get__(self, instance, owner): return 3 class Foo(object): d = descriptor() foo = Foo() # for a non data descriptor we return the instance attribute foo.__dict__['d'] = 1 self.assertEqual(getattr_static(foo, 'd'), 1) # if the descriptor is a data-desciptor we should return the # descriptor descriptor.__set__ = lambda s, i, v: None self.assertEqual(getattr_static(foo, 'd'), Foo.__dict__['d']) def test_metaclass_with_descriptor(self): class descriptor(object): def __get__(self, instance, owner): return 3 class meta(type): d = descriptor() class Thing(object, metaclass=meta): pass self.assertEqual(getattr_static(Thing, 'd'), meta.__dict__['d']) """ Cases that will break `getattr_static` or be handled incorrectly. They are pathological enough not to worry about (i.e. if you do any of these then you deserve to have everything break anyway): * `__dict__` existing (e.g. as a property) but returning the wrong dictionary or even returning something other than a dictionary * classes created with `__slots__` that then have the `__slots__` member deleted from the class, or a fake `__slots__` attribute attached to the instance, or any other monkeying with `__slots__` * objects that lie about their type by having __class__ as a descriptor (we traverse the mro of whatever type `obj.__class__` returns instead of the real type) * type objects that lie about their mro Fixing these would complicate the implimentation. Descriptors are not resolved (for example slot descriptors or getset descriptors on objects implemented in C). The descriptor is returned instead of the underlying attribute. You can handling these with code like the following. Note that for arbitrary getset descriptors invoking these may trigger code execution: # example code for resolving the builtin descriptor types class _foo(object): __slots__ = ['foo'] slot_descriptor = type(_foo.foo) attribute_descriptor = type(type(open(__file__)).name) wrapper_descriptor = type(str.__dict__['__add__']) descriptor_types = (slot_descriptor, attribute_descriptor, wrapper_descriptor) result = getattr_static(some_object, 'foo') if type(result) in descriptor_types: try: result = result.__get__() except AttributeError: # descriptors can raise AttributeError to # indicate there is no underlying value # in which case the descriptor itself will # have to do pass """ if __name__ == '__main__': unittest.main()