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

All examples are derived from real code hooked to Pytest.
Every change in source code enforce change in examples.
Outdated examples == failed build.

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

This example is not meant to run on production !!
It doesn’t implement validation and many more comparison methods.

But its nice to present general idea of Money Object.
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"}'