åãã³ãã掻çšããå ç¢ãªããŒã¿åŠçãšéçºå¹çåãå®çŸ
ã¯ããã«ïŒPydanticãšã¯ïŒ ð€
Pydanticã¯ãPythonã®åãã³ãïŒType HintsïŒã掻çšããŠããŒã¿ã®ããªããŒã·ã§ã³ïŒæ€èšŒïŒãšã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ïŒå€æïŒããããŠèšå®ç®¡çãç°¡åãã€åŒ·åã«è¡ãããã®ã©ã€ãã©ãªã§ããPythonã¯åçåä»ãèšèªã§ããããã®æè»æ§ãé åã§ãããäžæ¹ã§å®è¡æãŸã§åã®äžæŽåãããŒã¿ã®æ§é çãªèª€ãã«æ°ã¥ãã«ãããšããåŽé¢ããããŸããPydanticã¯ããã®åé¡ã解決ããéçºåæ段éã§ãšã©ãŒãçºèŠããããããããšã§ãã³ãŒãã®å ç¢æ§ãå¯èªæ§ãä¿å®æ§ãå€§å¹ ã«åäžãããŸãã
ç¹ã«APIéçºïŒFastAPIãªã©ã®ãã¬ãŒã ã¯ãŒã¯ã§åºãæ¡çšãããŠããŸãïŒãèšå®ãã¡ã€ã«ã®èªã¿èŸŒã¿ãããŒã¿åŠçãã€ãã©ã€ã³ãªã©ãå€éšããã®ããŒã¿ãæ§é åããŒã¿ãæ±ãå€ãã®å Žé¢ã§ãã®ç䟡ãçºæ®ããŸããåãã³ãã«åºã¥ããŠèªåçã«ããŒã¿ã®æ€èšŒãåå€æãè¡ã£ãŠããããããåé·ãªæ€èšŒã³ãŒããæžãæéãçããéçºè ã¯ããžãã¹ããžãã¯ã®å®è£ ã«éäžã§ããŸããâš
- ð éçºå¹çã®åäžïŒ åãã³ãã«ããæ確ãªå®çŸ©ãšèªåããªããŒã·ã§ã³ã§ããã€ã©ãŒãã¬ãŒãã³ãŒããåæžã
- ð¡ïž å ç¢æ§ã®åäžïŒ å®è¡æã®ããŒã¿ãšã©ãŒãæªç¶ã«é²ãããã°ã®å°ãªãå®å®ããã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ã
- ð å¯èªæ§ãšä¿å®æ§ã®åäžïŒ ããŒã¿æ§é ãã³ãŒãäžã§æ確ã«ãªããç解ããããã¡ã³ããã³ã¹ããããã³ãŒãã«ã
- ð ç°¡åãªããŒã¿å€æïŒ Pythonãªããžã§ã¯ããšJSON/èŸæžåœ¢åŒãªã©ãšã®çžäºå€æïŒã·ãªã¢ã©ã€ãº/ãã·ãªã¢ã©ã€ãºïŒã容æã
- ð€ ãšã³ã·ã¹ãã ã®çµ±åïŒ FastAPIãDjango NinjaãSQLAlchemyãªã©ãå€ãã®äººæ°ã©ã€ãã©ãªãšã¹ã ãŒãºã«é£æºã
Pydanticã®ã€ã³ã¹ããŒã« ð»
Pydanticã®å©çšãéå§ããã«ã¯ããŸãpipã䜿ã£ãŠã€ã³ã¹ããŒã«ããŸããPydantic V2ããã¯èšå®ç®¡çæ©èœïŒBaseSettings
ïŒãå¥ããã±ãŒãž pydantic-settings
ã«åé¢ãããŸããã®ã§ãå¿
èŠã«å¿ããŠãã¡ããã€ã³ã¹ããŒã«ããŸãã
pip install pydantic pydantic-settings
ç¹å®ã®æ©èœïŒã¡ãŒã«ã¢ãã¬ã¹æ€èšŒãªã©ïŒãå©çšããå Žåã¯ãè¿œå ã®äŸåé¢ä¿ãå¿ èŠã«ãªãããšããããŸãã
pip install pydantic[email] # EmailStr ã䜿ãå Žå
åºæ¬çãªäœ¿ãæ¹ïŒã¢ãã«ã®å®çŸ©ãšããªããŒã·ã§ã³ ð§±
Pydanticã®æãåºæ¬çãªäœ¿ãæ¹ã¯ãpydantic.BaseModel
ãç¶æ¿ããŠããŒã¿ã¢ãã«ã¯ã©ã¹ãå®çŸ©ããããšã§ããã¯ã©ã¹ã®å±æ§ãšããŠãã£ãŒã«ãåãå®çŸ©ããPythonã®åãã³ãã§æåŸ
ããããŒã¿åãæå®ããŸãã
from pydantic import BaseModel, EmailStr, ValidationError
from typing import List, Optional
from datetime import date
class Address(BaseModel):
street_address: str
postal_code: str
city: str
country: str = 'Japan' # ããã©ã«ãå€ã®èšå®
class User(BaseModel):
id: int
name: str
signup_ts: Optional[date] = None # ãªãã·ã§ãã«ãªãã£ãŒã«ã (None蚱容)
email: EmailStr # PydanticãæäŸããEmail圢åŒããªããŒã·ã§ã³ä»ãã®å
friends: List[int] = [] # ãªã¹ãåãããã©ã«ãã¯ç©ºãªã¹ã
address: Optional[Address] = None # ãã¹ããããã¢ãã« (ãªãã·ã§ãã«)
# ããŒã¿ãçšæ (èŸæžåœ¢åŒ)
user_data = {
'id': 123,
'name': 'Taro Yamada',
'signup_ts': '2024-01-15', # æååã§ãdateåã«å€æããã
'email': 'taro.yamada@example.com',
'friends': [1, 2, '3'], # '3' 㯠int ã«å€æããã
'address': {
'street_address': '1-2-3 Example St',
'postal_code': '100-0000',
'city': 'Tokyo'
# countryã¯ããã©ã«ãå€ 'Japan' ã䜿ããã
}
}
try:
# ã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãäœæ (ããã§ããªããŒã·ã§ã³ãå®è¡ããã)
user = User(**user_data)
print("ããªããŒã·ã§ã³æåïŒð")
print(user)
# ãã¹ããããã¢ãã«ã Pydantic ãªããžã§ã¯ãã«ãªã£ãŠãã
if user.address:
print(f"éœåž: {user.address.city}, åœ: {user.address.country}")
except ValidationError as e:
print("ããªããŒã·ã§ã³ãšã©ãŒãçºçããŸãã ð¥")
print(e)
# äžæ£ãªããŒã¿ã®äŸ
invalid_data = {
'id': 'not_an_integer', # åãéã
'name': 'Jiro Sato',
'email': 'jiro.sato@invalid', # Email圢åŒãäžæ£
'friends': ['a', 'b'] # intã«å€æã§ããªãèŠçŽ
}
try:
invalid_user = User(**invalid_data)
except ValidationError as e:
print("\näžæ£ãªããŒã¿ã§ã®ããªããŒã·ã§ã³ãšã©ãŒïŒ")
# ãšã©ãŒã®è©³çŽ°ãåºåããã
print(e.json(indent=2)) # JSON圢åŒã§ãšã©ãŒå
容ã確èªã§ãã
äžèšã®äŸã§ã¯ãUser
ã¢ãã«ãšããã«ãã¹ãããã Address
ã¢ãã«ãå®çŸ©ããŠããŸããUser
ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ãäœæããéã«ãuser_data
(èŸæž) ãã¢ã³ããã¯ããŠæž¡ããŠããŸãããã®ç¬éã«PydanticãããªããŒã·ã§ã³ãå®è¡ããŸãã
id
: æŽæ° (int
) ã§ããå¿ èŠããããŸããname
: æåå (str
) ã§ããå¿ èŠããããŸããsignup_ts
: æ¥ä»å (date
) ã§ããå¿ èŠããããŸããããªãã·ã§ãã« (Optional
) ãªã®ã§None
ã蚱容ãããŸããPydantic㯠âYYYY-MM-DDâ 圢åŒã®æååãèªåçã«date
ãªããžã§ã¯ãã«å€æããããšè©Šã¿ãŸããemail
:EmailStr
åã«ãããæå¹ãªã¡ãŒã«ã¢ãã¬ã¹åœ¢åŒãã©ããããã§ãã¯ãããŸããfriends
: æŽæ°ã®ãªã¹ã (List[int]
) ã§ããå¿ èŠããããŸããèŠçŽ ãæååã® â3â ã§ãã£ãŠããæŽæ°ã«å€æå¯èœã§ããã°èªåçã«å€æãããŸããããã©ã«ãå€ãšããŠç©ºãªã¹ã[]
ãèšå®ãããŠããŸããaddress
:Address
ã¢ãã«ã®ã€ã³ã¹ã¿ã³ã¹ããŸãã¯None
ã§ããå¿ èŠããããŸã (Optional[Address]
)ãcountry
(Addressã¢ãã«å ): ããã©ã«ãå€ âJapanâ ãèšå®ãããŠãããããããŒã¿ã«å«ãŸããŠããªããŠãèªåçã«è£å®ãããŸãã
äžæ£ãªããŒã¿ (invalid_data
) ãæž¡ããšãValidationError
ãçºçããã©ã®ãã£ãŒã«ãã§ã©ã®ãããªãšã©ãŒãèµ·ãããã®è©³çŽ°ãªæ
å ±ãå«ãŸããŸããe.json()
ã䜿ããšããšã©ãŒæ
å ±ãJSON圢åŒã§ååŸã§ãããããã°ããšã©ãŒã¬ã¹ãã³ã¹ã®çæã«äŸ¿å©ã§ãã
Pydantic V2ïŒRustã«ããé«éåãšæ°æ©èœ ð
2023幎äžé ã«ãªãªãŒã¹ãããPydantic V2ã¯ãã³ã¢éšåãRustèšèªã§æžãçŽãããããšã«ãããV1ãšæ¯èŒããŠ5åãã50åã®å€§å¹ ãªããã©ãŒãã³ã¹åäžãå®çŸããŸãããããã«ããã倧éã®ããŒã¿ãæ±ãã¢ããªã±ãŒã·ã§ã³ãããã©ãŒãã³ã¹ãéèŠãããAPIã§ã®å©çšãããã«å¿«é©ã«ãªããŸããã
V2ã§ã¯ããã©ãŒãã³ã¹åäžã ãã§ãªããå€ãã®æ°æ©èœãæ¹åãå°å ¥ãããŠããŸãã
- ð¥ Rustã³ã¢ã«ããé«éå (pydantic-core): ããªããŒã·ã§ã³ãšã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ã®é床ãåçã«åäžããŸããã
- ð Strict Mode: ããå³æ Œãªåãã§ãã¯ãå¯èœã«ãªããŸãã (äŸ:
int
åãã£ãŒã«ãã«æååã®'123'
ãæž¡ããšããã©ã«ãã§ã¯å€æãããŸãããStrict Modeã§ã¯ãšã©ãŒã«ãªããŸã)ã - ð·ïž
Annotated
ã«ããããªããŒã·ã§ã³ãšã¡ã¿ããŒã¿: Python 3.9以éã®typing.Annotated
ãå©çšããŠããã£ãŒã«ãå®çŸ©ãšããªããŒã·ã§ã³ã«ãŒã«ãã¡ã¿ããŒã¿ïŒäŸ: Fieldã®èª¬æããšã€ãªã¢ã¹ïŒããã宣èšçã«èšè¿°ã§ããããã«ãªããŸããã - ð é¢æ°ã®åŒæ°ããªããŒã·ã§ã³ (
@validate_call
): BaseModel ã䜿ããã«ãé¢æ°ã®åŒæ°ãè¿ãå€ãçŽæ¥ããªããŒã·ã§ã³ã§ããããã«ãªããŸããã - ð¯
TypeAdapter
: BaseModel ãå®çŸ©ããã«ãä»»æã®åã«å¯ŸããŠããªããŒã·ã§ã³ãã·ãªã¢ã©ã€ãºãè¡ããããã«ãªããŸããã - ð 匷åãªãšã€ãªã¢ã¹æ©èœ (
validation_alias
,serialization_alias
): ããªããŒã·ã§ã³æãšã·ãªã¢ã©ã€ãºæã§ç°ãªããã£ãŒã«ãåïŒãšã€ãªã¢ã¹ïŒãæå®å¯èœã«ãªããŸãããJSONã®ããŒåãšPythonã®å€æ°åãæè»ã«å¯Ÿå¿ä»ããããŸãã - ð JSON ã¹ããŒãçæã®æ¹å: çæãããJSONã¹ããŒããããæšæºã«æºæ ããã«ã¹ã¿ãã€ãºæ§ãåäžããŸãããå ¥åçšãšåºåçšã§ç°ãªãã¹ããŒããçæããããšãå¯èœã§ãã
- ð§ ããªããŒã¿ãŒ/ã·ãªã¢ã©ã€ã¶ãŒã®æ¹å:
@validator
ã¯@field_validator
ã«ã@root_validator
ã¯@model_validator
ã«å称å€æŽã»æ©èœæ¹åãããŸããããŸããã·ãªã¢ã©ã€ãºå°çšã®@field_serializer
ãè¿œå ãããŸããã - 𧹠åå空éã®ã¯ãªãŒã³ã¢ãã: ã¢ãã«ã¯ã©ã¹ã®ã¡ãœããåãªã©ãšãã£ãŒã«ãåã®è¡çªãèµ·ããã«ãããªããŸããã
- â ïž Deprecated Fields: ãã£ãŒã«ããéæšå¥šãšããŠããŒã¯ããã¢ã¯ã»ã¹æã«èŠåãåºãæ©èœãè¿œå ãããŸãã (Pydantic v2.7以é)ã
ãããã®å€æŽã«ãããPydanticã¯ããã«åŒ·åã§äœ¿ããããã©ã€ãã©ãªãžãšé²åããŸãããV1ããã®ç§»è¡ã«ã¯ããã€ãã®å€æŽãå¿
èŠã§ãããå
¬åŒããã¥ã¡ã³ãã«ã¯è©³çŽ°ãªç§»è¡ã¬ã€ããçšæãããŠããã移è¡ãæ¯æŽããããŒã« bump-pydantic
ãæäŸãããŠããŸãã
äž»èŠãªæ©èœã®è©³çŽ°è§£èª¬ ð§
1. ããŒã¿ããªããŒã·ã§ã³
Pydanticã®æ žãšãªãæ©èœã§ããåãã³ãã«åºã¥ãåºæ¬çãªåãã§ãã¯ã«å ãããã詳现ãªå¶çŽãèšå®ã§ããŸãã
çµã¿èŸŒã¿ããªããŒã·ã§ã³
Field
é¢æ°ã Annotated
ã䜿ã£ãŠãæ°å€ã®ç¯å²ãæååã®é·ããæ£èŠè¡šçŸãã¿ãŒã³ãªã©ãæå®ã§ããŸãã
from pydantic import BaseModel, Field, ValidationError
from typing import Annotated
import re
class Item(BaseModel):
name: Annotated[str, Field(min_length=3, max_length=50, pattern=r'^[a-zA-Z0-9_]+$')]
price: Annotated[float, Field(gt=0, le=100000)] # 0ãã倧ããã100000以äž
tax: Optional[Annotated[float, Field(ge=0, lt=1)]] = None # 0以äžã1æªæº (ãªãã·ã§ãã«)
tags: Annotated[List[str], Field(min_length=1, max_length=5)] # 1ã€ä»¥äžã5ã€ä»¥äžã®èŠçŽ ãæã€ãªã¹ã
try:
item1 = Item(name='item_123', price=99.99, tags=['electronics', 'gadget'])
print("Item1:", item1)
# item2 = Item(name='it', price=-10, tags=[]) # ããã¯ãšã©ãŒã«ãªã
# print(item2)
except ValidationError as e:
print("\nItem validation error:")
print(e.json(indent=2))
Annotated[å, Field(...)]
ã®åœ¢åŒã§ãåæ
å ±ãšå¶çŽãäžç·ã«èšè¿°ããŸããgt
(ãã倧ãã), ge
(以äž), lt
(ããå°ãã), le
(以äž), min_length
, max_length
, pattern
ãªã©ãæ§ã
ãªå¶çŽãå©çšå¯èœã§ãã
ã«ã¹ã¿ã ããªããŒã·ã§ã³
ç¬èªã®è€éãªæ€èšŒããžãã¯ãå¿
èŠãªå Žåã¯ã@field_validator
ãã³ã¬ãŒã¿ã䜿ã£ãŠã«ã¹ã¿ã ããªããŒã·ã§ã³é¢æ°ãå®çŸ©ããŸãã
from pydantic import BaseModel, field_validator, ValidationError
class Order(BaseModel):
item_id: int
quantity: int
discount_code: Optional[str] = None
@field_validator('quantity')
@classmethod
def quantity_must_be_positive(cls, value: int) -> int:
if value <= 0:
raise ValueError('Quantity must be positive')
return value
@field_validator('discount_code')
@classmethod
def discount_code_format(cls, value: Optional[str]) -> Optional[str]:
if value is None:
return None # Noneã®å Žåã¯OK
if not re.match(r'^[A-Z]{4}\d{4}$', value):
raise ValueError('Discount code must be 4 uppercase letters followed by 4 digits')
return value
try:
order1 = Order(item_id=101, quantity=5, discount_code='SAVE1234')
print("Order1:", order1)
# order2 = Order(item_id=102, quantity=-1) # quantity ãšã©ãŒ
# order3 = Order(item_id=103, quantity=2, discount_code='invalid-code') # discount_code ãšã©ãŒ
except ValidationError as e:
print("\nOrder validation error:")
print(e.json(indent=2))
@field_validator('ãã£ãŒã«ãå')
ã§å¯Ÿè±¡ãã£ãŒã«ããæå®ããã¯ã©ã¹ã¡ãœãããšããŠããªããŒã·ã§ã³ããžãã¯ãå®è£
ããŸããå€ãäžæ£ãªå Žå㯠ValueError
(ãŸã㯠AssertionError
) ãçºçãããŸãã
ã¢ãã«å šäœã®ããªããŒã·ã§ã³
è€æ°ã®ãã£ãŒã«ãéã®é¢ä¿æ§ãæ€èšŒãããå Žåã¯ã@model_validator
ã䜿çšããŸãã
from pydantic import BaseModel, model_validator, ValidationError
from datetime import date
class Event(BaseModel):
start_date: date
end_date: date
@model_validator(mode='after') # mode='after' ã¯åã
ã®ãã£ãŒã«ãããªããŒã·ã§ã³åŸã«å®è¡
def check_dates(self) -> 'Event':
if self.start_date > self.end_date:
raise ValueError('End date must be after start date')
return self
try:
event1 = Event(start_date='2024-05-01', end_date='2024-05-10')
print("Event1:", event1)
# event2 = Event(start_date='2024-05-10', end_date='2024-05-01') # ããã¯ãšã©ãŒ
except ValidationError as e:
print("\nEvent validation error:")
print(e.json(indent=2))
@model_validator
ã¯ã¢ãã«å
šäœã®ã€ã³ã¹ã¿ã³ã¹ (self
) ã«ã¢ã¯ã»ã¹ã§ããããããã£ãŒã«ãéã®æŽåæ§ãã§ãã¯ã«é©ããŠããŸãã
2. ããŒã¿ã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ãšãã·ãªã¢ã©ã€ãŒãŒã·ã§ã³
Pydanticã¢ãã«ã¯ãPythonãªããžã§ã¯ããšä»ã®ããŒã¿åœ¢åŒïŒäž»ã«èŸæžãJSONïŒãšã®çžäºå€æã容æã«ããŸãã
- ãã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ (å
¥åããŒã¿ã®å€æã»æ€èšŒ):
MyModel(**data_dict)
: èŸæžããã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãäœæ (åæåæã«å®è¡)ãMyModel.model_validate(data_dict)
: V2 ã§æšå¥šããããèŸæžããã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãäœæã»æ€èšŒããã¡ãœãããMyModel.model_validate_json(json_string)
: JSONæååããçŽæ¥ã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãäœæã»æ€èšŒããã¡ãœããã
- ã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ (åºåããŒã¿ã®å€æ):
model_instance.model_dump()
: ã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãèŸæžã«å€æããã¡ãœãã (V2 ã§.dict()
ããå€æŽ)ãexclude
,include
,by_alias
ãªã©ã®åŒæ°ã§åºåãå¶åŸ¡ã§ããŸããmodel_instance.model_dump_json()
: ã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãJSONæååã«å€æããã¡ãœãã (V2 ã§.json()
ããå€æŽ)ãåŒæ°ã¯model_dump()
ãšåæ§ã
from pydantic import BaseModel, Field
from datetime import datetime
class Task(BaseModel):
task_id: int = Field(alias='taskId') # JSONã§ã¯ 'taskId' ãšããããŒåã䜿ã
description: str
completed: bool = False
created_at: datetime = Field(default_factory=datetime.now)
# JSONæååãããã·ãªã¢ã©ã€ãº
json_data = '{"taskId": 1, "description": "Buy groceries", "completed": true}'
task1 = Task.model_validate_json(json_data)
print("Deserialized Task:", task1)
print("Created At:", task1.created_at) # default_factoryã§çŸåšæå»ãèšå®ããã
# Pythonãªããžã§ã¯ããèŸæžã«ã·ãªã¢ã©ã€ãº (ãšã€ãªã¢ã¹ã䜿çš)
task_dict = task1.model_dump(by_alias=True, exclude={'created_at'}) # by_alias=Trueã§'taskId'ã䜿çš, created_atãé€å€
print("Serialized Dict (by alias):", task_dict)
# Pythonãªããžã§ã¯ããJSONæååã«ã·ãªã¢ã©ã€ãº (ã€ã³ãã³ãä»ã)
task_json = task1.model_dump_json(indent=2, by_alias=True)
print("Serialized JSON:\n", task_json)
Field(alias='...')
ã䜿ãããšã§ãPythonã³ãŒãäžã®å±æ§åãšå€éšããŒã¿ïŒJSONãªã©ïŒã®ããŒåããããã³ã°ã§ããŸããmodel_dump()
ã model_dump_json()
ã® by_alias=True
åŒæ°ã§ãã·ãªã¢ã©ã€ãºæã«ãšã€ãªã¢ã¹åã䜿çšãããã©ãããæå®ããŸãã
3. èšå®ç®¡ç (pydantic-settings
)
ç°å¢å€æ°ã .env
ãã¡ã€ã«ãèšå®ãã¡ã€ã«ïŒYAMLãªã©ïŒããèšå®å€ãèªã¿èŸŒã¿ãåæ€èšŒãè¡ãããã®æ©èœã§ããV2ãã㯠pydantic-settings
ããã±ãŒãžãšããŠæäŸãããŠããŸããBaseSettings
ã¯ã©ã¹ãç¶æ¿ããŠèšå®ã¢ãã«ãå®çŸ©ããŸãã
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import SecretStr, PostgresDsn
from typing import List
# .env ãã¡ã€ã«ã®å
容 (äŸ)
# APP_ENV=production
# APP_DEBUG=false
# APP_DATABASE_URL=postgresql+psycopg2://user:secret_password@db.example.com:5432/prod_db
# APP_ALLOWED_HOSTS='["api.example.com", "admin.example.com"]'
class AppSettings(BaseSettings):
# model_configã§äœ¿çšããèšå® (V2ã®æžãæ¹)
model_config = SettingsConfigDict(
env_prefix='APP_', # ç°å¢å€æ°åã®ãã¬ãã£ãã¯ã¹
env_file='.env', # èªã¿èŸŒã .env ãã¡ã€ã«
env_file_encoding='utf-8',
case_sensitive=False, # ç°å¢å€æ°åã®å€§æåå°æåãåºå¥ããªã
extra='ignore' # ã¢ãã«ã«å®çŸ©ãããŠããªãç°å¢å€æ°ã¯ç¡èŠ
)
env: str = 'development' # ããã©ã«ãå€
debug: bool = False
database_url: PostgresDsn # Postgresã®æ¥ç¶URL圢åŒãæ€èšŒ
secret_key: SecretStr # 衚瀺æã«ãã¹ãã³ã°ãããæ©å¯æ
å ±
allowed_hosts: List[str] = ['localhost', '127.0.0.1']
# ç°å¢å€æ°ã .env ãã¡ã€ã«ããèšå®ãèªã¿èŸŒã
# (å®è¡åã«ç°å¢å€æ°ã .env ãã¡ã€ã«ãèšå®ããŠããå¿
èŠããããŸã)
try:
settings = AppSettings()
print(f"Environment: {settings.env}")
print(f"Debug Mode: {settings.debug}")
print(f"Database URL: {settings.database_url}")
# SecretStr ã¯è¡šç€ºæã«ãã¹ãã³ã°ããã
print(f"Secret Key: {settings.secret_key}")
# get_secret_value()ã§å®éã®å€ãååŸ
print(f"Secret Key (value): {settings.secret_key.get_secret_value()}")
print(f"Allowed Hosts: {settings.allowed_hosts}")
except ValidationError as e:
print("\nSettings validation error:")
print(e.json(indent=2))
# ã³ãŒãå
ã§èšå®ãäžæžãããããšãå¯èœ (ãã¹ããªã©ã§äŸ¿å©)
test_settings = AppSettings(debug=True, _env_file=None) # _env_file=None㧠.env ãèªã¿èŸŒãŸãªã
print(f"\nTest Debug Mode: {test_settings.debug}")
BaseSettings
ãç¶æ¿ããã¯ã©ã¹ã¯ãåæåæã«ãã£ãŒã«ãã«å¯Ÿå¿ããç°å¢å€æ°ãèªåçã«æ¢ãã«è¡ããŸããmodel_config
å±æ§ïŒV2ïŒãŸãã¯å
éšã¯ã©ã¹ Config
ïŒV1ïŒã§ãç°å¢å€æ°åã®ãã¬ãã£ãã¯ã¹ (env_prefix
)ã.env
ãã¡ã€ã«ã®ãã¹ (env_file
)ã倧æåå°æåã®åºå¥ (case_sensitive
) ãªã©ãèšå®ã§ããŸãã
PostgresDsn
ã RedisDsn
ãªã©ã®å°çšåããæ©å¯æ
å ±ãå®å
šã«æ±ãããã® SecretStr
ãªã©ã䟿å©ãªåãæäŸãããŠããŸããç°å¢å€æ°ã®å€ã¯ãåãã³ãã«åºã¥ããŠèªåçã«é©åãªåïŒbool
, int
, List[str]
ãªã©ïŒã«ããŒã¹ãããŸããJSON圢åŒã®æååããªã¹ããèŸæžã«å€æãããŸãã
å¿çšçãªæ©èœãšãã¹ããã©ã¯ãã£ã¹ âš
ãã¹ããããã¢ãã«ãšåæ¹åç §
ã¢ãã«ã®äžã«ä»ã®ã¢ãã«ããã¹ããããããšã§ãè€éãªããŒã¿æ§é ãè¡šçŸã§ããŸããèªå·±åç §ã®ãããªååž°çãªæ§é ãå¯èœã§ãã
from pydantic import BaseModel
from typing import List, Optional
class Employee(BaseModel):
id: int
name: str
manager: Optional['Employee'] = None # èªåèªèº«ã®åãåç
§ (åæ¹åç
§)
subordinates: List['Employee'] = []
# åæ¹åç
§ã解決ããããã«å¿
èŠ (ã¯ã©ã¹å®çŸ©åŸ)
# Pydantic V2 ã§ã¯èªå解決ãããå Žåãå€ãããæ瀺çã«åŒã¶ã®ãå®å
šãªå Žåããã
# Employee.model_rebuild() # V2ã§ã®æšå¥šã¡ãœãã
# ããŒã¿äŸ
employee_data = {
"id": 1,
"name": "CEO",
"subordinates": [
{
"id": 2,
"name": "VP Engineering",
"subordinates": [
{"id": 3, "name": "Lead Developer", "subordinates": []}
]
},
{"id": 4, "name": "VP Marketing", "subordinates": []}
]
}
# ãããŒãžã£ãŒæ
å ±ãè¿œå (IDã§åç
§)
def link_managers(emp_dict, manager=None):
emp_dict['manager'] = manager
new_subs = []
for sub_dict in emp_dict.get('subordinates', []):
new_subs.append(link_managers(sub_dict, emp_dict['id'])) # ããã§ã¯IDã§æž¡ãäŸ
emp_dict['subordinates'] = new_subs
return emp_dict
# linked_data = link_managers(employee_data.copy()) # æ¬æ¥ã¯ manager ãªããžã§ã¯ããæž¡ããããã§ã¯çç¥
ceo = Employee.model_validate(employee_data)
print("CEO:", ceo.name)
print("VP Engineering:", ceo.subordinates[0].name)
# print("VP Engineering's Manager ID:", ceo.subordinates[0].manager) # Noneã®ã¯ã
print("Lead Developer:", ceo.subordinates[0].subordinates[0].name)
# print("Lead Developer's Manager ID:", ceo.subordinates[0].subordinates[0].manager) # ID 2 ã®ã¯ã
èªåèªèº«ã®ã¯ã©ã¹ãåãã³ãã§äœ¿ãå ŽåïŒäŸ: Optional['Employee']
ïŒãæååãšããŠèšè¿°ãããåæ¹åç
§ããçšããŸããPydantic V2ã§ã¯å€ãã®å Žåèªåã§è§£æ±ºãããŸãããè€éãªã±ãŒã¹ã§ã¯ã¢ãã«å®çŸ©åŸã« Model.model_rebuild()
ãåŒã³åºãå¿
èŠããããããããŸããã(以åã® update_forward_refs()
ã¯éæšå¥š)
Generic ã¢ãã«
Pythonã®ãžã§ããªã¯ã¹ (typing.Generic
) ãšçµã¿åãããŠãæ±çšçãªããŒã¿æ§é ã¢ãã«ãäœæã§ããŸãã
from pydantic import BaseModel, Field
from typing import TypeVar, Generic, List, Optional
DataType = TypeVar('DataType')
class PaginatedResponse(BaseModel, Generic[DataType]):
page: int = Field(..., gt=0)
per_page: int = Field(..., gt=0, le=100)
total_items: int = Field(..., ge=0)
total_pages: int = Field(..., ge=0)
items: List[DataType]
class Product(BaseModel):
id: int
name: str
price: float
# Productãªã¹ããå«ãPaginatedResponseãå®çŸ©
product_response_data = {
"page": 1,
"per_page": 10,
"total_items": 55,
"total_pages": 6,
"items": [
{"id": 101, "name": "Laptop", "price": 1200.00},
{"id": 102, "name": "Keyboard", "price": 75.50},
]
}
response: PaginatedResponse[Product] = PaginatedResponse[Product].model_validate(product_response_data)
print(f"Page: {response.page}, Total Pages: {response.total_pages}")
print("First item:", response.items[0].name, response.items[0].price)
APIã®ã¬ã¹ãã³ã¹åœ¢åŒãªã©ãããŒã¿ã®å 容ã¯å€ãããæ§é ãå ±éããŠããå Žåã«äŸ¿å©ã§ãã
ãã¹ããã©ã¯ãã£ã¹
- ð¡ æ確ãªåãã³ã: å¯èœãªéãå
·äœçã§æ確ãªåãã³ãã䜿çšããŸã (äŸ:
list
ããList[str]
)ãtyping.Any
ã®äœ¿çšã¯æå°éã«ã - ð·ïž
Annotated
ã®æŽ»çš: V2ã§ã¯Annotated
ã䜿ã£ãŠãã£ãŒã«ãå®çŸ©ãšå¶çŽãã¡ã¿ããŒã¿ãäžç®æã«ãŸãšããã®ãæšå¥šãããŸãã - âïž èšå®ã®åé¢: ã¢ããªã±ãŒã·ã§ã³ã®èšå®ã¯
pydantic-settings
ã䜿ã£ãŠç°å¢å€æ°ãèšå®ãã¡ã€ã«ããèªã¿èŸŒã¿ãã³ãŒãããåé¢ããŸãã - 𧪠ã€ãã¥ãŒã¿ãã«ãªã¢ãã«: å¯èœã§ããã°
model_config = ConfigDict(frozen=True)
ãèšå®ããã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãäžå€ã«ããããšãæ€èšããŸããããã«ããäºæãã¬ç¶æ å€æŽãé²ããŸãã - 𩺠ãšã©ãŒãã³ããªã³ã°:
ValidationError
ãé©åã«ãã£ãããããŠãŒã¶ãŒãã¬ã³ããªãŒãªãšã©ãŒã¡ãã»ãŒãžãçæãããããã°ã«è©³çŽ°ãèšé²ããŸãã - ð ããã¥ã¡ã³ããšã®é£æº: FastAPIãªã©ã®ãã¬ãŒã ã¯ãŒã¯ã§ã¯ãPydanticã¢ãã«ããèªåçã«APIããã¥ã¡ã³ãïŒSwagger UI / OpenAPIïŒãçæãããŸãã
Field
ã®title
ãdescription
åŒæ°ã掻çšããŠãããããããããã¥ã¡ã³ããäœæããŸãããã - ð ã¢ãã«ã®åå©çšãšæ§æ: å ±éã®ãã£ãŒã«ããæã€ã¢ãã«ã¯ãç¶æ¿ã Mixin ã䜿ã£ãŠåå©çšæ§ãé«ããŸãããã ããé床ãªç¶æ¿ã¯è€éããå¢ãå¯èœæ§ãããã®ã§æ³šæãå¿ èŠã§ãã
- âš ã«ã¹ã¿ã åã®äœæ: ç¹å®ã®ãã©ãŒããããå¶çŽãæã€ããŒã¿ãé »ç¹ã«æ±ãå Žåã¯ã
Annotated
ã䜿ã£ãŠã«ã¹ã¿ã åãšã€ãªã¢ã¹ãäœæãããšäŸ¿å©ã§ãã
ãŸãšã ð
Pydanticã¯ãPythonã«ãããããŒã¿ããªããŒã·ã§ã³ãšèšå®ç®¡çã®ããã®åŒ·åãã€æè»ãªã©ã€ãã©ãªã§ããåãã³ããæ倧éã«æŽ»çšããéçºããã»ã¹ãå¹çåããã³ãŒãã®å質ãšä¿¡é Œæ§ãåäžãããŸããç¹ã«Pydantic V2ã§ã¯ãRustã«ããã³ã¢å®è£ ã§ããã©ãŒãã³ã¹ãé£èºçã«åäžããå€ãã®æ°æ©èœãè¿œå ãããŸããã
APIéçºãããŒã¿åŠçãèšå®ç®¡çãªã©ãçŸä»£çãªPythonã¢ããªã±ãŒã·ã§ã³éçºã«ãããŠãPydanticã¯æ¬ ãããªãããŒã«ã®äžã€ãšèšããã§ãããããã²ãããªãã®ãããžã§ã¯ãã«ãå°å ¥ãããã®ã¡ãªãããäœéšããŠã¿ãŠãã ããïŒðª
ãã詳ããæ å ±ãææ°ã®æ©èœã«ã€ããŠã¯ãPydantic å ¬åŒããã¥ã¡ã³ã ãåç §ããããšããå§ãããŸãã