From: Mike Bayer Date: Thu, 18 Aug 2022 15:54:04 +0000 (-0400) Subject: more abstractconcretebase clarity X-Git-Tag: rel_2_0_0b1~106 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a47d76ca25275344345b208def5f72292e8687b4;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git more abstractconcretebase clarity Change-Id: I9ddb6b1a2e0c0be1fe355a7ea714d0e16aa93b47 --- diff --git a/doc/build/orm/inheritance.rst b/doc/build/orm/inheritance.rst index 79d9b74051..f006b0ac74 100644 --- a/doc/build/orm/inheritance.rst +++ b/doc/build/orm/inheritance.rst @@ -669,7 +669,10 @@ Using :class:`.ConcreteBase`, we can set up our concrete mapping in almost the same way as we do other forms of inheritance mappings:: from sqlalchemy.ext.declarative import ConcreteBase + from sqlalchemy.orm import DeclarativeBase + class Base(DeclarativeBase): + pass class Employee(ConcreteBase, Base): __tablename__ = "employee" @@ -755,6 +758,12 @@ The above UNION query needs to manufacture "NULL" columns for each subtable in order to accommodate for those columns that aren't members of that particular subclass. +.. seealso:: + + :class:`.ConcreteBase` + +.. _abstract_concrete_base: + Abstract Concrete Classes +++++++++++++++++++++++++ @@ -769,6 +778,11 @@ tables, and leave the base class unmapped, this can be achieved very easily. When using Declarative, just declare the base class with the ``__abstract__`` indicator:: + from sqlalchemy.orm import DeclarativeBase + + class Base(DeclarativeBase): + pass + class Employee(Base): __abstract__ = True @@ -779,10 +793,6 @@ base class with the ``__abstract__`` indicator:: name = mapped_column(String(50)) manager_data = mapped_column(String(40)) - __mapper_args__ = { - "polymorphic_identity": "manager", - } - class Engineer(Employee): __tablename__ = "engineer" @@ -790,10 +800,6 @@ base class with the ``__abstract__`` indicator:: name = mapped_column(String(50)) engineer_info = mapped_column(String(40)) - __mapper_args__ = { - "polymorphic_identity": "engineer", - } - Above, we are not actually making use of SQLAlchemy's inheritance mapping facilities; we can load and persist instances of ``Manager`` and ``Engineer`` normally. The situation changes however when we need to **query polymorphically**, @@ -813,6 +819,10 @@ To help with this, Declarative offers a variant of the :class:`.ConcreteBase` class called :class:`.AbstractConcreteBase` which achieves this automatically:: from sqlalchemy.ext.declarative import AbstractConcreteBase + from sqlalchemy.orm import DeclarativeBase + + class Base(DeclarativeBase): + pass class Employee(AbstractConcreteBase, Base): @@ -842,13 +852,22 @@ class called :class:`.AbstractConcreteBase` which achieves this automatically:: "concrete": True, } -The :class:`.AbstractConcreteBase` helper class has a more complex internal -process than that of :class:`.ConcreteBase`, in that the entire mapping + Base.registry.configure() + +Above, the :meth:`_orm.registry.configure` method is invoked, which will +trigger the ``Employee`` class to be actually mapped; before the configuration +step, the class has no mapping as the sub-tables which it will query from +have not yet been defined. This process is more complex than that of +:class:`.ConcreteBase`, in that the entire mapping of the base class must be delayed until all the subclasses have been declared. With a mapping like the above, only instances of ``Manager`` and ``Engineer`` may be persisted; querying against the ``Employee`` class will always produce ``Manager`` and ``Engineer`` objects. +.. seealso:: + + :class:`.AbstractConcreteBase` + Classical and Semi-Classical Concrete Polymorphic Configuration +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/lib/sqlalchemy/ext/declarative/extensions.py b/lib/sqlalchemy/ext/declarative/extensions.py index 87fc702cfb..29eb76e82a 100644 --- a/lib/sqlalchemy/ext/declarative/extensions.py +++ b/lib/sqlalchemy/ext/declarative/extensions.py @@ -134,8 +134,8 @@ class AbstractConcreteBase(ConcreteBase): .. note:: - The :class:`.AbstractConcreteBase` class does not intend to set up the - mapping for the base class until all the subclasses have been defined, + The :class:`.AbstractConcreteBase` delays the mapper creation of the + base class until all the subclasses have been defined, as it needs to create a mapping against a selectable that will include all subclass tables. In order to achieve this, it waits for the **mapper configuration event** to occur, at which point it scans @@ -145,23 +145,22 @@ class AbstractConcreteBase(ConcreteBase): While this event is normally invoked automatically, in the case of :class:`.AbstractConcreteBase`, it may be necessary to invoke it explicitly after **all** subclass mappings are defined, if the first - operation is to be a query against this base class. To do so, invoke - :func:`.configure_mappers` once all the desired classes have been - configured:: - - from sqlalchemy.orm import configure_mappers - - configure_mappers() - - .. seealso:: - - :func:`_orm.configure_mappers` + operation is to be a query against this base class. To do so, once all + the desired classes have been configured, the + :meth:`_orm.registry.configure` method on the :class:`_orm.registry` + in use can be invoked, which is available in relation to a particular + declarative base class:: + Base.registry.configure() Example:: + from sqlalchemy.orm import DeclarativeBase from sqlalchemy.ext.declarative import AbstractConcreteBase + class Base(DeclarativeBase): + pass + class Employee(AbstractConcreteBase, Base): pass @@ -173,9 +172,10 @@ class AbstractConcreteBase(ConcreteBase): __mapper_args__ = { 'polymorphic_identity':'manager', - 'concrete':True} + 'concrete':True + } - configure_mappers() + Base.registry.configure() The abstract base class is handled by declarative in a special way; at class configuration time, it behaves like a declarative mixin @@ -211,18 +211,17 @@ class AbstractConcreteBase(ConcreteBase): __mapper_args__ = { 'polymorphic_identity':'manager', - 'concrete':True} + 'concrete':True + } - configure_mappers() + Base.registry.configure() When we make use of our mappings however, both ``Manager`` and ``Employee`` will have an independently usable ``.company`` attribute:: - session.query(Employee).filter(Employee.company.has(id=5)) - - .. versionchanged:: 1.0.0 - The mechanics of :class:`.AbstractConcreteBase` - have been reworked to support relationships established directly - on the abstract base, without any special configurational steps. + session.execute( + select(Employee).filter(Employee.company.has(id=5)) + ) .. seealso:: @@ -230,6 +229,8 @@ class AbstractConcreteBase(ConcreteBase): :ref:`concrete_inheritance` + :ref:`abstract_concrete_base` + """ __no_table__ = True