寺田 学
型安全とデータ構造
主題のPydanticの説明に入る前に、Pythonにおける型安全の考え方とデータ構造についておさらいしておきます。
型安全のための型ヒント
Pythonは動的型付け言語です。型を宣言せずにコーディングすることができますが、型ヒントを書くことで型安全にコーディングできます。最近のPythonコードには型ヒントが書かれていることが多くなっているかと思います。
本連載
- 2022年9月:Python最新バージョン対応!
より良い型ヒントの書き方 - 2024年11月:Python型ヒントの動向と新しい機能の紹介
私自身は型ヒントによって、より型を意識したコーディングスタイルに変化しています。旧来、関数やメソッドの引数や戻り値にタプルや辞書を使うことが多くありましたが、厳密に型を付けるために、TypedDictやdataclassを活用することが増えています。
型が厳密になっていることで、IDEなどでの補完が充実したり、エラーの早期発見につながることも多くあります。
データ構造
内部の状態を管理するには、リストと辞書を組み合わせることで多くのデータ構造を表すことが可能です。しかし、自由な構造で表現すると、前項で示した型安全なコーディングができません。さらに、関数の引数で受け取ったデータを毎回検証するという必要性が出てきます。
ここでは、以下のような支店名とスタッフを表すデータを考えていきます。
branch = {
"branch_name": "東京",
"staff": [
{"number": 1, "name": "佐藤", "is_leader": True, "leader_period": 3},
{"number": 5, "name": "田中"},
{"number": None, "name": "山本"},
],
}
注目すべきは、staffの中には各スタッフを表す辞書がリストの中に入っている点です。スタッフの辞書に、numberにNoneが入っていたり、is_キーやleader_キーがない場合があります。
このデータを受け取るときは、以下のように値を確認したり、キーが存在するかを確認したりする必要があります。
def get_user_attr(branch):
for staff in branch["staff"]:
number = staff["number"]
if number is None: # Noneの場合の条件を追加
continue
is_leader = staff.get("is_leader", False) # キーかあるかどうかを確認してデフォルト値を設定
...
このコードを型安全にするには、TypedDictを用いることができます。詳細は、先にも紹介した
しかし、TypedDictはデータの値やキーを保証していません。あくまでも、型ヒントのための宣言だからです。TypedDictで宣言していないキーを追加することや、違うデータ型を値として入れることもできます。
そこで、データ構造を明確にするための機能としてdataclassがあります。
先ほどのbranch辞書をdataclassで表現します。
from dataclasses import dataclass
@dataclass
class Staff:
"""スタッフ"""
number: int | None # 社員番号
name: str # 社員名
is_leader: bool = False # リーダー
leader_period: int = 0 # リーダー経験
@dataclass
class Branch:
"""支店"""
branch_name: str # 支店名
staff: list[Staff] # 社員リスト
このBranchクラスをインスタンス化して使うことで、データを使う時に属性の存在を確認する必要がなくなります。ただ、データの値については確認していません。インスタンス化する時に確認するか、dataclass内にバリデーションのコードを追記する必要があります。
このようなデータの値を検証するといった課題に対して、Pydanticを使うと簡潔にデータ検証機能が組み込めます。
Pydanticとは
Pydanticは、データ検証用のパッケージです。
- Pydantic
(PyPI) - Pydantic公式ドキュメント
- バージョン
(執筆時点:2025年10月20日):2. 12. 3 - 対応Pythonバージョン:3.
9から3. 14
PydanticはFastAPIのデータ検証で利用されていることで注目を浴びるようになりました。また、Django NinjaというDjango用のRESTフレームワークでも使われています。Pydanticは、これらのフレームワークから独立したPythonのパッケージですので、他のフレームワークで利用することや、独自のスクリプトなどでも利用可能です。
PydanticのBaseModelを継承したクラスを書くことで、データの検証されたオブジェクトを作ることができます。dataclassのように型ヒント付きのクラス属性を定義することで、インスタンス化する際にデータ検証が行われます。検証に失敗すると、ValidationErrorという専用のエラーが送出され、エラーオブジェクトの中にエラーの詳細が格納されています。
Pydanticは、独自の検証スクリプトを通さずにオブジェクトが安全に作れることが一番うれしいところです。オブジェクトが作られれば、適切なデータ及びデータ型となっていることが保証されるため、データ受け渡し時の条件分岐などによるハンドリングが不要になります。また、検証内容が、Pythonのコードとしてわかりやすく表現されることも良い点だと思います。
Pydanticの基本的な使い方
Pydanticを使うにはpipコマンドでインストールします。
$ pip install pydantic
先ほどのBranchクラスをPydanticに置き換えてみます。
from pydantic import BaseModel
class Staff(BaseModel):
"""スタッフ"""
number: int | None
name: str
is_leader: bool = False
leader_period: int = 0
class Branch(BaseModel):
"""支店"""
branch_name: str
staff: list[Staff]
dataclassとほぼ同じコードになります。違いはデコレータでの宣言ではなく、BaseModelを継承する点です。
ここからは、JSONファイルを入力に使いデータの検証を行います。データ構造の項で利用した、支店名とスタッフを表す辞書をJSON形式にしたものを準備します。このJSONファイルを読み込んで確認をしてみましょう。
{
"branch_name": "東京",
"staff": [
{ "number": 1, "name": "佐藤", "is_leader": true, "leader_period": 3 },
{ "number": 5, "name": "田中" },
{ "number": null, "name": "山本" }
]
}
JSONファイルを使ってBranchインスタンスを作り、生成されたオブジェクトをprint()関数で確認します。
import json
from model import Branch # 先ほど定義した Branch クラスをインポート
with open("branch.json", "r", encoding="utf-8") as f:
data = json.load(f)
branch = Branch(**data)
print(branch)
# branch_name='東京' staff=[
# Staff(number=1, name='佐藤', is_leader=True, leader_period=3),
# Staff(number=5, name='田中', is_leader=False, leader_period=0),
# Staff(number=None, name='山本', is_leader=False, leader_period=0)]
Branchクラスのインスタンスとしてbranchオブジェクトができたことがわかりました。
次に以下のように、想定しないデータ型や必要な属性がない場合の検証をしてみます。
{
"branch_name": 10,
"staff": [
{ "number": 1, "name": "佐藤", "is_leader": null, "leader_period": 3 },
{ "number": 5, "name": "田中" },
{ "name": "山本" }
]
}
このJSONファイルには3つの間違いがあります。
- branch_
nameが数値になっている - is_
leaderがnull(None)になっている - 3番目のstaffにnumberキーがない
import json
from pydantic import ValidationError
from model import Branch # 先ほど定義した Branch クラスをインポート
with open("branch-error.json", "r", encoding="utf-8") as f:
data = json.load(f)
try:
branch = Branch(**data)
except ValidationError as e:
print(e.errors())
PydanticのモデルであるBranchをインスタンス化する時に、データ検証が動作します。データ検証エラーになるとValidationErrorが送出されます。このエラーオブジェクトに.errors()メソッドがあり、メソッドを実行すると以下のようなリストの辞書が出力されます。
.errors()メソッドの結果は以下のようになりました。
[{'input': 10,
'loc': ('branch_name',),
'msg': 'Input should be a valid string',
'type': 'string_type',
'url': 'https://errors.pydantic.dev/2.12/v/string_type'},
{'input': None,
'loc': ('staff', 0, 'is_leader'),
'msg': 'Input should be a valid boolean',
'type': 'bool_type',
'url': 'https://errors.pydantic.dev/2.12/v/bool_type'},
{'input': {'name': '山本'},
'loc': ('staff', 2, 'number'),
'msg': 'Field required',
'type': 'missing',
'url': 'https://errors.pydantic.dev/2.12/v/missing'}]
3つのエラーが出ていることがわかりました。
1つ目は、'loc': ('branch_とありますので、branch_に関するエラーで、inputに10となっていて、msgにあるように文字列型が必要となっています。
2つ目は、'loc': ('staff', 0, 'is_となっていて、staff属性のインデックス0のis_に関するエラーで、inputがNoneであることがわかります。
3つ目も同様にloc、msg、inputを確認することで詳細がわかります。
このようにPydanticは全データを検証し、検証結果をわかりやすく出力してくれます。
データ形式をより具体的にする
PydanticのField()関数を使って属性値をより具体的に宣言し、データ検証することができます。
たとえば以下のようなことが可能です。
- 必須かどうか
- デフォルト値の指定
- 数値の範囲
- 属性のタイトルを付記
引き続きBranchクラスを変更していきます。今回の変更に合わせて、branch_を列挙型で候補を宣言しデータ検証をあわせて行います。
import enum
from pydantic import Field
class BRANCH_NAMES(str, enum.Enum): # 列挙型(enum)を使って宣言
"""支店名の列挙型"""
TOKYO = "東京"
OSAKA = "大阪"
FUKUOKA = "福岡"
SAPPORO = "札幌"
class Staff(BaseModel):
"""スタッフ"""
number: int | None = Field(..., title="社員番号")
name: str = Field(..., title="社員名")
is_leader: bool = Field(False, title="リーダー")
leader_period: int = Field(0, title="リーダー経験", ge=0, lt=20)
class Branch(BaseModel):
"""支店"""
branch_name: BRANCH_NAMES = Field(..., title="支店名")
staff: list[Staff] = Field([], title="社員リスト")
変更したコードについて説明します。
- Field()関数の第1引数はデフォルト値です。
...やFalse、0を指定しています。...は、エリプシスというPythonの省略を表す定数を指定しています。PydanticのField()関数にエリプシスを渡すことで必須を示します。- titleキーワード引数は、属性を表すタイトルです。
- ge、ltキーワード引数は、数値などの範囲を示すことができます。geは以上、ltは未満を示します。
- branch_
nameのデータ型は、strから列挙型で宣言した BRANCH_に変更しました。NAMES
続いて、以下のJSONファイルを検証してみます。
{
"branch_name": "広島",
"staff": [
{ "number": 1, "name": "佐藤", "is_leader": null, "leader_period": 35 },
{ "number": 5, "name": "田中" },
{ "name": "山本" }
]
}
import json
from pydantic import ValidationError
from model import Branch # 先ほど定義した Branch クラスをインポート
with open("branch-error2.json", "r", encoding="utf-8") as f:
data = json.load(f)
try:
branch = Branch(**data)
except ValidationError as e:
print(e.errors())
実行の結果は次のとおりです。
[{'ctx': {'expected': "'東京', '大阪', '福岡' or '札幌'"},
'input': '広島',
'loc': ('branch_name',),
'msg': "Input should be '東京', '大阪', '福岡' or '札幌'",
'type': 'enum',
'url': 'https://errors.pydantic.dev/2.12/v/enum'},
{'input': None,
'loc': ('staff', 0, 'is_leader'),
'msg': 'Input should be a valid boolean',
'type': 'bool_type',
'url': 'https://errors.pydantic.dev/2.12/v/bool_type'},
{'ctx': {'lt': 20},
'input': 35,
'loc': ('staff', 0, 'leader_period'),
'msg': 'Input should be less than 20',
'type': 'less_than',
'url': 'https://errors.pydantic.dev/2.12/v/less_than'},
{'input': {'name': '山本'},
'loc': ('staff', 2, 'number'),
'msg': 'Field required',
'type': 'missing',
'url': 'https://errors.pydantic.dev/2.12/v/missing'}]
エラーメッセージの確認方法は先ほどとほぼ同様です。今回はデータ検証でより詳細に確認している項目で、ctx
'loc': ('branch_は、列挙型で4つの文字列以外を受け付けないようにしたので、エラーになっていることがわかります。name',) 'loc': ('staff', 0, 'leader_は、0以上20未満と指定しているので、エラーとなっています。period')
詳細な検証とデータ変換
データの検証には、単一の属性に対してデータ型や値が条件にあっているのかをチェックするものと、複数の属性にまたがって条件をチェックするものがあります。
ここでは、開始時間
{
"branch_name": "東京",
"staff": [
{
"number": 1,
"name": "佐藤花子",
"is_leader": true,
"leader_period": 3,
"start_time": "9:00",
"end_time": "17:15"
},
{
"number": 5,
"name": "田中太郎",
"start_time": "12:00",
"end_time": "18:00"
},
{
"number": 8,
"name": "山本次郎",
"start_time": "8:00",
"end_time": "19:00"
}
]
}
検証の条件は以下のように設定します。
- 開始時間
(start_ time) と終了時間 (end_ time) のフォーマットは H:MMと:区切りの数値であること - それぞれの時間は30分単位とする
- 開始時間は8:00から11:00の間であること
- 終了時間は15:00から20:00の間であること
- 開始時間と終了時間の間隔は9時間以内であること
さらに、データ検証が通過した場合は、2つの時間をdatetime.オブジェクトに変換します。
これらの項目のうち、
単一の属性の検証とデータ変換
import datetime
from typing import Any
def _valid_half_time(v: Any) -> datetime.time:
"""30分単位の時間を検証する共通関数"""
if isinstance(v, datetime.time): # datetime.time型の場合
time_ = v # 変換せずにそのまま使う
elif isinstance(v, str): # 文字列型の場合
try:
h, m = v.split(":") # ':'で分割
except ValueError:
raise ValueError(f"{v}は ':' が1つで区切られていません")
try:
time_ = datetime.time(int(h), int(m)) # 24時間制の時間に変換
except ValueError:
raise ValueError(f"{v}を24時間制の時間に変換できません")
else:
raise ValueError(f"{v}のデータ型不正です: {type(v)}")
# 30分単位でない場合はエラー
if time_.minute not in (0, 30) or time_.second or time_.microsecond:
raise ValueError(f"{v}は30分単位の時間ではありません")
return time_
def valid_start_time(v: Any) -> datetime.time:
"""開始時間の範囲を検証する関数"""
time_ = _valid_half_time(v)
# 開始時間の範囲を検証
if not datetime.time(8, 0) <= time_ <= datetime.time(11, 0):
raise ValueError(f"{v}は指定された開始時間の範囲ではありません")
return time_
def valid_end_time(v: Any) -> datetime.time:
"""終了時間の範囲を検証する関数"""
time_ = _valid_half_time(v)
# 終了時間の範囲を検証
if not datetime.time(15, 0) <= time_ <= datetime.time(20, 0):
raise ValueError(f"{v}は指定された終了時間の範囲ではありません")
return time_
_valid_関数は、30分単位の時間であることを確認するための関数です。開始時間と終了時間が同じ条件で検証するために、共通で使う関数として宣言しています。
データを検証する際にどのようなデータ型が渡されてくるかわからないので、Anyとしてどのようなデータ型も受け入れるようにしています。JSONからデータが来る場合には文字列型を想定していますが、Python内部で利用する際にdatetime.型で渡ってくる場合も想定して、isinstanceでデータ型を確認して内部で挙動を変えています。また、strまたはdatetime.以外の場合にはValueErrorとしています。
valid_関数とvalid_関数は、許可されている時間の範囲が違うので個別に関数を宣言しています。
これらの関数を検証に使うために、start_とend_にAnnotatedで注釈を付けています。
from typing import Annotated
from pydantic import BeforeValidator
class Staff(BaseModel):
"""スタッフ"""
number: int | None = Field(..., title="社員番号")
name: str = Field(..., title="社員名")
is_leader: bool = Field(False, title="リーダー")
leader_period: int = Field(0, title="リーダー経験", ge=0, lt=20)
start_time: Annotated[datetime.time, BeforeValidator(valid_start_time)]
end_time: Annotated[datetime.time, BeforeValidator(valid_end_time)]
ここでは、Annotatedで、データ型と検証の方法を指定しました。
BeforeValidatorは、データ生成前に検証することを示しています。
単一属性に対する動作の確認のために、JSONファイルを検証します。
import json
from pydantic import ValidationError
from model import Branch # 先ほど定義した Branch クラスをインポート
with open("branch2.json", "r", encoding="utf-8") as f:
data = json.load(f)
try:
branch = Branch(**data)
except ValidationError as e:
print(e.errors())
[{'ctx': {'error': ValueError('17:15は30分単位の時間ではありません')},
'input': '17:15',
'loc': ('staff', 0, 'end_time'),
'msg': 'Value error, 17:15は30分単位の時間ではありません',
'type': 'value_error',
'url': 'https://errors.pydantic.dev/2.12/v/value_error'},
{'ctx': {'error': ValueError('12:00は指定された開始時間の範囲ではありません')},
'input': '12:00',
'loc': ('staff', 1, 'start_time'),
'msg': 'Value error, 12:00は指定された開始時間の範囲ではありません',
'type': 'value_error',
'url': 'https://errors.pydantic.dev/2.12/v/value_error'}]
17:15となっている部分が30分単位でないこと、start_
複数の属性にまたがる検証
ここからは、最後の条件である、start_
from typing import Self
from pydantic import model_validator
class Staff(BaseModel):
"""スタッフ"""
number: int | None = Field(..., title="社員番号")
name: str = Field(..., title="社員名")
is_leader: bool = Field(False, title="リーダー")
leader_period: int = Field(0, title="リーダー経験", ge=0, lt=20)
start_time: Annotated[datetime.time, BeforeValidator(valid_start_time)]
end_time: Annotated[datetime.time, BeforeValidator(valid_end_time)]
@model_validator(mode="after")
def check_duration(self) -> Self:
"""時間間隔を検証"""
today = datetime.datetime.today().date()
# 開始時間と終了時間を比較するために datetime.datetime型に変換
start_dt = datetime.datetime.combine(today, self.start_time)
end_dt = datetime.datetime.combine(today, self.end_time)
# 9時間を超えていないかを確認
if (end_dt - start_dt) > datetime.timedelta(hours=9):
raise ValueError("開始時間と終了時間が9時間を超えています")
return self # 検証に通過した場合は、インスタンスを返す
インスタンスメソッドcheck_を宣言します。複数の属性にまたがるデータ検証を行いたい場合は@model_デコレータを使います。インスタンスができた後にデータ検証が実行されるように、mode="after"と指定しています。
検証の結果は以下のようになります。
[{'ctx': {'error': ValueError('17:15は30分単位の時間ではありません')},
'input': '17:15',
'loc': ('staff', 0, 'end_time'),
'msg': 'Value error, 17:15は30分単位の時間ではありません',
'type': 'value_error',
'url': 'https://errors.pydantic.dev/2.12/v/value_error'},
{'ctx': {'error': ValueError('12:00は指定された開始時間の範囲ではありません')},
'input': '12:00',
'loc': ('staff', 1, 'start_time'),
'msg': 'Value error, 12:00は指定された開始時間の範囲ではありません',
'type': 'value_error',
'url': 'https://errors.pydantic.dev/2.12/v/value_error'},
{'ctx': {'error': ValueError('開始時間と終了時間が9時間を超えています')},
'input': {'end_time': '19:00',
'name': '山本次郎',
'number': 8,
'start_time': '8:00'},
'loc': ('staff', 2),
'msg': 'Value error, 開始時間と終了時間が9時間を超えています',
'type': 'value_error',
'url': 'https://errors.pydantic.dev/2.12/v/value_error'}]
9時間を超えるパターンの検証ができました。
単一属性に関する検証や複数の属性にまたがる検証ともに、Pythonのコードで書いていますので、アイデア次第では複雑な検証や外部の検証ツールを使うことも可能になります。
補足情報
JSONSchemaからの変換
既存の実装で、JSONSchemaを用いた検証を実施している場合があると思います。その際にPydanticでのデータ検証に置き換えたい場合に、サードパーティ製ライブラリでJSONSchemaをPydanticのコードに変換することができます。
datamodel-code-generatorを使うと、さまざまな入力に対してPydanticなどのモデルファイルを生成できます。このツールの作者は、PyCon JP 2025でPython3.
- PyPI:datamodel-code-generator -PyPI
- GitHub:datamodel-code-generator: Pydantic model and dataclasses.
dataclass generator for easy conversion of JSON, OpenAPI, JSON Schema, and YAML data sources. -GitHub
入力フォーマットは、OpenAPI 3、JSON Schema、JSON/
出力フォーマットは、pydantic.
すでに存在するJSONSchemaなどの仕様や検証ツールを、Pythonのコードに置き換えることができます。それにより、Pythonで仕様と実装の一元管理ができ、Pythonでコーディングする際にIDEでの補完が充実し、型ヒントの恩恵が得られるという大きなメリットが得られます。詳細は公式ドキュメントを確認してください。
TypedDict/dataclassとの使い分け
今回はPydanticを紹介しましたが、データ構造やデータモデルを作るときに、TypedDictやdataclassを用いる方法もあります。
TypedDictは、辞書を使ったデータ構造に型ヒントを追加するというアプローチになります。また、dataclassは型ヒントを使ったデータモデルを作るものです。
Pythonの標準に取り込まれている機能になりますが、どちらの方法もデータ検証をサポートしていません。また、TypedDictはあくまで型ヒントを付与しているだけなので、キーの存在が保証されているものではありません。dataclassは属性が決まっているので、TypedDictに比べると構造が明確になっています。
ここでは、筆者の使い分けの基準を示します。
- 辞書でデータ構造が存在している場合は、TypedDictで型ヒントを付与
- 改造やデータ構造が複雑になる場合は、dataclassへの置き換えを検討
- 新規にデータ構造を示すのであれば、dataclassでデータモデル化
- Pythonコードの内部で利用する場合は、dataclassを利用
- 外部
(Web APIなど) からのデータを使う場合は、データ検証を重視しPydanticを採用
まとめ
今回は、Pydanticを紹介しました。Pydanticはデータ検証を担うサードパーティ製ライブラリです。型ヒントを使い、明確なデータ構造を示すことができます。内部で自動的にデータ変換が行われたりと便利な機能が備わっています。
また、データの検証だけはなく、データの構造を明確にすることができることから、Pythonコードをスッキリとスマートに書くことができるライブラリであると考えています。
みなさんもデータ検証やデータ構造の見直しにPydanticを使ってみてください。
最後に、今回のコードすべてを掲載しておきます。
import datetime
import enum
from typing import Annotated, Any, Self
from pydantic import BaseModel, Field, BeforeValidator, model_validator
def _valid_half_time(v: Any) -> datetime.time:
"""30分単位の時間を検証する共通関数"""
if isinstance(v, datetime.time): # datetime.time型の場合
time_ = v # 変換せずにそのまま使う
elif isinstance(v, str): # 文字列型の場合
try:
h, m = v.split(":") # ':'で分割
except ValueError:
raise ValueError(f"{v}は ':' が1つで区切られていません")
try:
time_ = datetime.time(int(h), int(m)) # 24時間制の時間に変換
except ValueError:
raise ValueError(f"{v}を24時間制の時間に変換できません")
else:
raise ValueError(f"{v}のデータ型不正です: {type(v)}")
# 30分単位でない場合はエラー
if time_.minute not in (0, 30) or time_.second or time_.microsecond:
raise ValueError(f"{v}は30分単位の時間ではありません")
return time_
def valid_start_time(v: Any) -> datetime.time:
"""開始時間の範囲を検証する関数"""
time_ = _valid_half_time(v)
# 開始時間の範囲を検証
if not datetime.time(8, 0) <= time_ <= datetime.time(11, 0):
raise ValueError(f"{v}は指定された開始時間の範囲ではありません")
return time_
def valid_end_time(v: Any) -> datetime.time:
"""終了時間の範囲を検証する関数"""
time_ = _valid_half_time(v)
# 終了時間の範囲を検証
if not datetime.time(15, 0) <= time_ <= datetime.time(20, 0):
raise ValueError(f"{v}は指定された終了時間の範囲ではありません")
return time_
class BRANCH_NAMES(str, enum.Enum):
"""支店名の列挙型"""
TOKYO = "東京"
OSAKA = "大阪"
FUKUOKA = "福岡"
SAPPORO = "札幌"
class Staff(BaseModel):
"""スタッフ"""
number: int | None = Field(..., title="社員番号")
name: str = Field(..., title="社員名")
is_leader: bool = Field(False, title="リーダー")
leader_period: int = Field(0, title="リーダー経験", ge=0, lt=20)
start_time: Annotated[datetime.time, BeforeValidator(valid_start_time)]
end_time: Annotated[datetime.time, BeforeValidator(valid_end_time)]
@model_validator(mode="after")
def check_duration(self) -> Self:
"""時間間隔を検証"""
today = datetime.datetime.today().date()
# 開始時間と終了時間を比較するために datetime.datetime型に変換
start_dt = datetime.datetime.combine(today, self.start_time)
end_dt = datetime.datetime.combine(today, self.end_time)
# 9時間を超えていないかを確認
if (end_dt - start_dt) > datetime.timedelta(hours=9):
raise ValueError("開始時間と終了時間が9時間を超えています")
return self # 検証に通過した場合は、インスタンスを返す
class Branch(BaseModel):
"""支店"""
branch_name: BRANCH_NAMES = Field(..., title="支店名")
staff: list[Staff] = Field([], title="社員リスト")
if __name__ == "__main__":
import json
from pprint import pprint
from pydantic import ValidationError
with open("branch2.json", "r", encoding="utf-8") as f:
data = json.load(f)
try:
branch = Branch(**data)
except ValidationError as e:
pprint(e.errors())
