Traffine I/O

日本語

2023-03-10

データ検証のためのPydantic

Pydantic とは

Pydanticは、データのバリデーションや設定管理のプロセスを簡素化するPythonライブラリです。データモデルの定義や入力データのバリデーション、アプリケーションの設定管理を行うシンプルで直感的な方法を提供し、厳密な型付けとエラーハンドリングを維持します。

Pydanticは、FastAPIやStarletteなどの人気のあるWebフレームワークとシームレスに連携するように設計されていますが、データのバリデーションや設定管理が必要な任意のPythonアプリケーションで使用することができます。

Pydanticの主な利点の1つは、ボイラープレートコードを減らし、データのバリデーションをより宣言的にする能力です。開発者は、単純なクラス構文を使用してデータモデルを定義し、Pydanticがその他の作業、型チェック、バリデーション、エラーハンドリングなどを処理することができます。

Pydantic のインストール

Pydanticをインストールするには、以下のコマンドを実行します。

bash
$ pip install pydantic

Pydantic モデルの作成

Pydanticモデルの作成は、モデルのフィールドを定義し、必要な場合にバリデータを追加することで、簡単なプロセスです。

Pydanticモデルを作成する手順は以下のとおりです。

  1. pydanticモジュールからBaseModelクラスをインポートします。
python
from pydantic import BaseModel
  1. BaseModelをサブクラス化し、フィールドを追加してモデルを定義します。
python
class User(BaseModel):
    id: int
    name: str
    email: str

この例では、idnameemailの3つのフィールドを持つUserモデルを定義しています。各フィールドの型は、Pythonの注釈を使用して指定しています。

  1. 必要に応じて、フィールドにバリデータを追加します。Pydanticは、MinValueMaxValueRegexなど、入力データが特定の要件を満たすようにするために使用できる幅広い組み込みバリデータを提供しています。
python
from pydantic import EmailStr

class User(BaseModel):
    id: int
    name: str
    email: EmailStr

この更新された例では、emailフィールドをEmailStrフィールドに置き換え、入力データが有効な電子メールアドレスであることを自動的に検証しています。

  1. オプションで、typingモジュールからOptionalRequiredクラスを使用して、必須のフィールドとオプションのフィールドを指定することができます。
python
from typing import Optional

class User(BaseModel):
    id: int
    name: str
    email: Optional[EmailStr]

この最後の例では、emailフィールドをOptionalクラスでラップしてオプションとし、emailフィールドが含まれていなくても入力データが有効であることを意味します。

これらのステップに従うことで、入力データを検証し、Pythonアプリケーションがスムーズに実行されるようにすることができるPydanticモデルを簡単に作成できます。

Pydantic モデルを使用したデータ検証

Pydanticは、データモデルを定義して使用する簡単な方法を提供することで、Pythonアプリケーションでデータを簡単に検証することができます。この記事では、Pydanticモデルを使用してデータを検証する方法について説明します。

入力データの検証

Pydanticを使用して入力データを検証するには、期待される入力データの構造を定義するPydanticモデルを作成するだけです。その後、このモデルを使用して、アプリケーションが受信した任意の入力データを検証できます。

次は、Pydanticを使用して入力データを検証する例です。

python
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str

def create_user(user_data: dict):
    user = User(**user_data)
    # The above line validates the input data and raises a ValueError if it's invalid
    # If the input data is valid, you can continue with creating the user
    # ...

この例では、3つのフィールド(idname、およびemail)を持つUser Pydanticモデルを定義し、create_user関数を定義しています。この関数は、入力データの辞書を引数として受け取ります。

入力データを検証するために、**構文を使用してuser_data辞書を展開してUserモデルの新しいインスタンスを作成します。入力データが有効な場合、Userインスタンスは正常に作成されます。入力データが無効な場合、ValueErrorが発生します。

出力データの検証

入力データだけでなく、Pydanticを使用して出力データも検証できます。これは、アプリケーションが返すデータが特定の要件を満たしていることを確認したい場合に便利です。

以下は、Pydanticを使用して出力データを検証する例です。

python
from typing import List
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str

def get_users() -> List[User]:
    # Get a list of users from the database
    users_data = [
        {"id": 1, "name": "Alice", "email": "alice@example.com"},
        {"id": 2, "name": "Bob", "email": "bob@example.com"}
    ]
    # Convert the list of user data dictionaries to a list of User instances
    users = [User(**user_data) for user_data in users_data]
    # Validate the output data
    return users

この例では、ユーザーデータのリストを返すget_users関数を定義しています。データの辞書リストをリスト内包表記を使ってUserインスタンスのリストに変換します。

出力データを検証するには、単にUserインスタンスのリストを返すだけです。Pydanticは自動的に各Userインスタンスを検証し、無効なインスタンスがある場合は例外を発生させます。

組み込みバリデータ

Pydanticには、データモデルが特定の要件を満たしていることを確認するために使用できる多数の組み込みバリデータが用意されています。この記事では、もっとも一般的に使用されるバリデータのいくつかを説明し、使用方法の例を示します。

EmailStr

このバリデータは、文字列が有効なメールアドレスであることを保証します。

python
from pydantic import BaseModel, EmailStr

class User(BaseModel):
    name: str
    email: EmailStr

UrlStr

このバリデータは、文字列が有効なURLであることを保証します。

python
from pydantic import BaseModel, UrlStr

class Website(BaseModel):
    name: str
    url: UrlStr

Length

このバリデータは、文字列が最小長と/または最大長を持っていることを保証します。

python
from pydantic import BaseModel, constr, Length

class User(BaseModel):
    name: constr(min_length=1, max_length=50)
    bio: constr(max_length=500)

MinMaxValue

これらのバリデータは、数値が最小値以上または最大値以下であることを保証します。

python
from pydantic import BaseModel, conint, MinValue, MaxValue

class Product(BaseModel):
    name: str
    price: conint(gt=0, le=10000)
    quantity: conint(ge=0)
    discount: conint(ge=0, le=50)

Regex

このバリデータは、文字列が正規表現パターンに一致することを保証します。

python
from pydantic import BaseModel, constr, Regex

class User(BaseModel):
    name: constr(regex=r'^[a-zA-Z ]+$')

ConstrainedStr

このバリデータは、親クラスによって定義された制約を満たすように、文字列が保証されます。

python
from pydantic import BaseModel, ConstrainedStr

class Password(ConstrainedStr):
    min_length = 8
    max_length = 50
    regex = r'[a-zA-Z0-9_-]+'

class User(BaseModel):
    email: EmailStr
    password: Password

ConstrainedInt

このバリデータは、親クラスによって定義された制約を満たすように、整数が保証されます。

python
from pydantic import BaseModel, ConstrainedInt

class Age(ConstrainedInt):
    ge = 0
    le = 120

class User(BaseModel):
    name: str
    age: Age

Decimal

このバリデータは、数値が指定された小数点以下の桁数を持つ小数であることを保証します。

python
from pydantic import BaseModel, condecimal

class Product(BaseModel):
    price: condecimal(ge=0, le=10000, max_digits=8, decimal_places=2)

Uuid

このバリデータは、文字列値が有効なUUIDであることを保証します。

python
from pydantic import BaseModel, UUID4

class Order(BaseModel):
    id: UUID4

IPv4Address

このバリデータは、文字列値が有効なIPv4アドレスであることを保証します。

python
from pydantic import BaseModel, IPv4Address

class NetworkInterface(BaseModel):
    ip_address: IPv4Address

DirectoryPath

このバリデータは、文字列値が有効なディレクトリパスであることを保証します。

python
from pydantic import BaseModel, DirectoryPath

class Config(BaseModel):
    data_dir: DirectoryPath

FilePath

このバリデータは、文字列値が有効なファイルパスであることを保証します。

python
from pydantic import BaseModel, FilePath

class Config(BaseModel):
    data_file: FilePath

Pydantic の高度な機能

Pydanticは、強力で柔軟なデータモデルを作成するためのいくつかの高度な機能を提供しています。以下はそのうちのいくつかの主要な機能です。

カスタムバリデータの作成

Pydanticは、フィールド用のカスタムバリデータを作成できるようにし、データのバリデーション方法を細かく制御することができます。独自のバリデータ関数を定義して、データモデルで使用できます。

python
from pydantic import BaseModel, validator

class User(BaseModel):
    name: str
    age: int

    @validator('age')
    def validate_age(cls, age):
        if age < 0 or age > 120:
            raise ValueError('Invalid age')
        return age

この例では、Userモデルのageフィールドに対して、0から120の範囲内かどうかをチェックするカスタムバリデータを定義しています。

2 つのフィールドのバリデーション

カスタムバリデータの一般的な用途の1つは、2つのフィールドを同時にバリデーションして、2つのフィールドの値が特定の条件や制約を満たすことを保証することです。

例えば、配送注文を表すPydanticモデルがあり、2つの列が配送先の住所を表している場合、street_addresszip_codeです。郵便番号を住所に対応させるための郵便番号と住所のマッピングに基づいて、zip_codeが正しいstreet_addressに対応することを確認したい場合があります。

これを実現するために、street_addresszip_codeの両方の列に対して、値の組み合わせが有効かどうかをチェックするカスタムバリデータを定義することができます。

python
from pydantic import BaseModel, validator

class ShippingOrder(BaseModel):
    street_address: str
    zip_code: str

    @validator('zip_code')
    def validate_zip_code(cls, value, values):
        # get the value of the street_address column from the values dictionary
        street_address = values.get('street_address')

        # perform validation logic
        if not is_valid_zip_code(street_address, value):
            raise ValueError('Invalid zip code for street address')

        return value

    @validator('street_address')
    def validate_street_address(cls, value, values):
        # get the value of the zip_code column from the values dictionary
        zip_code = values.get('zip_code')

        # perform validation logic
        if not is_valid_zip_code(value, zip_code):
            raise ValueError('Invalid street address for zip code')

        return value

def is_valid_zip_code(street_address, zip_code):
    # perform validation logic, e.g. using a mapping of zip codes to street addresses
    if zip_code == '12345' and street_address != '123 Main St.':
        return False

    return True

この例では、zip_code列とstreet_address列の2つのカスタムバリデータを定義しています。各バリデータは2つの引数を取ります。バリデーションされるフィールドの名前(zip_codeまたはstreet_address)とその値(value)、およびモデル内の全てのフィールドの値の辞書(values)です。

各バリデータは、実際のバリデーションロジックを実行する別個のis_valid_zip_code関数も呼び出します。この場合、is_valid_zip_code関数は、郵便番号を住所にマッピングしたものに基づいて、street_addresszip_codeの組み合わせが有効かどうかを確認します。組み合わせが無効な場合、バリデータはエラーメッセージを伴うValueErrorを発生させます。

列挙型の使用

Pydanticは、特定の値のみを取ることができるフィールドを定義できる列挙型のビルトインサポートを提供しており、これによりデータモデルの正確性とセキュリティを確保できます。

python
from enum import Enum
from pydantic import BaseModel

class Gender(Enum):
    MALE = 'male'
    FEMALE = 'female'

class User(BaseModel):
    name: str
    gender: Gender

この例では、Genderの列挙型を定義し、Userモデルで使用して、genderフィールドがmaleまたはfemaleの値しか取らないようにします。

ネストされたモデルを扱う

Pydanticは、複雑なデータ構造を表現するために使用できるネストされたモデルを定義できます。

python
from pydantic import BaseModel

class Location(BaseModel):
    latitude: float
    longitude: float

class User(BaseModel):
    name: str
    location: Location

この例では、 Locationモデルを定義し、ユーザーの場所を表すためにUserモデル内のフィールドとして使用しています。

ORM ライブラリと Pydantic を使用する

PydanticはSQLAlchemyやTortoise-ORMなどの人気のあるORMライブラリと共に使用でき、データベースモデルとPydanticモデルの間で簡単に変換できます。

python
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer)

class UserIn(BaseModel):
    name: str
    age: int

class UserOut(UserIn):
    id: int

    class Config:
        orm_mode = True

この例では、 UserのSQLAlchemyモデルを定義し、入力および出力用のPydanticモデルを定義します。 Configクラスのorm_mode=Trueは、PydanticにSQLAlchemyモデルをPydanticモデルに変換し、その逆を行うように指示します。

参考

https://docs.pydantic.dev/

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!