Search code examples
pythonsqlalchemymypy

How do I make Mypy recognize non-nullable ORM attributes?


Mypy infers ORM non-nullable instance attributes as optionals.

Filename: test.py

from sqlalchemy.orm import decl_api, registry
from sqlalchemy import BigInteger, Column, String

mapper_registry = registry()


class Base(metaclass=decl_api.DeclarativeMeta):
    __abstract__ = True

    registry = mapper_registry
    metadata = mapper_registry.metadata

    __init__ = mapper_registry.constructor


class Person(Base):
    __tablename__ = "persons"

    id = Column(BigInteger, primary_key=True, autoincrement=True)
    name = Column(String(40), nullable=False)


def main(person: Person):
    person_id = person.id
    person_name = person.name

    reveal_locals()

Running mypy test.py yields:

test.py:27: note: Revealed local types are:
test.py:27: note:     person: test.Person
test.py:27: note:     person_id: Union[builtins.int, None]
test.py:27: note:     person_name: Union[builtins.str, None]

As far as my understanding goes, person_id and person_name should have been int and str respectively since they are set as non-nullable.

What am I missing here?


Relevant libraries:

SQLAlchemy         1.4.25
sqlalchemy2-stubs  0.0.2a15
mypy               0.910
mypy-extensions    0.4.3

Solution

  • I had the same question when evaluating a nullable=False column with mypy. One of my teammates found the answer in the SqlAlchemy docs:

    https://docs.sqlalchemy.org/en/14/orm/extensions/mypy.html#introspection-of-columns-based-on-typeengine

    The types are by default always considered to be Optional, even for the primary key and non-nullable column. The reason is because while the database columns “id” and “name” can’t be NULL, the Python attributes id and name most certainly can be None without an explicit constructor:

    There are also mitigations they list in that article with declaring the columns to be mapped types explicitly.