--- /dev/null
+.. change::
+ :tags: orm, feature
+ :tickets: 9060
+
+ Added a new parameter to :class:`_orm.Mapper` called
+ :paramref:`_orm.Mapper.polymorphic_abstract`. The purpose of this directive
+ is so that the ORM will not consider the class to be instantiated or loaded
+ directly, only subclasses. The actual effect is that the
+ :class:`_orm.Mapper` will prevent direct instantiation of instances
+ of the class and will expect that the class does not have a distinct
+ polymorphic identity configured.
+
+ In practice, the class that is mapped with
+ :paramref:`_orm.Mapper.polymorphic_abstract` can be used as the target of a
+ :func:`_orm.relationship` as well as be used in queries; subclasses must of
+ course include polymorphic identities in their mappings.
+
+ The new parameter is automatically applied to classes that subclass
+ the :class:`.AbstractConcreteBase` class, as this class is not intended
+ to be instantiated.
+
+ .. seealso::
+
+ :ref:`orm_inheritance_abstract_poly`
+
DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata.create_all(some_other_engine)
+.. seealso::
+
+ :ref:`orm_inheritance_abstract_poly` - an alternative form of "abstract"
+ mapped class that is appropriate for inheritance hierarchies.
+
``__table_cls__``
~~~~~~~~~~~~~~~~~
discriminator column is also required on the base table so that classes can be
differentiated from each other.
-Even though subclasses share the base table for all of their attributes,
-when using Declarative, :class:`_schema.Column` objects may still be specified on
-subclasses, indicating that the column is to be mapped only to that subclass;
-the :class:`_schema.Column` will be applied to the same base :class:`_schema.Table` object::
+Even though subclasses share the base table for all of their attributes, when
+using Declarative, :class:`_orm.mapped_column` objects may still be specified
+on subclasses, indicating that the column is to be mapped only to that
+subclass; the :class:`_orm.mapped_column` will be applied to the same base
+:class:`_schema.Table` object::
class Employee(Base):
__tablename__ = "employee"
class Manager(Employee):
- manager_data: Mapped[str]
+ manager_data: Mapped[str] = mapped_column(nullable=True)
__mapper_args__ = {
"polymorphic_identity": "manager",
class Engineer(Employee):
- engineer_info: Mapped[str]
+ engineer_info: Mapped[str] = mapped_column(nullable=True)
__mapper_args__ = {
"polymorphic_identity": "engineer",
Note that the mappers for the derived classes Manager and Engineer omit the
``__tablename__``, indicating they do not have a mapped table of
-their own.
+their own. Additionally, a :func:`_orm.mapped_column` directive with
+``nullable=True`` is included; as the Python types declared for these classes
+do not include ``Optional[]``, the column would normally be mapped as
+``NOT NULL``, which would not be appropriate as this column only expects to
+be populated for those rows that correspond to that particular subclass.
.. _orm_inheritance_column_conflicts:
__mapper_args__ = {
"polymorphic_identity": "engineer",
}
- start_date: Mapped[datetime]
+ start_date: Mapped[datetime] = mapped_column(nullable=True)
class Manager(Employee):
__mapper_args__ = {
"polymorphic_identity": "manager",
}
- start_date: Mapped[datetime]
+ start_date: Mapped[datetime] = mapped_column(nullable=True)
Above, the ``start_date`` column declared on both ``Engineer`` and ``Manager``
will result in an error:
"polymorphic_identity": "engineer",
}
- start_date: Mapped[datetime] = mapped_column(use_existing_column=True)
+ start_date: Mapped[datetime] = mapped_column(
+ nullable=True, use_existing_column=True
+ )
class Manager(Employee):
"polymorphic_identity": "manager",
}
- start_date: Mapped[datetime] = mapped_column(use_existing_column=True)
+ start_date: Mapped[datetime] = mapped_column(
+ nullable=True, use_existing_column=True
+ )
Above, when ``Manager`` is mapped, the ``start_date`` column is
already present on the ``Employee`` class, having been provided by the
class HasStartDate:
- start_date: Mapped[datetime] = mapped_column(use_existing_column=True)
+ start_date: Mapped[datetime] = mapped_column(
+ nullable=True, use_existing_column=True
+ )
class Engineer(HasStartDate, Employee):
class Manager(Employee):
- manager_data: Mapped[str]
+ manager_data: Mapped[str] = mapped_column(nullable=True)
__mapper_args__ = {
"polymorphic_identity": "manager",
class Engineer(Employee):
- engineer_info: Mapped[str]
+ engineer_info: Mapped[str] = mapped_column(nullable=True)
__mapper_args__ = {
"polymorphic_identity": "engineer",
class Manager(Employee):
- manager_name: Mapped[str]
+ manager_name: Mapped[str] = mapped_column(nullable=True)
company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
company: Mapped[Company] = relationship(back_populates="managers")
class Engineer(Employee):
- engineer_info: Mapped[str]
+ engineer_info: Mapped[str] = mapped_column(nullable=True)
__mapper_args__ = {
"polymorphic_identity": "engineer",
loads against the ``employee`` with an additional WHERE clause that
limits rows to those with ``type = 'manager'``.
+.. _orm_inheritance_abstract_poly:
+
+Building Deeper Hierarchies with ``polymorphic_abstract``
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+.. versionadded:: 2.0
+
+When building any kind of inheritance hierarchy, a mapped class may include the
+:paramref:`_orm.Mapper.polymorphic_abstract` parameter set to ``True``, which
+indicates that the class should be mapped normally, however would not expect to
+be instantiated directly and would not include a
+:paramref:`_orm.Mapper.polymorphic_identity`. Subclasses may then be declared
+as subclasses of this mapped class, which themselves can include a
+:paramref:`_orm.Mapper.polymorphic_identity` and therefore be used normally.
+This allows a series of subclasses to be referenced at once by a common base
+class which is considered to be "abstract" within the hierarchy, both in
+queries as well as in :func:`_orm.relationship` declarations. This use differs
+from the use of the :ref:`declarative_abstract` attribute with Declarative,
+which leaves the target class entirely unmapped and thus not usable as a mapped
+class by itself. :paramref:`_orm.Mapper.polymorphic_abstract` may be applied to
+any class or classes at any level in the hierarchy, including on multiple
+levels at once.
+
+As an example, suppose ``Manager`` and ``Principal`` were both to be classified
+against a superclass ``Executive``, and ``Engineer`` and ``Sysadmin`` were
+classified against a superclass ``Technologist``. Neither ``Executive`` or
+``Technologist`` is ever instantiated, therefore have no
+:paramref:`_orm.Mapper.polymorphic_identity`. These classes can be configured
+using :paramref:`_orm.Mapper.polymorphic_abstract` as follows::
+
+ class Employee(Base):
+ __tablename__ = "employee"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str]
+ type: Mapped[str]
+
+ __mapper_args__ = {
+ "polymorphic_identity": "employee",
+ "polymorphic_on": "type",
+ }
+
+
+ class Executive(Employee):
+ """An executive of the company"""
+
+ executive_background: Mapped[str] = mapped_column(nullable=True)
+
+ __mapper_args__ = {"polymorphic_abstract": True}
+
+
+ class Technologist(Employee):
+ """An employee who works with technology"""
+
+ competencies: Mapped[str] = mapped_column(nullable=True)
+
+ __mapper_args__ = {"polymorphic_abstract": True}
+
+
+ class Manager(Executive):
+ """a manager"""
+
+ __mapper_args__ = {"polymorphic_identity": "manager"}
+
+
+ class Principal(Executive):
+ """a principal of the company"""
+
+ __mapper_args__ = {"polymorphic_identity": "principal"}
+
+
+ class Engineer(Technologist):
+ """an engineer"""
+
+ __mapper_args__ = {"polymorphic_identity": "engineer"}
+
+
+ class SysAdmin(Technologist):
+ """a systems administrator"""
+
+ __mapper_args__ = {"polymorphic_identity": "engineer"}
+
+In the above example, the new classes ``Technologist`` and ``Executive``
+are ordinary mapped classes, and also indicate new columns to be added to the
+superclass called ``executive_background`` and ``competencies``. However,
+they both lack a setting for :paramref:`_orm.Mapper.polymorphic_identity`;
+this is because it's not expected that ``Technologist`` or ``Executive`` would
+ever be instantiated directly; we'd always have one of ``Manager``, ``Principal``,
+``Engineer`` or ``SysAdmin``. We can however query for
+``Principal`` and ``Technologist`` roles, as well as have them be targets
+of :func:`_orm.relationship`. The example below demonstrates a SELECT
+statement for ``Technologist`` objects:
+
+
+.. sourcecode:: python+sql
+
+ session.scalars(select(Technologist)).all()
+ {execsql}
+ SELECT employee.id, employee.name, employee.type, employee.competencies
+ FROM employee
+ WHERE employee.type IN (?, ?)
+ [...] ('engineer', 'sysadmin')
+
+The ``Technologist`` and ``Executive`` abstract mapped classes may also be
+made the targets of :func:`_orm.relationship` mappings, like any other
+mapped class. We can extend the above example to include ``Company``,
+with separate collections ``Company.technologists`` and ``Company.principals``::
+
+ class Company(Base):
+ __tablename__ = "company"
+ id = Column(Integer, primary_key=True)
+
+ executives: Mapped[List[Executive]] = relationship()
+ technologists: Mapped[List[Technologist]] = relationship()
+
+
+ class Employee(Base):
+ __tablename__ = "employee"
+ id: Mapped[int] = mapped_column(primary_key=True)
+
+ # foreign key to "company.id" is added
+ company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
+
+ # rest of mapping is the same
+ name: Mapped[str]
+ type: Mapped[str]
+
+ __mapper_args__ = {
+ "polymorphic_on": "type",
+ }
+
+
+ # Executive, Technologist, Manager, Principal, Engineer, SysAdmin
+ # classes from previous example would follow here unchanged
+
+Using the above mapping we can use joins and relationship loading techniques
+across ``Company.technologists`` and ``Company.executives`` individually:
+
+.. sourcecode:: python+sql
+
+ session.scalars(
+ select(Company)
+ .join(Company.technologists)
+ .where(Technologist.competency.ilike("%java%"))
+ .options(selectinload(Company.executives))
+ ).all()
+ {execsql}
+ SELECT company.id
+ FROM company JOIN employee ON company.id = employee.company_id AND employee.type IN (?, ?)
+ WHERE lower(employee.competencies) LIKE lower(?)
+ [...] ('engineer', 'sysadmin', '%java%')
+
+ SELECT employee.company_id AS employee_company_id, employee.id AS employee_id,
+ employee.name AS employee_name, employee.type AS employee_type,
+ employee.executive_background AS employee_executive_background
+ FROM employee
+ WHERE employee.company_id IN (?) AND employee.type IN (?, ?)
+ [...] (1, 'manager', 'principal')
+
+
+
+.. seealso::
+
+ :ref:`declarative_abstract` - Declarative parameter which allows a
+ Declarative class to be completely un-mapped within a hierarchy, while
+ still extending from a mapped superclass.
+
Loading Single Inheritance Mappings
+++++++++++++++++++++++++++++++++++
def mapper_args():
args = m_args()
args["polymorphic_on"] = pjoin.c[discriminator_name]
+ args["polymorphic_abstract"] = True
if strict_attrs:
args["include_properties"] = (
set(pjoin.primary_key)
polymorphic_identity: Optional[Any] = None,
concrete: bool = False,
with_polymorphic: Optional[_WithPolymorphicArg] = None,
+ polymorphic_abstract: bool = False,
polymorphic_load: Optional[Literal["selectin", "inline"]] = None,
allow_partial_pks: bool = True,
batch: bool = True,
produced as a result of the ``__tablename__``
and :class:`_schema.Column` arguments present.
+ :param polymorphic_abstract: Indicates this class will be mapped in a
+ polymorphic hierarchy, but not directly instantiated. The class is
+ mapped normally, except that it has no requirement for a
+ :paramref:`_orm.Mapper.polymorphic_identity` within an inheritance
+ hierarchy. The class however must be part of a polymorphic
+ inheritance scheme which uses
+ :paramref:`_orm.Mapper.polymorphic_on` at the base.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :ref:`orm_inheritance_abstract_poly`
+
:param always_refresh: If True, all query operations for this mapped
class will overwrite all data within object instances that already
exist within the session, erasing any in-memory changes with
:ref:`inheritance_toplevel`
:param polymorphic_identity: Specifies the value which
- identifies this particular class as returned by the
- column expression referred to by the ``polymorphic_on``
- setting. As rows are received, the value corresponding
- to the ``polymorphic_on`` column expression is compared
- to this value, indicating which subclass should
- be used for the newly reconstructed object.
+ identifies this particular class as returned by the column expression
+ referred to by the :paramref:`_orm.Mapper.polymorphic_on` setting. As
+ rows are received, the value corresponding to the
+ :paramref:`_orm.Mapper.polymorphic_on` column expression is compared
+ to this value, indicating which subclass should be used for the newly
+ reconstructed object.
+
+ .. seealso::
+
+ :ref:`inheritance_toplevel`
:param properties: A dictionary mapping the string names of object
attributes to :class:`.MapperProperty` instances, which define the
if polymorphic_on is not None
else None
)
+ self.polymorphic_abstract = polymorphic_abstract
self._dependency_processors = []
self.validators = util.EMPTY_DICT
self.passive_updates = passive_updates
if self.polymorphic_identity is None:
self._identity_class = self.class_
- if self.inherits.base_mapper.polymorphic_on is not None:
+ if (
+ not self.polymorphic_abstract
+ and self.inherits.base_mapper.polymorphic_on is not None
+ ):
util.warn(
- "Mapper %s does not indicate a polymorphic_identity, "
+ f"{self} does not indicate a 'polymorphic_identity', "
"yet is part of an inheritance hierarchy that has a "
- "polymorphic_on column of '%s'. Objects of this type "
- "cannot be loaded polymorphically which can lead to "
- "degraded or incorrect loading behavior in some "
- "scenarios. Please establish a polmorphic_identity "
- "for this class, or leave it un-mapped. "
- "To omit mapping an intermediary class when using "
- "declarative, set the '__abstract__ = True' "
- "attribute on that class."
- % (self, self.inherits.base_mapper.polymorphic_on)
+ f"'polymorphic_on' column of "
+ f"'{self.inherits.base_mapper.polymorphic_on}'. "
+ "If this is an intermediary class that should not be "
+ "instantiated, the class may either be left unmapped, "
+ "or may include the 'polymorphic_abstract=True' "
+ "parameter in its Mapper arguments. To leave the "
+ "class unmapped when using Declarative, set the "
+ "'__abstract__ = True' attribute on the class."
)
elif self.concrete:
self._identity_class = self.class_
# column in the property
self.polymorphic_on = prop.columns[0]
polymorphic_key = prop.key
-
else:
# no polymorphic_on was set.
# check inheriting mappers for one.
self._polymorphic_attr_key = None
return
+ if self.polymorphic_abstract and self.polymorphic_on is None:
+ raise sa_exc.InvalidRequestError(
+ "The Mapper.polymorphic_abstract parameter may only be used "
+ "on a mapper hierarchy which includes the "
+ "Mapper.polymorphic_on parameter at the base of the hierarchy."
+ )
+
if setter:
def _set_polymorphic_identity(state):
dict_ = state.dict
# TODO: what happens if polymorphic_on column attribute name
# does not match .key?
+
+ polymorphic_identity = (
+ state.manager.mapper.polymorphic_identity
+ )
+ if (
+ polymorphic_identity is None
+ and state.manager.mapper.polymorphic_abstract
+ ):
+ raise sa_exc.InvalidRequestError(
+ f"Can't instantiate class for {state.manager.mapper}; "
+ "mapper is marked polymorphic_abstract=True"
+ )
+
state.get_impl(polymorphic_key).set(
state,
dict_,
- state.manager.mapper.polymorphic_identity,
+ polymorphic_identity,
None,
)
if self.single and self.inherits and self.polymorphic_on is not None:
return self.polymorphic_on._annotate(
{"parententity": self, "parentmapper": self}
- ).in_([m.polymorphic_identity for m in self.self_and_descendants])
+ ).in_(
+ [
+ m.polymorphic_identity
+ for m in self.self_and_descendants
+ if not m.polymorphic_abstract
+ ]
+ )
else:
return None
self._roundtrip(Employee, Manager, Engineer, Boss)
+ with expect_raises_message(
+ sa_exc.InvalidRequestError,
+ r"Can't instantiate class for Mapper\[Employee\(pjoin\)\]; "
+ r"mapper is marked polymorphic_abstract=True",
+ ):
+ Employee()
+
@testing.combinations(True, False)
def test_abstract_concrete_extension_descriptor_refresh(
self, use_strict_attrs
class DeclarativeInheritanceTest(DeclarativeTestBase):
- @testing.emits_warning(r".*does not indicate a polymorphic_identity")
+ @testing.emits_warning(r".*does not indicate a 'polymorphic_identity'")
def test_we_must_copy_mapper_args(self):
class Person(Base):
)
with expect_warnings(
- r"Mapper Mapper\[B\(base\)\] does not indicate a "
- "polymorphic_identity,"
+ r"Mapper\[B\(base\)\] does not indicate a "
+ "'polymorphic_identity',"
):
cls.mapper_registry.map_imperatively(B, inherits=A)
cls.mapper_registry.map_imperatively(
__mapper_args__ = {"polymorphic_identity": "b"}
with expect_warnings(
- r"Mapper Mapper\[C\(a\)\] does not indicate a "
- "polymorphic_identity,"
+ r"Mapper\[C\(a\)\] does not indicate a " "'polymorphic_identity',"
):
class C(A):
+from __future__ import annotations
+
from contextlib import nullcontext
+from typing import List
from sqlalchemy import and_
+from sqlalchemy import exc
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import inspect
from sqlalchemy.orm import column_property
from sqlalchemy.orm import join as orm_join
from sqlalchemy.orm import joinedload
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
+from sqlalchemy.orm import selectinload
from sqlalchemy.orm import Session
from sqlalchemy.orm import subqueryload
from sqlalchemy.orm import with_polymorphic
from sqlalchemy.sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
from sqlalchemy.testing import AssertsCompiledSQL
from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_raises_message
from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import mock
from sqlalchemy.testing.assertsql import CompiledSQL
from sqlalchemy.testing.fixtures import fixture_session
from sqlalchemy.testing.schema import Column
"SELECT (SELECT max(employee.id) AS max_1 FROM employee "
"WHERE employee.type IN (__[POSTCOMPILE_type_1])) AS anon_1",
)
+
+
+class AbstractPolymorphicTest(
+ AssertsCompiledSQL, fixtures.DeclarativeMappedTest
+):
+ """test new polymorphic_abstract feature added as of #9060"""
+
+ __dialect__ = "default"
+
+ @classmethod
+ def setup_classes(cls):
+ Base = cls.DeclarativeBasic
+
+ class Company(Base):
+ __tablename__ = "company"
+ id = Column(Integer, primary_key=True)
+
+ executives: Mapped[List[Executive]] = relationship()
+ technologists: Mapped[List[Technologist]] = relationship()
+
+ class Employee(Base):
+ __tablename__ = "employee"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
+ name: Mapped[str]
+ type: Mapped[str]
+
+ __mapper_args__ = {
+ "polymorphic_on": "type",
+ }
+
+ class Executive(Employee):
+ """An executive of the company"""
+
+ executive_background: Mapped[str] = mapped_column(nullable=True)
+ __mapper_args__ = {"polymorphic_abstract": True}
+
+ class Technologist(Employee):
+ """An employee who works with technology"""
+
+ competencies: Mapped[str] = mapped_column(nullable=True)
+ __mapper_args__ = {"polymorphic_abstract": True}
+
+ class Manager(Executive):
+ """a manager"""
+
+ __mapper_args__ = {"polymorphic_identity": "manager"}
+
+ class Principal(Executive):
+ """a principal of the company"""
+
+ __mapper_args__ = {"polymorphic_identity": "principal"}
+
+ class Engineer(Technologist):
+ """an engineer"""
+
+ __mapper_args__ = {"polymorphic_identity": "engineer"}
+
+ class SysAdmin(Technologist):
+ """a systems administrator"""
+
+ __mapper_args__ = {"polymorphic_identity": "sysadmin"}
+
+ def test_select_against_abstract(self):
+ Technologist = self.classes.Technologist
+
+ self.assert_compile(
+ select(Technologist).where(
+ Technologist.competencies.like("%Java%")
+ ),
+ "SELECT employee.id, employee.company_id, employee.name, "
+ "employee.type, employee.competencies FROM employee "
+ "WHERE employee.competencies LIKE :competencies_1 "
+ "AND employee.type IN (:type_1_1, :type_1_2)",
+ checkparams={
+ "competencies_1": "%Java%",
+ "type_1_1": "engineer",
+ "type_1_2": "sysadmin",
+ },
+ render_postcompile=True,
+ )
+
+ def test_relationship_join(self):
+ Technologist = self.classes.Technologist
+ Company = self.classes.Company
+
+ self.assert_compile(
+ select(Company)
+ .join(Company.technologists)
+ .where(Technologist.competencies.like("%Java%")),
+ "SELECT company.id FROM company JOIN employee "
+ "ON company.id = employee.company_id AND employee.type "
+ "IN (:type_1_1, :type_1_2) WHERE employee.competencies "
+ "LIKE :competencies_1",
+ checkparams={
+ "competencies_1": "%Java%",
+ "type_1_1": "engineer",
+ "type_1_2": "sysadmin",
+ },
+ render_postcompile=True,
+ )
+
+ @testing.fixture
+ def data_fixture(self, connection):
+ Company = self.classes.Company
+ Engineer = self.classes.Engineer
+ Manager = self.classes.Manager
+ Principal = self.classes.Principal
+
+ with Session(connection) as sess:
+ sess.add(
+ Company(
+ technologists=[
+ Engineer(name="e1", competencies="Java programming")
+ ],
+ executives=[
+ Manager(name="m1", executive_background="eb1"),
+ Principal(name="p1", executive_background="eb2"),
+ ],
+ )
+ )
+ sess.flush()
+
+ yield sess
+
+ def test_relationship_join_w_eagerload(self, data_fixture):
+ Company = self.classes.Company
+ Technologist = self.classes.Technologist
+
+ session = data_fixture
+
+ with self.sql_execution_asserter() as asserter:
+ session.scalars(
+ select(Company)
+ .join(Company.technologists)
+ .where(Technologist.competencies.ilike("%java%"))
+ .options(selectinload(Company.executives))
+ ).all()
+
+ asserter.assert_(
+ CompiledSQL(
+ "SELECT company.id FROM company JOIN employee ON "
+ "company.id = employee.company_id AND employee.type "
+ "IN (__[POSTCOMPILE_type_1]) WHERE "
+ "lower(employee.competencies) LIKE lower(:competencies_1)",
+ [
+ {
+ "type_1": ["engineer", "sysadmin"],
+ "competencies_1": "%java%",
+ }
+ ],
+ ),
+ CompiledSQL(
+ "SELECT employee.company_id AS employee_company_id, "
+ "employee.id AS employee_id, employee.name AS employee_name, "
+ "employee.type AS employee_type, "
+ "employee.executive_background AS "
+ "employee_executive_background "
+ "FROM employee WHERE employee.company_id "
+ "IN (__[POSTCOMPILE_primary_keys]) "
+ "AND employee.type IN (__[POSTCOMPILE_type_1])",
+ [
+ {
+ "primary_keys": [mock.ANY],
+ "type_1": ["manager", "principal"],
+ }
+ ],
+ ),
+ )
+
+ @testing.variation("given_type", ["none", "invalid", "valid"])
+ def test_no_instantiate(self, given_type):
+ Technologist = self.classes.Technologist
+
+ with expect_raises_message(
+ exc.InvalidRequestError,
+ r"Can't instantiate class for Mapper\[Technologist\(employee\)\]; "
+ r"mapper is marked polymorphic_abstract=True",
+ ):
+ if given_type.none:
+ Technologist()
+ elif given_type.invalid:
+ Technologist(type="madeup")
+ elif given_type.valid:
+ Technologist(type="engineer")
+ else:
+ given_type.fail()
+
+ def test_not_supported_wo_poly_inheriting(self, decl_base):
+ class MyClass(decl_base):
+ __tablename__ = "my_table"
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+
+ with expect_raises_message(
+ exc.InvalidRequestError,
+ "The Mapper.polymorphic_abstract parameter may only be used "
+ "on a mapper hierarchy which includes the Mapper.polymorphic_on",
+ ):
+
+ class Nope(MyClass):
+ __mapper_args__ = {"polymorphic_abstract": True}
+
+ def test_not_supported_wo_poly_base(self, decl_base):
+ with expect_raises_message(
+ exc.InvalidRequestError,
+ "The Mapper.polymorphic_abstract parameter may only be used "
+ "on a mapper hierarchy which includes the Mapper.polymorphic_on",
+ ):
+
+ class Nope(decl_base):
+ __tablename__ = "my_table"
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+ __mapper_args__ = {"polymorphic_abstract": True}