[pydantic V2] model_configという名前のフィールドを作るには?
背景
pydantic V1では、BaseModelを継承したモデルの各種設定のために Config
という名前の内部クラスを定義する仕様でした。これがV2では、model_config = pydantic.ConfigDict(...)
の要領でフィールドとして定義するように改められています(V1の方法もまだ使えますが非推奨)。
class MyModel(BaseModel):
name: str
model_config = ConfigDict(str_to_upper=True)
model = MyModel(name="Alice")
print(model.model_dump()) # {'name': 'ALICE'}
さてここで疑問です。model_config
という名前をフィールド名に使いたいときはどうすれば?
稀かもしれませんが、変え難いスキーマとしてどうしてもこの名前が使いたいと仮定します。
検証環境
pydantic==2.1.1
fastapi==0.100.1
先に答え
alias
class MyModel(BaseModel):
name: str
my_model_config: str = Field(alias="model_config")
dumpするときはby_alias=True
を指定します。model_dump_json
でも同様です。aliasした場合、初期化ではその別名の方で指定します。逆にdictからモデルを作る際は何も指定しなくてよいようです。
# モデル -> dict
model = MyModel(name="Alice", model_config="XXX")
print(model.model_dump(by_alias=True))) # {'name': 'ALICE', 'model_config': 'XXX' }
# dict -> モデル
d = {"name": "Alice", "model_config": "AAA"}
print(MyModel.model_validate(d)) # name='ALICE' my_model_config='AAA'
本件はpydanticの一種の予約語と被った場合でした。同様にPythonの予約語等(class, def等)と被る場合にもaliasは有効でしょう。
FastAPIでaliasを使う場合
pydanticと言えばFastAPIですが、by_alias=True
のように自分で指定しての変換は普通書きません。しかし心配無用で、自動的にaliasのほうを使ってくれるようです。
from fastapi import FastAPI
from pydantic import BaseModel, Field, ConfigDict
class MyModel(BaseModel):
name: str
my_model_config: str = Field(alias="model_config")
model_config = ConfigDict(str_to_upper=True)
app = FastAPI()
@app.post("/post")
def post(m: MyModel) -> MyModel:
return MyModel(name=m.name+"-san", model_config=m.my_model_config+"BBB")
> curl -X POST -H "Content-Type:application/json" -d "{""name"":""Bob"",""model_config"":""AAA""}" http://127.0.0.1:8000/post
{"name":"BOB-SAN","model_config":"AAABBB"}
ちなみに、逆にaliasを使いたくない場合はこちらが参考になります。
失敗の履歴
上の解に至るまでに色々試した結果を残しておきます。
pydanticの仕様など無視して普通に定義
スルーされます。
class MyModel(BaseModel):
name: str
model_config: str
model = MyModel(name="Alice", model_config="aaa")
print(model1.model_dump())
結果からはmodel_configは消えました。
{'name': 'Alice'}
model_configの中身を以下のように見てみると、結局dict(TypedDict)になっており、指定が効いていない(誤った型付けしただけ?)になっています。そもそもこの方法が仮に妥当でも、pydantic仕様の方のmodel_configを併せて使いたい場合に困りますので、やはりナシです。
print(model.model_config) # {}
print(type(model.model_config)) # <class 'dict'>
V1流にConfig内部クラス
やはりスルーされます。この場合はConfigの設定が勝ちます。
class MyModel(BaseModel):
name: str
model_config: str
class Config:
str_to_upper = True
model = MyModel(name="Alice", model_config="aaa")
print(model.model_dump())
print(model.model_config)
print(type(model.model_config))
Config内部クラスに定義しても model_config
フィールドに値が入るのですね。
{'name': 'Alice'}
{'str_to_upper': True}
<class 'dict'>
脱線: ConfigDictをmodel_config以外に使ってみる
本題とそれますが、以下のように model_config
というフィールド名以外でConfigDict
を使ってみると、盛大に怒られます。typoした結果こうなるケースがあり得そうですが、気づけるということで安心ですね。
class MyModel(BaseModel):
name: str
my_config: ConfigDict = ConfigDict(str_to_upper=True)
エラー出力の例
Traceback (most recent call last):
File "C:\Projects\Python\pydantic2_sample\main.py", line 5, in <module>
class MyModel(BaseModel):
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_model_construction.py", line 173, in __new__
complete_model_class(
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_model_construction.py", line 463, in complete_model_class
schema = cls.__get_pydantic_core_schema__(cls, handler)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\main.py", line 549, in __get_pydantic_core_schema__
return __handler(__source)
^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_schema_generation_shared.py", line 82, in __call__
schema = self._handler(__source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 426, in generate_schema
return self._generate_schema_for_type(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 458, in _generate_schema_for_type
schema = self._generate_schema(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 684, in _generate_schema
return self._model_schema(obj)
^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 528, in _model_schema
{k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 528, in <dictcomp>
{k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 848, in _generate_md_field_schema
common_field = self._common_field_schema(name, field_info, decorators)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 900, in _common_field_schema
schema = self._apply_annotations(
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 1586, in _apply_annotations
schema = get_inner_schema(source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_schema_generation_shared.py", line 82, in __call__
schema = self._handler(__source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 1550, in inner_handler
schema = self._generate_schema(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 689, in _generate_schema
return self.match_type(obj)
^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 737, in match_type
return self._typed_dict_schema(obj, None)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_generate_schema.py", line 1066, in _typed_dict_schema
for field_name, annotation in get_type_hints_infer_globalns(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Projects\Python\pydantic2_sample\.venv\Lib\site-packages\pydantic\_internal\_fields.py", line 50, in get_type_hints_infer_globalns
return get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python311\Lib\typing.py", line 2302, in get_type_hints
value = _eval_type(value, base_globals, base_locals)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python311\Lib\typing.py", line 359, in _eval_type
return t._evaluate(globalns, localns, recursive_guard)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python311\Lib\typing.py", line 854, in _evaluate
eval(self.__forward_code__, globalns, localns),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 1, in <module>
NameError: name '_GenerateSchema' is not defined
Discussion