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
I had the same question when evaluating a nullable=False
column with mypy. One of my teammates found the answer in the SqlAlchemy docs:
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.