Pydantic: Python에서 데이터 유효성 검사 단순화 III

2024. 4. 29. 21:09python/intermediate

유효성 검사기 작업

 

지금까지 Pydantic의 BaseModel을 사용하여 사전 정의된 유형으로 모델 필드를 검증했으며 검증을 추가로 사용자 정의하기 위해 Field를 통합했습니다. BaseModel과 Field 단독으로 꽤 멀리 갈 수 있지만 사용자 정의 논리가 필요한 보다 복잡한 검증 시나리오의 경우 Pydantic 검증기를 사용해야 합니다.

유효성 검사기를 사용하면 함수로 표현할 수 있는 거의 모든 유효성 검사 논리를 실행할 수 있습니다. 다음에서 이 작업을 수행하는 방법을 살펴보겠습니다.

 

모델 및 필드 검증

 

계속해서 직원을 예를 들어, 회사에 18세 이상의 직원만 고용한다는 정책이 있다고 가정해 보겠습니다. 새 Employee 개체를 만들 때마다 직원이 18세 이상인지 확인해야 합니다. 이를 처리하려면 age 필드를 추가하고 Field 클래스를 사용하여 직원이 18세 이상임을 강제할 수 있습니다. 그러나 직원의 생년월일을 이미 저장했기 때문에 이는 중복된 것 같습니다.

더 나은 해결책은 Pydantic 필드 유효성 검사기를 사용하는 것입니다. 필드 유효성 검사기를 사용하면 BaseModel 모델에 클래스 메서드를 추가하여 필드에 사용자 지정 유효성 검사 논리를 적용할 수 있습니다. 모든 직원이 18세 이상인지 확인하려면 Employee 모델에 다음 Field 유효성 검사기를 추가하면 됩니다.

 
[ ]:
 

 
# pydantic_models.py
from datetime import date
from uuid import UUID, uuid4
from enum import Enum
from pydantic import BaseModel, EmailStr, Field, field_validator
class Department(Enum):
    HR = "HR"
    SALES = "SALES"
    IT = "IT"
    ENGINEERING = "ENGINEERING"
class Employee(BaseModel):
    employee_id: UUID = Field(default_factory=uuid4, frozen=True)
    name: str = Field(min_length=1, frozen=True)
    email: EmailStr = Field(pattern=r".+@example\.com$")
    date_of_birth: date = Field(alias="birth_date", repr=False, frozen=True)
    salary: float = Field(alias="compensation", gt=0, repr=False)
    department: Department
    elected_benefits: bool
    @field_validator("date_of_birth")
    @classmethod
    def check_valid_age(cls, date_of_birth: date) -> date:
        today = date.today()
        eighteen_years_ago = date(today.year - 18, today.month, today.day)
        if date_of_birth > eighteen_years_ago:
            raise ValueError("Employees must be at least 18 years old.")
        return date_of_birth
 
 

이 블록에서는 field_validator를 가져와서 .check_valid_age()라 부르는Employee에서 클래스 메서드를 장식하는 데 사용합니다. 필드 유효성 검사기는 클래스 메서드로 정의되어야 합니다. .check_valid_age()에서는 18년 전의 오늘 날짜를 계산합니다. 직원의 date_of_birth가 해당 날짜 이후이면 오류가 발생합니다.

이 유효성 검사기가 어떻게 작동하는지 보려면 다음 예를 확인하세요.

 
[ ]:
 
 
from pydantic_models import Employee
from datetime import date, timedelta
young_employee_data = {
    "name": "Jake Bar",
    "email": "jbar@example.com",
    "birth_date": date.today() - timedelta(days=365 * 17),
    "compensation": 90_000,
    "department": "SALES",
    "elected_benefits": True,
}
Employee.model_validate(young_employee_data)
 
 

이 예에서는 현재 날짜보다 17년 뒤의 birth_date를 지정합니다. young_employee_data 확인을 위해 .model_validate()를 호출하면 직원이 18세 이상이어야 한다는 오류가 표시됩니다.

상상할 수 있듯이 Pydantic의 field_validator()을 사용하면 필드 유효성 검사를 임의로 사용자 정의할 수 있습니다. 그러나 field_validator()는 여러 필드를 서로 비교하거나 모델을 전체적으로 검증하려는 경우에는 작동하지 않습니다. 이를 위해서는 모델 유효성 검사기를 사용해야 합니다.

예를 들어 회사에서 IT 부서의 계약직 직원만 채용한다고 가정해 보겠습니다. 이로 인해 IT 작업자는 혜택을 받을 자격이 없으며 elected_benefits 필드는 False가 될 것입니다. Pydantic의 model_validator()을 사용하여 이 제약 조건을 적용할 수 있습니다.

 
[ ]:
 

 
# pydantic_models.py
from typing import Self
from datetime import date
from uuid import UUID, uuid4
from enum import Enum
from pydantic import (
    BaseModel,
    EmailStr,
    Field,
    field_validator,
    model_validator,
)
class Department(Enum):
    HR = "HR"
    SALES = "SALES"
    IT = "IT"
    ENGINEERING = "ENGINEERING"
class Employee(BaseModel):
    employee_id: UUID = Field(default_factory=uuid4, frozen=True)
    name: str = Field(min_length=1, frozen=True)
    email: EmailStr = Field(pattern=r".+@example\.com$")
    date_of_birth: date = Field(alias="birth_date", repr=False, frozen=True)
    salary: float = Field(alias="compensation", gt=0, repr=False)
    department: Department
    elected_benefits: bool
    @field_validator("date_of_birth")
    @classmethod
    def check_valid_age(cls, date_of_birth: date) -> date:
        today = date.today()
        eighteen_years_ago = date(today.year - 18, today.month, today.day)
        if date_of_birth > eighteen_years_ago:
            raise ValueError("Employees must be at least 18 years old.")
        return date_of_birth
    @model_validator(mode="after")
    def check_it_benefits(self) -> Self:
        department = self.department
        elected_benefits = self.elected_benefits
        if department == Department.IT and elected_benefits:
            raise ValueError(
                "IT employees are contractors and don't qualify for benefits"
            )
        return self
 
 

여기에서는 Python의 Self 유형과 Pydantic의 유형을 model_validator()를 imports에 추가합니다. 그런 다음, 직원이 IT 부서에 속하고 elected_benefits 필드가 True인 경우 오류를 발생시키는 .check_it_benefits() 메서드를 만듭니다. @model_validator에서 mode를 after로 설정한 경우 Pydantic은 모델이 .check_it_benefits()이 실행될 때까지 기다립니다.

참고 .check_it_benefits()이 Python의 Self 유형으로 주석이 달린 것을 눈치챘을 것입니다. 이는 .check_it_benefits()이 Employee 클래스 인스턴스를 반환하고 Self 유형이 이에 대해 선호되는 주석이기 때문입니다. 3.11 미만의 Python 버전을 사용하는 경우 Self type from typing_extensions을 가져와야 합니다.

새 모델 유효성 검사기가 실제로 작동하는 모습을 보려면 다음 예를 확인하세요.

 
[ ]:
 

 
from pydantic_models import Employee
new_employee = {
    "name": "Alexis Tau",
    "email": "ataue@example.com",
    "birth_date": "2001-04-012",
    "compensation": 100_000,
    "department": "IT",
    "elected_benefits": True,
}
Employee.model_validate(new_employee)
 
 

이 예에서는 IT 부서가 포함된 Employee 모델을 생성하고 elected_benefitsdmf Truefh 설정 하려고 합니다. .model_validate()를 호출하면 Pydantic은 IT 직원이 계약직이기 때문에 혜택을 받을 자격이 없다는 것을 알려주는 오류를 발생시킵니다.

모델 및 필드 유효성 검사기를 사용하면 생각할 수 있는 모든 사용자 정의 유효성 검사를 구현할 수 있습니다. 이제 자신의 사용 사례에 맞는 Pydantic 모델을 생성할 수 있는 탄탄한 기반이 있어야 합니다. 다음으로, 기어를 바꾸고 Pydantic을 사용하여 BaseModel 필드뿐만 아니라 임의의 기능을 검증하는 방법을 살펴보겠습니다.

 

유효성 검사 데코레이터를 사용하여 함수 유효성 검사

 

BaseModel이 데이터 스키마 유효성을 검사하기 위한 Pydantic의 빵과 버터 클래스인 동시에 Pydantic을 사용하여 @validate_call 데코레이터를 사용하여 함수 인수의 유효성을 검사할 수도 있습니다. 이를 통해 유효성 검사 논리를 수동으로 구현하지 않고도 정보 유형 오류가 있는 강력한 함수를 만들 수 있습니다.

이것이 어떻게 작동하는지 확인하기 위해 고객이 구매한 후 고객에게 송장을 보내는 함수를 작성한다고 가정해 보겠습니다. 귀하의 기능은 클라이언트의 이름, 이메일, 구매한 항목 및 총 청구 금액을 가져와 이메일을 구성하고 보냅니다. 잘못 입력하면 이메일이 전송되지 않거나 형식이 잘못되거나 고객에게 청구서가 잘못 발행될 수 있으므로 이러한 입력을 모두 확인해야 합니다.

이를 수행하려면 다음 함수를 작성합니다.

 
[ ]:
 
 
# validate_functions.py
import time
from typing import Annotated
from pydantic import PositiveFloat, Field, EmailStr, validate_call
@validate_call
def send_invoice(
    client_name: Annotated[str, Field(min_length=1)],
    client_email: EmailStr,
    items_purchased: list[str],
    amount_owed: PositiveFloat,
) -> str:
    email_str = f"""
    Dear {client_name}, \n
    Thank you for choosing xyz inc! You
    owe ${amount_owed:,.2f} for the following items: \n
    {items_purchased}
    """
    print(f"Sending email to {client_email}...")
    time.sleep(2)
    return email_str
 
 

먼저, send_invoice()를 작성하고 주석을 다는 데 필요한 종속성을 가져옵니다. 그런 다음 @validate_call로 장식된 send_invoice()을 만듭니다. send_invoice()를 실행하기 전에, @validate_call은 각 입력이 주석을 준수하는지 확인하세요. 이 경우 @validate_call이 client_name에 문자가 하나 이상 있는지, client_email은 형식이 올바르게 지정되었는지, items_purchased는 문자열 목록인지, amount_owed가 양수 부동 소수점인지 확인합니다.

입력 중 하나가 주석을 따르지 않으면 Pydantic은 BaseModel에서 이미 본 것과 유사한 오류를 발생시킵니다. 모든 입력이 유효하면 send_invoice()는 문자열을 생성하고 고객에게 time.sleep(2)를 보내도록 시뮬레이트를 합니다.

참고 client_name이 Python Annotated 유형으로 주석이 달린 것을 눈치챘을 것입니다. 일반적으로 함수 인수에 대한 메타데이터를 제공하려는 경우에 를 Annotated를 사용할 수 있습니다. Pydantic은 Field로 지정된 메타데이터가 있는 함수 인수의 유효성을 검사해야 할 때 Annotated를 사용할 것을 권장합니다.

그러나 default_factory를 사용하여 함수 인수에 기본값을 할당하는 경우 인수를 Field 인스턴스에 직접 할당해야 합니다. Pydantic의 문서에서 이에 대한 예를 볼 수 있습니다.

@validate_call을 확인하고 send_invoice()를 실행하려면 새 Python REPL을 열고 다음 코드를 실행하세요.

 
[ ]:
 
 
from validate_functions import send_invoice
 
 
[ ]:
 
 
send_invoice(
    client_name="",
    client_email="ajolawsonfakedomain.com",
    items_purchased=["pie", "cookie", 17],
    amount_owed=0,
)
 
 

이 예에서는 send_invoice()를 가져와 잘못된 함수 인수를 전달합니다. Pydantic의 @validate_call은 이를 인식하고 client_name이 최소한 하나의 문자가 필요하고 client_email은 유효하지 않으며 items_purchased가 문자열을 포함해야 하며 amount_owed는 0보다 커야 한다는 것을 말하면서 오류를 발생시킵니다.

유효한 입력을 전달하면 send_invoice()는 예상대로 실행됩니다.

 
[ ]:
 
 
email_str = send_invoice(
    client_name="Andrew Jolawson",
    client_email="ajolawson@fakedomain.com",
    items_purchased=["pie", "cookie", "cake"],
    amount_owed=20,
)
 
 
[ ]:
 
 
print(email_str)
 
 

@validate_call이 BaseModel처럼 유연하지는 않지만 함수 인수에 강력한 유효성 검사를 적용하는 데 사용할 수 있습니다. 이렇게 하면 많은 시간이 절약되고 상용구 유형 확인 및 유효성 검사 논리를 작성하지 않아도 됩니다. 이전에 이 작업을 수행한 적이 있다면 각 함수 인수에 대한 assert 명령문을 작성하는 것이 얼마나 번거로울 수 있는지 알 것입니다. 많은 사용 사례의 경우 @validate_call이 문제를 자동으로 처리합니다.

이 튜토리얼의 마지막 섹션에서는 설정 관리 및 구성에 Pydantic을 사용하는 방법을 배우게 됩니다.

 

설정 관리

 

Python 애플리케이션을 구성하는 가장 널리 사용되는 방법 중 하나는 환경 변수를 사용하는 것입니다. 환경 변수는 Python 코드 외부의 운영 체제에 있지만 코드나 다른 프로그램에서 읽을 수 있는 변수입니다. 환경 변수로 저장하려는 데이터의 예로는 비밀 키, 데이터베이스 자격 증명, API 자격 증명, 서버 주소 및 액세스 토큰이 있습니다.

환경 변수는 개발과 프로덕션 사이에 변경되는 경우가 많으며, 그 중 상당수에는 민감한 정보가 포함되어 있습니다. 이 때문에 코드에서 환경 변수를 구문 분석, 검증 및 통합하는 강력한 방법이 필요합니다. 이는 pydantic-settings의 완벽한 사용 사례이며 이 섹션에서 살펴볼 내용입니다.

 

BaseSettings로 애플리케이션 구성

 

pydantic-settings은 Python에서 환경 변수를 관리하는 가장 강력한 방법 중 하나이며 FastAPI 와 같은 널리 사용되는 라이브러리에서 널리 채택되고 권장됩니다. pydantic-settings를 사용하여 환경 변수를 구문 분석하고 검증하는 BaseModel와 유사한 모델을 생성 할 수 있습니다.

pydantic-settings의 기본 클래스는 BaseSettings이며 BaseModel와 동일한 기능을 모두 갖습니다. 그러나 BaseSettings에서 상속되는 모델을 생성하는 경우 모델 이니셜라이저는 환경 변수에서 키워드 인수로 전달되지 않은 모든 필드를 읽으려고 시도합니다.

이것이 어떻게 작동하는지 확인하기 위해 애플리케이션이 데이터베이스 및 다른 API 서비스에 연결되어 있다고 가정합니다. 데이터베이스 자격 증명과 API 키는 시간이 지남에 따라 변경될 수 있으며 배포 중인 환경에 따라 자주 변경될 수 있습니다. 이를 처리하려면 다음 BaseSettings 모델을 생성할 수 있습니다.

 
[ ]:
 
 
# settings_management.py
from pydantic import HttpUrl, Field
from pydantic_settings import BaseSettings
class AppConfig(BaseSettings):
    database_host: HttpUrl
    database_user: str = Field(min_length=5)
    database_password: str = Field(min_length=10)
    api_key: str = Field(min_length=20)
 
 

이 스크립트에서는 BaseSettings 모델을 생성하는 데 필요한 종속성을 가져옵니다. 대시 대신 밑줄을 사용하여 BaseSettings from pydantic_settings을 가져 옵니다. 그런 다음 데이터베이스 및 API 키에 대한 필드를 상속 하고 저장하는 BaseSettings에서 상속받는 AppConfig 모델을 정의합니다. 이 예에서는 database_host는 유효한 HTTP URL이어야 하며 나머지 필드에는 최소 길이 제약 조건이 있습니다.

그런 다음 터미널을 열고 다음 환경 변수를 추가합니다. Linux, macOS 또는 Windows Bash를 사용하는 경우 다음 export 명령을 사용하여 이 작업을 수행할 수 있습니다.

 
[ ]:
 
 
!export DATABASE_HOST="http://somedatabaseprovider.us-east-2.com"
!export DATABASE_USER="username"
!export DATABASE_PASSWORD="asdfjl348ghl@9fhsl4"
!export API_KEY="ajfsdla48fsdal49fj94jf93-f9dsal"
 
 

Windows PowerShell에서 환경 변수를 설정할 수도 있습니다. 그런 다음 새 Python REPL을 열고 AppConfig를 인스턴스화할 수 있습니다.

 
[ ]:
 
 
from settings_management import AppConfig
 
 
[ ]:
 
 
AppConfig()
 
 

AppConfig 인스턴스화할 때 필드 이름을 지정하지 않는 방법에 유의하세요. 대신 BaseSettings 모델은 사용자가 설정한 환경 변수에서 필드를 읽습니다. 또한 환경 변수를 모두 대문자로 내보냈지만 AppConfig은 성공적으로 구문 분석하고 저장했습니다. 이는 BaseSettings이 환경 변수를 필드 이름과 일치시킬 때 대소문자를 구분하지 않기 때문입니다.

그런 다음 Python REPL을 닫고 잘못된 환경 변수를 만듭니다.

 
[ ]:
 
 
!export DATABASE_HOST="somedatabaseprovider.us-east-2"
!export DATABASE_USER="usee"
!export DATABASE_PASSWORD="asdf"
!export API_KEY="ajf"
 
 

이제 다른 Python REPL을 열고 AppConfig을 다시 인스턴스화합니다.

 
[ ]:
 
 
from settings_management import AppConfig
 
 
[ ]:
 
 
AppConfig()
 
 

이번에는 AppConfig를 인스턴스화하려고 하면 pydantic-settings이 database_host가 유효한 URL이 아니며 나머지 필드가 최소 길이 제약 조건을 충족하지 않는다는 오류가 발생합니다.

이는 단순화된 구성 예였지만 환경 변수에서 필요한 모든 것을 구문 분석하고 검증하는 데 BaseSettings를 활용할 수 있습니다. 모델 및 필드 유효성 검사기를 사용한 사용자 정의 유효성 검사를 포함하여 BaseSettings이 수행할 수 있는 모든 유효성 검사는 BaseModel에서도 수행할 수 있습니다.

마지막으로 SettingsConfigDict와 같이 BaseSettings의 동작을 추가로 사용자 정의하는 방법을 알아봅니다.

 

SettingsConfigDict를 사용하여 설정 사용자 정의

 

이전 예에서는 환경 변수를 구문 분석하고 유효성을 검사하는 BaseSettings 모델을 만드는 방법에 대한 기본 예를 확인했습니다. 그러나 BaseSettings 모델의 동작을 추가로 사용자 정의하고 싶을 수도 있으며 SettingsConfigDict를 사용하여 이를 수행할 수 있습니다.

각 환경 변수를 수동으로 내보낼 수 없고 .env 파일에서 읽어야 하는 경우가 많다고 가정해 보겠습니다. 이 BaseSettings이 구문 분석할 때 대소문자를 구분하는지 확인하고 모델에서 지정한 것 외에 .env 파일에 추가 환경 변수가 없는지 확인하고 싶을 것입니다. SettingsConfigDict이 이것을 수행하는 방법은 다음과 같습니다.

 
[ ]:
 
 
# settings_management.py
from pydantic import HttpUrl, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class AppConfig(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        case_sensitive=True,
        extra="forbid",
    )
    database_host: HttpUrl
    database_user: str = Field(min_length=5)
    database_password: str = Field(min_length=10)
    api_key: str = Field(min_length=20)
 
 

이 스크립트는 이전 예제와 동일하지만 이번에는 SettingsConfigDict를 가져오고 AppConfig를 초기화해야 합니다. SettingsConfigDict에서 환경 변수를 .env 파일에서 읽어야 하고, 대소문자를 구분해야 하며, .env 파일에서 추가 환경 변수가 금지되도록 지정합니다.

그런 다음 settings_management.py과 동일한 디렉터리에 이름이 지정된 .env 파일을 만들고 다음 환경 변수로 채웁니다.

 

# .env

database_host=http://somedatabaseprovider.us-east-2.com/
database_user=username
database_password=asdfjfffffl348ghl@9fhsl4
api_key=ajfsdla48fsdal49fj94jf93-f9dsal

 

이제 Python REPL을 열고 AppConfig모델을 초기화할 수 있습니다.

 
[ ]:
 
 
from settings_management import AppConfig
 
 
[ ]:
 
 
AppConfig()
 
 

보시다시피 AppConfig이 .env파일의 환경 변수를 성공적으로 구문 분석하고 유효성을 검사했습니다.

마지막으로 .env 파일에 잘못된 변수를 추가하세요.

 

# .env

DATABASE_HOST=http://somedatabaseprovider.us-east-2.com/
database_user=username
database_password=asdfjfffffl348ghl@9fhsl4
api_key=ajfsdla48fsdal49fj94jf93-f9dsal
extra_var=shouldntbehere

 

여기에서는 대소문자 구분 제약 조건을 위반하여 database_host를 DATABASE_HOST로 변경했으며, 있어서는 안 되는 추가 환경 변수를 추가했습니다. 이를 검증하려고 할 때 모델이 응답하는 방식은 다음과 같습니다.

 
[ ]:
 
 
from settings_management import AppConfig
 
 
[ ]:
 
 
AppConfig()
 
 

database_host가 누락되었으며 .env 파일에 추가 환경 변수가 있다는 뛰어난 오류 목록이 표시됩니다. 대소문자 구분 제약으로 인해 모델은 DATABASE_HOST가 extra_var와 함께 추가 변수로 생각합니다.

SettingsConfigDict와 은 BaseSettings은 더 많은 일을 할 수 있고 더, 일반적으로 할 수 있지만, 이러한 예는 자신의 사용 사례에 맞게 환경 변수를 관리하는 데 pydantic-settings을 사용할 수 있는 방법에 대한 아이디어를 제공합니다.

 

결론

 

결론 Pydantic은 사용하기 쉽고 빠르며 널리 신뢰받는 Python 데이터 검증 라이브러리입니다. 귀하는 Pydantic에 대한 광범위한 개요를 얻었으며 이제 자신의 프로젝트에서 Pydantic을 사용하는 데 필요한 지식과 리소스를 갖게 되었습니다.

이 튜토리얼에서는 다음을 배웠습니다 .

  • Pydantic이 무엇이며 왜 그렇게 널리 채택되었는지
  • Pydantic 설치 방법
  • BaseModel과 유효성 검사기를 사용하여 데이터 스키마를 구문 분석, 유효성 검사 및 직렬화하는 방법
  • @validate_call을 사용하여 함수에 대한 사용자 지정 유효성 검사 논리를 작성하는 방법
  • pydantic-settings으로 환경 변수를 구문 분석하고 검증하는 방법

Pydantic은 코드를 더욱 강력하고 신뢰할 수 있게 만들어 주며 Python의 사용 용이성과 정적으로 유형이 지정된 언어의 내장 데이터 유효성 검사 간의 격차를 부분적으로 메워줍니다. 귀하가 가질 수 있는 거의 모든 데이터 구문 분석, 검증 및 직렬화 사용 사례에 대해 Pydantic은 우아한 솔루션을 제공합니다.

코드 받기: Pydantic이 Python에서 데이터 검증을 단순화하는 데 어떻게 도움이 되는지 알아보는 데 사용할 무료 샘플 코드를 다운로드하려면 https://realpython.com/bonus/pydantic-simplifying-data-validation-in-python/ 를 클릭하세요 .