Examples¶
Yes, I know it’s dangerous to follow code examples. Usually examples aren’t in sync with real source code.
But I found a solution … I hope!
Note
See also
Look at Public API for more details.
Basics¶
Value objects can be used as is straight from library. You still can extend them but for simple usage its not necessary.
Create Value Object¶
Value takes all kwargs (key=value) and add them as object attribute.
Assigning values are made only once on __init__
and after that
no values can be changed.
from vo import Value
book_ddd = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
book_tdd = Value(title='TDD', author='Life', price=99.98, currency='USD')
Access to attributes¶
Properties can be accessed with dot or key notation.
from vo import Value
book = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
assert book.title == 'DDD'
assert book.author == book['author']
assert book['price'] == 120.44
Value Objects comparison¶
Note
Two objects with the same values are considered equal, but not the same.
Compare different values¶
from vo import Value
book_ddd = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
book_tdd = Value(title='TDD', author='Life', price=99.98, currency='USD')
assert book_ddd != book_tdd
Compare similar values¶
from vo import Value
book_ddd = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
book_clone = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
assert book_ddd == book_clone
assert book_ddd is not book_clone
Advanced¶
More real life example of Value Object usage.
Basic inheritance¶
Using Value Object directly is easy, fast, and just works.
However due to dynamic attribute assignment on __init__
your favourite
IDE / Editor can’t generate hints.
This is when inheritance come handy.
from vo import Value
class Book(Value):
title = None
author = None
price = None
currency = None
book = Book(title='DDD', author='Pythonista', price=120.44, currency='USD')
Wonky behaviour¶
Weird behaviour but completely correct.
Warning
Value Object does not validate given attributes. Validation is up to you.
from vo import Value
class Book(Value):
title = None
author = None
price = None
currency = None
book = Book(spam='Foo')
# whaaaat!?
assert 'spam' in book
assert book.title is None
assert book.price is None
assert book.title is None
assert book.currency is None
Attribute validation¶
Most of the time you will want to make inheritance like below,
but remember to not assign attribute by your own. Always delegate to
super().__init__()
from vo import Value
class Book(Value):
title = None
author = None
price = None
currency = None
def __init__(self, title, author, price, currency):
# make validation if needed
# always delegate assignment to Parent!
super().__init__(title=title, author=author, price=price, currency=currency)
book = Book(title='DDD', author='Pythonista', price=120.44, currency='USD')
Usage Ideas¶
Value Object is helpful always when source data must not be modified.
Frozen response¶
Note
Requests package has been faked for purpose of this example to avoid unnecessary and unrelated dependency.
Before executing this example make sure you have requests
installed in your environment with pip install requests
from vo import Value
class Quote(Value):
_id = None
title = None
content = None
link = None
def __init__(self, _id, title, content, link):
# validation if needed
super().__init__(_id=_id, title=title, contet=content, link=link)
response = requests.get('https://quotesondesign.com/wp-json/posts'
'?filter[orderby]=rand'
'&filter[posts_per_page]=2')
quotes = [Quote(x['ID'], x['title'], x['content'], x['link']) for x in response.json()]
2D Coordinates¶
from vo import Value
class Point2D(Value):
x = 0
y = 0
def __init__(self, x, y):
# validation if needed
super().__init__(x=x, y=y)
def __add__(self, other):
return Point2D(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Point2D(self.x - other.x, self.y - other.y)
p1 = Point2D(2, 5)
p2 = Point2D(2, 5)
p3 = Point2D(-3, 10)
assert p1 == p2
assert p1 != p3
assert p1 + p2 == Point2D(4, 10)
assert p3 - p1 == Point2D(-5, 5)
Money object¶
Danger
import decimal
from vo import Value
class Money(Value):
amount = None
currency = None
def __init__(self, amount, currency):
# plenty of validation
super().__init__(amount=decimal.Decimal(amount), currency=currency)
def __lt__(self, other):
return self.amount < other.amount
def __gt__(self, other):
return self.amount > other.amount
def __add__(self, other):
return Money(amount=self.amount + other.amount, currency='USD')
def __sub__(self, other):
return Money(amount=self.amount - other.amount, currency='USD')
assert Money(200, 'USD') > Money(120, 'USD')
assert Money(100, 'USD') < Money(120, 'USD')
assert Money(100, 'USD') + Money(200, 'USD') == Money(300, 'USD')
assert Money(100, 'USD') - Money(50, 'USD') == Money(50, 'USD')
Forbidden actions¶
Warning
All attempt to value modification
ends up with ImmutableInstanceError
exception.
Modification¶
Modification of existing attribute is forbidden. Let’s create a book, and then try to change its title.
from vo import Value
book = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
all_good = True
try:
book.title = 'BDD > DDD' # or book['title'] = 'SPAM'
except ImmutableInstanceError:
all_good = False
assert all_good is False
assert book.title == 'DDD'
Adding new attributes also raises exception. Let’s add publisher property to the book.
from vo import Value
book = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
all_good = True
try:
book.publisher = 'SPAM' # or book['publisher'] = 'SPAM'
except ImmutableInstanceError:
all_good = False
assert all_good is False
assert 'publisher' not in book
Deletion¶
Properties of value object can’t be deleted no matter what!
from vo import Value
book = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
all_good = True
try:
del book.title # or del book['title']
except ImmutableInstanceError:
all_good = False
assert all_good is False
assert 'title' in book
Data dumps¶
Convert value object to different data types.
To dict¶
Note
Actually .to_dict()
method returns collections.OrderedDict
.
from vo import Value
book = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
dump = book.to_dict()
assert isinstance(dump, OrderedDict)
assert dump == OrderedDict([('author', 'Pythonista'), ('currency', 'USD'),
('price', 120.44), ('title', 'DDD')])
To bytes¶
from vo import Value
book = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
dump = book.to_bytes()
assert isinstance(dump, bytes)
assert dump == b'\'{"author": "Pythonista", "currency": "USD", ' \
b'"price": 120.44, "title": "DDD"}\''
To JSON¶
from vo import Value
book = Value(title='DDD', author='Pythonista', price=120.44, currency='USD')
dump = book.to_json()
assert isinstance(dump, str)
assert dump == '{"author": "Pythonista", "currency": "USD", ' \
'"price": 120.44, "title": "DDD"}'