Home > Software engineering >  How to dynamically validate custom Pydantic models against an object?
How to dynamically validate custom Pydantic models against an object?

Time:06-22

I have a function like this:

class Name(BaseModel):
    name_id: str
    first_name: str 
    last_name: str 


def get_all_names() -> List[Name]:
    names = []
    try:

        # this API returns a list of NAME objects
        names = requests.get("some-url")

        # I want to validate, that each NAME object comforms to the model "Name" above
        # this is what I do currently
        validate_name_objects = [Name(**each_name_object) for each_name_object in names]
    except Exception as e:
        # if any of the NAME object fails the validation check above, then it will automatically
        # be caught in this exception block and logged
        logger.info(f"log this error (could be requests error, could be validation error: {e}")
    
    return names

FastAPI does this validation check automatically, it somehow takes in type hint of response from function signature which in this case will be List[Name] and then automatically raise exception if the response does not conform to it.

I have these kind of checks in a lot of places in my code with different custom Pydantic models.

So I am looking for a mechanism of this sort where:

# Here SOME_FUNCTION takes in 2 arguments: A custom model to compare(which could be in any custom form 
# made from Pydantic models like List[some-model] or Dict[str, some-model], etc) 
# and a payload to validate that model against
validate_name_objects = SOME_FUNCTION(List[Name], names)

How to achieve this?


The closest content I found to my problem is provided by Pydantic here - https://pydantic-docs.helpmanual.io/usage/validation_decorator/ but this is only for validating input arguments of a given function and does not accept custom models dynamically.


Update: After answer by @MatsLindh, here are more flexible ways by which we can use the solution he gave: (but remember, as pointed out, Pydantic is a parsing library -> not a Validation library)

We can use it with just native python data types

from typing import Dict
from pydantic import parse_obj_as

items = parse_obj_as(Dict[int, str], {"asdasd": "23232"})
print(items)

This will as expected give Validation error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/tools.py", line 35, in pydantic.tools.parse_obj_as
  File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for ParsingModel[Dict]
__root__ -> __key__
  value is not a valid integer (type=type_error.integer)

We can also use the same function, for custom data models:

from typing import List, Dict
from pydantic import BaseModel, parse_obj_as

class Item(BaseModel):
    id: int
    name: str

items = parse_obj_as(Dict[int, Item], {1: "asdfasdasf"})
print(items)

This will as expected give Validation error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/tools.py", line 35, in pydantic.tools.parse_obj_as
  File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for ParsingModel[Dict]
__root__ -> 1
  value is not a valid dict (type=type_error.dict)

CodePudding user response:

You can use parse_obj_as to convert a list of dictionaries to a list of given Pydantic models, effectively doing the same as FastAPI would do when returning the response.

from pydantic import parse_obj_as

...

name_objects = parse_obj_as(List[Name], names)

However, it's important to consider that Pydantic is a parser library, not a validation library - so it will do conversions if your models allow for them.

  • Related