我有以下模型:
from pydantic import BaseModel
class User(BaseModel):
user_id: Optional[int] = None
name: str
user_id最初可能是未知的( None),但是當它設定為非None值時,之后它應該是不可變的。
例子:
user1 = User(name="John")
user1.user_id = 1 # user_id is set to a non-None value for the first time
# from now on, user_id must be immutable
user1.user_id = 10 # should raise a ValidationError
user2 = User(user_id=None, name="John")
user2.user_id = 2 # user_id is set to a non-None value for the first time
# from now on, user_id must be immutable
user2.user_id = 20 # should raise a ValidationError
user2.user_id = None # should raise a ValidationError
user3 = User(user_id=None, name="John")
user3.user_id = None # that's ok, the value is still None
user3.user_id = 3 # user_id is now set to a non-None value
# from now on, user_id must be immutable
user3.user_id = 30 # should raise a ValidationError
user4 = User(user_id=4, name="John")
# from now on, user_id must be immutable
user4.user_id = 40 # should raise a ValidationError
此解決方案需要user_id初始化 ,因此無法分配user1.user_id = 1。
uj5u.com熱心網友回復:
我同意@chepner 的觀點,您可能應該重新考慮您的演算法。如果您知道這不應該發生,您甚至有可能重新分配似乎很奇怪。此外,您可以在分配前簡單地檢查。但很難說,如果沒有額外的背景,什么可能是更好的行動方案。
對于手頭的事情。
據我所知,Pydantic 模型驗證在設計上是無狀態的。所有驗證器都是類方法,并且沒有內置方法可以讓賦值驗證取決于正在驗證的模型實體的狀態。
話雖如此,您始終可以使用標準的 Python“dunder”魔法構建一種解決方法,而不會過多地使用 Pydantic-specifics。__setattr__即使在 Pydantic 模型的情況下,屬性分配也是通過 完成的。我們可以最低限度地使用該方法并在那里進行檢查。
為了盡可能符合“Pydantic 方式”,我們不應該ValidationError立即在那里提出。相反,我們可以簡單地分配一個唯一的物件,該物件保證被驗證器拾取,這樣我們就會得到一個“干凈”的驗證錯誤。
這是如何實作的:
from typing import Any, ClassVar
from pydantic import BaseModel, validator
class User(BaseModel):
_USER_ID_SENTINEL: ClassVar[object] = object()
user_id: int | None = None
name: str
class Config:
validate_assignment = True
def __setattr__(self, key: str, value: Any) -> None:
if key == "user_id" and self.user_id is not None:
value = self.__class__._USER_ID_SENTINEL
super().__setattr__(key, value)
@validator("user_id", pre=True)
def ensure_no_reassignment(cls, v: Any) -> Any:
if v is cls._USER_ID_SENTINEL:
raise ValueError("Re-assignment of user_id not allowed")
return v
這是一個完整的作業測驗用例:
from unittest import TestCase, main
from typing import Any, ClassVar
from pydantic import BaseModel, ValidationError, validator
class User(BaseModel):
... # see above
class Test(TestCase):
def test_default_delayed_assignment(self) -> None:
user = User(name="John")
user.user_id = 1
with self.assertRaises(ValidationError):
user.user_id = 10
with self.assertRaises(ValidationError):
user.user_id = None
def test_explicit_none_delayed_assignment(self) -> None:
user = User(user_id=None, name="John")
user.user_id = 2
with self.assertRaises(ValidationError):
user.user_id = 20
with self.assertRaises(ValidationError):
user.user_id = None
def test_delayed_assignment_none_first(self) -> None:
user = User(user_id=None, name="John")
user.user_id = None
user.user_id = 3
with self.assertRaises(ValidationError):
user.user_id = 30
with self.assertRaises(ValidationError):
user.user_id = None
def test_init_assignment(self) -> None:
user = User(user_id=4, name="John")
with self.assertRaises(ValidationError):
user.user_id = 40
def test_normal_functionality(self) -> None:
with self.assertRaises(ValidationError):
User(**{"name": object()})
with self.assertRaises(ValidationError):
User.parse_obj({"name": "John", "user_id": object()})
user = User(name="John")
user.name = "Alice"
self.assertDictEqual({"user_id": None, "name": "Alice"}, user.dict())
if __name__ == '__main__':
main()
pre=True中的是@validator必要的,以便我們提供有用的錯誤資訊。我們可以完全省略自定義驗證器。然后,常規int驗證器將拾取object并引發錯誤,但該錯誤將具有誤導性,表明型別錯誤。
中的validate_assignment = True設定Config顯然是驗證器在初始化后被呼叫所必需的。
此外,如果您想在錯誤訊息中包含更多資訊,您可以(例如)使用特殊類而不是object哨兵,并替換該類的實體以__setattr__包含預先存在的值user_id和試圖被分配。然后您可以在驗證器方法中選擇它并將該資訊包含在錯誤訊息中。我不知道這是否有用。
請注意,(一如既往)沒有真正的不變性,因為在 Python 中總有一種方法可以改變任何物件的任何屬性。然而,這個解決方案與 Pydantic 的不變性概念一樣“真實”。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/521179.html
