python 我可以覆盖Pydantic父模型中的字段,使它们成为可选的吗?

ma8fv8wu  于 2023-05-27  发布在  Python
关注(0)|答案(7)|浏览(301)

我有两个这样的pydantic类。

  1. class Parent(BaseModel):
  2. id: int
  3. name: str
  4. email: str
  5. class ParentUpdate(BaseModel):
  6. id: Optional[int]
  7. name: Optional[str]
  8. email: Optional[str]

这两个类实际上是相同的,但Parent类使所有字段都是必需的。我想在FastAPI中使用Parent类作为POST请求体,因此所有字段都应该是必需的。但是我想使用后者作为PUT请求体,因为用户可以设置选择性字段,其余字段保持不变。我已经看了一下必填的可选字段,但它们与我想做的不对应。
如果有一种方法,我可以继承ParentUpdate中的Parent类,并修改Parent中的所有字段,使它们成为Optional,这将减少混乱。另外,在Parent类中有一些验证器,我必须在ParentUpdate类中重写,这也是我想避免的。
有什么办法可以做到这一点吗?

vql8enpb

vql8enpb1#

可以在子类中将可选字段设为必填字段,但不能在子类中将必填字段设为可选字段。在fastapi作者tiangolo的样板项目中,他在示例中使用了这样的模式:

  1. class ParentBase(BaseModel):
  2. """Shared properties."""
  3. name: str
  4. email: str
  5. class ParentCreate(ParentBase):
  6. """Properties to receive on item creation."""
  7. # dont need id here if your db autocreates it
  8. pass
  9. class ParentUpdate(ParentBase):
  10. """Properties to receive on item update."""
  11. # dont need id as you are likely PUTing to /parents/{id}
  12. # other fields should not be optional in a PUT
  13. # maybe what you are wanting is a PATCH schema?
  14. pass
  15. class ParentInDBBase(ParentBase):
  16. """Properties shared by models stored in DB - !exposed in create/update."""
  17. # primary key exists in db, but not in base/create/update
  18. id: int
  19. class Parent(ParentInDBBase):
  20. """Properties to return to client."""
  21. # optionally include things like relationships returned to consumer
  22. # related_things: List[Thing]
  23. pass
  24. class ParentInDB(ParentInDBBase):
  25. """Additional properties stored in DB."""
  26. # could be secure things like passwords?
  27. pass

是的,我同意这是令人难以置信的冗长,我希望它不是。您仍然可能会在UI中使用更特定于特定表单的其他模式。显然,您可以删除其中的一些,因为它们在本例中是不必要的,但根据数据库中的其他字段,它们可能是需要的,或者您可能需要设置默认值、验证等。
根据我对验证器的经验,你必须重新声明它们,但你可以使用一个共享函数,即:

  1. def clean_article_url(cls, v):
  2. return clean_context_url(v.strip())
  3. class MyModel(BaseModel):
  4. article_url: str
  5. _clean_url = pydantic.validator("article_url", allow_reuse=True)(clean_article_url)
展开查看全部
zazmityj

zazmityj2#

覆盖字段是可能且容易的。(有人提到不可能将必填字段覆盖为可选字段,但我不同意)。
这个例子没有任何问题:

  1. class Parent(BaseModel):
  2. id: int
  3. name: str
  4. email: str
  5. class ParentUpdate(Parent): ## Note that this inherits 'Parent' class (not BaseModel)
  6. id: Optional[int] # this will convert id from required to optional
xmakbtuz

xmakbtuz3#

我的建议是不要发明困难的模式,我也对pydantic功能感兴趣,但所有这些功能看起来都很难看,很难理解(甚至不适合某些任务,并且有限制)。参见pydantic维护者的Python pydantic, make every field of ancestor are Optional答案

fruv7luv

fruv7luv4#

正如在回答类似问题时所概述的那样,我使用以下方法(归功于Aron Podrigal):

  1. import inspect
  2. from pydantic import BaseModel
  3. def optional(*fields):
  4. """Decorator function used to modify a pydantic model's fields to all be optional.
  5. Alternatively, you can also pass the field names that should be made optional as arguments
  6. to the decorator.
  7. Taken from https://github.com/samuelcolvin/pydantic/issues/1223#issuecomment-775363074
  8. """
  9. def dec(_cls):
  10. for field in fields:
  11. _cls.__fields__[field].required = False
  12. return _cls
  13. if fields and inspect.isclass(fields[0]) and issubclass(fields[0], BaseModel):
  14. cls = fields[0]
  15. fields = cls.__fields__
  16. return dec(cls)
  17. return dec

在你的例子中,你会这样使用它:

  1. @optional
  2. class ParentUpdate(Parent):
  3. pass
展开查看全部
kqlmhetl

kqlmhetl5#

我提前道歉,我肯定这是一个可怕的解决方案,但它对我有效:

  1. def make_child_fields_optional(parent_class: Type[BaseModel], child_class: Type[BaseModel]):
  2. for key in parent_class.__fields__:
  3. child_class.__fields__.get(key).required = False
  1. class BasePerson(BaseModel):
  2. name: str
  3. email: str
  4. login: str
  1. class UpdatePerson(BasePerson):
  2. pass # or whatever
  3. make_child_fields_optional(BasePerson, UpdatePerson)
fdbelqdn

fdbelqdn6#

对于我的情况,创建一个新类是唯一有效的解决方案,但打包到一个函数中非常方便:

  1. from pydantic import BaseModel, create_model
  2. from typing import Optional
  3. def make_optional(baseclass):
  4. # Extracts the fields and validators from the baseclass and make fields optional
  5. fields = baseclass.__fields__
  6. validators = {'__validators__': baseclass.__validators__}
  7. optional_fields = {key: (Optional[item.type_], None) for key, item in fields.items()}
  8. return create_model(f'{baseclass.__name__}Optional', **optional_fields, __validators__=validators)
  9. class Parent(BaseModel):
  10. id: int
  11. name: str
  12. email: str
  13. ParentUpdate = make_optional(Parent)

对比之前和之后:

  1. Parent.__fields__
  2. {'id': ModelField(name='id', type=int, required=True),
  3. 'name': ModelField(name='name', type=str, required=True),
  4. 'email': ModelField(name='email', type=str, required=True)}
  5. ParentUpdate.__fields__
  6. {'id': ModelField(name='id', type=Optional[int], required=False, default=None),
  7. 'name': ModelField(name='name', type=Optional[str], required=False, default=None),
  8. 'email': ModelField(name='email', type=Optional[str], required=False, default=None)}

它确实有效,而且如果需要的话,它还允许你过滤掉类的一些字段。
此外,对于FastApi,您可以直接使用make_optional(Parent)作为API调用中的类型提示,这将正确生成文档。这种方法的另一个优点是它可以大大减少样板文件。

展开查看全部
g0czyy6m

g0czyy6m7#

如果你想只覆盖一些给定的字段,而不重复类型提示,你可以使用这样的装饰器来实现:

  1. from typing import Optional
  2. from pydantic import BaseModel
  3. def set_fields_optional(*field_names):
  4. def decorator(cls: BaseModel):
  5. for field_name in field_names:
  6. field = cls.__fields__[field_name]
  7. field.required = False
  8. field.annotation = Optional[field.annotation]
  9. field.allow_none = True
  10. return cls
  11. return decorator

根据文档,__fields__是一个模型属性,它可以用来通过名称访问模型的字段并修改它们的属性。
应用装饰器,其中包含您希望设置为可选的字段的名称:

  1. class BaseWithOnlyRequiredFields(BaseModel):
  2. x: int
  3. y: str
  4. z: float
  5. @set_fields_optional('x', 'z')
  6. class DerivedWithSomeOptionalFields(BaseWithOnlyRequiredFields):
  7. pass
  8. DerivedWithSomeOptionalFields(y='y-value', x=None)
  9. # DerivedWithSomeOptionalFields(x=None, y='y-value', z=None)
展开查看全部

相关问题