코딩한걸음
Published 2024. 1. 19. 09:00
[Python] Pydantic Python
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
반응형
profile

코딩한걸음

@Joonyeol_Yoon

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!