Hi Mike, Thanks for getting back to me and suggesting viable workarounds :) Will probably go with the event listener approach. Is it worthwhile submitting this as an issue on bitbucket?
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 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.