6

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
eudoxos
  • 18,545
  • 10
  • 61
  • 110
  • Reference this -- https://stackoverflow.com/questions/63264888/pydantic-using-property-getter-decorator-for-a-field-with-an-alias – abby37 Jun 07 '22 at 08:25

3 Answers3

8

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).

Jakub Kukul
  • 12,032
  • 3
  • 54
  • 53
benvdh
  • 454
  • 5
  • 13
  • 1
    This is half-way through, as it won't allow assignment of `composite_name` (which would trigger a routine splitting it into `first_name` and `last_name`). – eudoxos Feb 04 '22 at 10:58
  • @eudoxos You are right the above answer only works for computed read-only properties. So if I understand you correctly, you would like to have a full property where you can control both the setter and getter behaviour? Additionally, do you need to be able to access currently set values on the object from the setter too? – benvdh Feb 04 '22 at 16:45
5

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.

Paco
  • 602
  • 1
  • 9
  • 19
1

Created a pip package that allows you to easily create computed properties. Here you can check it out: https://pypi.org/project/pydantic-computed/

By using the package the example with getting the half of a value would look like this :

from pydantic import BaseModel
from pydantic_computed import Computed, computed

class SomeModel(BaseModel):
    value: float
    value_half: Computed[float]

    @computed("value_half")
    def compute_value_half(value: float):
        return value / 2
  • I don't really see difference of this vs the existing @computed_field – Wang Jul 27 '23 at 19:11
  • 1
    @Wang the existing "computed_field" decorator is only available in pydantic > 2.0 so this answer might be a viable alternative to people still on pydantic 1 – Jakub Kukul Aug 10 '23 at 10:25