PythonのPydanticで堅牢な開発

## Pydanticとは？
PydanticはPython用のデータバリデーションとデータ解析のライブラリです。Pydanticの特徴は、Pythonの型ヒントを活用してデータをバリデーションできる点です。これによりモデルを定義するだけで型の変換や検証を自動で行うことができ、エラーの少ない堅牢なコードが書けます。


## 基本的な使い方
Pydanticの中心的な機能は「モデル」です。モデルはPythonのクラスとして定義され、BaseModelを継承することでPydanticの機能を使うことができます。
クラス変数にPythonの型ヒント（int, str, floatなど）を記述することで、データの検証と型チェックが自動で行われます。

**・モデルの定義例**
```python
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str
```
上記のように、UserクラスをPydanticモデルとして定義しました。
idは整数型（int）、nameは文字列型（str）、emailも文字列型として定義されています。このモデルを使うことで、ユーザー情報が正しいデータ型かどうかを自動でチェックできます。

**・モデルのインスタンス化と型変換**
Pydanticモデルは、辞書形式のデータを引数として渡すことでインスタンス化できます。
```python
user = User(id='1', name='Taro', email='taro@example.com')
print(user)

#出力結果
id=1 name='Taro' email='taro@example.com'
```
この例では、idに文字列 '1' を渡していますが、Pydanticは自動的に型を変換し、int型に変換しています。
これにより、APIなどから受け取るデータが想定外の形式でも、エラーを起こすことなく適切に動作します。

**・エラーハンドリング**
定義された型に一致しないデータが渡された場合は例外を発生させることもできます。
```python
try:
    invalid_user = User(id='abc', name='Taro', email='invalid_email')
except ValueError as e:
    print(e)

#出力結果
1 validation error for User
id
  value is not a valid integer (type=type_error.integer)
```
この例では、idに文字列 'abc' を渡しているため、int型への変換に失敗しValueErrorが発生しています。
また、Pydanticはエラーメッセージに何が間違っていたかを詳細に表示するので、これによりバグの原因を素早く検出することができます。

**・ネストされたモデル**
モデルの中に別のモデルを含めることもできます。
```python
class Address(BaseModel):
    street: str
    city: str

class UserWithAddress(BaseModel):
    id: int
    name: str
    address: Address

user = UserWithAddress(
    id=1,
    name='Taro',
    address={'street': 'Main St', 'city': 'Tokyo'}
)
print(user)

#出力結果
id=1 name='Taro' address=Address(street='Main St', city='Tokyo')
```
このように、UserWithAddressモデルのaddressフィールドにはAddressモデルを使っています。これにより、APIレスポンスや複雑なデータ構造もシンプルに扱うことができます。

**・Optional型とデフォルト値**
Optionalを使うことでフィールドが必須でないことを示し、デフォルト値を設定することもできます。
```python
from typing import Optional

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

user = User(id=1, name='Taro')
print(user)

#出力結果
id=1 name='Taro' email=None
```

## カスタムバリデーション
基本の型チェックだけでは満たせない条件や制約はカスタムバリデーションを使って実現します。カスタムバリデーションは@validatorデコレータを使って定義します。

**・メール形式の検証**
```python
from pydantic import BaseModel, validator, ValidationError

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

    @validator('email')
    def validate_email(cls, value):
        if '@' not in value or '.' not in value:
            raise ValueError('Invalid email address')
        return value

# 正しいメールアドレス
user = User(id=1, name='Taro', email='taro@example.com')
print(user)

# 間違ったメールアドレス
try:
    invalid_user = User(id=2, name='Hanako', email='invalid-email')
except ValidationError as e:
    print(e)



#出力
id=1 name='Taro' email='taro@example.com'

1 validation error for User
email
  Invalid email address (type=value_error)
```
この例では、emailフィールドに渡された値が「@」と「.」を含んでいない場合、ValueErrorを発生させています。もしメールアドレスが不正な形式だった場合、エラーメッセージとともに例外が発生し実行が停止されます。

## FastAPIとの連携
FastAPIなどのWebフレームワークで使用するときは、APIのリクエストボディやレスポンスをPydanticモデルで定義することで、データの検証が自動化され安全性が高くなります。

例えば、FastAPIでユーザー登録APIを構築する場合だと、
```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr

app = FastAPI()

class User(BaseModel):
    id: int
    name: str
    email: EmailStr # メールアドレス形式を期待

@app.post("/register/")
async def register_user(user: User):
    # IDが0以下の場合、400エラーを返す
    if user.id <= 0:
        raise HTTPException(status_code=400, detail="ID must be positive.")
    return {"message": f"User {user.name} registered successfully!"}
```

EmailStr型は、文字列として受け取ったデータが正しいメールアドレス形式であるかを検証するので、無効なメールアドレスをユーザーが入力した場合、Pydanticは自動的に例外を発生させます。
また、HTTPExceptionを使用すると、適切なエラー内容をフィードバックします。