I searched for Value object implementation in Python, and found keleshev's implementation in github. But it doesn't provide the feature of immutability of Value object. So, I implemented property based immutable Value object class by myself.
- explicit property definition
- value based comparison, including hash
Any feedbacks are welcome.
ValueObject class
class ValueObject(object):
""" base class for Value Objects
please call _set_properties in constructor.
"""
def __new__(class_, *args, **kwargs):
self = object.__new__(class_, *args, **kwargs)
self.__initialized = False
self.__params = dict()
return self
def _set_properties(self, mapping):
if self.__initialized:
raise AttributeError('callable only by constructor')
self.__initialized = True
self.__params = dict(mapping)
self.__labels = [k for k, v in mapping]
def setprop(key):
setattr(self.__class__, key, property(lambda x: x.__params[key]))
for k, v in mapping:
if not hasattr(self.__class__, k):
setprop(k)
def get_values(self):
return self.__params
def __repr__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return u'%s(%s)' % (
self.__class__.__name__,
u', '.join(unicode(self.__params[k]) for k in self.__labels))
def __hash__(self):
return hash(repr(self))
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return repr(self) == repr(other)
def __ne__(self, other):
if not isinstance(other, self.__class__):
return True
return repr(self) != repr(other)
Test
# -*- coding: utf-8 -*-
import pytest
from . import ValueObject
class Date(ValueObject):
def __init__(self, year, month, day):
self._set_properties([
('year', int(year)),
('month', int(month)),
('day', int(day)),
])
class Foo(ValueObject):
def __init__(self, text='foo'):
self._set_properties([
('text', text),
])
@pytest.fixture
def date():
return Date(2012, 2, 20)
@pytest.fixture
def foo_unicode():
return Foo(u'ふー')
def test_properties(date):
assert date.year == 2012
assert date.month == 2
assert date.day == 20
def test_immutable(date):
with pytest.raises(AttributeError):
date.year = 2015
def test_set_properties(date):
with pytest.raises(AttributeError):
date._set_properties([
('year', 2015),
])
def test_repr(date):
assert repr(date) == "Date(2012, 2, 20)"
def test_get_values(date):
date.get_values == {'year': 2012, 'month': 2, 'day': 20}
def test_unicode(foo_unicode):
assert foo_unicode.text == u'ふー'
assert unicode(foo_unicode) == u"Foo(ふー)"
assert repr(foo_unicode) == u"Foo(ふー)".encode('utf-8')