Search code examples
pythonpydantic

Is there any post_load in pydantic?


Previously I used the marshmallow library with the Flask. Some time ago I have tried FastAPI with Pydantic. At first glance pydantic seems similar to masrhmallow but on closer inspection they differ. And for me the main difference between them is post_load methods which are from marshmallow. I can't find any analogs for it in pydantic.

post_load is decorator for post-processing methods. Using it I can handle return object on my own, can do whatever I want:

class ProductSchema(Schema):
    alias = fields.Str()
    category = fields.Str()
    brand = fields.Str()

    @post_load
    def check_alias(self, params, **kwargs):
        """One of the fields must be filled"""
        if not any([params.get('alias'), params.get('category'), params.get('brand')]):
            raise ValidationError('No alias provided', field='alias')
        return params

Besides it used not only for validation. Code example is just for visual understanding, do not analyze it, I have just invented it.

So my question is: is there any analog for post_load in pydantic?


Solution

  • It is not obvious but pydantic's validator returns value of the field.

    Pydantic v1

    There are two ways to handle post_load conversions: validator and root_validator.

    validator gets the field value as argument and returns its value. root_validator is the same but manipulates with the whole object.

    from pydantic import validator, root_validator
    
    
    class PaymentStatusSchema(BaseModel):
        order_id: str = Param(..., title="Order id in the shop")
        order_number: str = Param(..., title="Order number in chronological order")
        status: int = Param(..., title="Payment status")
    
        @validator("status")
        def convert_status(cls, status):
            return "active" if status == 1 else "inactive"
    
        @root_validator
        def validate_order_id(cls, values):
            """Check order id"""
            if not values.get('orderNumber') and not values.get('mdOrder'):
                raise HTTPException(status_code=400, detail='No order data provided')
            return values
    

    By default pydantic runs validators as post-processing methods. For pre-processing you should use validators with pre argument:

        @root_validator(pre=True)
        def validate_order_id(cls, values):
            """Check order id"""
            # some code here
            return values
    

    Pydantic v2

    In Pydantic v2 validators migrated: validator to field_validator and root_validator to model_validator.

    @field_validator('status')
    @classmethod
    def convert_status(cls, status: int):
        return "active" if status == 1 else "inactive"
    
    @model_validator(mode='after')
    @classmethod
    def validate_order_id(cls, values):
        """Check order id"""
        if not values.get('orderNumber') and not values.get('mdOrder'):
            raise HTTPException(status_code=400, detail='No order data provided')
        return values
    

    For post or pre-processing use mode parameter.