728x90
반응형
Pydantic
- latest : v2.5.3
- dependency : python 3.7 +
installation
$ pip install pydantic
# poetry
$ poetry add pydantic
$ poetry install
$ poetry export -f requirements.txt --output requirements.txt
optional dependencies : github
pip install pydantic[email]
# or
pip install email-validator
주요 기능
BaseModel
입력을 받아 데이터 형식과 제약조건을 보장
- example
from datetime import datetime
from pydantic import BaseModel, PositiveInt
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None
tastes: dict[str, PositiveInt]
@validator('name')
def validate_name(cls, v):
if not v.isalnum():
raise ValueError('name field must be alphanumeric.')
return v
external_data = {
'id': 123,
'signup_ts': '2019-06-01 12:22',
'tastes': {
'wine': 9,
'cheese': 7,
'cabbage': '1',
},
}
user = User(**external_data)
print(user.id)
#> 123
print(user.dict())
"""
{
'id': 123,
'name': 'John Doe',
'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1},
}
"""
field : <type or default> 형태로 정의한다
- type을 적을 경우 required
- 다른 type을 입력할 경우 변환 가능하면 자동 변환
- 자동 변환이 불가능하다면 raise ValidationError
- datetime의 경우 UNIX time이나 date, time을 나타내는 str으로 입력이 온다면 자동 변환
- default 값이 있다면 해당 type을 입력으로 받음. required가 아님
- default 값을 None이나 type을 지정할 수있음
- typing 모듈을 사용해 위 예제(dict[str, PositiveInt]) 처럼 사용 가능
- 마찬가지로 다른 type이 들어오면 자동 변환
주요 메서드
- dict() : 객체의 필드를 dict로 반환
- json() : 객체의 필드를 json으로 반환
- schema() : 모델 객체에 대한 스키마를 dict로 반환
- schema_json() : 모델 객체에 대한 스키마를 json으로 반환
@validator
class User(BaseModel):
id: int
name: str = 'John Doe'
@validator('name')
def validate_name(cls, v):
...
- validator 데코레이터는 첫 번째 인자에 필드 이름을 넣어 각 필드에 대한 유효성 검사를 할 수 있다
- vlidator 메서드는 클레스 메서드. 첫번째 인자로 클래스를 받고, 두번째로 유효성 검사를 할 데이터를 받는다
- kwargs를 추가 할 수 있다
- values : dict 형태로 pre-validated values가 담겨있음
- config : 모델의 config 클래스
- field : 검증중인 필드 클래스 (pydantic.fields.ModelField)
Secrets
from pydantic import BaseModel, SecretBytes, SecretStr
class Model(BaseModel):
password: SecretStr
password_bytes: SecretBytes
model = Model(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes')
print(model)
#> password=SecretStr('**********') password_bytes=SecretBytes(b'**********')
print(model.password)
#> **********
print(model.dict())
"""
{
'password': SecretStr('**********'),
'password_bytes': SecretBytes(b'**********'),
}
"""
print(model.json())
#> {"password":"IAmSensitive","password_bytes":"IAmSensitiveBytes"}
EmailStr (pip install pydantic[email])
from pydantic import BaseModel, EmailStr
class Model(BaseModel):
email: EmailStr
print(Model(email='contact@mail.com'))
#> email='contact@mail.com'
NameEmail (pip install pydantic[email])
from pydantic import BaseModel, NameEmail
class User(BaseModel):
email: NameEmail
user = User(email='Fred Bloggs <fred.bloggs@example.com>')
print(user.email)
#> Fred Bloggs <fred.bloggs@example.com>
print(user.email.name)
#> Fred Bloggs
user = User(email='fred.bloggs@example.com')
print(user.email)
#> fred.bloggs <fred.bloggs@example.com>
print(user.email.name)
#> fred.bloggs
IPvAnyAddress
from pydantic import BaseModel
from pydantic.networks import IPvAnyAddress
class IpModel(BaseModel):
ip: IPvAnyAddress
print(IpModel(ip='127.0.0.1'))
#> ip=IPv4Address('127.0.0.1')
try:
IpModel(ip='<http://www.example.com>')
except ValueError as e:
print(e.errors())
'''
[
{
'loc': ('ip',),
'msg': 'value is not a valid IPv4 or IPv6 address',
'type': 'value_error.ipvanyaddress',
}
]
'''
dataclass
pydantic의 BaseModel 사용 없이 dataclass 데코레이터로 동일하게 유효성 검사 가능
dataclass 는 python의 빌트인 모듈인 dataclasses 기반
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name = '홍길동'
user = User(id='42')
DRF Serializer vs pydantic
Serializer | Pydantic | |
필드(CharField, IntegerField, ...) validation | O | X |
필드 custom validation | O | O |
모든 필드를 종합적으로 validation | O | O |
Serialization | O | X |
인스턴트 생성 | O | X |
인스턴트 업데이트 | O | X |
Nested validation | O | O |
속도 | 느림 | 빠름 |
속도
performance example
import json
import timeit
from urllib.parse import urlparse
import requests
from pydantic import HttpUrl, TypeAdapter
reps = 7
number = 100
r = requests.get('<https://api.github.com/emojis>')
r.raise_for_status()
emojis_json = r.content
def emojis_pure_python(raw_data):
data = json.loads(raw_data)
output = {}
for key, value in data.items():
assert isinstance(key, str)
url = urlparse(value)
assert url.scheme in ('https', 'http')
output[key] = url
emojis_pure_python_times = timeit.repeat(
'emojis_pure_python(emojis_json)',
globals={
'emojis_pure_python': emojis_pure_python,
'emojis_json': emojis_json,
},
repeat=reps,
number=number,
)
print(f'pure python: {min(emojis_pure_python_times) / number * 1000:0.2f}ms')
#> pure python: 5.32ms
type_adapter = TypeAdapter(dict[str, HttpUrl])
emojis_pydantic_times = timeit.repeat(
'type_adapter.validate_json(emojis_json)',
globals={
'type_adapter': type_adapter,
'HttpUrl': HttpUrl,
'emojis_json': emojis_json,
},
repeat=reps,
number=number,
)
print(f'pydantic: {min(emojis_pydantic_times) / number * 1000:0.2f}ms')
#> pydantic: 1.54ms
print(
f'Pydantic {min(emojis_pure_python_times) / min(emojis_pydantic_times):0.2f}x faster'
)
#> Pydantic 3.45x faster
- pydantic docs #performance에 따르면, pure python 에 비해 3배이상 빠르다.
- data > valid data 속도만 비교하면 DRF Serializer 에 비해 12배이상 빠르다
Djantic
Django가 적용된 pydantic을 사용하기위한 프로젝트가 있으나 test 중이고 23.4 이후 업데이트가 없다.
728x90
반응형