Cheers Mike, will give it a go. Thanks again :) Example slightly different in that I am using an association table many to many bi-directional relationship in accordance with documentation at http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many.
Thanks again, Cheers Simon On Tuesday, 6 December 2016 17:21:33 UTC, Mike Bayer wrote: > > the formatting of your code is coming out largely unreadable for me, > here is a short example illustrating how to get an IntegrityError when > appending to a collection. Try running this to confirm it works, and > then seeing what's different about your own application versus this > example. > > from sqlalchemy import * > from sqlalchemy.orm import * > from sqlalchemy.ext.declarative import declarative_base > > Base = declarative_base() > > > class A(Base): > __tablename__ = 'a' > id = Column(Integer, primary_key=True) > bs = relationship("B") > > > class B(Base): > __tablename__ = 'b' > id = Column(Integer, primary_key=True) > a_id = Column(ForeignKey('a.id')) > some_unique_thing = Column(Integer) > > __table_args__ = ( > UniqueConstraint("some_unique_thing", name="uq_1"), > ) > > e = create_engine("postgresql://scott:tiger@localhost/test", echo=True) > Base.metadata.drop_all(e) > Base.metadata.create_all(e) > > s = Session(e) > > a1 = A(bs=[B(some_unique_thing=1)]) > s.add(a1) > s.commit() > > a1.bs.append(B(some_unique_thing=1)) > s.commit() > > > > > > On 12/06/2016 12:10 PM, 'dcs3spp' via sqlalchemy wrote: > > Hi Mike, > > > > > > Thanks again for responding :) > > > > Currently testing with default collection of list. Have also tried > > previously with set before original post, but still no exception > > generated. Difference is the set silently ignores duplicates in memory > > when adding items. > > > > > > Hmmm interesting. Sees the model as in the dirty state for both > > associating table rows. SQLAlchemy only generating INSERT SQL for > > outcome (4), i.e. not already present in the relationship. Outcome 2 is > > ignored, i.e. no INSERT statement generated. Outcome 2 is already > > present in association table. > > > > > > Cheers > > > > Simon > > > > > > assignment object=Transient: False, Pending: False, Persistent: True, > > Detatched: False > > > > outcome_1 object=Transient: False, Pending: False, Persistent: True, > > Detatched: False > > > > outcome_4 object=Transient: False, Pending: False, Persistent: True, > > Detatched: False > > > > dbsession.dirty=IdentitySet([<LearningOutcome('1014760','2', '1', > > 'Understand the components of computer systems')>, > > <Assignment('1','1014760', '2', '2015-12-10 00:00:00+00:00', 'Assignment > > 1')>, <LearningOutcome('1014760','2', '4', 'Unit 2 : Learning Outcome > > 4')>]), .new=IdentitySet([]), .deleted=IdentitySet([]) > > > > 2016-12-06 17:00:33,878 INFO sqlalchemy.engine.base.Engine INSERT INTO > > proposed_coursemanagement.assignmentoutcome ("CourseID", "UnitID", > > "OutcomeID", "AssignmentID") VALUES (%(CourseID)s, %(UnitID)s, > > %(OutcomeID)s, %(AssignmentID)s) > > > > 2016-12-06 17:00:33,878 INFO > > [sqlalchemy.engine.base.Engine:109][MainThread] INSERT INTO > > proposed_coursemanagement.assignmentoutcome ("CourseID", "UnitID", > > "OutcomeID", "AssignmentID") VALUES (%(CourseID)s, %(UnitID)s, > > %(OutcomeID)s, %(AssignmentID)s) > > > > 2016-12-06 17:00:33,878 INFO sqlalchemy.engine.base.Engine {'CourseID': > > 1014760, 'OutcomeID': 4, 'AssignmentID': 1, 'UnitID': 2} > > > > 2016-12-06 17:00:33,878 INFO > > [sqlalchemy.engine.base.Engine:109][MainThread] {'CourseID': 1014760, > > 'OutcomeID': 4, 'AssignmentID': 1, 'UnitID': 2} > > > > 2016-12-06 17:00:33,879 INFO sqlalchemy.engine.base.Engine COMMIT > > > > 2016-12-06 17:00:33,879 INFO > > [sqlalchemy.engine.base.Engine:109][MainThread] COMMIT > > > > Test Script complete... > > > > > > On Tuesday, 6 December 2016 16:27:35 UTC, Mike Bayer wrote: > > > > > > > > On 12/06/2016 11:21 AM, 'dcs3spp' via sqlalchemy wrote: > > > Hi, > > > > > > Good suggestion by Mike, regarding possibility that problem could > be > > > with postgreSQL. Tried inserting duplicate record in association > > table > > > from within postgreSQL. This correctly raises a duplicate key > error. > > > > > > proposed_coursemanagement=> INSERT INTO assignmentoutcome VALUES > (1, > > > 1014760, 2, 1); > > > > > > ERROR: duplicate key value violates unique constraint > > > "assignmentoutcome_pkey" > > > > > > DETAIL: Key ("AssignmentID", "OutcomeID", "UnitID", > > "CourseID")=(1, 1, > > > 2, 1014760) already exists. > > > > > > OK then when you set up your model state and do a session.commit(), > > turn > > on echo=True and watch the SQL that is emitted. You would ideally > see > > it attempting to insert this row as well, assuming the collection on > > your relationship allows duplicates in the first place (e.g. it's a > > list, not a set). Since you aren't getting an integrity error, > it's > > likely not doing that as yet. > > > > > > > > > > > > > > > > > Cheers > > > > > > Simon > > > > > > On Tuesday, 6 December 2016 14:36:15 UTC, dcs3spp wrote: > > > > > > Hi, > > > > > > > > > I am running SQLAlchemy 1.1.4 connected to postgreSQL 9.6 on > OSX > > > Sierra 10.12.1. I am experiencing difficulty getting > > SQLAlchemy to > > > raise an integrity error when a persistent object is appended > > to a > > > relationship for which it is already associated with. > > > > > > > > > > > > Is the accepted method to manually raise an exception by > > testing for > > > existing membership in the collection_class of the > relationship > > > before inserting the persistent object into the relationship? > > I have > > > tested with default list and also setting the collection_class > to > > > set. Would be grateful if anyone could help with this and > > point out > > > where I am going wrong :) > > > > > > > > > I have included details of code and DB structure below. > > Currently, > > > duplicate associations are silently ignored, i.e. no INSERT > > > statements generated. > > > > > > > > > Cheers > > > > > > > > > Simon > > > > > > > > > > > > > > > DB Structure > > > > > > Assignment —< AssignmentOutcome >— LearningOutcome > > > > > > > > > TABLE KEYS > > > > > > Assignment identified by AssignmentID > > > > > > LearningOutcome identified by CourseID, UnitID, OutcomeID > > > > > > AssignmentOutcome identified by AssignmentID, CourseID, > UnitID, > > > OutcomeID > > > > > > > > > ASSOCIATION TABLE STATE BEFORE TEST SCRIPT RUN > > > > > > proposed_coursemanagement=> SELECT * FROM assignment outcome; > > > > > > AssignmentID | CourseID | UnitID | OutcomeID > > > > > > --------------+----------+--------+----------- > > > > > > 1 | 1014760 | 2 | 1 > > > > > > 1 | 1014760 | 2 | 2 > > > > > > 2 | 1014760 | 2 | 1 > > > > > > 2 | 1014760 | 2 | 3 > > > > > > 3 | 1014760 | 2 | 2 > > > > > > 4 | 1014760 | 2 | 3 > > > > > > (6 rows) > > > > > > > > > TEST SCRIPT PURPOSE: Try appending outcome 1 again to > > assignment 1. > > > Should SQLAlchemy raise and exception since this is already > > mapped > > > in association table? Current behaviour is that duplicate is > > > silently ignored. When I append outcome 4 for assignment 1 it > is > > > mapped to the association table. > > > > > > > > > TEST SCRIPT CODE: > > > > > > def usage(argo): > > > > > > cmd = os.path.basename(argv[0]) > > > > > > print('usage: %s <config_uri> [var=value]\n' > > > > > > '(example: "%s development.ini")' % (cmd, cmd)) > > > > > > > > > def main(argv=sys.argv): > > > > > > > > > > > > if len(argv) < 2: > > > > > > usage(argv) > > > > > > > > > print ("Initialising SQLAlchemy engine instance and > > tables.......") > > > > > > config_uri = argv[1] > > > > > > options = parse_vars(argv[2:]) > > > > > > setup_logging(config_uri) > > > > > > settings = get_appsettings(config_uri, options=options) > > > > > > > > > engine = get_engine(settings) > > > > > > Base.metadata.create_all(engine) > > > > > > session_factory = get_session_factory(engine) > > > > > > print ("Completed initialisation.....") > > > > > > print ("Starting Test Script...") > > > > > > with transaction.manager: > > > > > > dbsession = get_tm_session(session_factory, > > transaction.manager) > > > > > > > > > # load into session assignment 1 and learning outcomes > > 1 and 4 > > > > > > # outcome 1 already exists in the association table > for > > > assignment 1 > > > > > > # outcome 4 does not exist yet in the association > > table for > > > assignment 1 > > > > > > assignment = dbsession.query(AssignmentModel).get(1) > > > > > > outcome_1 = > > > dbsession.query(LearningOutcomeModel).get((1014760, 2, 1)) > > > > > > outcome_4 = > > > dbsession.query(LearningOutcomeModel).get((1014760, 2, 4)) > > > > > > > > > # add outcome 1 and outcome 4 to assignmentoutcomes > > relationship > > > > > > assignment.assignmentoutcomes.append(outcome_1) > > > > > > assignment.assignmentoutcomes.append(outcome_4) > > > > > > > > > # inspect and display state for assignment and outcome > > objects > > > > > > assignmentIns = inspect (assignment) > > > > > > outcome1Ins = inspect (outcome_1) > > > > > > outcome4Ins = inspect (outcome_4) > > > > > > print("assignment object=Transient: {0}, Pending: {1}, > > > Persistent: {2}, Detatched: {3}".format( > > > > > > assignmentIns.transient, assignmentIns.pending, > > > assignmentIns.persistent, assignmentIns.detached)) > > > > > > print("outcome_1 object=Transient: {0}, Pending: {1}, > > > Persistent: {2}, Detatched: {3}".format( > > > > > > outcome1Ins.transient, outcome1Ins.pending, > > > outcome1Ins.persistent, outcome1Ins.detached)) > > > > > > print("outcome_4 object=Transient: {0}, Pending: {1}, > > > Persistent: {2}, Detatched: {3}".format( > > > > > > outcome4Ins.transient, outcome4Ins.pending, > > > outcome4Ins.persistent, outcome4Ins.detached)) > > > > > > > > > # inspect and display session states .dirty, .new and > > .deleted > > > > > > print ("dbsession.dirty={0}, .new={1}, > > .deleted={2}".format( > > > > > > dbsession.dirty, dbsession.new, > dbsession.deleted)) > > > > > > print ("Test Script complete...") > > > > > > > > > TEST SCRIPT OUTPUT > > > > > > assignment object=Transient: False, Pending: False, > Persistent: > > > True, Detatched: False > > > > > > outcome_1 object=Transient: False, Pending: False, Persistent: > > True, > > > Detatched: False > > > > > > outcome_4 object=Transient: False, Pending: False, Persistent: > > True, > > > Detatched: False > > > > > > dbsession.dirty=IdentitySet([<LearningOutcome('1014760','2', > '4', > > > 'Unit 2 : Learning Outcome 4')>, > <LearningOutcome('1014760','2', > > > '1', 'Understand the components of computer systems')>, > > > <Assignment('1','1014760', '2', '2015-12-10 00:00:00+00:00', > > > 'Assignment 1')>]), .new=IdentitySet([]), > > .deleted=IdentitySet([]) > > > > > > 2016-12-06 12:33:10,691 INFO > > > [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO > > > proposed_coursemanagement.assignmentoutcome ("CourseID", > > "UnitID", > > > "OutcomeID", "AssignmentID") VALUES (%(CourseID)s, %(UnitID)s, > > > %(OutcomeID)s, %(AssignmentID)s) > > > > > > 2016-12-06 12:33:10,691 INFO > > > [sqlalchemy.engine.base.Engine:1100][MainThread] {'OutcomeID': > 4, > > > 'CourseID': 1014760, 'UnitID': 2, 'AssignmentID': 1} > > > > > > 2016-12-06 12:33:10,692 INFO > > > [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT > > > > > > Test Script complete... > > > > > > > > > > > > > > > ASSOCIATION TABLE STATE AFTER TEST SCRIPT RUN > > > > > > proposed_coursemanagement=> SELECT * FROM assignmentoutcome; > > > > > > AssignmentID | CourseID | UnitID | OutcomeID > > > > > > --------------+----------+--------+----------- > > > > > > 1 | 1014760 | 2 | 1 * No duplicate > > raised > > > - does SQLAlchemy silently ignore? > > > > > > 1 | 1014760 | 2 | 2 > > > > > > 2 | 1014760 | 2 | 1 > > > > > > 2 | 1014760 | 2 | 3 > > > > > > 3 | 1014760 | 2 | 2 > > > > > > 4 | 1014760 | 2 | 3 > > > > > > 1 | 1014760 | 2 | 4 > > > > > > (7 rows) > > > > > > > > > > > > MODEL CODE: > > > > > > > > > assignmentoutcomeallocation = Table('assignmentoutcome', > > Base.metadata, > > > > > > Column('CourseID', BigInteger, primary_key=True), > > > > > > Column('UnitID', Integer, primary_key=True), > > > > > > Column('OutcomeID', Integer, primary_key=True), > > > > > > Column('AssignmentID', BigInteger, > > > ForeignKey('assignment.AssignmentID'), primary_key=True), > > > > > > UniqueConstraint('CourseID', 'UnitID', 'OutcomeID', > > 'AssignmentID'), > > > > > > ForeignKeyConstraint( > > > > > > ['CourseID', 'UnitID', 'OutcomeID'], > > > > > > ['learningoutcome.CourseID', 'learningoutcome.UnitID', > > > 'learningoutcome.OutcomeID']), > > > > > > ) > > > > > > > > > class AssignmentModel(Base): > > > > > > __tablename__ = 'assignment' > > > > > > __table_args__ = ( > > > > > > ForeignKeyConstraint(['CourseID','UnitID'], > > > ['unit.CourseID', 'unit.UnitID']), > > > > > > ) > > > > > > > > > AssignmentID = Column(BigInteger, primary_key=True, > > > > > > > > > > > > server_default=text("nextval('proposed_coursemanagement.\"assignment_AssignmentID_seq\"::regclass)")) > > > > > > > > > > CourseID = Column(BigInteger, nullable=False) > > > > > > UnitID = Column(Integer, nullable=False) > > > > > > AssignmentSetDate = Column(DateTime(timezone=True), > > nullable=False) > > > > > > AssignmentSubmissionDate = Column(DateTime(timezone=True), > > > nullable=False) > > > > > > AssignmentResubmissionDate = > Column(DateTime(timezone=True), > > > nullable=False) > > > > > > AssignmentTitle = Column(String(200), nullable=False) > > > > > > > > > indx = Index('assignment_indx', 'CourseID', 'UnitID', > > > text('lower(\"AssignmentTitle\")'), 'AssignmentSetDate') > > > > > > > > > unit = relationship('UnitModel') > > > > > > assignmentcriteria = relationship('UnitCriteriaModel', > > > > > > > > > secondary=assignmentcriteriaallocation, > > > > > > > > > back_populates='criteria_assignments') > > > > > > assignmentoutcomes = relationship('LearningOutcomeModel', > > > > > > > > > secondary=assignmentoutcomeallocation, > > > > > > > > > > > > > > > back_populates='outcome_assignments') > > > > > > > > > > > > def __repr__(self): > > > > > > return "<Assignment('%s','%s', '%s', '%s', '%s')>" % > > > (self.AssignmentID, self.CourseID, self.UnitID, > > > self.AssignmentSetDate, self.AssignmentTitle) > > > > > > > > > def __str__(self): > > > > > > return "<Assignment('%s','%s', '%s', '%s', '%s')>" % > > > (self.AssignmentID, self.CourseID, self.UnitID, > > > self.AssignmentSetDate, self.AssignmentTitle) > > > > > > > > > > > > class LearningOutcomeModel(Base): > > > > > > __tablename__ = 'learningoutcome' > > > > > > __table_args__ = ( > > > > > > ForeignKeyConstraint(['CourseID','UnitID'], > > > ['unit.CourseID', 'unit.UnitID']), > > > > > > ) > > > > > > > > > CourseID = Column(BigInteger, primary_key=True, > > nullable=False) > > > > > > UnitID = Column(Integer, primary_key=True, nullable=False) > > > > > > OutcomeID = Column(Integer, primary_key=True, > nullable=False) > > > > > > LearningOutcome = Column(String(100), nullable=False) > > > > > > > > > unit = relationship('UnitModel') > > > > > > outcome_assignments = relationship ( > > > > > > 'AssignmentModel', > > > > > > secondary=assignmentoutcomeallocation, > > > > > > back_populates='assignmentoutcomes') > > > > > > > > > def __repr__(self): > > > > > > return "<LearningOutcome('%s','%s', '%s', '%s')>" % > > > (self.CourseID, self.UnitID, self.OutcomeID, > > self.LearningOutcome) > > > > > > > > > def __str__(self): > > > > > > return "<LearningOutcome('%s','%s', '%s', '%s')>" % > > > (self.CourseID, self.UnitID, self.OutcomeID, > > self.LearningOutcome) > > > > > > > > > -- > > > 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+...@googlegroups.com <javascript:> > > > <mailto:sqlalchemy+unsubscr...@googlegroups.com <javascript:> > <javascript:>>. > > > To post to this group, send email to sqlal...@googlegroups.com > > <javascript:> > > > <mailto:sqlal...@googlegroups.com <javascript:>>. > > > 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 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+...@googlegroups.com <javascript:> > > <mailto:sqlalchemy+unsubscr...@googlegroups.com <javascript:>>. > > To post to this group, send email to sqlal...@googlegroups.com > <javascript:> > > <mailto:sqlal...@googlegroups.com <javascript:>>. > > 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 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.