This is what I ended up with, and it *seems* to work pretty well. Does my
approach here pass the sniff test?

from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import sessionmaker, relationship, column_property
from sqlalchemy import (
    Column, Integer, ForeignKey, Text,
    select, create_engine,
)

Base = declarative_base()


class HybridType(Base):
    __tablename__ = 'hybrid_types'
    id = Column(Integer, primary_key=True)
    name = Column(Text)


class HybridModel(Base):
    __tablename__ = 'hybrids'
    hybrid_type_identity = 'base'

    id = Column(Integer, primary_key=True)
    name = Column(Text)
    hybrid_type_id = Column(Integer, ForeignKey('hybrid_types.id'))
    hybrid_type = relationship('HybridType')

    @declared_attr
    def __mapper_args__(cls):
        return dict(
            polymorphic_on=cls.hybrid_type_name,
            polymorphic_identity=cls.hybrid_type_identity,
        )

    def __repr__(self):
        return "{} ({}:{})".format(type(self).__name__, self.name, self.id)

    def __init__(self, **kwargs):
        self.hybrid_type_name = self.hybrid_type_identity
        super(HybridModel, self).__init__(**kwargs)

    @hybrid_property
    def hybrid_type_name(self):
        return self.hybrid_type.name

    @hybrid_type_name.expression
    def hybrid_type_name(cls):
        return (
            select([HybridType.name]).
            where(HybridType.id == cls.hybrid_type_id).
            as_scalar()
        )

    @hybrid_type_name.setter
    def hybrid_type_name(self, value):
        self.hybrid_type_id = (
            select([HybridType.id]).
            where(HybridType.name == value)
        )


class HybridAlpha(HybridModel):
    hybrid_type_identity = 'alpha'


class HybridBeta(HybridModel):
    hybrid_type_identity = 'beta'


engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
session.add(HybridType(name='base'))
session.add(HybridType(name='alpha'))
session.add(HybridType(name='beta'))
session.add(HybridModel(name='base_instance'))
session.add(HybridAlpha(name='alpha_instance'))
session.add(HybridBeta(name='beta_instance'))
print(session.query(HybridModel).all())
print(session.query(HybridAlpha).all())
print(session.query(HybridAlpha).one().hybrid_type_name)
session.query(HybridAlpha).one().hybrid_type_name = 'beta'
print(session.query(HybridBeta).all())


On Wed, Nov 30, 2016 at 7:20 AM, mike bayer <mike...@zzzcomputing.com>
wrote:

>
>
> On 11/29/2016 07:31 PM, Tucker Beck wrote:
>
>> Thanks for this! What allows the query to filter in the derived classes
>> when using a column property instead of a hybrid property? Is it
>> declaring the __mapper_args__ in the base class declaration instead of
>> after it?
>>
>
> Yea I don't think setting __mapper_args__ after the fact on the class does
> anything.   Those __mapper_args__ are only for the mapper() constructor
> directly, which gets called when the class is declared.
>
> you can set the actual polymorphic_on property right now only using the
> special class.__mapper__._set_polymorphic_on(<something>), that's one of
> a very few setters that's available on the mapper after the fact and even
> then it's not fully public API right now.
>
>
>
>> Also, it seems like having the hybrid_type_prop column property obviates
>> the need for the hybrid property hybrid_type_name...that is, except for
>> the setter. Is there a way to give a column_property a setter?
>>
>
> a column_property against a SQL expression is strictly a read-only thing,
> since "writing" to it makes no sense in terms of persistence.
>
>
> the column_property aspect here isn't strictly necessary, the mapper will
> create the property internally as a special case for the polymorphic_on
> setting if not provided.  the example can be re-organized w/ your hybrid in
> at least two ways:
>
>
> class HybridModel(Base):
>     # ...
>
>     @declared_attr
>     def __mapper_args__(cls):
>         return {
>             "polymorphic_on": cls.hybrid_type_name
>         }
>
>
> or
>
>
> class HybridModel(Base):
>     # ...
>
>     _hybrid_name_expr = (
>         select([HybridType.name]).
>         where(HybridType.id == hybrid_type_id).
>         as_scalar()
>     )
>
>     __mapper_args__ = {
>         "polymorphic_on": _hybrid_name_expr
>     }
>
>     # ...
>
>     @hybrid_type_name.expression
>     def hybrid_type_name(cls):
>         return cls._hybrid_name_expr
>
>
>
>> On Tue, Nov 29, 2016 at 2:59 PM, mike bayer <mike...@zzzcomputing.com
>> <mailto:mike...@zzzcomputing.com>> wrote:
>>
>>
>>
>>     On 11/29/2016 05:01 PM, Tucker Beck wrote:
>>
>>         The issue with using the foreign_key integer value as the
>>         discriminator
>>         is that you won't know what that is at class declaration time. The
>>         type_name, however, you can declare as a part of the class as
>>         you would
>>         with a normal string discriminator. I'm not sure how you would do
>> a
>>         correlated subquery for the polymorphic_on attribute. I modified
>> the
>>         query object so that I could filter results for the derived
>>         classes. It
>>         doesn't seem like that happens automatically. Maybe I'm just not
>>         doing
>>         it right.
>>
>>
>>     The correlated subquery will not scale as well as a direct
>>     identifier, but here is an adaption of your test using
>>     column_property(), and you should be able to set polymorphic_on to
>>     the select() object directly too.  There's another example of this
>>     at
>>     http://docs.sqlalchemy.org/en/latest/orm/mapping_api.html?hi
>> ghlight=polymorphic_on#sqlalchemy.orm.mapper.params.polymorphic_on
>>     <http://docs.sqlalchemy.org/en/latest/orm/mapping_api.html?
>> highlight=polymorphic_on#sqlalchemy.orm.mapper.params.polymorphic_on>.
>>
>>     For the "use the id" approach, you would need to query from
>>     HybridType up front and populate the polymorphic_identity attributes
>>     after the fact, this is a feature that is not directly supported yet
>>     but there is a recipe to do so at
>>     https://bitbucket.org/zzzeek/sqlalchemy/issues/2792/settable
>> -polymorphic-identity
>>     <https://bitbucket.org/zzzeek/sqlalchemy/issues/2792/settabl
>> e-polymorphic-identity>.
>>
>>
>>
>>     from sqlalchemy.ext.hybrid import hybrid_property
>>     from sqlalchemy.ext.declarative import declarative_base
>>     from sqlalchemy.orm import sessionmaker, relationship, column_property
>>     from sqlalchemy import (
>>         Column, Integer, ForeignKey, Text,
>>         select, create_engine,
>>     )
>>
>>     Base = declarative_base()
>>
>>
>>     class HybridType(Base):
>>         __tablename__ = 'hybrid_types'
>>         id = Column(Integer, primary_key=True)
>>         name = Column(Text)
>>
>>
>>     class HybridModel(Base):
>>         __tablename__ = 'hybrids'
>>
>>         id = Column(Integer, primary_key=True)
>>         name = Column(Text)
>>         hybrid_type_id = Column(Integer, ForeignKey('hybrid_types.id
>>     <http://hybrid_types.id>'))
>>         hybrid_type = relationship('HybridType')
>>
>>         hybrid_type_prop = column_property(
>>             select([HybridType.name]).
>>             where(HybridType.id == hybrid_type_id).
>>             as_scalar()
>>         )
>>
>>         __mapper_args__ = {
>>             "polymorphic_on": hybrid_type_prop
>>         }
>>
>>         def __repr__(self):
>>             return "{} ({}:{})".format(type(self).__name__, self.name
>>     <http://self.name>, self.id <http://self.id>)
>>
>>         def __init__(self, **kwargs):
>>             self.hybrid_type_name =
>>     type(self).__mapper_args__['polymorphic_identity']
>>             super(HybridModel, self).__init__(**kwargs)
>>
>>         @hybrid_property
>>         def hybrid_type_name(self):
>>             return self.hybrid_type.name <http://self.hybrid_type.name>
>>
>>         @hybrid_type_name.setter
>>         def hybrid_type_name(self, value):
>>             self.hybrid_type_id = (
>>                 select([HybridType.id]).
>>                 where(HybridType.name == value)
>>             )
>>
>>
>>     class HybridAlpha(HybridModel):
>>         __mapper_args__ = {'polymorphic_identity': 'alpha'}
>>
>>
>>     class HybridBeta(HybridModel):
>>         __mapper_args__ = {'polymorphic_identity': 'beta'}
>>
>>
>>     engine = create_engine('sqlite:///:memory:', echo=True)
>>     Base.metadata.create_all(engine)
>>     Session = sessionmaker(bind=engine)
>>     session = Session()
>>     session.add(HybridType(name='alpha'))
>>     session.add(HybridType(name='beta'))
>>     session.add(HybridAlpha(name='alpha_instance'))
>>     session.add(HybridBeta(name='beta_instance'))
>>     print(session.query(HybridAlpha).all())
>>
>>
>>
>>         On Tue, Nov 29, 2016 at 7:16 AM, mike bayer
>>         <mike...@zzzcomputing.com <mailto:mike...@zzzcomputing.com>
>>         <mailto:mike...@zzzcomputing.com
>>
>>         <mailto:mike...@zzzcomputing.com>>> wrote:
>>
>>
>>
>>             On 11/28/2016 05:58 PM, Tucker Beck wrote:
>>
>>                 Hello, I'm writing today about an interesting problem we
>> ran
>>                 into with
>>                 our sqlalchemy based data store api.
>>
>>                 Our schema is based on the star-schema idea where we have
>> a
>>                 large 'fact
>>                 table' with lots of rows. Within that table, each row has
>> a
>>                 foreign key
>>                 to a small 'dimension table' in which each row has a
>> unique
>>                 name. Thus,
>>                 the type of each row can be defined by the relationship
>>         between
>>                 the fact
>>                 row and the dimension row.
>>
>>                 We wanted to be able to use SQLAlchemy to add some custom
>>                 functionality
>>                 for the different types of rows we have in our 'fact
>>         table'. After
>>                 learning about SQLAlchemy's inheritance models, I
>>         decided to see
>>                 if we
>>                 could support the schema that we had already devised for
>> our
>>                 project.
>>                 The single-inheritance pattern seemed to fit the best,
>> but I
>>                 couldn't
>>                 find a single case where someone was using a star-schema
>> and
>>                 needed the
>>                 type discriminator to be derived from the foreign key to
>> the
>>                 dimension
>>                 table.
>>
>>                 Further, I found as I was digging into the mechanics of
>> the
>>                 thing that
>>                 you could not create a row in the fact table that was
>>         typed by the
>>                 derived class at creation time. And, you cannot limit
>>         queries
>>                 from the
>>                 fact table by creating the queries against the derived
>>         classes.
>>                 Suppose
>>                 that (using declarative base) I have the fact table
>>         represented by a
>>                 model called HybridModel. This model has two derived
>> classes
>>                 HybridAlpha
>>                 and HybridBeta. I would like to be able to create a new
>>         row in
>>                 the table
>>                 wrapped by HybridModel by calling something like
>>                 HybridAlpha(**kwargs)
>>                 and have the type of the new row reflect the inheritance
>>         model I've
>>                 described above. Next I wanted to be able to formulate a
>>         query
>>                 against
>>                 one of the derived models and have it limited by the
>>         type associated
>>                 with the derived class. So, calling something like
>>                 `session.query(HybridAlpha).all()` would only return
>>         rows with a
>>                 type
>>                 associated with the HybridAlpha model.
>>
>>                 After a lot of tinkering and experimentation, I've come
>>         up with the
>>                 following solution:
>>
>>
>>         https://gist.github.com/dusktreader/2658dedb8ca8e2fd941f8075
>> c75b3c89
>>         <https://gist.github.com/dusktreader/2658dedb8ca8e2fd941f807
>> 5c75b3c89>
>>
>>         <https://gist.github.com/dusktreader/2658dedb8ca8e2fd941f807
>> 5c75b3c89
>>         <https://gist.github.com/dusktreader/2658dedb8ca8e2fd941f807
>> 5c75b3c89>>
>>
>>                 I would appreciate any thoughts and feedback on the
>>         matter. I'm
>>                 not sure
>>                 that my approach to this solution has been sound, and I
>>         would
>>                 appreciate
>>                 feedback.
>>
>>
>>             I'd try to do things much more simply than this.   Most
>>         simply, just
>>             assign polymorphic_identity to be the integer foreign key
>> value.
>>             Otherwise, you should be able to do polymorphic_on on a
>>         correlated
>>             subquery, which itself you set up as a column_property().
>>          There
>>             should be no need to modify Query or anything like that.
>>
>>
>>             I'm out of time today but if you need more help I can try to
>>         work up
>>             an example later on.
>>
>>
>>
>>
>>
>>
>>                 Thanks, and keep up the great work! SQLAlchemy is just
>>         magic!
>>
>>                 --
>>                 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
>>         <http://stackoverflow.com/help/mcve>
>>                 <http://stackoverflow.com/help/mcve
>>         <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 sqlalchemy+unsubscr...@googlegroups.com
>>         <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>
>>                 <mailto:sqlalchemy%2bunsubscr...@googlegroups.com
>>         <mailto:sqlalchemy%252bunsubscr...@googlegroups.com>>
>>                 <mailto:sqlalchemy+unsubscr...@googlegroups.com
>>         <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>
>>                 <mailto:sqlalchemy%2bunsubscr...@googlegroups.com
>>         <mailto:sqlalchemy%252bunsubscr...@googlegroups.com>>>.
>>
>>                 To post to this group, send email to
>>         sqlalchemy@googlegroups.com <mailto:sqlalchemy@googlegroups.com>
>>                 <mailto:sqlalchemy@googlegroups.com
>>         <mailto:sqlalchemy@googlegroups.com>>
>>                 <mailto:sqlalchemy@googlegroups.com
>>         <mailto:sqlalchemy@googlegroups.com>
>>                 <mailto:sqlalchemy@googlegroups.com
>>         <mailto:sqlalchemy@googlegroups.com>>>.
>>                 Visit this group at
>>         https://groups.google.com/group/sqlalchemy
>>         <https://groups.google.com/group/sqlalchemy>
>>                 <https://groups.google.com/group/sqlalchemy
>>         <https://groups.google.com/group/sqlalchemy>>.
>>                 For more options, visit
>>         https://groups.google.com/d/optout
>>         <https://groups.google.com/d/optout>
>>                 <https://groups.google.com/d/optout
>>         <https://groups.google.com/d/optout>>.
>>
>>
>>             --
>>             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
>>         <http://stackoverflow.com/help/mcve>
>>             <http://stackoverflow.com/help/mcve
>>         <http://stackoverflow.com/help/mcve>> for a full description.
>>             --- You received this message because you are subscribed to
>>         a topic
>>             in the Google Groups "sqlalchemy" group.
>>             To unsubscribe from this topic, visit
>>
>>         https://groups.google.com/d/topic/sqlalchemy/KJXSHwbhbLA/uns
>> ubscribe
>>         <https://groups.google.com/d/topic/sqlalchemy/KJXSHwbhbLA/un
>> subscribe>
>>
>>         <https://groups.google.com/d/topic/sqlalchemy/KJXSHwbhbLA/un
>> subscribe
>>         <https://groups.google.com/d/topic/sqlalchemy/KJXSHwbhbLA/un
>> subscribe>>.
>>             To unsubscribe from this group and all its topics, send an
>>         email to
>>             sqlalchemy+unsubscr...@googlegroups.com
>>         <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>
>>             <mailto:sqlalchemy%2bunsubscr...@googlegroups.com
>>         <mailto:sqlalchemy%252bunsubscr...@googlegroups.com>>.
>>             To post to this group, send email to
>>         sqlalchemy@googlegroups.com <mailto:sqlalchemy@googlegroups.com>
>>             <mailto:sqlalchemy@googlegroups.com
>>         <mailto:sqlalchemy@googlegroups.com>>.
>>             Visit this group at
>>         https://groups.google.com/group/sqlalchemy
>>         <https://groups.google.com/group/sqlalchemy>
>>             <https://groups.google.com/group/sqlalchemy
>>         <https://groups.google.com/group/sqlalchemy>>.
>>             For more options, visit https://groups.google.com/d/optout
>>         <https://groups.google.com/d/optout>
>>             <https://groups.google.com/d/optout
>>         <https://groups.google.com/d/optout>>.
>>
>>
>>
>>
>>
>>         --
>>         -=Tucker A. Beck=-
>>
>>         Illustrious Writer
>>           Devious Coder
>>             Last Hope for the Free World
>>               Also, Modest
>>
>>         --
>>         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
>>         <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 sqlalchemy+unsubscr...@googlegroups.com
>>         <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>
>>         <mailto:sqlalchemy+unsubscr...@googlegroups.com
>>         <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>>.
>>         To post to this group, send email to sqlalchemy@googlegroups.com
>>         <mailto:sqlalchemy@googlegroups.com>
>>         <mailto:sqlalchemy@googlegroups.com
>>         <mailto:sqlalchemy@googlegroups.com>>.
>>         Visit this group at https://groups.google.com/group/sqlalchemy
>>         <https://groups.google.com/group/sqlalchemy>.
>>         For more options, visit https://groups.google.com/d/optout
>>         <https://groups.google.com/d/optout>.
>>
>>
>>     --
>>     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
>>     <http://stackoverflow.com/help/mcve> for a full description.
>>     --- You received this message because you are subscribed to a topic
>>     in the Google Groups "sqlalchemy" group.
>>     To unsubscribe from this topic, visit
>>     https://groups.google.com/d/topic/sqlalchemy/KJXSHwbhbLA/unsubscribe
>>     <https://groups.google.com/d/topic/sqlalchemy/KJXSHwbhbLA/unsubscribe
>> >.
>>     To unsubscribe from this group and all its topics, send an email to
>>     sqlalchemy+unsubscr...@googlegroups.com
>>     <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>.
>>     To post to this group, send email to sqlalchemy@googlegroups.com
>>     <mailto:sqlalchemy@googlegroups.com>.
>>     Visit this group at https://groups.google.com/group/sqlalchemy
>>     <https://groups.google.com/group/sqlalchemy>.
>>     For more options, visit https://groups.google.com/d/optout
>>     <https://groups.google.com/d/optout>.
>>
>>
>>
>>
>> --
>> -=Tucker A. Beck=-
>>
>> Illustrious Writer
>>   Devious Coder
>>     Last Hope for the Free World
>>       Also, Modest
>>
>> --
>> 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 sqlalchemy+unsubscr...@googlegroups.com
>> <mailto:sqlalchemy+unsubscr...@googlegroups.com>.
>> To post to this group, send email to sqlalchemy@googlegroups.com
>> <mailto:sqlalchemy@googlegroups.com>.
>> Visit this group at https://groups.google.com/group/sqlalchemy.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
> --
> 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 a topic in the
> Google Groups "sqlalchemy" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/to
> pic/sqlalchemy/KJXSHwbhbLA/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> sqlalchemy+unsubscr...@googlegroups.com.
> To post to this group, send email to sqlalchemy@googlegroups.com.
> Visit this group at https://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/d/optout.
>



-- 
-=Tucker A. Beck=-

Illustrious Writer
  Devious Coder
    Last Hope for the Free World
      Also, Modest

-- 
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 sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to