業界・業務から探す
導入目的・課題から探す
データ・AIについて学ぶ
News
Hakkyについて
ウェビナーコラム
◆トップ【Hakkyの社内Wiki】Strapi
AI

執筆者:Handbook編集部

Modelとは

pydantic のモデルとは

pydentaic のモデルは BaseModel クラスを継承したクラスのことを指します。

モデルを用いることでモデル内の変数(フィールドと言います)を型で指定できるようになります。

外部から渡された JSON の各値をそれぞれ指定した型でデータで扱いたいときや、クラスの変数に厳密な型を適用したいときなどに用いると便利かと思われます。

pydantic のモデルの使い方

モデルの使う場合、BaseModel を継承する以外は、通常の class の定義方法と同様です。

モデルはいくつかのフィールドを持ち、そのフィールドは変更可能な値となります。

フィールドは型を指定するか、デフォルト値を指定して定義します。

フィールドに型指定と異なる値を渡された場合(例えば、int 型に対して”123”を渡された場合など)はキャスト可能な場合は指定した型にキャストされます。渡された値がキャスト不可能な場合(例えば、int 型に対して”Jane Doe”が渡された場合など)は Validation Error を返します。

具体的な使い方

from pydantic import BaseModel

# モデルの定義
class User(BaseModel):
    id: int
    name = "Jane Doe"


# Userのインスタンスを作成 idを初期化時に設定しています
user = User(id="123")

# 値のチェック例
assert user.id == 123  # intにキャストされます
assert user.name == "Jane Doe"  # デフォルト値が使われています
assert user.__fields_set__ == {"id"}  # ユーザーが初期化されたときに提供されたフィールドになります。

# フィールド値は変更可能です。
user.id = 321
assert user.id == 321

print(user.dict())

上記を実行すると、結果は下記のようになります。

{'id': 321, 'name': 'Jane Doe'}

フィールドの順序について

モデルでは、次の理由からフィールドの順序が重要になります。

  • 検証は、フィールドが定義された順序で実行されます。そのため、前のフィールド値にはアクセスできますが、後ろにはアクセスできません。
  • フィールドの順序はモデルスキーマに保持されます
  • フィールドの順序は検証エラーで保持されます
  • フィールドの順序は.dict()、.json()などによって保持されます。

なお、注釈付きのすべてのフィールド(注釈のみまたはデフォルト値あり)は、注釈なしのすべてのフィールドの前に配置されます。

詳しくは下記コード例をご覧ください。

from pydantic import BaseModel, ValidationError


class Model(BaseModel):
    a: int
    b = 2
    c: int = 1
    d = 0
    e: float


print(Model.__fields__.keys())
# > dict_keys(['a', 'c', 'e', 'b', 'd'])
m = Model(e=2, a=1)
print(m.dict())
# > {'a': 1, 'c': 1, 'e': 2.0, 'b': 2, 'd': 0}
try:
    Model(a="x", b="x", c="x", d="x", e="x")
except ValidationError as e:
    error_locations = [e["loc"] for e in e.errors()]

print(error_locations)
# > [('a',), ('c',), ('e',), ('b',), ('d',)]

上記を実行すると、結果は下記のようになります。

dict_keys(['a', 'c', 'e', 'b', 'd'])
{'a': 1, 'c': 1, 'e': 2.0, 'b': 2, 'd': 0}
[('a',), ('c',), ('e',), ('b',), ('d',)]

エラーハンドリングの方法

検証時にエラーがあるとValidation Errorとなります。Validation Error には全てのエラーと発生方法の情報が含まれております。

エラー情報にアクセスする方法には下記があります。

  • e.errors() : list としてエラー情報を返します。
  • e.json() : json の文字列としてエラー情報を返します。
  • str(e) : 文字列としてエラー情報を返します。

確認方法は下記コード例をご参考ください。

from typing import List
from pydantic import BaseModel, ValidationError, conint


class Location(BaseModel):
    lat = 0.1
    lng = 10.1


class Model(BaseModel):
    is_required: float
    gt_int: conint(gt=42)
    list_of_ints: List[int] = None
    a_float: float = None
    recursive_model: Location = None


data = dict(
    list_of_ints=["1", 2, "bad"],
    a_float="not a float",
    recursive_model={"lat": 4.2, "lng": "New York"},
    gt_int=21,
)

try:
    Model(**data)
except ValidationError as e:
    print("=== e.errors() ===")
    print()
    print(e.errors())
    print()
    print("==================")

    print()

    print("==== e.json() ====")
    print()
    print(e.json())
    print()
    print("==================")

    print()

    print("==== str(e) ======")
    print()
    print(str(e))
    print()
    print("==================")

上記を実行すると、結果は下記のようになります。

=== e.errors() ===

[{'loc': ('is_required',), 'msg': 'field required', 'type': 'value_error.missing'}, {'loc': ('gt_int',), 'msg': 'ensure this value is greater than 42', 'type': 'value_error.number.not_gt', 'ctx': {'limit_value': 42}}, {'loc': ('list_of_ints', 2), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}, {'loc': ('a_float',), 'msg': 'value is not a valid float', 'type': 'type_error.float'}, {'loc': ('recursive_model', 'lng'), 'msg': 'value is not a valid float', 'type': 'type_error.float'}]

==================

==== e.json() ====

[
  {
    "loc": [
      "is_required"
    ],
    "msg": "field required",
    "type": "value_error.missing"
  },
  {
    "loc": [
      "gt_int"
    ],
    "msg": "ensure this value is greater than 42",
    "type": "value_error.number.not_gt",
    "ctx": {
      "limit_value": 42
    }
  },
  {
    "loc": [
      "list_of_ints",
      2
    ],
    "msg": "value is not a valid integer",
    "type": "type_error.integer"
  },
  {
    "loc": [
      "a_float"
    ],
    "msg": "value is not a valid float",
    "type": "type_error.float"
  },
  {
    "loc": [
      "recursive_model",
      "lng"
    ],
    "msg": "value is not a valid float",
    "type": "type_error.float"
  }
]

==================

==== str(e) ======

5 validation errors for Model
is_required
  field required (type=value_error.missing)
gt_int
  ensure this value is greater than 42 (type=value_error.number.not_gt; limit_value=42)
list_of_ints -> 2
  value is not a valid integer (type=type_error.integer)
a_float
  value is not a valid float (type=type_error.float)
recursive_model -> lng
  value is not a valid float (type=type_error.float)

==================

パースを行うためのヘルパー関数

外部からのデータを pydantic のモデルにしたい場合、 パースを行うヘルパー関数が便利です。

ヘルパー関数には下記があります。

  • parse_obj : dict を引数としてモデルを作成します。
  • parse_raw : str や byte を引数としてモデルを作成します。
  • parse_file: File からモデルを作成します。Json などを読みたい場合に便利です。

詳しい動作はコード例をご覧ください。

import pickle
from datetime import datetime
from pathlib import Path

from pydantic import BaseModel, ValidationError


class User(BaseModel):
    id: int
    name = "John Doe"
    signup_ts: datetime = None

# parse_obj
m = User.parse_obj({"id": 123, "name": "James"})
print(m)

# parse_objでエラーの場合
try:
    User.parse_obj(["not", "a", "dict"])
except ValidationError as e:
    print(e)

# parse_raw (json)
m = User.parse_raw('{"id": 123, "name": "James"}')
print(m)

# parse_raw (pickle)
pickle_data = pickle.dumps(
    {"id": 123, "name": "James", "signup_ts": datetime(2017, 7, 14)}
)
m = User.parse_raw(pickle_data, content_type="application/pickle", allow_pickle=True)
print(m)

# parse_file
path = Path("data.json")
path.write_text('{"id": 123, "name": "James"}')
m = User.parse_file(path)
print(m)

上記を実行すると、結果は下記のようになります。

id=123 signup_ts=None name='James'
1 validation error for User
__root__
  User expected dict not list (type=type_error)
id=123 signup_ts=None name='James'
id=123 signup_ts=datetime.datetime(2017, 7, 14, 0, 0) name='James'
id=123 signup_ts=None name='James'

再帰モデルの作り方

モデルは入れ子にして定義することができます。

下記コード例では、Spam モデルに Foo モデルと Bar モデルのリストが含まれています。

from typing import List
from pydantic import BaseModel


class Foo(BaseModel):
    count: int
    size: float = None


class Bar(BaseModel):
    apple = "x"
    banana = "y"


class Spam(BaseModel):
    foo: Foo
    bars: List[Bar]


m = Spam(foo={"count": 4}, bars=[{"apple": "x1"}, {"apple": "x2"}])
print(m)
print(m.dict())

上記を実行すると、結果は下記のようになります。

oo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')]
{'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}

ジェネリックモデルの作り方

ジェネリック型のモデルも定義することができます。

ジェネリック型を扱いたい場合、参照先がfrom pydantic.generics import GenericModelになるので注意しましょう。

from typing import Generic, TypeVar, Optional
from pydantic.generics import GenericModel #参照先が異なるので注意

DataT = TypeVar("DataT")


class Response(GenericModel, Generic[DataT]):
    data: Optional[DataT]


print(Response[int](data=1))
print(Response[str](data="value"))
print(Response[float](data=3.14))
print(Response[list](data=[1, 2, 3]))
print(Response[str](data="value").dict())

上記を実行すると、結果は下記のようになります。

ata=1
data='value'
data=3.14
data=[1, 2, 3]
{'data': 'value'}

参考

info
備考

Hakky ではエンジニアを募集中です!まずは話してみたいなどでも構いませんので、ぜひお気軽に採用ページからお問い合わせくださいませ。

2025年06月02日に最終更新
読み込み中...