The setup for each of the following sections is as follows::
+ from __future__ import annotations
+
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
that the :class:`_schema.ForeignKey` directives can locate the remote tables
with which to link::
+ from __future__ import annotations
+
from sqlalchemy import Column
from sqlalchemy import Table
+ from sqlalchemy import ForeignKey
+ from sqlalchemy import Integer
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import relationship
+
+ class Base(DeclarativeBase):
+ pass
# note for a Core table, we use the sqlalchemy.Column construct,
# not sqlalchemy.orm.mapped_column
__tablename__ = "left"
id: Mapped[int] = mapped_column(primary_key=True)
- children: Mapped[list["Child"]] = relationship(secondary=association_table)
+ children: Mapped[list[Child]] = relationship(secondary=association_table)
class Child(Base):
collection. Specify using :paramref:`_orm.relationship.back_populates`, and
for each :func:`_orm.relationship` specify the common association table::
+ from __future__ import annotations
+
from sqlalchemy import Column
from sqlalchemy import Table
+ from sqlalchemy import ForeignKey
+ from sqlalchemy import Integer
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import relationship
+
+ class Base(DeclarativeBase):
+ pass
association_table = Table(
"association",
__tablename__ = "left"
id: Mapped[int] = mapped_column(primary_key=True)
- children: Mapped[list["Child"]] = relationship(
+ children: Mapped[list[Child]] = relationship(
secondary=association_table, back_populates="parents"
)
__tablename__ = "right"
id: Mapped[int] = mapped_column(primary_key=True)
- parents: Mapped[list["Parent"]] = relationship(
+ parents: Mapped[list[Parent]] = relationship(
secondary=association_table, back_populates="children"
)
use the same :paramref:`_orm.relationship.secondary` argument for the
reverse relationship::
+ from __future__ import annotations
+
from sqlalchemy import Column
from sqlalchemy import Table
+ from sqlalchemy import ForeignKey
+ from sqlalchemy import Integer
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import relationship
+
+ class Base(DeclarativeBase):
+ pass
association_table = Table(
"association",
Base.metadata,
- mapped_column("left_id", ForeignKey("left.id"), primary_key=True),
- mapped_column("right_id", ForeignKey("right.id"), primary_key=True),
+ Column("left_id", ForeignKey("left.id"), primary_key=True),
+ Column("right_id", ForeignKey("right.id"), primary_key=True),
)
__tablename__ = "left"
id: Mapped[int] = mapped_column(primary_key=True)
- children: Mapped[list["Child"]] = relationship(
+ children: Mapped[list[Child]] = relationship(
secondary=association_table, backref="parents"
)
from typing import Optional
+ from sqlalchemy import ForeignKey
+ from sqlalchemy import Integer
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import relationship
+
+ class Base(DeclarativeBase):
+ pass
+
class Association(Base):
__tablename__ = "association"
left_id: Mapped[int] = mapped_column(ForeignKey("left.id"), primary_key=True)
from typing import Optional
+ from sqlalchemy import ForeignKey
+ from sqlalchemy import Integer
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import relationship
+
+ class Base(DeclarativeBase):
+ pass
+
class Association(Base):
__tablename__ = "association"
left_id: Mapped[int] = mapped_column(ForeignKey("left.id"), primary_key=True)
between ``Parent.child_associations -> Association.child``
and ``Child.parent_associations -> Association.parent``::
- from typing import Optional
+ from typing import Optional
+
+ from sqlalchemy import ForeignKey
+ from sqlalchemy import Integer
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import relationship
+
+ class Base(DeclarativeBase):
+ pass
- class Association(Base):
- __tablename__ = "association"
+ class Association(Base):
+ __tablename__ = "association"
- left_id: Mapped[int] = mapped_column(ForeignKey("left.id"), primary_key=True)
- right_id: Mapped[int] = mapped_column(ForeignKey("right.id"), primary_key=True)
- extra_data: Mapped[Optional[str]]
+ left_id: Mapped[int] = mapped_column(ForeignKey("left.id"), primary_key=True)
+ right_id: Mapped[int] = mapped_column(ForeignKey("right.id"), primary_key=True)
+ extra_data: Mapped[Optional[str]]
- # association between Assocation -> Child
- child: Mapped["Child"] = relationship(back_populates="parent_associations")
+ # association between Assocation -> Child
+ child: Mapped["Child"] = relationship(back_populates="parent_associations")
- # association between Assocation -> Parent
- parent: Mapped["Parent"] = relationship(back_populates="child_associations")
+ # association between Assocation -> Parent
+ parent: Mapped["Parent"] = relationship(back_populates="child_associations")
- class Parent(Base):
- __tablename__ = "left"
+ class Parent(Base):
+ __tablename__ = "left"
- id: Mapped[int] = mapped_column(primary_key=True)
+ id: Mapped[int] = mapped_column(primary_key=True)
- # many-to-many relationship to Child, bypassing the `Association` class
- children: Mapped[list["Child"]] = relationship(secondary="association", back_populates="parents")
+ # many-to-many relationship to Child, bypassing the `Association` class
+ children: Mapped[list["Child"]] = relationship(secondary="association", back_populates="parents")
- # association between Parent -> Association -> Child
- child_associations: Mapped[list["Association"]] = relationship(back_populates="parent")
+ # association between Parent -> Association -> Child
+ child_associations: Mapped[list["Association"]] = relationship(back_populates="parent")
- class Child(Base):
- __tablename__ = "right"
+ class Child(Base):
+ __tablename__ = "right"
- id: Mapped[int] = mapped_column(primary_key=True)
+ id: Mapped[int] = mapped_column(primary_key=True)
- # many-to-many relationship to Parent, bypassing the `Association` class
- parents: Mapped[list["Parent"]] = relationship(secondary="association", back_populates="children")
+ # many-to-many relationship to Parent, bypassing the `Association` class
+ parents: Mapped[list["Parent"]] = relationship(secondary="association", back_populates="children")
- # association between Child -> Association -> Parent
- parent_associations: Mapped[list["Association"]] = relationship(back_populates="child")
+ # association between Child -> Association -> Parent
+ parent_associations: Mapped[list["Association"]] = relationship(back_populates="child")
When using this ORM model to make changes, changes made to
``Parent.children`` will not be coordinated with changes made to
additional arguments that will refer to the polymorphic discriminator
column as well as the identifier for the base class::
+ from sqlalchemy import ForeignKey
+ from sqlalchemy.orm import DeclarativeBase
+ from sqlalchemy.orm import Mapped
+ from sqlalchemy.orm import mapped_column
+
+ class Base(DeclarativeBase):
+ pass
+
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(50))
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "employee",
- "polymorphic_on": type,
+ "polymorphic_on": "type",
}
Above, an additional column ``type`` is established to act as the
-**discriminator**, configured as such using the :paramref:`.mapper.polymorphic_on`
-parameter. This column will store a value which indicates the type of object
+**discriminator**, configured as such using the
+:paramref:`_orm.Mapper.polymorphic_on` parameter, which accepts a column-oriented
+expression specified either as a string name of the mapped attribute to use, or
+as a column expression object such as :class:`_schema.Column` or
+:func:`_orm.mapped_column` construct.
+
+This column will store a value which indicates the type of object
represented within the row. The column may be of any datatype, though string
and integer are the most common. The actual data value to be applied to this
column for a particular row in the database is specified using the
-:paramref:`.mapper.polymorphic_identity` parameter, described below.
+:paramref:`_orm.Mapper.polymorphic_identity` parameter, described below.
While a polymorphic discriminator expression is not strictly necessary, it is
required if polymorphic loading is desired. Establishing a simple column on
class Engineer(Employee):
__tablename__ = "engineer"
- id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
- engineer_name = mapped_column(String(30))
+ id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
+ engineer_name: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "engineer",
class Manager(Employee):
__tablename__ = "manager"
- id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
- manager_name = mapped_column(String(30))
+ id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
+ manager_name: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "manager",
}
In the above example, each mapping specifies the
-:paramref:`.mapper.polymorphic_identity` parameter within its mapper arguments.
+:paramref:`_orm.Mapper.polymorphic_identity` parameter within its mapper arguments.
This value populates the column designated by the
-:paramref:`.mapper.polymorphic_on` parameter established on the base mapper.
-The :paramref:`.mapper.polymorphic_identity` parameter should be unique to
+:paramref:`_orm.Mapper.polymorphic_on` parameter established on the base mapper.
+The :paramref:`_orm.Mapper.polymorphic_identity` parameter should be unique to
each mapped class across the whole hierarchy, and there should only be one
"identity" per mapped class; as noted above, "cascading" identities where some
subclasses introduce a second identity are not supported.
-The ORM uses the value set up by :paramref:`.mapper.polymorphic_identity` in
+The ORM uses the value set up by :paramref:`_orm.Mapper.polymorphic_identity` in
order to determine which class a row belongs towards when loading rows
polymorphically. In the example above, every row which represents an
``Employee`` will have the value ``'employee'`` in its ``type`` row; similarly,
distinct joined tables for subclasses as in joined table inheritance, or all
one table as in single table inheritance, this value is expected to be
persisted and available to the ORM when querying. The
-:paramref:`.mapper.polymorphic_identity` parameter also applies to concrete
+:paramref:`_orm.Mapper.polymorphic_identity` parameter also applies to concrete
table inheritance, but is not actually persisted; see the later section at
:ref:`concrete_inheritance` for details.
the ``company`` table, the relationships are set up between ``Company``
and ``Employee``::
+ from __future__ import annotations
+
+ from sqlalchemy.orm import relationship
+
class Company(Base):
__tablename__ = "company"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- employees = relationship("Employee", back_populates="company")
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ employees: Mapped[list[Employee]] = relationship(back_populates="company")
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(50))
- company_id = mapped_column(ForeignKey("company.id"))
- company = relationship("Company", back_populates="employees")
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
+ company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
+ company: Mapped[Company] = relationship(back_populates="employees")
__mapper_args__ = {
"polymorphic_identity": "employee",
- "polymorphic_on": type,
+ "polymorphic_on": "type",
}
class Company(Base):
__tablename__ = "company"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- managers = relationship("Manager", back_populates="company")
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ managers: Mapped[list[Manager]] = relationship(back_populates="company")
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(50))
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "employee",
class Manager(Employee):
__tablename__ = "manager"
- id = mapped_column(Integer, ForeignKey("employee.id"), primary_key=True)
- manager_name = mapped_column(String(30))
+ id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
+ manager_name: Mapped[str]
- company_id = mapped_column(ForeignKey("company.id"))
- company = relationship("Company", back_populates="managers")
+ company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
+ company: Mapped[Company] = relationship(back_populates="managers")
__mapper_args__ = {
"polymorphic_identity": "manager",
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(20))
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
__mapper_args__ = {
- "polymorphic_on": type,
+ "polymorphic_on": "type",
"polymorphic_identity": "employee",
}
class Manager(Employee):
- manager_data = mapped_column(String(50))
+ manager_data: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "manager",
class Engineer(Employee):
- engineer_info = mapped_column(String(50))
+ engineer_info: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "engineer",
declaration on a subclass that has no table of its own. A tricky case
comes up when two subclasses want to specify *the same* column, as below::
+ from datetime import datetime
+
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(20))
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
__mapper_args__ = {
- "polymorphic_on": type,
+ "polymorphic_on": "type",
"polymorphic_identity": "employee",
}
__mapper_args__ = {
"polymorphic_identity": "engineer",
}
- start_date = mapped_column(DateTime)
+ start_date: Mapped[datetime]
class Manager(Employee):
__mapper_args__ = {
"polymorphic_identity": "manager",
}
- start_date = mapped_column(DateTime)
+ start_date: Mapped[datetime]
Above, the ``start_date`` column declared on both ``Engineer`` and ``Manager``
will result in an error::
taking care to return the **existing column** via the parent ``__table__``
if it already exists::
+ from sqlalchemy import DateTime
from sqlalchemy.orm import declared_attr
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(20))
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
__mapper_args__ = {
- "polymorphic_on": type,
+ "polymorphic_on": "type",
"polymorphic_identity": "employee",
}
}
@declared_attr
- def start_date(cls):
+ def start_date(cls) -> Mapped[datetime]:
"Start date column, if not present already."
- return Employee.__table__.c.get("start_date", mapped_column(DateTime))
+
+ # the DateTime type is required in the mapped_column
+ # at the moment when used inside of a @declared_attr
+ return Employee.__table__.c.get(
+ "start_date", mapped_column(DateTime) # type: ignore
+ )
class Manager(Employee):
}
@declared_attr
- def start_date(cls):
+ def start_date(cls) -> Mapped[datetime]:
"Start date column, if not present already."
- return Employee.__table__.c.get("start_date", mapped_column(DateTime))
+
+ # the DateTime type is required in the mapped_column
+ # at the moment when used inside of a @declared_attr
+ return Employee.__table__.c.get(
+ "start_date", mapped_column(DateTime) # type: ignore
+ )
Above, when ``Manager`` is mapped, the ``start_date`` column is
already present on the ``Employee`` class; by returning the existing
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(20))
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
__mapper_args__ = {
"polymorphic_on": type,
class HasStartDate:
@declared_attr
- def start_date(cls):
- return cls.__table__.c.get("start_date", mapped_column(DateTime))
+ def start_date(cls) -> Mapped[datetime]:
+ return cls.__table__.c.get(
+ "start_date", mapped_column(DateTime) # type: ignore
+ )
class Engineer(HasStartDate, Employee):
class Company(Base):
__tablename__ = "company"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- employees = relationship("Employee", back_populates="company")
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ employees: Mapped[list[Employee]] = relationship(back_populates="company")
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(50))
- company_id = mapped_column(ForeignKey("company.id"))
- company = relationship("Company", back_populates="employees")
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
+ company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
+ company: Mapped[Company] = relationship(back_populates="employees")
__mapper_args__ = {
"polymorphic_identity": "employee",
- "polymorphic_on": type,
+ "polymorphic_on": "type",
}
class Manager(Employee):
- manager_data = mapped_column(String(50))
+ manager_data: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "manager",
class Engineer(Employee):
- engineer_info = mapped_column(String(50))
+ engineer_info: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "engineer",
class Company(Base):
__tablename__ = "company"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- managers = relationship("Manager", back_populates="company")
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ managers: Mapped[list[Manager]] = relationship(back_populates="company")
class Employee(Base):
__tablename__ = "employee"
- id = mapped_column(Integer, primary_key=True)
- name = mapped_column(String(50))
- type = mapped_column(String(50))
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "employee",
- "polymorphic_on": type,
+ "polymorphic_on": "type",
}
class Manager(Employee):
- manager_name = mapped_column(String(30))
+ manager_name: Mapped[str]
- company_id = mapped_column(ForeignKey("company.id"))
- company = relationship("Company", back_populates="managers")
+ company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
+ company: Mapped[Company] = relationship(back_populates="managers")
__mapper_args__ = {
"polymorphic_identity": "manager",
class Engineer(Employee):
- engineer_info = mapped_column(String(50))
+ engineer_info: Mapped[str]
__mapper_args__ = {
"polymorphic_identity": "engineer",
classes is also more awkward.
To establish a class as using concrete inheritance, add the
-:paramref:`.mapper.concrete` parameter within the ``__mapper_args__``.
+:paramref:`_orm.Mapper.concrete` parameter within the ``__mapper_args__``.
This indicates to Declarative as well as the mapping that the superclass
table should not be considered as part of the mapping::
As discussed in :ref:`inheritance_loading_toplevel`, mapper inheritance
configurations of any type can be configured to load from a special selectable
-by default using the :paramref:`.mapper.with_polymorphic` argument. Current
+by default using the :paramref:`_orm.Mapper.with_polymorphic` argument. Current
public API requires that this argument is set on a :class:`_orm.Mapper` when
it is first constructed.
However, in the case of Declarative, both the mapper and the :class:`_schema.Table`
that is mapped are created at once, the moment the mapped class is defined.
-This means that the :paramref:`.mapper.with_polymorphic` argument cannot
+This means that the :paramref:`_orm.Mapper.with_polymorphic` argument cannot
be provided yet, since the :class:`_schema.Table` objects that correspond to the
subclasses haven't yet been defined.
we will have only an ``engineer`` and a ``manager`` table and no ``employee``
table, however the ``Employee`` mapper will be mapped directly to the
"polymorphic union", rather than specifying it locally to the
-:paramref:`.mapper.with_polymorphic` parameter.
+:paramref:`_orm.Mapper.with_polymorphic` parameter.
To help with this, Declarative offers a variant of the :class:`.ConcreteBase`
class called :class:`.AbstractConcreteBase` which achieves this automatically::
With the above :class:`_schema.Table` objects, the mappings can be produced using "semi-classical" style,
where we use Declarative in conjunction with the ``__table__`` argument;
our polymorphic union above is passed via ``__mapper_args__`` to
-the :paramref:`.mapper.with_polymorphic` parameter::
+the :paramref:`_orm.Mapper.with_polymorphic` parameter::
class Employee(Base):
__table__ = employee_table
The "abstract" example can also be mapped using "semi-classical" or "classical"
style. The difference is that instead of applying the "polymorphic union"
-to the :paramref:`.mapper.with_polymorphic` parameter, we apply it directly
+to the :paramref:`_orm.Mapper.with_polymorphic` parameter, we apply it directly
as the mapped selectable on our basemost mapper. The semi-classical
mapping is illustrated below::