Hello, are you Kakyo. This time, I would like to write an article about pydantic.
Pydantic is a library that uses Python type annotations to provide run-time type hints and easily provide error settings during data validation.
This library is useful for defining database models in SQLAlchemy.
First, when defining it, define it as follows.
from datetime import datetime
from typing import List
from pydantic import BaseModel
class Summer(BaseModel):
id: int
name: str # (variable):(Mold)として、Moldを宣言する
friends: List[int] = [] # "="You can also use to define a default value
created_at: datetime
external_data={
'id': '1',
'name' :'Beast senior',
'created_at': '2019-11-03 03:34',
'friends': [114,'514']
}
summer = Summer(**external_data)
print(summer.id)
#> 1 #Even if the entered value is a string type, it will be automatically converted to an Int type.
print(repr(summer.created_at))
#> datetime.datetime(2019, 11, 3, 3, 34)
print(user.friends)
#> [114,514]
print(user.dict())
"""
{
'id': 1,
'name': 'Beast senior',
'created_at': datetime.datetime(2019, 11, 3, 3, 34),
'friends': [114, 514]
}
"""
So, when a Validation Error occurs, it looks like this:
try:
User(created_at="Painted in black",friends=[1,'2','luxury car'])
except ValidationError as e:
print(e.json())
"""
[
{
"loc": [
"id"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"name"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"created_at"
],
"msg": "invalid datetime format",
"type": "type_error.datetime"
},
{
"loc": [
"friends",
2
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
"""
Here, the objects returned when an error occurs are as follows.
Now, using SQLAlchemy, Python's ORM Wrapper, we'll do the following: You can design an SQL model.
The code below quotes from here.
from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, constr
Base = declarative_base()
class CompanyOrm(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True)
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))
class CompanyModel(BaseModel):
id: int
public_key: constr(max_length=20)
name: constr(max_length=63)
domains: List[constr(max_length=255)]
class Config:
orm_mode = True
co_orm = CompanyOrm(
id=123,
public_key='foobar',
name='Testing',
domains=['example.com', 'foobar.com']
)
print(co_orm)
#> <orm_mode.CompanyOrm object at 0x7f0de1bc1cd0>
co_model = CompanyModel.from_orm(co_orm)
print(co_model)
#> id=123 public_key='foobar' name='Testing' domains=['example.com',
#> 'foobar.com']
Validators
You can use the ** Validator Decorator ** to see if the values you enter are correct and the complex relationships between the objects.
from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel):
name: str
username: str
password1: str
password2: str
@validator('name')
def name_must_contain_space(cls, v):
if ' ' not in v:
raise ValueError('must contain a space')
return v.title()
@validator('password2')
def passwords_match(cls, v, values, **kwargs):
if 'password1' in values and v != values['password1']:
raise ValueError('passwords do not match')
return v
@validator('username')
def username_alphanumeric(cls, v):
assert v.isalpha(), 'must be alphanumeric'
return v
print(UserModel(name='samuel colvin', username='scolvin', password1='zxcvbn',
password2='zxcvbn'))
#> name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'
try:
UserModel(name='samuel', username='scolvin', password1='zxcvbn',
password2='zxcvbn2')
except ValidationError as e:
print(e)
"""
2 validation errors for UserModel
name
must contain a space (type=value_error)
password2
passwords do not match (type=value_error)
"""
Pre and per-item validators
To set the priority when starting the Validator, use the following arguments pre
, pre_item
.
pre
starts the Validator before any other Validator you have set.
If ʻeach_item = True`, Validation will be executed for each element such as list and dictionary.
from typing import List
from pydantic import BaseModel, ValidationError, validator
class DemoModel(BaseModel):
square_numbers: List[int] = []
cube_numbers: List[int] = []
# '*' is the same as 'cube_numbers', 'square_numbers' here:
@validator('*', pre=True)
def split_str(cls, v):
if isinstance(v, str):
return v.split('|')
return v
@validator('cube_numbers', 'square_numbers')
def check_sum(cls, v):
if sum(v) > 42:
raise ValueError(f'sum of numbers greater than 42')
return v
@validator('square_numbers', each_item=True)
def check_squares(cls, v):
assert v ** 0.5 % 1 == 0, f'{v} is not a square number'
return v
@validator('cube_numbers', each_item=True)
def check_cubes(cls, v):
# 64 ** (1 / 3) == 3.9999999999999996 (!)
# this is not a good way of checking cubes
assert v ** (1 / 3) % 1 == 0, f'{v} is not a cubed number'
return v
print(DemoModel(square_numbers=[1, 4, 9]))
#> square_numbers=[1, 4, 9] cube_numbers=[]
print(DemoModel(square_numbers='1|4|16'))
#> square_numbers=[1, 4, 16] cube_numbers=[]
print(DemoModel(square_numbers=[16], cube_numbers=[8, 27]))
#> square_numbers=[16] cube_numbers=[8, 27]
try:
DemoModel(square_numbers=[1, 4, 2])
except ValidationError as e:
print(e)
"""
1 validation error for DemoModel
square_numbers -> 2
2 is not a square number (type=assertion_error)
"""
try:
DemoModel(cube_numbers=[27, 27])
except ValidationError as e:
print(e)
"""
1 validation error for DemoModel
cube_numbers
sum of numbers greater than 42 (type=value_error)
"""
Validate Always By default, for performance reasons, this Validator will not start if no value is given. However, the argument must be set to ʻalways = True` in order to run even if no value is given.
from datetime import datetime
from pydantic import BaseModel, validator
class DemoModel(BaseModel):
ts: datetime = None
@validator('ts', pre=True, always=True)
def set_ts_now(cls, v):
return v or datetime.now()
print(DemoModel())
#> ts=datetime.datetime(2019, 10, 24, 15, 7, 51, 449261)
print(DemoModel(ts='2017-11-08T14:00'))
#> ts=datetime.datetime(2017, 11, 8, 14, 0)
The above is the basic usage of Pydantic. You can use this to set validation and types in the SQL steamer in Python. For detailed settings, refer to the official document below.
Official Pydanit documentation (https://pydantic-docs.helpmanual.io/)
Recommended Posts