Is there any post_load in pydantic?

Question:

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?

Asked By: egvo

||

Answers:

Yes, you can use Pydantic’s @validator decorator to do pre-load, post-load, model validating etc.

Here is a Post load example

from pydantic import validator

class Person(BaseModel):
    first_name: str
    second_name: str

    @validator("first_name")
    def make_it_formal(cls, first_name):
        return f"Mr. {first_name.capitalize()}"



p = Person(first_name="egvo", second_name="Example")

p.first_name
Out: Mr. Egvo
Answered By: Yagiz Degirmenci

It is not obvious but pydantic's validator returns value of the field. So 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 check_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 check_order_id(cls, values):
        """Check order id"""
        # some code here
        return values
Answered By: egvo

Alternatively, you can also override __init__ and post-process the instance there:

from pydantic import BaseModel

class ProductSchema(BaseModel):
    alias: str
    category: str
    brand: str

    def __init__(self, *args, **kwargs):

        # Do Pydantic validation
        super().__init__(*args, **kwargs)

        # Do things after Pydantic validation
        if not any([self.alias, self.category, self.brand]):
            raise ValueError("No alias provided")

Though this happens outside of Pydantic’s validation.

Answered By: miksus
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.