Home > Blockchain >  Pydantic: Using single validator to validate multiple attributs in the same time
Pydantic: Using single validator to validate multiple attributs in the same time

Time:09-16

Requirements:

1 - Use pydantic for data validation
2 - validate each data keys individually against string a given pattern
3 - validate some keys against each other (ex: k1 and k3 values must have the same length)

Here is the program

# System libraries
from typing import List
from typing import Union
from typing import Optional
import re

# External libraries
from pydantic import BaseModel, validator, ValidationError


def str_match(pattern, test_string):
    result = re.match(pattern, test_string, re.IGNORECASE)
    if result:
        return True
    else:
        return False


# Example of test input data
data_set = {
  "data1": {
      "k1": "xv1_blabla",
      "k2": "v2_blabla",
      "k3": None
  },
  "data2": {
      "k1": "v1_blabla",
      "k2": "v2_blabla",
      "k3": "v3_blabla"

  }
}


k1_pattern = "v1_\w "
k2_pattern = "v2_\w "
k3_pattern = "v3_\w "


class ValidateData(BaseModel):
    k1: str
    k2: Union[str, None]
    k3: Union[str, None]


    @validator('k1', pre=True, allow_reuse=True)
    def check_k1_conformity(cls, value):
        if value is not None:
            if not str_match(k1_pattern, value):
                raise ValueError(f"k1 {value} Not Conform to {k1_pattern}")
        return value

    @validator('k2', pre=True, allow_reuse=True)
    def check_k2_conformity(cls, value):
        if value is not None:
            if not str_match(k2_pattern, value):
                raise ValueError(f"k2 {value} Not Conform to {k2_pattern}")
        return value

    @validator('k3', pre=True, allow_reuse=True)
    def check_k3_conformity(cls, value):
        if value is not None:
            if not str_match(k3_pattern, value):
                raise ValueError(f"k3 {value} Not Conform to {k3_pattern}")
        return value


    @validator('k1', 'k2', 'k3', allow_reuse=True)
    def check_k2k3_conformity(cls, value, field, values):
        print(f"Field: {field.name} has value {value}")


final_result = list()
for key, value in data_set.items():
    try:
        print(f"\nValidating key: ----- {key} -----")
        pfx = ValidateData(**value)
    except ValidationError as e:
        print(e)

I have such data set:

data_set = {
  "data1": {
      "k1": "xv1_blabla",
      "k2": "v2_blabla",
      "k3": None
  },
  "data2": {
      "k1": "v1_blabla",
      "k2": "v2_blabla",
      "k3": "v3_blabla"

  }
}

Script results:

Validating key: ----- data1 -----
Field: k2 has value v2_blabla
Field: k3 has value None
1 validation error for ValidateData
k1
  k1 xv1_blabla Not Conform to v1_\w  (type=value_error)

Validating key: ----- data2 -----
Field: k1 has value v1_blabla
Field: k2 has value v2_blabla
Field: k3 has value v3_blabla

What works:

1- Methods "check_kn_conformity": correctly check conformity of the keys
(ex: data1['k1'] not conform)

What doesn't work:

1- Method check_k2k3_conformity: get individual keys and values, but cannot have them in the same iteration to check one against the other.

Any hint?

CodePudding user response:

Found the solution using root_validator decorator

Root Validators¶

Validation can also be performed on the entire model's data.

and values as input to the decorated method

The values argument will be a dict containing the values which passed field validation and field defaults where applicable.

https://pydantic-docs.helpmanual.io/usage/validators/#root-validators

What changed in the code

@root_validator()
def check_k2k3_conformity(cls, values):
    print(f"k1 has value: {values.get('k1')}")
    print(f"k2 has value: {values.get('k2')}")
    print(f"k3 has value: {values.get('k3')}")
    if values.get('k1') is not None and values.get('k3') is not None:
        if len(values.get('k1')) != len(values.get('k3')):
            raise ValueError(f"k1 value length ({values.get('k1')}):({len(values.get('k1'))}) is not equal to k3 value length ({values.get('k3')}):({len(values.get('k3'))})")
    return values

Complete code

# System libraries
from typing import List
from typing import Union
from typing import Optional
import re

# External libraries
from pydantic import BaseModel, validator, ValidationError, root_validator


def str_match(pattern, test_string):
    result = re.match(pattern, test_string, re.IGNORECASE)
    if result:
        return True
    else:
        return False


# Example of test input data
data_set = {
  "data1": {
      "k1": "xv1_blabla",
      "k2": "v2_blabla",
      "k3": None
  },
  "data2": {
      "k1": "v1_blabla",
      "k2": "v2_blabla",
      "k3": "v3_blabla1"

  }
}


k1_pattern = "v1_\w "
k2_pattern = "v2_\w "
k3_pattern = "v3_\w "


class ValidateData(BaseModel):
    k1: str
    k2: Union[str, None]
    k3: Union[str, None]


    @validator('k1', pre=True, allow_reuse=True)
    def check_k1_conformity(cls, value):
        if value is not None:
            if not str_match(k1_pattern, value):
                raise ValueError(f"k1 {value} Not Conform to {k1_pattern}")
        return value

    @validator('k2', pre=True, allow_reuse=True)
    def check_k2_conformity(cls, value):
        if value is not None:
            if not str_match(k2_pattern, value):
                raise ValueError(f"k2 {value} Not Conform to {k2_pattern}")
        return value

    @validator('k3', pre=True, allow_reuse=True)
    def check_k3_conformity(cls, value):
        if value is not None:
            if not str_match(k3_pattern, value):
                raise ValueError(f"k3 {value} Not Conform to {k3_pattern}")
        return value


    # @validator('k1', 'k2', 'k3', allow_reuse=True)
    # def check_k2k3_conformity(cls, value, field):
    #     print(f"Field: {field.name} has value {value}")
        
    @root_validator()
    def check_k2k3_conformity(cls, values):
        print(f"k1 has value: {values.get('k1')}")
        print(f"k2 has value: {values.get('k2')}")
        print(f"k3 has value: {values.get('k3')}")
        if values.get('k1') is not None and values.get('k3') is not None:
            if len(values.get('k1')) != len(values.get('k3')):
                raise ValueError(f"k1 value length ({values.get('k1')}):({len(values.get('k1'))}) is not equal to k3 value length ({values.get('k3')}):({len(values.get('k3'))})")
        return values


final_result = list()
for key, value in data_set.items():
    try:
        print(f"\nValidating key: ----- {key} -----")
        pfx = ValidateData(**value)
    except ValidationError as e:
        print(e)

The result:

Validating key: ----- data1 -----
k1 has value: None
k2 has value: v2_blabla
k3 has value: None
1 validation error for ValidateData
k1
  k1 xv1_blabla Not Conform to v1_\w  (type=value_error)

Validating key: ----- data2 -----
k1 has value: v1_blabla
k2 has value: v2_blabla
k3 has value: v3_blabla1
1 validation error for ValidateData
__root__
  k1 value length (v1_blabla):(9) is not equal to k3 value length (v3_blabla1):(10) (type=value_error)
  • Related