Skip to content

Instantly share code, notes, and snippets.

@ignacysokolowski
Created March 15, 2017 17:52
Show Gist options
  • Save ignacysokolowski/415f695107146cdd93c07d0cb9ffbf60 to your computer and use it in GitHub Desktop.
Save ignacysokolowski/415f695107146cdd93c07d0cb9ffbf60 to your computer and use it in GitHub Desktop.
Python 3.6 data structure
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