Skip to content

Container Fields

ValueObject

Represents a field that holds a value object. This field is used to embed a Value Object within an entity.

Arguments

  • value_object_cls: The class of the value object to be embedded.
from protean import Domain
from protean.fields import Float, String, ValueObject

domain = Domain(__file__, load_toml=False)


@domain.value_object
class Balance:
    """A composite amount object, containing two parts:
    * currency code - a three letter unique currency code
    * amount - a float value
    """

    currency = String(max_length=3, required=True)
    amount = Float(required=True, min_value=0.0)


@domain.aggregate
class Account:
    balance = ValueObject(Balance)
    name = String(max_length=30)

You can provide an instance of the Value Object as input to the value object field:

In [1]: account = Account(
   ...:     balance=Balance(currency="USD", amount=100.0),
   ...:     name="Checking"
   ...: )

In [2]: account.to_dict()
Out[2]: 
{'balance': {'currency': 'USD', 'amount': 100.0},
 'name': 'Checking',
 'id': '513b8a78-e00f-45ce-bb6f-11ef0cccbec6'}

List

A field that represents a list of values.

Optional Arguments

  • content_type: The type of items in the list. Defaults to String. Accepted field types are Boolean, Date, DateTime, Float, Identifier, Integer, String, and Text.
  • pickled: Whether the list should be pickled when stored. Defaults to False.

Note

Some database implementations (like Postgresql) can store lists by default. You can force it to store the pickled value as a Python object by specifying pickled=True. Databases that don’t support lists simply store the field as a python object.

from protean import Domain
from protean.fields import List, String

domain = Domain(__file__, load_toml=False)


@domain.aggregate
class User:
    email = String(max_length=255, required=True, unique=True)
    roles = List()

The value is provided as a list, and the values in the list are validated to be of the right type.

In [1]: user = User(email="john.doe@gmail.com", roles=['ADMIN', 'EDITOR'])

In [2]: user.to_dict()
Out[2]: 
{'email': 'john.doe@gmail.com',
 'roles': ['ADMIN', 'EDITOR'],
 'id': '582d946b-409b-4b15-b3be-6a90284264b3'}

In [3]: user2 = User(email="jane.doe@gmail.com", roles=[1, 2])
ERROR: Error during initialization: {'roles': ['Invalid value [1, 2]']}
...
ValidationError: {'roles': ['Invalid value [1, 2]']}

List of Value Objects

A List field can even hold a list of ValueObject instances. The content of the List will be persisted as a list of dicts, so the field will behave essentially like List(Dict()) when it comes to persistence. However, it will have the added benefit of a validation structure of content within the List.

from protean import Domain
from protean.fields import HasOne, List, String, ValueObject

domain = Domain(__file__, load_toml=False)


@domain.value_object
class Address:
    street = String(max_length=100)
    city = String(max_length=25)
    state = String(max_length=25)
    country = String(max_length=25)


@domain.entity(part_of="Order")
class Customer:
    name = String(max_length=50, required=True)
    email = String(max_length=254, required=True)
    addresses = List(content_type=ValueObject(Address))


@domain.aggregate
class Order:
    customer = HasOne(Customer)
In [1]: order = Order(
   ...:         customer=Customer(
   ...:             name="John Doe",
   ...:             email="john@doe.com",
   ...:             addresses=[
   ...:                 Address(street="123 Main St", city="Anytown", state="CA", country="USA"),
   ...:                 Address(street="321 Side St", city="Anytown", state="CA", country="USA"),
   ...:             ],
   ...:         )
   ...:     )

In [2]: order.to_dict()
Out[2]: 
{'customer': {'name': 'John Doe',
  'email': 'john@doe.com',
  'addresses': [{'street': '123 Main St',
    'city': 'Anytown',
    'state': 'CA',
    'country': 'USA'},
   {'street': '321 Side St',
    'city': 'Anytown',
    'state': 'CA',
    'country': 'USA'}],
  'id': 'f5c5a750-e9fe-47db-877e-44b7c0ca1dfc'},
 'id': '4a9538bf-1eb1-4621-8ced-86bcc4362a51'}

In [3]: domain.repository_for(Order).add(order)
Out[3]: <Order: Order object (id: 4a9538bf-1eb1-4621-8ced-86bcc4362a51)>

In [4]: retrieved_order = domain.repository_for(Order).get(order.id)

In [5]: len(retrieved_order.customer.addresses)
Out[5]: 2

Note that unlike HasMany fields, you have to supply a new entire list of Value Objects if you want to update the field. Appendind to the list will not work.

In [6]: retrieved_order.customer.addresses.append(
   ...:     Address(street="456 Side St", city="Anytown", state="CA", country="USA")
   ...: )

In [7]: domain.repository_for(Order).add(retrieved_order)
Out[7]: <Order: Order object (id: 4a9538bf-1eb1-4621-8ced-86bcc4362a51)>

In [8]: updated_order = domain.repository_for(Order).get(order.id)

In [9]: len(updated_order.customer.addresses)
Out[9]: 2
# This did not work!
In [10]: updated_order.customer.addresses = [
    ...:     Address(street="123 Main St", city="Anytown", state="CA", country="USA"),
    ...:     Address(street="321 Side St", city="Anytown", state="CA", country="USA"),
    ...:     Address(street="456 Side St", city="Anytown", state="CA", country="USA"),
    ...: ]

In [11]: domain.repository_for(Order).add(updated_order)
Out[11]: <Order: Order object (id: 4a9538bf-1eb1-4621-8ced-86bcc4362a51)>

In [12]: refreshed_order = domain.repository_for(Order).get(order.id)

In [13]: len(refreshed_order.customer.addresses)
Out[13]: 3
# This worked!

Dict

A field that represents a dictionary.

Optional Arguments

  • pickled: Whether the dict should be pickled when stored. Defaults to False.
from protean import Domain
from protean.fields import Dict, String

domain = Domain(__file__, load_toml=False)


@domain.aggregate
class UserEvent:
    name = String(max_length=255)
    payload = Dict()

A regular dictionary can be supplied as value to payload:

In [1]: event=UserEvent(
   ...:     name="UserRegistered",
   ...:     payload={'name': 'John Doe', 'email': 'john.doe@example.com'}
   ...: )

In [2]: event.to_dict()
Out[2]: 
{'name': 'UserRegistered',
 'payload': {'name': 'John Doe', 'email': 'john.doe@example.com'},
 'id': '44e9143f-f4a6-40da-9128-4b6c013420d4'}

Note

Some database implementations (like Postgresql) can store dicts as JSON by default. You can force it to store the pickled value as a Python object by specifying pickled=True. Databases that don’t support lists simply store the field as a python object.