To cover the major areas where this occurs, consider the following ORM
mapping, using the typical example of the ``User`` class::
- from sqlalchemy import Column
- from sqlalchemy import Integer
- from sqlalchemy import String
- from sqlalchemy import select
+ from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import declarative_base
# "Base" is a class that is created dynamically from the
# declarative_base() function
Base = declarative_base()
+
class User(Base):
- __tablename__ = 'user'
+ __tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
+
# "some_user" is an instance of the User class, which
# accepts "id" and "name" kwargs based on the mapping
- some_user = User(id=5, name='user')
+ some_user = User(id=5, name="user")
# it has an attribute called .name that's a string
print(f"Username: {some_user.name}")
# a select() construct makes use of SQL expressions derived from the
# User class itself
- select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains('s'))
+ select_stmt = (
+ select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))
+ )
Above, the steps that the Mypy extension can take include:
definition and Python code passed to the Mypy tool is equivalent to the
following::
- from sqlalchemy import Column
- from sqlalchemy import Integer
- from sqlalchemy import String
- from sqlalchemy import select
- from sqlalchemy.orm import declarative_base
- from sqlalchemy.orm.decl_api import DeclarativeMeta
+ from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm.decl_api import DeclarativeMeta
+
class Base(metaclass=DeclarativeMeta):
__abstract__ = True
+
class User(Base):
- __tablename__ = 'user'
+ __tablename__ = "user"
id: Mapped[Optional[int]] = Mapped._special_method(
Column(Integer, primary_key=True)
)
- name: Mapped[Optional[str]] = Mapped._special_method(
- Column(String)
- )
+ name: Mapped[Optional[str]] = Mapped._special_method(Column(String))
- def __init__(self, id: Optional[int] = ..., name: Optional[str] = ...) -> None:
+ def __init__(
+ self, id: Optional[int] = ..., name: Optional[str] = ...
+ ) -> None:
...
- some_user = User(id=5, name='user')
+
+ some_user = User(id=5, name="user")
print(f"Username: {some_user.name}")
- select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains('s'))
+ select_stmt = (
+ select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))
+ )
+
The key steps which have been taken above include:
from sqlalchemy.orm import Mapped
+
class MyClass(Base):
# ...
Base = declarative_base()
+
class User(Base):
- __tablename__ = 'user'
+ __tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
+
class Address(Base):
- __tablename__ = 'address'
+ __tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id = Column(ForeignKey("user.id"))
column::
class Address(Base):
- __tablename__ = 'address'
+ __tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
Base.metadata,
Column(Integer, primary_key=True),
Column("employee_name", String(50), nullable=False),
- Column(String(50))
+ Column(String(50)),
)
id: Mapped[int]
is a string or callable, and not a class::
class User(Base):
- __tablename__ = 'user'
+ __tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
+
class Address(Base):
- __tablename__ = 'address'
+ __tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
or by providing the type, in this case the scalar ``User`` object::
class Address(Base):
- __tablename__ = 'address'
+ __tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
the `TYPE_CHECKING block <https://www.python.org/dev/peps/pep-0484/#runtime-or-type-checking>`_
as appropriate::
- from typing import List, TYPE_CHECKING
+ from typing import TYPE_CHECKING, List
+
from .mymodel import Base
if TYPE_CHECKING:
# that cannot normally be imported at runtime
from .myaddressmodel import Address
+
class User(Base):
- __tablename__ = 'user'
+ __tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
applied explicitly::
class User(Base):
- __tablename__ = 'user'
+ __tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
- addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
+ addresses: Mapped[List["Address"]] = relationship(
+ "Address", back_populates="user"
+ )
+
class Address(Base):
- __tablename__ = 'address'
+ __tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
:func:`_orm.declarative_mixin` decorator, which provides a hint to the Mypy
plugin that a particular class intends to serve as a declarative mixin::
- from sqlalchemy.orm import declared_attr
- from sqlalchemy.orm import declarative_mixin
+ from sqlalchemy.orm import declarative_mixin, declared_attr
+
@declarative_mixin
class HasUpdatedAt:
def updated_at(cls) -> Column[DateTime]: # uses Column
return Column(DateTime)
+
@declarative_mixin
class HasCompany:
-
@declared_attr
def company_id(cls) -> Mapped[int]: # uses Mapped
return Column(ForeignKey("company.id"))
def company(cls) -> Mapped["Company"]:
return relationship("Company")
+
class Employee(HasUpdatedAt, HasCompany, Base):
- __tablename__ = 'employee'
+ __tablename__ = "employee"
id = Column(Integer, primary_key=True)
name = Column(String)
company_id: Mapped[int]
company: Mapped["Company"]
-
Combining with Dataclasses or Other Type-Sensitive Attribute Systems
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
is significant. That is, a class as follows has to be stated exactly
as it is in order to be accepted by dataclasses::
- mapper_registry : registry = registry()
+ mapper_registry: registry = registry()
@mapper_registry.mapped
addresses: List[Address] = field(default_factory=list)
__mapper_args__ = { # type: ignore
- "properties" : {
- "addresses": relationship("Address")
- }
+ "properties": {"addresses": relationship("Address")}
}
We can't apply our ``Mapped[]`` types to the attributes ``id``, ``name``,
_mypy_mapped_attrs = [id, name, "fullname", "nickname", addresses]
__mapper_args__ = { # type: ignore
- "properties" : {
- "addresses": relationship("Address")
- }
+ "properties": {"addresses": relationship("Address")}
}
With the above recipe, the attributes listed in ``_mypy_mapped_attrs``