I've managed to achieve something close to what I want by manually
setting with_polymorphic to refer to a selectable, and then assigning
columns:

    column_names = ["id", "name"]
    select_employees = sql.union_all(*[
        sql.select(
            [getattr(model, name) for name in column_names] +
            
[sql.literal(model.__mapper_args__["polymorphic_identity"]).label("type")]
        ) \
            .select_from(model.__table__.join(Employee.__table__))
        for model in [Manager, Engineer]
    ]).alias("employees")

    Employee.__mapper__.with_polymorphic = ('*', select_employees)
    Employee.__mapper__.polymorphic_on = select_employees.c.type
    Employee.type = column_property(select_employees.c.type)
    Employee.name = column_property(select_employees.c.name)

The downsides of this approach are:

* If the select doesn't cover all of the columns, the generated SQL
  seems to start doing cartesian products and getting the wrong
  results. I don't mind having to manually list all of the columns, but
  I'd prefer an exception if I miss one (rather than a cartesian
  product). Is there a way to enforce this?

* Joining onto the employee is redundant since we already have the ID
  from the foreign key on the subtypes table. If there is a way to get
  rid of this join, that would be great, but I can live with it if not.
  (Leaving it out causes a cartesian product to be generated, as if the
  IDs on the subtypes are missing.)

Full code for reference:

    from __future__ import unicode_literals

    from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, 
sql
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import Session, column_property


    Base = declarative_base()


    class Employee(Base):
        __tablename__ = "employee"
        id = Column(Integer, primary_key=True)


    class Manager(Employee):
        __tablename__ = "manager"
        __mapper_args__ = {
            "polymorphic_identity": "manager",
        }

        id = Column(Integer, ForeignKey(Employee.id), primary_key=True)
        name = Column(String)


    class Engineer(Employee):
        __tablename__ = "engineer"
        __mapper_args__ = {
            "polymorphic_identity": "employee",
        }

        id = Column(Integer, ForeignKey(Employee.id), primary_key=True)
        name = Column(String)


    column_names = ["id", "name"]
    select_employees = sql.union_all(*[
        sql.select(
            [getattr(model, name) for name in column_names] +
            
[sql.literal(model.__mapper_args__["polymorphic_identity"]).label("type")]
        ) \
            .select_from(model.__table__.join(Employee.__table__))
        for model in [Manager, Engineer]
    ]).alias("employees")

    Employee.__mapper__.with_polymorphic = ('*', select_employees)
    Employee.__mapper__.polymorphic_on = select_employees.c.type
    Employee.type = column_property(select_employees.c.type)
    Employee.name = column_property(select_employees.c.name)

    engine = create_engine("sqlite:///:memory:", echo=True)

    Base.metadata.create_all(engine)
    session = Session(engine)

    session.add(Engineer(name="Jim"))
    session.add(Manager(name="Jules"))
    session.commit()

    print(session.query(Employee).all())


On Wed, 16 Nov 2016 15:18:54 +0000
Michael Williamson <[email protected]> wrote:

> When using AbstractConcreteBase to implement inheritance with a
> separate table per type (and no common table), it seems like
> properties that are present on all of the subtypes are lifted up onto
> the super type. Is there a way to get the same behaviour when using
> joined table inheritance? For instance, at the moment, the below
> fails since Employee.name doesn't exist. Is there a way to get
> Employee.name to be automatically generated from the subtypes?
> 
>     from __future__ import unicode_literals
> 
>     from sqlalchemy import create_engine, Column, Integer, String,
>     ForeignKey from sqlalchemy.ext.declarative import declarative_base
>     from sqlalchemy.orm import Session
> 
> 
>     Base = declarative_base()
> 
> 
>     class Employee(Base):
>         __tablename__ = "employee"
>         __mapper_args__ = {
>             "polymorphic_on": "discriminator",
>         }
>         id = Column(Integer, primary_key=True)
>         discriminator = Column(String)
> 
> 
>     class Manager(Employee):
>         __tablename__ = "manager"
>         __mapper_args__ = {
>             "polymorphic_identity": "manager",
>         }
> 
>         id = Column(Integer, ForeignKey(Employee.id),
> primary_key=True) name = Column(String)
> 
> 
>     class Engineer(Employee):
>         __tablename__ = "engineer"
>         __mapper_args__ = {
>             "polymorphic_identity": "employee",
>         }
> 
>         id = Column(Integer, ForeignKey(Employee.id),
> primary_key=True) name = Column(String)
> 
>     engine = create_engine("sqlite:///:memory:", echo=True)
> 
>     Base.metadata.create_all(engine)
>     session = Session(engine)
> 
>     session.add(Engineer(name="Jim"))
>     session.add(Manager(name="Jules"))
>     session.commit()
> 
>     print(session.query(Employee.name).all())
> 
> 
> For comparison, when using AbstractConcreteBase, it's possible to
> reference Employee.name:
> 
>     from __future__ import unicode_literals
> 
>     from sqlalchemy import create_engine, Column, Integer, String
>     from sqlalchemy.ext.declarative import declarative_base,
>     AbstractConcreteBase from sqlalchemy.orm import Session
> 
> 
>     Base = declarative_base()
> 
> 
>     class Employee(AbstractConcreteBase, Base):
>         pass
> 
> 
>     class Manager(Employee):
>         __tablename__ = "manager"
>         __mapper_args__ = {
>             "polymorphic_identity": "manager",
>             "concrete": True
>         }
> 
>         id = Column(Integer, primary_key=True)
>         name = Column(String)
> 
> 
>     class Engineer(Employee):
>         __tablename__ = "engineer"
>         __mapper_args__ = {
>             "polymorphic_identity": "employee",
>             "concrete": True,
>         }
> 
>         id = Column(Integer, primary_key=True)
>         name = Column(String)
> 
>     engine = create_engine("sqlite:///:memory:", echo=True)
> 
>     Base.metadata.create_all(engine)
>     session = Session(engine)
> 
>     session.add(Engineer(name="Jim"))
>     session.add(Manager(name="Jules"))
>     session.commit()
> 
>     print(session.query(Employee.name).all())
> 
> 
> Thanks (both in advance for this question, and for putting up with my
> other questions!)
> 
> Michael

-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to