Hi Mike (et al.), I'm searching for a way to achieve defaultdict-like functionality for association proxies, so that a function that refers to a collection (or key within that collection) before it exists can create the collection/key with a default value.
In a previous post (https://groups.google.com/forum/#!msg/sqlalchemy/kxU-FaDGO2Q/b8ScnTXvPyIJ) you helped me to set up a composite association proxy, where I had a User object, a Course object, and a UserCourse object with keys to the User and Course objects as well as users' grades for each course. class User(Base): __tablename__ = 'users' # Columns id = Column(Integer, primary_key=True) name = Column(Text) # Relations courses = association_proxy( 'user_courses', 'course', creator=lambda k, v: UserCourse(course=k, grade=v) ) def __init__(self, name): self.name = name class Course(Base): __tablename__ = 'courses' # Columns id = Column(Integer, primary_key=True) title = Column(Text, unique=True) def __init__(self, title): self.title = title # Composite association proxy linking users and courses with grade class UserCourse(Base): __tablename__ = 'user_courses' # Columns user_id = Column(Integer, ForeignKey(User.id), primary_key=True) course_id = Column(Integer, ForeignKey(Course.id), primary_key=True) grade = Column(Integer) # Relations user = relationship( User, backref=backref( 'user_courses', collection_class=attribute_mapped_collection('course_title'), cascade='all, delete-orphan' ) ) course = relationship(Course) def __init__(self, course_title, grade): self._course_title = course_title # temporary, will turn into a # Course when we attach to a Session self.grade = grade @property def course_title(self): if self.course is not None: return self.course.title else: return self._course_title @event.listens_for(Session, "after_attach") def after_attach(session, instance): # when UserCourse objects are attached to a Session, # figure out what Course in the database it should point to, # or create a new one. if isinstance(instance, UserCourse): with session.no _autoflush: course = session.query(Course).filter_by( title=instance._course_title).first() if course is None: course = Course(title=instance._course_title) instance.course = course I've since added an event listener to perform a calculation each time a UserCourse object is set: # Recalculate 'bar' after updating UserCourse @event.listens_for(UserCourse.grade, 'set') def foo(target, value, oldvalue, initiator): courses = DBSession.query(Course).all() user = User.from_id(target.user_id) bar = 0 for course in courses: bar += user.courses[course.title] user.bar = bar Here, 'bar' is some calculation involving a user's grade for each course. This is a somewhat contrived model (my application isn't really about courses and grades), but I thought it'd help to simplify my use case. There are no issues when a user, the courses, and the user's grades already exist in the database. However, when a new user submits a form with course grades in it, the 'foo' function is triggered and I get AttributeError: 'NoneType' object has no attribute 'courses' with the traceback pointing to the line in the 'foo' function that refers to user.courses[course.title]. I understand that columns default to the NoneType type when the type is None or omitted, so is this a timing/sequencing issue with my listener? Should I be using something other than 'set' (or add another listener that is triggered first)? If I manually enter some course grades into the database with psql, I get a KeyError on the first course I didn't manually input, hence the request for defaultdict-like functionality. That would at least help with the KeyError. How would you recommend tackling these problems? Thanks, Brian -- 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 http://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.