Ok cheers Mike. So I detect at collection level and then throw Exception if
already a member. So I might get better performance by changing collection
to a set and checking for membership before adding via the event listener.
Ok, thanks for your help and suggestions. Much appreciated :)
Cheers
Simon
On Wednesday, 7 December 2016 17:00:04 UTC, dcs3spp wrote:
>
> 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 [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.