I'm playing a bit with composite Association Proxies and stumbled upon this 
discussion. Just wanted to point out that here:
   @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

when we add objects in this way, for example: 
students = # ... list of User objects
courses = {
   'math': 10, 
   'italian': 9
}

for student in students:
   student.courses= courses
   session.add(student)

session.commit()

Would cause again an IntegrityError. So I modified the recipe like this: 
   @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):
           cache = getattr(session, _unique_cache)
           if cache is None:
               session._unique_cache = {}
           with session.no_autoflush:
               key = instance._course_title
               course = session.query(Course).filter_by(
                                       title=key).first()
               if course is None:
                   # Here we check that the object is in the session (but 
not committed yet
                   # in the db
                   if key in cache: 
                       course = cache[key]
                   else:
                   course = Course(title=instance._course_title)
                   cache[key] = course
               instance.course = course


Don't know if there's a better way of achieving this. 

On Thursday, January 9, 2014 at 1:44:00 AM UTC+1, Michael Bayer wrote:
>
> OK well to do it exactly the way the example does it, each time we create 
> a UserCourse, it will also create a Course.  That’s pretty simple, we use 
> two association proxies, one for User.courses and the other for 
> UserCourse.course, mappings are like this:
>
> class User(Base):
>     __tablename__ = 'users'
>
>     # Columns
>     id = Column(Integer, primary_key=True)
>     name = Column(Text)
>
>     # Relations
>     courses = association_proxy('user_courses', 'grade',
>         creator=lambda k, v: UserCourse(course_title=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 proxies linking users and preferences
> 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)
>
>     course_title = association_proxy("course", "title”)  # will create a 
> new Course object when course_title is set
>
>     def __init__(self, course_title, grade):
>         self.course_title = course_title
>         self.grade = grade
>
> the other way that’s maybe a little more “real world” is that if two 
> different UserCourse objects are for “math”, we’d want only one Course 
> object with “math”.  There’s a few ways to go about making those unique 
> Course objects - one common one is the “unique object” recipe at 
> http://www.sqlalchemy.org/trac/wiki/UsageRecipes/UniqueObject .
>
> A variant on that which I’ve been using lately doesn’t rely upon any kind 
> of global session and instead uses events.    In this approach, we modify 
> the above so that UserCourse.course_title temporarily points to a plain 
> string, then when attached to a Session looks up and/or creates the unique 
> Course object, looks like this:
>
> from sqlalchemy import event
>
> # same User and Course...
>
> # Composite association proxies linking users and preferences
> 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
>
>
> with either of these, a simple test run is like:
>
> e = create_engine("sqlite://", echo=True)
> Base.metadata.create_all(e)
>
> s = Session(e)
>
> user = User(name='u1')
> s.add(user)
> s.commit()
>
> user.courses['math'] = 100
> s.commit()
>
> assert user.courses['math'] == 100
>
>
>
>
>
> On Jan 8, 2014, at 6:32 PM, Brian Findlay <brian.m...@gmail.com 
> <javascript:>> wrote:
>
> Hi, all. I've been trying to modify the example of a composite association 
> proxy (
> http://www.sqlalchemy.org/docs/orm/extensions/associationproxy.html#composite-association-proxies)
>  
> to fit my needs.
>
> In the documentation example, there is a User object, a Keyword object, 
> and a UserKeyword association object that stores a 'special_key' for each 
> of a user's keywords. In the provided example, the result is a collection 
> of dictionaries where the 'special_key' is the key and the 'keyword' is the 
> value. I'm trying to inverse that mapping.
>
> In my particular use case (which I've simplified so as to make it as clear 
> as possible...I hope), I have a User object (a student), a Course object 
> (an academic course), and a UserCourse association object that stores each 
> user's grade for each course. My goal is to be able to set a student's 
> grade something like this:
>
> user.course['math'] = 100
>
> This is what I've come up with, but it (obviously) isn't working yet.
>
>
> from sqlalchemy import Column, Integer, Text, ForeignKey
> from sqlalchemy.ext.associationproxy import association_proxy
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy.orm.collections import attribute_mapped_collection
> from sqlalchemy.orm import scoped_session, sessionmaker, relationship, 
> backref
>
>
> Base = declarative_base()
>
>
> 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 proxies linking users and preferences
> 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('grade'),
>             cascade='all, delete-orphan'
>             )
>         )
>     c = relationship('Course')
>     course = association_proxy('c', 'title')
>
>
>
> I'd really appreciate anyone's help here, even if it's just showing me how 
> to modify the example in the documentation.
>
> -- 
> 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:>.
> To post to this group, send email to sqlal...@googlegroups.com 
> <javascript:>.
> Visit this group at http://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>
>

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

Reply via email to