Hi Michael,

That does exactly what I was after (and I've learned a little bit more 
about sqalchemy!)

Thank you very much for your help.




On Thursday, 23 August 2012 19:13:27 UTC+1, Michael Bayer wrote:
>
>
> On Aug 23, 2012, at 10:04 AM, Rob wrote:
>
> Hi Michael,
>
> I have a similar (but subtly different) problem to this, trying to mix 
> single- and joined-table inheritance.
> Essentially my model looks as follows:
>
> Product(Base)
> PhysicalProduct(Product)       
> NonPhysicalProduct(Product)
>
> The Physical/NonPhysicalProduct use single table inheritance whilst 
> objects inheriting from them use joined tables...  
>
> I have a fully working model --- and there's no question that it works!! 
>  But I can't help feeling that I've missed something relating to the 
> __mapper_args__ which is then requiring explicit calls to __init__ objects 
> higher up the tree.  (rather than bunging up this message, please see the 
> attached file) 
>
> I'd be really grateful if you could take a look and hopefully point me in 
> the right direction.
>
>
>
> this model wouldn't "work" fully, as if you did query(Product), the 
> discriminator would only be the "product_type" column.  p_discr and 
> np_discr would be ignored.   The polymorphic load only checks for one key 
> in one map, and that map is always shared among all classes in the 
> hierarchy starting at Product.
>
> I'm going to call this pattern "cascading polymorphic ons" for now, I 
> guess.   The attached file will get you there,  but requires two techniques 
> that are probably outside of the scope of what a the average SQLA user 
> could be expected to come up with on his or her own.  To get SQLA to do 
> these kinds of things automatically should be possible, most likely by just 
> having the "match polymorphic_on to class" phase during loading continue 
> it's process several times.   The concept is not too hard but there's edges 
> to it which may make it a more involved enhancement, this is 
> http://www.sqlalchemy.org/trac/ticket/2555.
>
> For now here's the workaround version:
>
> """
> mixed single and joined table inheritance.
> """
>
> from sqlalchemy import *
> from sqlalchemy import types
> from sqlalchemy.orm import *
> from sqlalchemy.ext.declarative import declarative_base,  declared_attr
> from sqlalchemy import event
>
> Base = declarative_base()
>
> class Product(Base):
>     __tablename__ = 'products'
>     id = Column(types.Integer, primary_key=True)
>     discriminator = Column('product_type', types.String(50), 
> nullable=False)
>
>     _discriminator = "discriminator"
>
>     def price_history(self):
>         return []
>
> class PhysicalProduct(Product):
>     p_discr = Column(types.String(50))
>
>     _discriminator = "p_discr"
>
>     @declared_attr
>     def __mapper_args__(cls):
>         return {'polymorphic_identity': 'physical_product'}
>
>     def inventory(self):
>         return "computed inventory"
>
> class NonPhysicalProduct(Product):
>     np_discr = Column(types.String(50))
>
>     _discriminator = "np_discr"
>
>     @declared_attr
>     def __mapper_args__(cls):
>         return {'polymorphic_identity': 'nonphysical_product'}
>
>     def somefunc(self):
>         return "someval"
>
> # set polymorphic on as a coalesce of those three
> # columns.  It's after the fact beacuse p_discr and np_discr
> # are defined after Product, but if you move them up then
> # this can be inline inside of Product.__mapper_args__.
> # this would improve loads too as it appears the p_discr/np_discr columns
> # aren't loaded directly when you query for Product
> for mp in Product.__mapper__.self_and_descendants:
>     mp._set_polymorphic_on(
>             func.coalesce(
>                     Product.__table__.c.p_discr,
>                     Product.__table__.c.np_discr,
>                     Product.__table__.c.product_type
>             ))
>
> # build our own system of assigning polymorphic identities
> # to instances; use the 'init' event.
> # Add a "print" for the "identity" dict to see what it's doing.
> @event.listens_for(Product, "init", propagate=True)
> def init(target, args, kwargs):
>     identity = {}
>     for cls, supercls in zip(type(target).__mro__, 
> type(target).__mro__[1:]):
>         if not hasattr(supercls, '_discriminator'):
>             break
>         discriminator_attr = supercls._discriminator
>         poly_identity = cls.__mapper__.polymorphic_identity
>         identity.setdefault(discriminator_attr, poly_identity)
>     for key in identity:
>         setattr(target, key, identity[key])
>
>
> class Newspaper(PhysicalProduct):
>     __tablename__ = 'newspapers'
>     __mapper_args__ = {'polymorphic_identity': 'newspaper'}
>
>     id = Column(types.Integer,
>                 ForeignKey('products.id'),
>                 primary_key=True
>                 )
>     title = Column(types.String(50))
>
>     def __init__(self, title):
>         self.title = title
>
>
> class NewspaperDelivery(NonPhysicalProduct):
>     __tablename__ = 'deliveries'
>     __mapper_args__ = {'polymorphic_identity': 'delivery'}
>
>     id = Column(types.Integer,
>                 ForeignKey('products.id'),
>                 primary_key=True
>                 )
>     destination = Column(types.String(50))
>
>     def __init__(self, destination):
>         self.destination = destination
>
>
> # note here how the polymorphic map works out:
> print Product.__mapper__.polymorphic_map
> # {'newspaper': <Mapper at 0x1014d8890; Newspaper>,
> # 'delivery': <Mapper at 0x1014dec90; NewspaperDelivery>,
> # 'nonphysical_product': <Mapper at 0x1014d2350; NonPhysicalProduct>,
> # 'physical_product': <Mapper at 0x1014d00d0; PhysicalProduct>}
>
>
> e = create_engine('sqlite:///:memory:', echo='debug')
> Base.metadata.drop_all(e)
> Base.metadata.create_all(e)
>
> session = Session(e, autoflush=True, autocommit=False)
>
> session.add_all([
>     Newspaper(title="Financial Times"),
>     NewspaperDelivery(destination="__somewhere__"),
>     PhysicalProduct(),
>     NonPhysicalProduct()
> ])
>
> session.commit()
>
> # the important part - that a row only known as Product can
> # interpret as a specific subclass
> assert [
>     type(c) for c in session.query(Product).order_by(Product.id)
> ] == [Newspaper, NewspaperDelivery, PhysicalProduct, NonPhysicalProduct]
>
> # test sub-table load.  The load for "title" apparently emits a JOIN still 
> because
> # in order to refresh the subclass of "Product" it also wants to get
> # at p_discr.
> np = session.query(Product).filter_by(id=1).first()
> assert np.title == "Financial Times"
>
> session.close()
>
> # in this version, it emits two separate, single table SELECT statements,
> # since the first query loads the full set of columns for PhysicalProduct.
> np = session.query(PhysicalProduct).filter_by(id=1).first()
> assert np.title == "Financial Times"
>
>
>
>
>
>
>
>
>
>
> Many thanks,
> Rob 
>    
>
>
>
>
>
> On Wednesday, 17 August 2011 00:42:28 UTC+1, Michael Bayer wrote:
>>
>>
>> On Aug 16, 2011, at 5:37 PM, Mike Gilligan wrote:
>>
>> I have a single table that looks similar to the following:
>>
>> class Equipment(Base):
>>     type = Column(CHAR(1), primary_key=True)
>>     sub_type = Column(CHAR(1), primary_key=True)
>>     code = Column(CHAR(5), primary_key=True)
>>
>>
>> For historical purposes, I cannot modify this table. I would like to 
>> setup multi-level inheritance similar to this, however it does not work:
>>
>> class Equipment(Base):
>>     type = Column(CHAR(1), primary_key=True)
>>     sub_type = Column(CHAR(1), primary_key=True)
>>     code = Column(CHAR(5), primary_key=True)
>>     __mapper_args__ = {'polymorphic_on': type}
>>
>>
>> class Vehicle(Equipment):
>>      __mapper_args__ = {'polymorphic_identity': 'V', 'polymorphic_on': 
>> sub_type}
>>
>>
>> class Bus(Vehicle)
>>      __mapper_args__ = {'polymorphic_identity': 'B'}
>>
>>
>> class Rail(Vehicle)
>>      __mapper_args__ = {'polymorphic_identity': 'R'}
>>
>>
>> I can concatenate the multiple column values into a single discriminator 
>> column_property but then I do not have an easy way to retrieve all 
>> vehicles. Any ideas?
>>
>>
>> The inheritance querying does handle multi-level inheritance so if your 
>> discriminator was on a concatenation of both things would work just fine, 
>> i.e. if you queried for Vehicle, etc.   Each object's 
>> "polymorphic_identity" would need to include the concatenated value, of 
>> course.
>>
>> Unfortunately we're just beginning to support inheritance discriminators 
>> on a column_property(), and you need to use a very specific approach to 
>> make this work right now.  There's some tickets in trac to allow this 
>> functionality out of the box.    Attached is an example script which 
>> exercises the above mapping - it uses declarative to minimize the impact of 
>> the workaround.
>>
>>
> -- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To view this discussion on the web visit 
> https://groups.google.com/d/msg/sqlalchemy/-/KzPgMan_6MIJ.
> To post to this group, send email to sqlal...@googlegroups.com<javascript:>
> .
> To unsubscribe from this group, send email to 
> sqlalchemy+...@googlegroups.com <javascript:>.
> For more options, visit this group at 
> http://groups.google.com/group/sqlalchemy?hl=en.
> <product_inheritance.py>
>
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/sqlalchemy/-/iLn184dhMhcJ.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to