FastAPI 型のヒントと自動変換

## はじめに
この記事は、FastAPI の型ヒントや型の自動変換について
私が気になった箇所を掘り下げた記録です。

## 前提知識
Pythonの基本知識
APIの基本知識

## Pydantic
FastAPI では、「型ヒント」「自動変換」「APIの入出力のバリデーション」を Python のライブラリ [Pydantic]( https://docs.pydantic.dev/latest/) で行っている。

FastAPI の作者は Sebastián Ramírez で、Pydnatic の作者は Samuel Colvinですが
Sebastián Ramírez氏は Pydantic の開発にも貢献しているとのこと。[^1]
そのため、互換性が高く Pydanticは、FastAPI にデフォルトでインストールされています。

## 型ヒント
Pythonは、動的型付け言語だから、型情報はなくても動きます。
しかし、明示的に型情報を定義することもできます。
```Python
class Task():
    id: int
    title: str
```
クラスのアノテーションで型を確認することができます。
```Python
print(Task.__annotations__)
# {'id': <class 'int'>, 'title': <class 'str'>}
```
ここまでで Pydantic はまだ出てきていません。型ヒントはPythonの標準機能です。
Pydnatic は Python の「型ヒント」を使って、「APIの入出力のバリデーション」を行っています。

## APIの入出力のバリデーション
「APIの入出力のバリデーション」を使用するために Pydnatic をインポートします。
```Python
import pydantic
```
さらに、バリデーションは、Pydnatic の BaseModel クラスで行なわれます。
```Python
BaseModel = pydantic.BaseModel
```
これらを１行にまとめると
```Python
from pydantic import BaseModel
```
になります。Pythonで宣言したクラスの引数に BaseModel を
BaseModel クラスを引数にすることで BaseModel クラスが継承できます。
``` Python
class Task(BaseModel):
    id: int
    title: str
```
型の情報は Pythonと同じくアノテーションに入っています。
```Python
print(Task.__annotations__)
# {'id': <class 'int'>, 'title': <class 'str'>}
```
本題のバリデーションは、Pydnatic-Coreというライブライで行われています。
Pydnatic-Core は、プログラミング言語 Rust で組まれており、以下の Github からソースコードを確認することができます。

https://github.com/pydantic/pydantic-core/tree/main

バリデーションを行っているライブラリはわかりましたので、
Pydnatic-Core へ渡すまでの FastAPI が行っている処理を確認します。

Field で title に詳細なバリデーション、メタデータを追加しました。
```Python
from pydantic import BaseModel, Field
class Task(BaseModel):
    id: int
    title: str = Field( 
        default=None, 
        title="The description of the item", 
        max_length=300
)
```
今後、バリデーションを行うのに元（設計図）となるデータを
FastAPI は CoreSchema として Pydnatic-Core に渡します。
```Python
import pprint
schema = Task.__pydantic_core_schema__
pprint.pprint(schema)
```
CoreSchema 構造は型情報を含め、かなり細かな情報を返します。
先ほど追加した Field の情報も返しています。
```json
'schema': {'computed_fields': [],                                            
    fields': {'id': {'metadata': {},                                           
        'schema': {'type': 'int'},                                                   
        'type': 'model-field'},
    'title': {'metadata': {'pydantic_js_updates': {
                'title': 'The description of the item'
            }},
            'schema': {'default': None,
                        'schema': {'max_length': 300, 'type': 'str'},
                        type': 'default'},
            'type': 'model-field'}},
    model_name': 'Task',
    'type': 'model-fields'},
'type': 'model'}

```
BaseModel の [init](https://github.com/pydantic/pydantic/blob/main/pydantic/main.py#L243) で CoreSchema と APIの入力値 (data) を
バリデーションしています。
```Python
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
```
Rust で書かれた Pydnatic-Core をもっと掘り下げるのも良さそうですね。

## 自動変換
自動変換も Pydnatic-Core の validate_python 関数で行っています。
validate_python 関数は Pydnatic の TypeAdapter から呼び出せます。[^2]
```Python 
from typing import List
from pydantic import BaseModel, TypeAdapter
class Item(BaseModel):
    id: int
    name: str
    
item_data = [{'id': "1", 'name': 'My Item'}]
# [Item(id=1, name='My Item')]
```

変換の動作は以下のようになります。( 動作を確認済み )

|入力	|型ヒント	|結果|
|:-----------|------------:|:------------:|
|"42"	|int	|42 に変換される
|"42.5"|	int	|42.5 に変換される
|"true" / "True"|	bool	|True に変換される
|"false" / "False"|	bool	|False に変換される
|["tag1", "tag2"]|	List[str]	|　["tag1", "tag2"]
|["1", "2"]|	List[int]	|　[1, 2]
|{"x": 1, "y": 2}|	dict	| {'x': 1, 'y': 2} 

Pydanticint v2 から int, float, Decimal を str ( 文字列 ) に変更することはできなくなっています。
[coerce_numbers_to_str](https://pydantic.com.cn/api/config/#pydantic.config.ConfigDict.coerce_numbers_to_str) の設定を変更することで文字列への自動変換が可能になります

自動変換も Rust のコードを掘り下げるのも良さそうです。


[^1]: 以下のリンク先で対談が聞けます。
https://talkpython.fm/episodes/show/415/future-of-pydantic-and-fastapi
https://www.youtube.com/watch?v=vh0Lv-2trBQ
[^2]: コードは、Pydantic のサイトから参照しました。
https://docs.pydantic.dev/2.3/usage/type_adapter/