mypy doesn't recognize SQLAlchemy columns with hybrid_property

Question:

I’m trying to use mypy with SQLAlchemy.
In order to validate/modify specific column value (email in this case), SQLAlchemy official document provides hybrid_property decorator.

The problem is, mypy doesn’t recognize EmailAddress class constructor properly, it gives:

email_address.py:31: error: Unexpected keyword argument "email" for "EmailAddress"; did you mean "_email"?

How can I tell mypy to recognize these columns?

from typing import TYPE_CHECKING

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

# I don't even like the following statements just for setter
if TYPE_CHECKING:
    hybrid_property = property
else:
    from sqlalchemy.ext.hybrid import hybrid_property

Base = declarative_base()


class EmailAddress(Base):
    __tablename__ = "email_address"

    id = Column(Integer, primary_key=True)

    _email = Column("email", String)

    @hybrid_property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email


EmailAddress(email="[email protected]")
# email_address.py:31: error: Unexpected keyword argument "email" for "EmailAddress"; did you mean "_email"?

I’m using following packages:

SQLAlchemy==1.4.35
mypy==0.942
mypy-extensions==0.4.3
sqlalchemy2-stubs==0.0.2a22
Asked By: ernix

||

Answers:

OK, it seems like I finally found a way to solve the problem.

This reminds me of uncooperative behaviors between dataclass/property decorators discussed in here.

I end up with splitting EmailAddress class into 2:

  1. Use @dataclass decorator on base class in order to indicate constructor options.
  2. Override email property so that mypy doesn’t complain redef.
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional

from sqlalchemy import Column, Integer, String, Table
from sqlalchemy.orm import registry

mapper_registry: registry = registry()

# I don't even like the following statements just for setter
if TYPE_CHECKING:
    hybrid_property = property
else:
    from sqlalchemy.ext.hybrid import hybrid_property


@dataclass
@mapper_registry.mapped
class EmailAddressBase:
    __tablename__ = "email address"

    id: int = Column(Integer, primary_key=True)
    email: Optional[str] = None


class EmailAddress(EmailAddressBase):
    _email = Column("email", String)

    @hybrid_property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email


email = EmailAddress(email="[email protected]")
print(email.email)
Answered By: ernix
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.