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)