Validate model based on property

Viewed 196

I'm trying to create a Pydantic struct definition to hold & validate user-defined input conditions to my application.

Assuming that my schema is as following:

class UserInput(BaseModel):
    name: Condition[str]
    allowed_operations: ArrayCondition[str]

I want to somehow define Condition and ArrayCondition to be able to hold & validate values as follows:

# String comparison

# pseudo: name == "lucy"
Condition[str](operator=Operator.EQ, value="lucy") # OK

# pseudo: name == 5
Condition[str](operator=Operator.EQ, value=5) # ERROR

# pseudo: name in ["lucy", "bob"]
Condition[str](operator=Operator.IN, value=["lucy", "bob"]) # OK

# pseudo: name in ["lucy", 5]
Condition[str](operator=Operator.IN, value=["lucy", 5]) # ERROR

# pseudo: any([name == i for i in ["lucy", "bob"]])
Condition[str](list_predicate=ListPredicate.ANY, operator=Operator.EQ, value=["lucy", "bob"]) # OK

# pseudo: any([name in i for i in [["lucy", "bob"], ["amy", "ryan"]]])
Condition[str](list_predicate=ListPredicate.ANY, operator=Operator.IN, value=[["lucy", "bob"], ["amy", "ryan"]]) # OK

# pseudo: any([name in i for i in [["lucy", "bob"], ["amy", 5]]])
Condition[str](list_predicate=ListPredicate.ANY, operator=Operator.IN, value=[["lucy", "bob"], ["amy", 5]]) # ERROR


# String array comparison

# pseudo: empty(allowed_operations) == False
ArrayCondition[str](operator=Operator.EMPTY, value=False) # OK

# pseudo: empty(allowed_operations) == "a"
ArrayCondition[str](operator=Operator.EMPTY, value="a") # ERROR

# pseudo: allowed_operations == ["read", "write"]
ArrayCondition[str](operator=Operator.EQ, value=["read", "write"]) # OK

# pseudo: allowed_operations in [["read", "write"], ["share", "delete"]]
ArrayCondition[str](operator=Opertator.IN, value=[["read", "write"], ["share", "delete"]]) # OK

# pseudo: allowed_operations in [["read", "write"], "delete"]
ArrayCondition[str](operator=Opertator.IN, value=[["read", "write"], "delete"]) # ERROR

# pseudo: "delete" in allowed_operations
ArrayCondition[str](operator=Operator.CONTAINS, value="delete") # OK

# pseudo: any([i.startswith("d") for i in allowed_operations])
ArrayCondition[str](list_predicate=ListPredicate.ANY, operator=Operator.STARTSWITH, value="d") # OK

# pseudo: any([i in ["read", "write"] for i in allowed_operations)
ArrayCondition[str](list_predicate=ListPredicate.ANY, operator=Operator.IN, value=["read", "write"])

So far I tried using generics and all sorts of hacks to get it done but no success so far

1 Answers

We can achieve the same without creating custom type like Condition or ArrayCondtion

from pydantic import StrictStr, BaseModel, ValidationError, validator
from typing import List, Union

class User(BaseModel):
    name: Union[List[List[StrictStr]], List[StrictStr], StrictStr]
    cond: Union[List[StrictStr], StrictStr]

    @validator('cond')
    def check_cond(cls, v):
        assert not v == True, 'List is Empty'
        if isinstance(v, str) == True:
            del_opr = "delete"
            msg = "Operator not allowed `%s`" % (v)
            if len(del_opr) == len(v):
                assert v == "delete", msg
            else:
                assert v == del_opr[:len(v)], msg
        return v

try:
    User(name="this", cond="delete") # OK
    User(name=["this"], cond="delete") # OK
    User(name=[["this"]], cond="delete") # OK
    User(name=[["foo", "bar"], ["hey", "hi"]], cond="delete") # OK
    User(name=[["foo", "bar"], ["hey", "hi"]], cond="d") # OK
    User(name=[["this", 1]]) # ERR
    User(name=[[1]], cond="delete") # ERR
    User(name=5, cond="delete") # ERR
    User(name=[["foo", "bar"], ["hey", "hi"]], cond=1) # Err
    User(name=[["foo", "bar"], ["hey", "hi"]], cond=[]) # Err
    User(name=[["foo", "bar"], ["hey", "hi"]], cond=[1]) # Err
    User(name=[["foo", "bar"], ["hey", "hi"]], cond=["delete"]) # Err
    User(name=[["foo", "bar"], ["hey", "hi"]], cond="done") # Err
except ValidationError as e:
    print(e)
Related