On Feb 25, 2014, at 2:17 AM, Luke <[email protected]> wrote:

> 
> 
> Michael,
> 
> I am continuing to get the same AmbiguousForeignKeysError with your first 
> suggestion. Your simpler suggestion, using an abstract Base, does not seem to 
> want to *easily* handle a many to many relationship in the base class. 
> (Authors <-> Writing)  I don't want you to think I'm not pouring over the 
> docs, SO, and indeed several of your conference presentations. I have. But 
> there is so much going on here.  What I am looking for is either: 
> 
> (1) A reason this primary join isn't working for me.  
> 

typo, need double underscores for __mapper_args__ :

    __mapper_args__ = {
        'polymorphic_identity': 'response',
        'inherit_condition': id == Writing.id
    }

after that the mapping configures for me.

It might be a good idea if the error message here makes it clear when it’s 
talking about the “inherit condition” versus a relationship, and also maybe the 
inheritance should try to assume the primary key/fk of the sub-class should 
take precedence.

> (2) A way to have the Author <-> Writing relationship with a Writing abstract 
> base (I tried `declared_attr`). 

OK well since you’re looking to have Author linked to any of Article or 
Response, this gets into the “Generic association” use case.   There are four 
varieties of these illustrated at 
http://docs.sqlalchemy.org/en/rel_0_9/orm/examples.html#module-examples.generic_associations.

Here’s your example using the generic association “table_per_association”, 
since you want to share Author rows among multiple Article/Response instances.  
A key change in this pattern is that distinct relationships are used when 
referring to different tables (Author.article_collection, 
Author.response_collection, Response.article, Response.in_response_to):

from sqlalchemy import Column, Integer, Table, ForeignKey, String, create_engine
from sqlalchemy.orm import Session, relationship, backref
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy import event

Base = declarative_base()

class Writing(Base):
    __abstract__ = True

    id = Column('id', Integer, primary_key=True)

    @declared_attr
    def authors(cls):
        author_to_writing = Table(
            'author_to_%s' % cls.__tablename__, cls.metadata,
                Column(
                    '%s_id' % cls.__tablename__,
                    ForeignKey('%s.id' % cls.__tablename__)
                ),
                Column('author_id', ForeignKey('author.id'))
        )

        return relationship('Author', secondary=author_to_writing,
                    backref=backref(
                                "%s_collection" % cls.__name__.lower() ,
                                lazy='dynamic')
                    )

class Article(Writing):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True)

class Response(Writing):
    __tablename__ = 'response'
    id = Column(Integer, primary_key=True)

    article_id = Column(ForeignKey('article.id'), nullable=False)
    article = relationship(Article, backref='responses')

    response_id = Column(ForeignKey('response.id'), nullable=True)
    in_response_to = relationship("Response", backref='responses', 
remote_side=id)

@event.listens_for(Response, "before_insert")
@event.listens_for(Response, "before_update")
def set_article_id(mapper, connection, target):
    """ensure all Reponse objects have a non-null article_id"""
    if target.in_response_to and not target.article_id:
        target.article_id = target.in_response_to.article_id

class Author(Base):
    __tablename__ = 'author'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

a1, a2, a3 = Author(name='a1'), Author(name='a2'), Author(name='a3')

art1 = Article(
        authors=[a1],
        responses=[
            Response(
                authors=[a2],
                responses=[
                    Response(authors=[a3]),
                    Response(authors=[a1]),
                ]
            ),
            Response(authors=[a3])
        ],

    )

s = Session(e)
s.add(art1)
s.commit()

assert a1.article_collection[0] is art1
resp1 = a2.response_collection[0]
resp2 = a1.response_collection[0]

assert resp2.in_response_to is resp1




> 
> Thanks again.
> 
>  - Luke
> 
> 
> author_to_writing = Table(
>     'author_to_writing', Base.metadata,
>     Column('writing_id', Integer, ForeignKey('writing.id')),
>     Column('author_id', Integer, ForeignKey('author.id')))
> 
> 
> class Writing(Base):
>     __tablename__ = 'writing'
>     id = Column('id', Integer, primary_key=True)
>     type = Column(String)
>     __mapper_args__ = { 
>         'polymorphic_identity': 'writing',
>         'polymorphic_on': type
>     } 
>     authors = relationship('Author', secondary=author_to_writing,
>         backref=backref('author', lazy='dynamic'))
> 
> 
> class Article(Writing):
>     __tablename__ = 'article'
>     id = Column('id', ForeignKey('writing.id'), primary_key=True)
>     __mapper_args_ = {'polymorphic_identity': 'article'}
> 
> 
> class Response(Writing):
>     __tablename__ = 'response'
>     id = Column('id', ForeignKey('writing.id'), primary_key=True)
>     __mapper_args_ = { 
>         'polymorphic_identity': 'response',
>         'inherit_condition': id == Writing.id}
>     respondee_id = Column(ForeignKey('writing.id'))
>     writing = relationship(
>         Writing,
>         remote_side=Writing.id,
>         backref='responses',
>         primaryjoin=Writing.id == respondee_id)
> 
> 
> class Author(Base):
>     __tablename__ = 'author'
>     id = Column(Integer, primary_key=True)
>     name = Column(String, nullable=False)
> 
> 
> 
> -- 
> 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 http://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/groups/opt_out.

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to