Pydantic Data Model入門

前回の記事「[FastAPIで作るWebアプリ - Body validation](https://qiita.com/sand/items/470b33d7539713432090)」でも少し触れましたが、FastAPIのData validationには全面的に**Pydantic**が採用されています。


**Pydanticはpythonのtype annotationsを利用した、Data validation を提供します。**pydantic はruntime時にtype hintsを使い、Dataがinvalid時にユーザフレンドりなerrorsを吐き出してくれます。

[Pydantic 公式サイト](https://pydantic-docs.helpmanual.io/)


今回は、FastAPIから離れて、純粋にPydanticとはどんなものかを見ていきます。

【過去記事】
[Python Asyncio入門](https://qiita.com/sand/items/0e445a13d81d20ea33c3)
[Aiohttpで作るSimple Web Server - Qiita](https://qiita.com/sand/items/971c851598ff59c1c905)
[Starletteで作る Simple Web Server - QIita](https://qiita.com/sand/items/dd622ce4f9249399974c)
[FastAPIで作るWebアプリ - 基本](https://qiita.com/sand/items/bba0913fe4504c612b3a)
[FastAPIで作るWebアプリ - validation](https://qiita.com/sand/items/85068532388826258df4)
[FastAPIで作るWebアプリ - Body validation](https://qiita.com/sand/items/470b33d7539713432090)
[FastAPIで作るWebアプリ - Form validation](https://qiita.com/sand/items/207f306bdacdc0fb9571)
[Pydantic Data Model入門](https://qiita.com/sand/items/ca2c7c49e8bd94053b04)
[Tortoise ORM入門](https://qiita.com/sand/items/074866157635b50eb447)


#1. 基本


Pydanticではまず**Model**（データのスキーマみたいなもの）を作成します。そしてModelのインスタンスを作成するときにvalidationが行われます。エラーがないときはそのままインスタンスが作成されますが、エラーがあるときは例外を投げてくれます。

以下の例では、BaseModelを使ってModelを作成します。またValidationErrorで例外を受けています。

```python:example.py
from pydantic import BaseModel,ValidationError

class User(BaseModel):
    id: int
    name = 'Jane Doe'


user1 = User(id='123')
print(user1)

user2 = User(id='456')
print(user2)

user3 = User(id=789,name='Jhon Lennon')
print(user3)

try:
    user4 = User(id='abc')
    print(user4)
except ValidationError as e:
    print(e.json())
```

エラーは以下のようにわかり易いものとなっています。"loc"はエラーの場所を示し、"msg"と"type"でエラーの内容がわかるようになっています。

```
id=123 name='Jane Doe'
id=456 name='Jane Doe'
id=789 name='Jhon Lennon'
[
  {
    "loc": [
      "id"
    ],
    "msg": "value is not a valid integer",
    "type": "type_error.integer"
  }
]
```


# 2. いろいろなData型


```python:example2.py
from enum import Enum
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel

class Gender(str, Enum):
    male = 'male'
    female = 'female'
    other = 'other'
    not_given = 'not_given'

class User(BaseModel):
    id: int
    name = 'John Doe'
    gender: Gender
    signup_ts: Optional[datetime] = None
    friends: List[int] = []


external_data = {
    'id': '123',
    'gender': 'male',
    'signup_ts': '2019-06-01 12:22',
    'friends': [1, 2, '3'],
}
user = User(**external_data)
print(user.dict())
```

* id はint型です。**型が指定してあるだけなので id は必須となります**。Strings, bytes または floats の値が指定されると、可能であれば、型変換されます。それ以外は例外が発生します。
* nameはstringのdefault値が指定されているので、stringであると型推論されます。defaultが指定されているので必須ではありません。
* genderはEnum classであるGenderの値をとります。'mail', 'femail', 'other', 'not_given'　以外は例外が発生します。
* signup_ts は、datetime型またはNone、を許容する型となります（Optionalの定義より）。defaultはNoneとなります。
* friends はint型の値のリストとなります。int型に変換可能な値は自動的に変換されます。

これを動かすと以下のprint文が表示されます。

```
{'id': 123, 'gender': <Gender.male: 'male'>, 'signup_ts': datetime.datetime(2019, 6, 1, 12, 22), 'friends': [1, 2, 3], 'name': 'John Doe'}
```


それではexternal_dataの値に不正値を入れて、ちゃんとエラーになることを確認してみます。



まずgenderの値を変えてみます

```
external_data = {
    'id': '123',
    'gender': 'woman',
    'signup_ts': '2019-06-01 12:22',
    'friends': [1, 2, '3'],
}
```

ちゃんとエラーになりますね

```
pydantic.error_wrappers.ValidationError: 1 validation error for User
gender
  value is not a valid enumeration member; permitted: 'male', 'female', 'other', 'not_given' (type=type_error.enum; enum_values=[<Gender.male: 'male'>, <Gender.female: 'female'>, <Gender.other: 'other'>, <Gender.not_given: 'not_given'>])
```

次にsignup_tsの時刻指定を25時にしてみます

```
external_data = {
    'id': '123',
    'gender': 'male',
    'signup_ts': '2019-06-01 25:22',
    'friends': [1, 2, '3'],
}
```

ちゃんと怒ってくれます。

```
pydantic.error_wrappers.ValidationError: 1 validation error for User
signup_ts
  invalid datetime format (type=value_error.datetime)
```

最後にfriendsのリストにint変換できない文字列を入れてみます

```
external_data = {
    'id': '123',
    'gender': 'male',
    'signup_ts': '2019-06-01 12:22',
    'friends': [1, 2, 'not number'],
}
```

やはりエラーとなります。

```
pydantic.error_wrappers.ValidationError: 1 validation error for User
friends -> 2
  value is not a valid integer (type=type_error.integer)
```

今回は以上です