Created
March 15, 2017 17:52
-
-
Save ignacysokolowski/415f695107146cdd93c07d0cb9ffbf60 to your computer and use it in GitHub Desktop.
Python 3.6 data structure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pytest | |
class _DataStructureMeta(type): | |
def __new__(cls, class_name, bases, members): | |
try: | |
annotations = members['__annotations__'] | |
except KeyError: | |
pass | |
else: | |
members['__slots__'] = tuple( | |
arg for arg in annotations if arg not in members | |
) | |
return super().__new__(cls, class_name, bases, members) | |
class DataStructure(metaclass=_DataStructureMeta): | |
__slots__ = () | |
def __init__(self, **kwargs): | |
args_allowed = self.__slots__ | |
args_passed = tuple(kwargs.keys()) | |
if args_passed != args_allowed: | |
error = ( | |
'{self.__class__.__name__}.__init__ takes arguments ' | |
'{allowed}, got {passed}' | |
).format( | |
self=self, | |
allowed=args_allowed, | |
passed=args_passed, | |
) | |
raise TypeError(error) | |
for k, v in kwargs.items(): | |
super().__setattr__(k, v) | |
def __setattr__(self, name, value): | |
raise AttributeError('{} is immutable'.format(self.__class__.__name__)) | |
def __repr__(self): | |
data = ( | |
'{}={!r}'.format(name, getattr(self, name)) | |
for name in self.__slots__ | |
) | |
return '{self.__class__.__name__}({data})'.format( | |
self=self, | |
data=', '.join(data), | |
) | |
def __eq__(self, other): | |
if not isinstance(other, self.__class__): | |
return NotImplemented | |
for name in self.__slots__: | |
if getattr(other, name) != getattr(self, name): | |
return False | |
return True | |
class Foo(DataStructure): | |
foo: int | |
bar: str | |
@property | |
def baz(self) -> str: | |
return '{}{}'.format(self.foo * 2, self.bar) | |
class TestDataStructure: | |
def test_sets_data_from_annotations(self): | |
foo = Foo(foo=1, bar='x') | |
assert foo.foo == 1 | |
assert foo.bar == 'x' | |
def test_all_arguments_are_required(self): | |
with pytest.raises(TypeError) as error: | |
Foo(foo=1) | |
assert str(error.value) == ( | |
"Foo.__init__ takes arguments ('foo', 'bar'), " | |
"got ('foo',)" | |
) | |
def test_no_extra_arguments_can_be_given(self): | |
with pytest.raises(TypeError) as error: | |
Foo(foo=1, bar='x', baz='y') | |
assert str(error.value) == ( | |
"Foo.__init__ takes arguments ('foo', 'bar'), " | |
"got ('foo', 'bar', 'baz')" | |
) | |
def test_can_not_change_arguments_order(self): | |
with pytest.raises(TypeError) as error: | |
Foo(bar='x', foo=1) | |
assert str(error.value) == ( | |
"Foo.__init__ takes arguments ('foo', 'bar'), " | |
"got ('bar', 'foo')" | |
) | |
def test_can_not_init_without_argument_names(self): | |
with pytest.raises(TypeError): | |
Foo(1, 'x') | |
def test_properties_can_be_set(self): | |
foo = Foo(foo=1, bar='x') | |
assert foo.baz == '2x' | |
def test_can_not_set_attributes_not_defined_in_annotations(self): | |
foo = Foo(foo=1, bar='x') | |
with pytest.raises(AttributeError): | |
foo.z = 2 | |
def test_can_not_mutate_attributes(self): | |
foo = Foo(foo=1, bar='x') | |
with pytest.raises(AttributeError) as error: | |
foo.foo = 2 | |
assert str(error.value) == 'Foo is immutable' | |
def test_can_define_class_variables(self): | |
class Bar(DataStructure): | |
FOO: int = 1 | |
foo: int | |
assert Bar.FOO == 1 | |
bar = Bar(foo=2) | |
assert bar.foo == 2 | |
assert bar.FOO == 1 | |
class TestDataStructureRepr: | |
def test_includes_all_data(self): | |
foo = Foo(foo=1, bar='x') | |
assert repr(foo) == "Foo(foo=1, bar='x')" | |
class TestDataStructureEquality: | |
def test_two_instances_with_the_same_data_are_equal(self): | |
assert Foo(foo=1, bar='x') == Foo(foo=1, bar='x') | |
def test_two_instances_with_different_data_are_not_equal(self): | |
assert Foo(foo=1, bar='x') != Foo(foo=2, bar='x') | |
assert Foo(foo=1, bar='x') != Foo(foo=1, bar='y') | |
def test_does_not_try_to_compare_with_different_types(self): | |
assert Foo(foo=1, bar='x') != [] | |
def test_does_not_try_to_compare_with_different_data_structures(self): | |
class Bar(DataStructure): | |
foo: int | |
bar: str | |
assert Foo(foo=1, bar='x') != Bar(foo=1, bar='x') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment