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()
@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 toString. Accepted field types areBoolean,Date,DateTime,Float,Identifier,Integer,String, andText.pickled: Whether the list should be pickled when stored. Defaults toFalse.
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()
@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()
@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 toFalse.
from protean import Domain
from protean.fields import Dict, String
domain = Domain()
@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.