diff --git a/iommi/struct.py b/iommi/struct.py index 5e414437..bf166874 100644 --- a/iommi/struct.py +++ b/iommi/struct.py @@ -34,19 +34,11 @@ def __repr__(self): __str__ = __repr__ def __getattribute__(self, item): - if not dict.__contains__(self, item): - try: - return object.__getattribute__(self, item) - except AttributeError as e: - try: - missing_ = object.__getattribute__(self, '__missing__') - except AttributeError: - pass - else: - return missing_.__get__(self)(item) - raise e return dict.__getitem__(self, item) + def __missing__(self, item): + return object.__getattribute__(self, item) + def __setattr__(self, key, value): self[key] = value diff --git a/iommi/struct__tests.py b/iommi/struct__tests.py new file mode 100644 index 00000000..bb035bcd --- /dev/null +++ b/iommi/struct__tests.py @@ -0,0 +1,295 @@ +import pickle +import platform + +import pytest + +from iommi.struct import ( + Frozen, + FrozenStruct, + merged, + Struct, +) + + +def test_pybasestruct(): + s = Struct(a=1) + assert s['a'] == 1 + assert s.a == 1 + + s.b = 2 + assert s['b'] == 2 + + +def test_constructor(): + s = Struct(a=1, b=2, c=3) + assert s == Struct(dict(a=1, b=2, c=3)) + assert s == Struct([('a', 1), ('b', 2), ('c', 3)]) + assert s == Struct((k, v) for k, v in zip('abc', (1, 2, 3))) + + +def test_get_item(): + s = Struct(a=1, b=2, c=3) + assert 1 == s['a'] + assert 1 == s.a + assert 1 == s.get('a') + + +def test_get_attr_broken_shadow(): + # This is mostly to document the somewhat quirky behavior when shadowing __getitem__ + class MyStruct(Struct): + def __getitem__(self, key): + return 17 + + assert MyStruct(x=19)['x'] == 17 + assert MyStruct(x=19).x == 19 + + +def test_set_item(): + s = Struct(a=1, b=2, c=3) + s['a'] = 8 + assert 8 == s['a'] + assert 8 == s.a + + +def test_isinstance(): + s = Struct() + assert isinstance(s, dict) + + class FooStruct(Struct): + pass + + f = FooStruct(x=17) + assert isinstance(f, Struct) + + +def test_containment(): + s = Struct(c=3) + assert 3 not in s + assert 'c' in s + + +def test_copy(): + s = Struct(a=1, b=2, c=3) + q = s.copy() + + assert 'a' in q + assert 'b' in q + assert 'c' in q + + q.x = 6 + with pytest.raises(AttributeError) as e: + # noinspection PyStatementEffect + s.x + + assert "'Struct' object has no attribute 'x'" in str(e.value) + + class MyStruct(Struct): + pass + + s = MyStruct() + q = s.copy() + + assert type(s) == type(q) + + +def test_to_dict(): + s = Struct(a=1, b=2, c=3) + assert {'a': 1, 'b': 2, 'c': 3} == dict(s) + + +def test_items(): + s = Struct(a=1, b=2, c=3) + assert [('a', 1), ('b', 2), ('c', 3)] == sorted(s.items()) + + +def test_no_longer_has_dict(): + s = Struct() + with pytest.raises(AttributeError) as e: + s.__dict__ + assert "'Struct' object has no attribute '__dict__'" in str(e.value) + + fs = FrozenStruct() + with pytest.raises(AttributeError) as e: + fs.__dict__ + assert "'%s' object has no attribute '__dict__'" % FrozenStruct.__name__ in str(e.value) + + +def test_shadow_methods(): + if platform.python_implementation() == "PyPy": + method_str = "