siva
siva

Reputation: 1523

Model defined with SQLAlchemy and mypy requires a relationship parameter during initialization

I have a model defined as following:

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)

When I want to create an instance of the model:

db_prompt = models.Prompt(text=text, display_name=display_name, owner_id=user_id)

I receive the following error:

Missing positional arguments "owner", "transcripts" in call to "Prompt"  [call-arg]mypy

How can I fix it?

I already tried to:

owner: Optional[Mapped[User]] = relationship("User", back_populates="prompts")

=> Same error.

I thought mypy understands automatically that a relationship field is not required during init.

EDIT:

My 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"
  )

Upvotes: 0

Views: 944

Answers (1)

Louis Huang
Louis Huang

Reputation: 994

Since your Base extends MappedAsDataClass, you are using a Python dataclass here. The error message "Missing positional arguments "owner", ..." comes from the fact that in the generated init method, "owner" is a required field (mypy is just reminding you of that fact).

You can exclude owner from the required field by adding init=False:

owner: Mapped[User] = relationship("User", back_populates="prompts", init=False)

Alternatively, you can give it a default value of None:

owner: Mapped[User] = relationship("User", back_populates="prompts", default=None)

However, if you give it a default value of None and still use your previous method to initialize the instance:

db_prompt = models.Prompt(text=text, display_name=display_name, owner_id=user_id)

The owner field will overwrite owner_id with None due to how a dataclass generates the init method.

So, you can either:

  1. Don't use dataclass to automatically generate the init method for this class:
class Prompt(Base, init=False):
  1. Or, add a post init method and remove the the attribute if it's None so your owner_id does not get overwritten by a null value:
def __post_init__(self):
    if (self.owner is None):
        delattr(self, "owner")

This is kind of a hack and is not recommended. After you made the modification, you can instantiate an instance with either Prompt(owner_id=user_id, ... or Prompt(owner=user....

Upvotes: 1

Related Questions