I've cobbled together a complete and simplified test case given your mapping 
and example code and I cannot reproduce with either 0.7 or 0.8 - the count of 
rows in the association table is one on the first commit, and two on the second.

You need to adapt the attached test case into a full reproducing case so that 
the specific trigger is illustrated...thanks.





On Dec 14, 2012, at 10:22 PM, Donald Stufft wrote:

> I have 2 tables with a third intermediary table. These tables are (shorted): 
> https://gist.github.com/9ff8afa793c9150c6b70
> 
> Using this the association_proxy correctly reuses existing rows in the 
> database if they already exist. However if I do this:
> 
> v = Version.query.first()
> v.classifiers = [u"Foo"]
> db.session.commit()  # A Classifier with trove=u"Foo" is either retrieved or 
> created
> 
> v.classifiers = [u"Foo", u"Bar"]
> db.session.commit()
> 
> An error occurs because SQLAlchemy tried to insert a second row in the 
> intermediary table for this Version + Classifier(u"Foo").
> 
> So my question is how can I get it to properly handle the fact that there 
> should only ever be 1 mapping of a particular Version
> to a particular Classifier?
> 
> Note, I can work around this by doing:
> 
> classifiers = [u"Foo", u"Bar"]
> for c in classifiers:
>     if not c in v.classifiers:
>         v.classifiers.append(c)
> 
> But I would really rather have a solution that is contained inside of my 
> Model.
> 
> -- 
> 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/-/9BCPl6rebKsJ.
> 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.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
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.




On Dec 14, 2012, at 10:22 PM, Donald Stufft wrote:

I have 2 tables with a third intermediary table. These tables are (shorted): https://gist.github.com/9ff8afa793c9150c6b70

Using this the association_proxy correctly reuses existing rows in the database if they already exist. However if I do this:

v = Version.query.first()
v.classifiers = [u"Foo"]
db.session.commit()  # A Classifier with trove=u"Foo" is either retrieved or created

v.classifiers = [u"Foo", u"Bar"]
db.session.commit()

An error occurs because SQLAlchemy tried to insert a second row in the intermediary table for this Version + Classifier(u"Foo").

So my question is how can I get it to properly handle the fact that there should only ever be 1 mapping of a particular Version
to a particular Classifier?

Note, I can work around this by doing:

classifiers = [u"Foo", u"Bar"]
for c in classifiers:
    if not c in v.classifiers:
        v.classifiers.append(c)

But I would really rather have a solution that is contained inside of my Model.

--
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/-/9BCPl6rebKsJ.
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.

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm.exc import NoResultFound


Base = declarative_base()

classifiers = Table("version_classifiers",
    Base.metadata,
    Column("id", Integer, primary_key=True),
    Column("classifier_id", Integer,
              ForeignKey("classifiers.id", ondelete="CASCADE"),
              nullable=False),
    Column("version_id", Integer,
              ForeignKey("versions.id", ondelete="CASCADE"),
              nullable=False),
    UniqueConstraint("classifier_id", "version_id")
)

class Classifier(Base):

    __tablename__ = "classifiers"

    id = Column(Integer, primary_key=True)
    trove = Column(UnicodeText, unique=True, nullable=False)

    def __init__(self, trove):
        self.trove = trove

    def __repr__(self):
        return "<Classifier: {trove}>".format(trove=self.trove)

    @classmethod
    def get_or_create(cls, trove):
        try:
            obj = Session.query(cls).filter_by(trove=trove).one()
        except NoResultFound:
            obj = cls(trove)
        return obj

class Version(Base):

    __tablename__ = "versions"

    id = Column(Integer, primary_key=True)
    _classifiers = relationship("Classifier", secondary=classifiers,
                               backref=backref("versions", lazy='dynamic'))
    classifiers = association_proxy("_classifiers", "trove",
                                    creator=Classifier.get_or_create)

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

Session = scoped_session(sessionmaker(e))

Session.add(Version())

v = Session.query(Version).first()
v.classifiers = [u"Foo"]
Session.commit()  # A Classifier with trove=u"Foo" is either retrieved or created

assert Session.scalar(select([func.count('*')]).select_from(classifiers)) == 1

v.classifiers = [u"Foo", u"Bar"]
Session.commit()

assert Session.scalar(select([func.count('*')]).select_from(classifiers)) == 2


Reply via email to