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 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.