I have a class deriving from pydantic.BaseModel and would like to create a "fake" attribute, i.e. a computed property. The propery keyword does not seem to work with Pydantic the usual way. Below is the MWE, where the class stores value and defines read/write property called half with the obvious meaning. Reading the property works fine with Pydantic, but the assignment fails.
I know Pydantic is modifying low-level details of attribute access; perhaps there is a way to define computed field in Pydantic in a different way?
import pydantic
class Object(object):
def __init__(self,*,value): self.value=value
half=property(lambda self: .5*self.value,lambda self,h: setattr(self,'value',h*2))
class Pydantic(pydantic.BaseModel):
class Config:
extra='allow'
value: float
half=property(lambda self: .5*self.value,lambda self,h: setattr(self,'value',h*2))
o,p=Object(value=1.),Pydantic(value=1.)
print(o.half,p.half)
o.half=p.half=2
print(o.value,p.value)
outputs (value=1. was not modified by assigning half in the Pydantic case):
0.5 0.5
4 1.0
I happened to be working on the same problem today. Officially it is not supported yet, as discussed here.
However, I did find the following example which works well:
class Person(BaseModel):
first_name: str
last_name: str
full_name: str = None
@validator("full_name", always=True)
def composite_name(cls, v, values, **kwargs):
return f"{values['first_name']} {values['last_name']}"
Do make sure your derived field comes after the fields you want to derive it from, else the values dict will not contain the needed values (e.g. full_name comes after first_name and last_name that need to be fetched from values).
UPDATE: As Hyagoro mentions below, there is now an officially supported computed_field decorator in Pydantic 2. Unfortunately, that feature won't be backported to Pydantic v1 (Source). So in Pydantic v2 the above example becomes:
from pydantic import BaseModel, computed_field
class Person(BaseModel):
first_name: str
last_name: str
full_name: str = None
@computed_field
@property
def composite_name(self):
return f"{self.first_name} {self.last_name}"
Instead of using a property, here's an example which shows how to use pydantic.root_validator to compute the value of an optional field:
https://daniellenz.blog/2021/02/20/computed-fields-in-pydantic/
I've adapted this for a similar application:
class Section (BaseModel):
title: constr(strip_whitespace=True)
chunks: conlist(min_items=1, item_type=Chunk)
size: typing.Optional[ PositiveInt ] = None
role: typing.Optional[ typing.List[ str ]] = []
license: constr(strip_whitespace=True)
@root_validator
def compute_size (cls, values) -> typing.Dict:
if values["size"] is None:
values["size"] = sum([
chunk.get_size()
for chunk in values["chunks"]
])
return values
In this case each element of the discriminated union chunks has a get_size() method to compute its size. If the size field isn't specified explicitly in serialization (e.g., input from a JSON file) then it gets computed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With