10

I want to create a Pydantic class with a constructor that does some math on inputs and set the object variables accordingly:

class PleaseCoorperate(BaseModel):
    self0: str
    next0: str

    def __init__(self, page: int, total: int, size: int):
        # Do some math here and later set the values
        self.self0 = ""
        self.next0 = ""
    

But when I try to actually use that class page = PleaseCoorperate(0, 1, 50) I get this error:

main_test.py:16: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
main.py:46: in __init__
    self.self0 = ""
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???
E   AttributeError: __fields_set__

What is happening here? Can I not use constructors on a Pydantic class?

Jenia Ivanov
  • 2,485
  • 3
  • 41
  • 69

2 Answers2

14

You are not invoking the BaseModel constructor, and thus, you are skipping all the pydantic magic that turns class variables into attributes. This:

class PleaseCoorperate(BaseModel):
    self0: str
    next0: str

    def __init__(self, page: int, total: int, size: int):
        # Do some math here and later set the values
        self0 = ""
        next0 = ""
        super().__init__(self0=self0, next0=next0)

should do the trick. However, may I suggest a better option that doesn't override the much excellent default constructor from pydantic with a class method:

class PleaseCoorperate(BaseModel):
    self0: str
    next0: str

    @classmethod
    def custom_init(cls, page: int, total: int, size: int):
        # Do some math here and later set the values
        self0 = ""
        next0 = ""
        return cls(self0=self0, next0=next0)

x = PleaseCoorperate.custom_init(10, 10, 10)
amiasato
  • 914
  • 6
  • 14
  • 1
    The "excellent default constructor" is the ctor that comes for free with every pydantic model, where you can init class members by simply specifying them as named parameters in the ctor, as in the answer: `cls(self0=self0, next0=next0)`. – Hawkeye Parker Apr 06 '23 at 17:51
1

You can use pydantic validators. These are perfect candidate for your solution.

from pydantic import BaseModel, validator

class PleaseCoorperate(BaseModel):
    self0: str
    next0: str

    @validator('self0')
    def self0_math_test(cls, v):  # v set the values passed for self0
        # your math here
        return new_self0

    @validator('next0', always=True)  # always if you want to run it even when next0 is not passed (optional)
    def next0_must_have(cls, v, values, **kwargs):  # values sets other variable values
        # your math here
        return new_next0

Validators Documentation: here

PaxPrz
  • 1,778
  • 1
  • 13
  • 29
  • Thanks Pax. This is a surprise. Basically Pydantic objects are data holders... By the way, I need not validate the input. I need to calculate the object's fields based on the input. – Jenia Ivanov Apr 03 '21 at 15:46
  • They have an example there: @validator('cube_numbers', 'square_numbers') def check_sum(cls, v): if sum(v) > 42: raise ValueError('sum of numbers greater than 42') return v I don't quit understand it. Is v the `cube_numbers` or `square_numbers`? – Jenia Ivanov Apr 03 '21 at 15:48
  • In this case this validator validates both `cubes` and `squares` one after other. So, `v` is both of them. – PaxPrz Apr 03 '21 at 15:59