使用SQLAlchemy与mypy定义的模型初始化时关联参数缺失问题
问题描述
我定义了如下ORM模型:
class Base(MappedAsDataclass, DeclarativeBase): """subclasses will be converted to dataclasses""" class Prompt(Base): __tablename__ = "Prompt" id = mapped_column( "id", UUID(as_uuid=True), primary_key=True, index=True, server_default=sa.text("gen_random_uuid()"), ) created_at = mapped_column( "created_at", DateTime(timezone=True), server_default=func.now(), nullable=False ) text: Mapped[str] = mapped_column(Text) display_name: Mapped[str] = mapped_column("display_name", String) # many to one relationship owner_id: Mapped[uuid.UUID] = mapped_column( "owner_id", UUID(as_uuid=True), ForeignKey("User.id"), ) owner: Mapped[User] = relationship("User", back_populates="prompts") # many-to-many relationship transcripts: Mapped[List[Transcript]] = relationship( "Transcript", secondary=transcript_prompt_association, back_populates="prompts", ) deleted: Mapped[bool] = mapped_column("deleted", Boolean, default=False)
当我创建模型实例时:
db_prompt = models.Prompt(text=text, display_name=display_name, owner_id=user_id)
收到mypy报错:
Missing positional arguments "owner", "transcripts" in call to "Prompt" [call-arg]mypy
我曾尝试将关联字段设为Optional:
owner: Optional[Mapped[User]] = relationship("User", back_populates="prompts")
但仍触发相同报错。我原本认为mypy会自动识别关联字段无需在初始化时传入。
附我的mypy.ini配置:
[mypy] python_version = 3.11 plugins = pydantic.mypy,sqlalchemy.ext.mypy.plugin ignore_missing_imports = True disallow_untyped_defs = True exclude = (?x)( alembic # files named "one.py" )
请问该如何解决此问题?
解决方法
问题根源在于MappedAsDataclass会将所有Mapped字段自动纳入dataclass的构造函数参数,包括SQLAlchemy的关系字段,而这些字段本不需要在实例化时传入。解决方式如下:
1. 给关系字段添加init=False参数
在定义relationship时,通过init=False告诉dataclass不要将该字段加入构造函数的必填参数列表:
修改owner字段:
owner: Mapped[User] = relationship("User", back_populates="prompts", init=False)
修改transcripts字段:
transcripts: Mapped[List[Transcript]] = relationship( "Transcript", secondary=transcript_prompt_association, back_populates="prompts", init=False )
这样修改后,dataclass生成的__init__方法就不会要求传入owner和transcripts,mypy的报错会直接消失。
2. 可选:给关系字段设置默认值(按需使用)
如果需要在某些场景下允许初始化时传入关系字段,可以保留init=True(默认值),但给字段设置合适的默认值:
比如给transcripts设置空列表默认值:
transcripts: Mapped[List[Transcript]] = relationship( "Transcript", secondary=transcript_prompt_association, back_populates="prompts", default_factory=list )
但通常SQLAlchemy关系字段不需要在实例化时手动传入,因此第一种方法更贴合ORM的使用习惯。
为什么Optional无法解决问题?
Optional[Mapped[...]]仅表示字段值可以为None,但dataclass的规则是:没有默认值的字段,即使类型是Optional,依然会被当作构造函数的必填参数。只有通过init=False直接排除字段,或者给字段设置默认值,才能让mypy不再要求传入该参数。
内容的提问来源于stack exchange,提问作者siva
相关产品推荐
相关产品推荐

