【问题标题】:Can I override fields from a Pydantic parent model to make them optional?我可以覆盖 Pydantic 父模型中的字段以使其可选吗?
【发布时间】:2021-01-25 11:29:14
【问题描述】:

我有两个这样的 pydantic 类。

class Parent(BaseModel):
    id: int
    name: str
    email: str

class ParentUpdate(BaseModel):
    id: Optional[int]
    name: Optional[str]
    email: Optional[str]

这两者实际上是相同的,但 Parent 类使所有字段都成为必需的。 我想在 FastAPI 中使用 Parent 类作为 POST 请求正文,因此所有字段都应该是必需的。但我想将后者用于 PUT 请求正文,因为用户可以设置选择性字段并且其余字段保持不变。 我看过Required Optional Fields,但它们与我想要做的不对应。

如果有办法我可以继承ParentUpdate 中的Parent 类并修改Parent 中的所有字段以使它们成为Optional,这将减少混乱。此外,Parent 类中存在一些验证器,我必须在 ParentUpdate 类中重写它们,我也想避免。

有没有办法做到这一点?谢谢。

【问题讨论】:

    标签: python fastapi pydantic


    【解决方案1】:

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

    class ParentBase(BaseModel):
        """Shared properties."""
        name: str
        email: str
    
    class ParentCreate(ParentBase):
        """Properties to receive on item creation."""
        # dont need id here if your db autocreates it
        pass
    
    class ParentUpdate(ParentBase):
        """Properties to receive on item update."""
        # dont need id as you are likely PUTing to /parents/{id}
        # other fields should not be optional in a PUT
        # maybe what you are wanting is a PATCH schema?
        pass
    
    class ParentInDBBase(ParentBase):
        """Properties shared by models stored in DB - !exposed in create/update."""
        # primary key exists in db, but not in base/create/update
        id: int                             
    
    class Parent(ParentInDBBase):
        """Properties to return to client."""
        # optionally include things like relationships returned to consumer
        # related_things: List[Thing]
        pass
    
    class ParentInDB(ParentInDBBase):
        """Additional properties stored in DB."""
        # could be secure things like passwords?
        pass
    

    是的,我同意这非常冗长,但我希望不是。您仍然可能会在 UI 中使用更特定于特定表单的其他模式。显然,您可以删除其中的一些,因为在此示例中它们不是必需的,但根据您数据库中的其他字段,可能需要它们,或者您可能需要设置默认值、验证等。

    根据我对验证器的经验,您必须重新声明它们,但您可以使用共享函数,即:

    def clean_article_url(cls, v):
        return clean_context_url(v.strip())
    
    class MyModel(BaseModel):
        article_url: str
    
        _clean_url = pydantic.validator("article_url", allow_reuse=True)(clean_article_url)
    

    【讨论】:

    • “你可以在子类中设置可选字段”——你能多说一些吗?我一直在试图弄清楚如何做到这一点,但除了重新定义字段之外找不到正确的方法。
    • @FragLegs 我刚刚为你快速整理了一个gist
    【解决方案2】:

    覆盖字段可能且容易。 (有人提到不可能将必填字段覆盖为可选,但我不同意)。

    这个例子没有任何问题:

    class Parent(BaseModel):
        id: int
        name: str
        email: str
    
    class ParentUpdate(Parent): ## Note that this inherits 'Parent' class
        id: Optional[int]  # this will convert id from required to optional
    

    【讨论】:

    • 如果您使用此语法 ParentUpdate(**myMap) 初始化此类 (ParentUpdate)。获取错误-> id 的多个参数。这不是一个干净的答案。
    【解决方案3】:

    我提前道歉,我确信这是一个可怕的解决方法,但它对我有用:

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

    【讨论】:

      【解决方案4】:

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

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

      在您的示例中,您会像这样使用它:

      @optional
      class ParentUpdate(Parent):
          pass
      

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-01
          • 1970-01-01
          • 2023-03-08
          • 2010-10-13
          • 1970-01-01
          • 2013-08-07
          相关资源
          最近更新 更多