Trouble with AbstractConcreteBase and aliased columns

137 views
Skip to first unread message

Alex Grönholm

unread,
Jul 9, 2015, 11:18:28 AM7/9/15
to sqlal...@googlegroups.com
The following script no longer works in 1.0.6, but does in 0.9.9:


from sqlalchemy.ext.declarative import declarative_base, AbstractConcreteBase
from sqlalchemy.ext.declarative.api import declared_attr
from sqlalchemy.orm.mapper import configure_mappers
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.schema import Column, ForeignKey
from sqlalchemy.sql.sqltypes import Date, String, Integer

Base = declarative_base()


class Company(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True)


class Document(object):
date = Column(Date)
documentType = Column('documenttype', String)


class ContactDocument(AbstractConcreteBase, Base, Document):
contactPersonName = Column('contactpersonname', String)
salesPersonName = Column(String)
sendMethod = Column('sendmethod', String)

@declared_attr
def company_id(self):
return Column(ForeignKey('companies.id'))


class Offer(ContactDocument):
__tablename__ = 'offers'

id = Column(Integer, primary_key=True)


class SalesOrder(ContactDocument):
__tablename__ = 'orders'

id = Column(Integer, primary_key=True)


configure_mappers()
session = Session()
query = session.query(ContactDocument)
print(query)


On 1.0.6, I get an error: sqlalchemy.exc.ArgumentError: When configuring property 'documentType' on Mapper|ContactDocument|pjoin, column 'documenttype' is not represented in the mapper's table. Use the `column_property()` function to force this column to be mapped as a read-only attribute.
Why am I getting this? Is this a bug or am I not understanding something?

Also, is it possible to have both Document and ContactDocument as abstract concrete base classes (ie. I want the union from Document to include both the direct concrete subclasses of Document and all concrete subclasses of ContactDocument as well)?

Mike Bayer

unread,
Jul 9, 2015, 11:31:36 AM7/9/15
to sqlal...@googlegroups.com
Thanks for reporting.   Issue https://bitbucket.org/zzzeek/sqlalchemy/issues/3480/abstractconcretebase-regression-with is created, create the Column objects with an explicit key for now:



class Document(object):
    date = Column(Date)
    documentType = Column('documenttype', String, key="documentType")


class ContactDocument(AbstractConcreteBase, Base, Document):
    contactPersonName = Column('contactpersonname', String, key="contactPersonName")
    salesPersonName = Column(String)
    sendMethod = Column('sendmethod', String, key="sendMethod")


    @declared_attr
    def company_id(self):
        return Column(ForeignKey('companies.id'))



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

Alex Grönholm

unread,
Jul 9, 2015, 12:17:16 PM7/9/15
to sqlal...@googlegroups.com
Thanks. What about my other question? Is it possible to have two layers of classes (Document and ContactDocument) mapped to polymorphic unions?

Mike Bayer

unread,
Jul 9, 2015, 7:25:46 PM7/9/15
to sqlal...@googlegroups.com


On 7/9/15 12:17 PM, Alex Grönholm wrote:
Thanks. What about my other question? Is it possible to have two layers of classes (Document and ContactDocument) mapped to polymorphic unions?
OK.   So, AbstractConcreteBase struggles hard against Declarative wanting to map things.   So as far as how to get it to take effect multiple times in a hierarchy, with ABC itself it would require more weird class tricks, of the kind where we always have to see, "does class A declare "_x" or is it inheriting it?" which is why declarative has gotten so crazy compared to its innocent beginnings.     This might be something that can be added but I'd have to think about it, ABC is still pretty brittle overall.

I can have you just use the API that ABC uses internally.   Concrete mappings in classical SQLA were really easy, because we had those Table objects up front before we did anything with the classes.  With declarative we don't have that because it makes the table/mapper at the same time.   This architecture has opened up a lot in 1.0 but still doesn't make this kind of thing that simple.  But the main thing that was added probably in 0.8 or 0.9 to make this possible was a way to attach the "base" underneath a concrete mapper after the subclass is set up.  Instead of ABC doing that for us, we can do it the "old" way manually, using polymophic_union() in the old way and calling mapper(), just using one newish API function so that we can still use declarative for the subclasses.  It's one private API function at the moment.    It maps and creates the queries, so that should be pretty much it - we can try to make these API patterns more accessible.  Let me know if this works more fully.

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative.api import declared_attr
from sqlalchemy.orm import configure_mappers, mapper, Session

from sqlalchemy.sql.schema import Column, ForeignKey
from sqlalchemy.sql.sqltypes import Date, String, Integer

Base = declarative_base()


class Company(Base):
    __tablename__ = 'companies'
    id = Column(Integer, primary_key=True)


class Document(Base):
    date = Column(Date)
    documentType = Column(String)

    __abstract__ = True
    __mapper_args__ = {"concrete": True}


class SomeDocument(Document):
    """extends Document but not ContactDocument """
    __tablename__ = 'some_document'

    id = Column(Integer, primary_key=True)


class ContactDocument(Document):
    contactPersonName = Column(String)
    salesPersonName = Column(String)
    sendMethod = Column(String)


    @declared_attr
    def company_id(self):
        return Column(ForeignKey('companies.id'))

    __abstract__ = True



class Offer(ContactDocument):
    __tablename__ = 'offers'

    id = Column(Integer, primary_key=True)


class SalesOrder(ContactDocument):
    __tablename__ = 'orders'

    id = Column(Integer, primary_key=True)


from sqlalchemy.orm.util import polymorphic_union

document_pjoin = polymorphic_union({
    'offer': Offer.__table__,
    'orders': SalesOrder.__table__,
    'somedocument': SomeDocument.__table__
}, 'type', 'd_pjoin'
)

contact_document_pjoin = polymorphic_union({
    'offer': Offer.__table__,
    'orders': SalesOrder.__table__,
}, 'type', 'cd_pjoin'
)

md = mapper(
    Document,
    document_pjoin,
    polymorphic_on=document_pjoin.c.type,
    concrete=True)
mcd = mapper(
    ContactDocument,
    contact_document_pjoin,
    inherits=md,
    polymorphic_on=contact_document_pjoin.c.type,
    concrete=True)

# AbstractConcreteBase does this part by looking at cls.__subclasses__()
Offer.__mapper__._set_concrete_base(mcd)
SalesOrder.__mapper__._set_concrete_base(mcd)
SomeDocument.__mapper__._set_concrete_base(md)

configure_mappers()
session = Session()
print "-----------"
print session.query(Document)
print "-----------"
print session.query(ContactDocument)

Alex Grönholm

unread,
Jul 9, 2015, 7:34:44 PM7/9/15
to sqlal...@googlegroups.com
Thanks for the response! The solution looks too hairy for my tastes, and I can manage with what I currently have. Good to know it's possible though!
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/U8mpVm8udi8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to sqlalchemy+...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages